absperf-dm-ssbe-adapter 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.mkd +6 -0
- data/Rakefile +67 -0
- data/VERSION.yml +4 -0
- data/dm-ssbe-adapter.gemspec +70 -0
- data/example.rb +31 -0
- data/lib/dm-ssbe-adapter.rb +186 -0
- data/lib/dm-ssbe-adapter/model_extensions.rb +105 -0
- data/lib/dm-ssbe-adapter/service.rb +33 -0
- data/lib/dm-ssbe-adapter/ssbe_authenticator.rb +50 -0
- data/lib/dm-types/href.rb +19 -0
- data/spec/create_spec.rb +14 -0
- data/spec/models.rb +35 -0
- data/spec/reading_spec.rb +49 -0
- data/spec/simple_sinatra_server.rb +140 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +34 -0
- metadata +115 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Paul Sadauskas
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mkd
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "dm-ssbe-adapter"
|
8
|
+
gem.summary = %Q{A DataMapper adapter for System Shepherd flavored REST services}
|
9
|
+
gem.email = "psadauskas@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/absperf/dm-ssbe-adapter"
|
11
|
+
gem.authors = ["Paul Sadauskas"]
|
12
|
+
|
13
|
+
gem.add_dependency 'dm-core', '~> 0.10.0'
|
14
|
+
gem.add_dependency 'resourceful', '~> 0.5.0'
|
15
|
+
gem.add_dependency 'extlib', '~> 0.9.11'
|
16
|
+
gem.add_dependency 'json', '~> 1.1.0'
|
17
|
+
|
18
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'spec/rake/spectask'
|
26
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
27
|
+
t.spec_opts << '--options spec/spec.opts' if File.exists?('spec/spec.opts')
|
28
|
+
t.libs << 'lib'
|
29
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
task :spec do
|
33
|
+
abort 'rspec is not available'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'rcov/rcovtask'
|
39
|
+
Rcov::RcovTask.new do |test|
|
40
|
+
test.libs << 'test'
|
41
|
+
test.pattern = 'test/**/*_test.rb'
|
42
|
+
test.verbose = true
|
43
|
+
end
|
44
|
+
rescue LoadError
|
45
|
+
task :rcov do
|
46
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
task :default => :spec
|
52
|
+
|
53
|
+
require 'rake/rdoctask'
|
54
|
+
Rake::RDocTask.new do |rdoc|
|
55
|
+
if File.exist?('VERSION.yml')
|
56
|
+
config = YAML.load(File.read('VERSION.yml'))
|
57
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
58
|
+
else
|
59
|
+
version = ""
|
60
|
+
end
|
61
|
+
|
62
|
+
rdoc.rdoc_dir = 'rdoc'
|
63
|
+
rdoc.title = "dm-ssbe-adapter #{version}"
|
64
|
+
rdoc.rdoc_files.include('README*')
|
65
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
66
|
+
end
|
67
|
+
|
data/VERSION.yml
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{dm-ssbe-adapter}
|
5
|
+
s.version = "0.10.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Paul Sadauskas"]
|
9
|
+
s.date = %q{2009-06-10}
|
10
|
+
s.email = %q{psadauskas@gmail.com}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"LICENSE",
|
13
|
+
"README.mkd"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".document",
|
17
|
+
".gitignore",
|
18
|
+
"LICENSE",
|
19
|
+
"README.mkd",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION.yml",
|
22
|
+
"dm-ssbe-adapter.gemspec",
|
23
|
+
"example.rb",
|
24
|
+
"lib/dm-ssbe-adapter.rb",
|
25
|
+
"lib/dm-ssbe-adapter/model_extensions.rb",
|
26
|
+
"lib/dm-ssbe-adapter/service.rb",
|
27
|
+
"lib/dm-ssbe-adapter/ssbe_authenticator.rb",
|
28
|
+
"lib/dm-types/href.rb",
|
29
|
+
"spec/create_spec.rb",
|
30
|
+
"spec/models.rb",
|
31
|
+
"spec/reading_spec.rb",
|
32
|
+
"spec/simple_sinatra_server.rb",
|
33
|
+
"spec/spec.opts",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/absperf/dm-ssbe-adapter}
|
37
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.3.3}
|
40
|
+
s.summary = %q{A DataMapper adapter for System Shepherd flavored REST services}
|
41
|
+
s.test_files = [
|
42
|
+
"spec/simple_sinatra_server.rb",
|
43
|
+
"spec/reading_spec.rb",
|
44
|
+
"spec/spec_helper.rb",
|
45
|
+
"spec/create_spec.rb",
|
46
|
+
"spec/models.rb"
|
47
|
+
]
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
51
|
+
s.specification_version = 3
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
54
|
+
s.add_runtime_dependency(%q<dm-core>, ["~> 0.10.0"])
|
55
|
+
s.add_runtime_dependency(%q<resourceful>, ["~> 0.5.0"])
|
56
|
+
s.add_runtime_dependency(%q<extlib>, ["~> 0.9.11"])
|
57
|
+
s.add_runtime_dependency(%q<json>, ["~> 1.1.0"])
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<dm-core>, ["~> 0.10.0"])
|
60
|
+
s.add_dependency(%q<resourceful>, ["~> 0.5.0"])
|
61
|
+
s.add_dependency(%q<extlib>, ["~> 0.9.11"])
|
62
|
+
s.add_dependency(%q<json>, ["~> 1.1.0"])
|
63
|
+
end
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<dm-core>, ["~> 0.10.0"])
|
66
|
+
s.add_dependency(%q<resourceful>, ["~> 0.5.0"])
|
67
|
+
s.add_dependency(%q<extlib>, ["~> 0.9.11"])
|
68
|
+
s.add_dependency(%q<json>, ["~> 1.1.0"])
|
69
|
+
end
|
70
|
+
end
|
data/example.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'resourceful'
|
4
|
+
require 'dm-core'
|
5
|
+
require 'lib/dm-ssbe-adapter'
|
6
|
+
|
7
|
+
DataMapper.setup(:default, :adapter => :ssbe,
|
8
|
+
:username => 'admin',
|
9
|
+
:password => 'admin',
|
10
|
+
:services_uri => 'http://auth.v6.localhost/services',
|
11
|
+
:logger => Resourceful::StdOutLogger.new)
|
12
|
+
|
13
|
+
|
14
|
+
puts "GET Service"
|
15
|
+
puts Service.get("http://auth.v6.localhost/services/AllAccounts").inspect
|
16
|
+
|
17
|
+
puts "All Services"
|
18
|
+
puts Service.all.inspect
|
19
|
+
|
20
|
+
puts "Create Service"
|
21
|
+
s = Service.create(:name => "OtherAccounts",
|
22
|
+
:resource_href => "http://auth.v6.localhost/services/AllOtherAccounts")
|
23
|
+
|
24
|
+
puts s.inspect
|
25
|
+
|
26
|
+
puts "Update Service"
|
27
|
+
s.resource_href = "http://auth.v6.localhost/services/OtherAccounts"
|
28
|
+
s.save
|
29
|
+
puts s.inspect
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
|
2
|
+
#require 'dm-core'
|
3
|
+
require 'resourceful'
|
4
|
+
require 'extlib'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
__DIR__ = File.dirname(__FILE__)
|
8
|
+
require File.join(__DIR__, 'dm-ssbe-adapter', 'ssbe_authenticator')
|
9
|
+
require File.join(__DIR__, 'dm-types', 'href')
|
10
|
+
require File.join(__DIR__, 'dm-ssbe-adapter', 'service')
|
11
|
+
require File.join(__DIR__, 'dm-ssbe-adapter', 'model_extensions')
|
12
|
+
|
13
|
+
module DataMapper::Adapters
|
14
|
+
|
15
|
+
class HttpAdapter < AbstractAdapter
|
16
|
+
attr_reader :http
|
17
|
+
|
18
|
+
def initialize(name, options = {})
|
19
|
+
super
|
20
|
+
|
21
|
+
@http = Resourceful::HttpAccessor.new
|
22
|
+
@http.cache_manager = Resourceful::InMemoryCacheManager.new
|
23
|
+
@http.logger = options[:logger] || Resourceful::BitBucketLogger.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def logger
|
27
|
+
http.logger
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class SsbeAdapter < HttpAdapter
|
32
|
+
attr_reader :services_uri
|
33
|
+
|
34
|
+
SSJ = 'application/vnd.absperf.ssbe+json'
|
35
|
+
|
36
|
+
def initialize(name, options = {})
|
37
|
+
super
|
38
|
+
|
39
|
+
username, password = options[:username], options[:password]
|
40
|
+
|
41
|
+
http.add_authenticator(Resourceful::SSBEAuthenticator.new(username, password))
|
42
|
+
|
43
|
+
@services_uri = options[:services_uri]
|
44
|
+
end
|
45
|
+
|
46
|
+
def create(resources)
|
47
|
+
resources.each do |resource|
|
48
|
+
http_resource = collection_resource_for(resource.model)
|
49
|
+
document = serialize(resource)
|
50
|
+
|
51
|
+
response = http_resource.post(document, :content_type => SSJ)
|
52
|
+
|
53
|
+
update_attributes(resource, deserialize(response.body))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def read(query)
|
58
|
+
## [dm-core] need an easy way to determine if we're
|
59
|
+
# looking up a single record by key
|
60
|
+
if querying_on_href?(query)
|
61
|
+
href = if query.respond_to?(:location)
|
62
|
+
query.location
|
63
|
+
else
|
64
|
+
operand = query.conditions.operands.first
|
65
|
+
operand.value
|
66
|
+
end
|
67
|
+
|
68
|
+
http_resource = http.resource(href, :accept => SSJ)
|
69
|
+
begin
|
70
|
+
response = http_resource.get
|
71
|
+
rescue Resourceful::UnsuccessfulHttpRequestError => e
|
72
|
+
if e.http_response.code == 404
|
73
|
+
return []
|
74
|
+
else
|
75
|
+
raise e
|
76
|
+
end
|
77
|
+
end
|
78
|
+
record = deserialize(response.body)
|
79
|
+
if record.has_key?(:items)
|
80
|
+
query.filter_records(record[:items])
|
81
|
+
else
|
82
|
+
[record]
|
83
|
+
end
|
84
|
+
else
|
85
|
+
resource = collection_resource_for(query)
|
86
|
+
opts = {}
|
87
|
+
opts.merge(:cache_control => 'no-cache') if query.reload?
|
88
|
+
|
89
|
+
response = resource.get(opts)
|
90
|
+
|
91
|
+
records = deserialize(response.body)
|
92
|
+
query.filter_records(records[:items])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def update(attributes, collection)
|
97
|
+
collection.each do |resource|
|
98
|
+
http_resource = http.resource(resource.href, :accept => SSJ)
|
99
|
+
response = http_resource.put(serialize(attributes), :content_type => SSJ)
|
100
|
+
|
101
|
+
update_attributes(resource, deserialize(response.body))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def delete(collection)
|
106
|
+
collection.each do |resource|
|
107
|
+
http_resource = http.resource(resource.href, :accept => SSJ)
|
108
|
+
response = http_resource.delete
|
109
|
+
|
110
|
+
update_attributes(resource, deserialize(response.body))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
|
116
|
+
## [dm-core] resource.update_fields(attributes)
|
117
|
+
# updates any changed fields from a response
|
118
|
+
# eg, created_at, updated_at, etc...
|
119
|
+
def update_attributes(resource, attributes)
|
120
|
+
attributes.each do |field, value|
|
121
|
+
property = resource.model.properties.detect { |p| p.field == field }
|
122
|
+
property.set!(resource, value) if property
|
123
|
+
end
|
124
|
+
resource
|
125
|
+
end
|
126
|
+
|
127
|
+
def serialize(resource_or_attributes)
|
128
|
+
if resource_or_attributes.is_a?(DataMapper::Resource)
|
129
|
+
attributes_as_fields(resource_or_attributes.dirty_attributes)
|
130
|
+
else
|
131
|
+
attributes_as_fields(resource_or_attributes)
|
132
|
+
end.merge(:_type => resource_or_attributes.model).to_json
|
133
|
+
end
|
134
|
+
|
135
|
+
def deserialize(document)
|
136
|
+
Mash.new(JSON.parse(document))
|
137
|
+
end
|
138
|
+
|
139
|
+
def querying_on_href?(query)
|
140
|
+
return true if query.respond_to?(:location) && query.location
|
141
|
+
|
142
|
+
return false unless query.conditions.operands.size == 1
|
143
|
+
|
144
|
+
operand = query.conditions.operands.first
|
145
|
+
return false unless operand.is_a?(DataMapper::Query::Conditions::EqualToComparison)
|
146
|
+
|
147
|
+
query.model.key.first == operand.subject
|
148
|
+
end
|
149
|
+
|
150
|
+
def collection_resource_for(query_or_model)
|
151
|
+
if query_or_model.is_a?(DataMapper::Query)
|
152
|
+
query = query_or_model
|
153
|
+
model = query.model
|
154
|
+
else
|
155
|
+
model = query_or_model
|
156
|
+
end
|
157
|
+
|
158
|
+
## [dm-core] Make it easy to add more things to a query
|
159
|
+
collection_uri = if model == Service
|
160
|
+
@services_uri
|
161
|
+
elsif query && query.respond_to?(:location)
|
162
|
+
query.location
|
163
|
+
elsif query && uri = association_collection_uri(query)
|
164
|
+
uri
|
165
|
+
else
|
166
|
+
Service[model.service_name].resource_href
|
167
|
+
end
|
168
|
+
|
169
|
+
http.resource(collection_uri, :accept => SSJ)
|
170
|
+
end
|
171
|
+
|
172
|
+
def association_collection_uri(query)
|
173
|
+
return false unless query.conditions.operands.size == 1
|
174
|
+
|
175
|
+
operand = query.conditions.operands.first
|
176
|
+
return false unless operand.is_a?(DataMapper::Query::Conditions::EqualToComparison)
|
177
|
+
return false unless operand.subject.name.to_s =~ /_href\Z/
|
178
|
+
|
179
|
+
operand.value
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
module DataMapper
|
3
|
+
|
4
|
+
module SsbeModelExtensions
|
5
|
+
|
6
|
+
def service_name(name = nil)
|
7
|
+
if name
|
8
|
+
@service_name = name
|
9
|
+
else
|
10
|
+
@service_name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def refers_to_collection_of(collection_name, options = {})
|
15
|
+
options.merge!(:min => 0, :max => n)
|
16
|
+
options[:child_repository_name] = options.delete(:repository)
|
17
|
+
options[:parent_repository_name] = repository.name
|
18
|
+
|
19
|
+
rel = CollectionReference.new(collection_name, nil, self, options)
|
20
|
+
relationships(repository.name)[collection_name] = rel
|
21
|
+
end
|
22
|
+
|
23
|
+
def refers_to(name, options = {})
|
24
|
+
options[:child_repository_name] = options.delete(:repository)
|
25
|
+
options[:parent_repository_name] = repository.name
|
26
|
+
|
27
|
+
rel = Reference.new(name, self, nil, options)
|
28
|
+
relationships(repository.name)[name] = rel
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
Model.send(:include, SsbeModelExtensions)
|
34
|
+
|
35
|
+
module SsbeQueryExtensions
|
36
|
+
|
37
|
+
attr_accessor :location
|
38
|
+
|
39
|
+
def initialize(repository, model, options = {})
|
40
|
+
super
|
41
|
+
@location = @options.fetch(:location, nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_valid_options(options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def location=(location)
|
48
|
+
@location = location
|
49
|
+
@options = @options.dup # Why do you hate freedom?
|
50
|
+
@options[:location] = location # so copy works
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class CollectionReference < Associations::OneToMany::Relationship
|
56
|
+
|
57
|
+
# NOTE: This is why asserting valid options is a retarded idea. It
|
58
|
+
# makes it a giant pain in the ass to extend anything. I want a
|
59
|
+
# :location option on query. I have to have to extend every instance
|
60
|
+
# of a query, and set the location manually. The single commented line
|
61
|
+
# in `#source_scope` would be all thats needed, otherwise.
|
62
|
+
def query_for(source, other_query = nil)
|
63
|
+
query = super
|
64
|
+
query.extend(SsbeQueryExtensions)
|
65
|
+
query.location = reference_property.get(source)
|
66
|
+
query
|
67
|
+
end
|
68
|
+
|
69
|
+
def source_scope(source)
|
70
|
+
#{:location => child_key.get(source)}
|
71
|
+
{}
|
72
|
+
end
|
73
|
+
|
74
|
+
def reference_property
|
75
|
+
property_name = "#{name}_href".to_sym
|
76
|
+
|
77
|
+
parent_model.properties(parent_repository_name)[property_name]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
class Reference < Associations::ManyToOne::Relationship
|
83
|
+
|
84
|
+
def query_for(source, other_query = nil)
|
85
|
+
query = super
|
86
|
+
query.extend(SsbeQueryExtensions)
|
87
|
+
query.location = reference_property.get(source)
|
88
|
+
query
|
89
|
+
end
|
90
|
+
|
91
|
+
def source_scope(source)
|
92
|
+
#{:location => child_key.get(source)}
|
93
|
+
{}
|
94
|
+
end
|
95
|
+
|
96
|
+
def reference_property
|
97
|
+
property_name = "#{name}_href".to_sym
|
98
|
+
|
99
|
+
child_model.properties(parent_repository_name)[property_name]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
class Service
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
def self.default_repository_name
|
6
|
+
:ssbe
|
7
|
+
end
|
8
|
+
|
9
|
+
property :href, Href, :key => true, :serial => true
|
10
|
+
property :name, String, :nullable => false
|
11
|
+
property :resource_href, Href, :nullable => false
|
12
|
+
|
13
|
+
property :created_at, DateTime
|
14
|
+
property :updated_at, DateTime
|
15
|
+
|
16
|
+
def self.[](name)
|
17
|
+
first(:name => name.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.register(name, resource_href)
|
21
|
+
if service = self.first(:name => name)
|
22
|
+
service.href = href
|
23
|
+
service.save
|
24
|
+
else
|
25
|
+
service = self.create(:name => name.to_s,
|
26
|
+
:resource_href => href.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
service
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Resourceful
|
2
|
+
|
3
|
+
class SSBEAuthenticator
|
4
|
+
require 'httpauth'
|
5
|
+
require 'addressable/uri'
|
6
|
+
|
7
|
+
|
8
|
+
attr_reader :username, :password, :realm, :domain, :challenge
|
9
|
+
|
10
|
+
def initialize(username, password)
|
11
|
+
@username, @password = username, password
|
12
|
+
@realm = 'SystemShepherd'
|
13
|
+
@domain = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_credentials(challenge_response)
|
17
|
+
@domain = Addressable::URI.parse(challenge_response.uri).host
|
18
|
+
@challenge = HTTPAuth::Digest::Challenge.from_header(challenge_response.header['WWW-Authenticate'].first)
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_for?(challenge_response)
|
22
|
+
return false unless challenge_header = challenge_response.header['WWW-Authenticate']
|
23
|
+
begin
|
24
|
+
challenge = HTTPAuth::Digest::Challenge.from_header(challenge_header.first)
|
25
|
+
rescue HTTPAuth::UnwellformedHeader
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
challenge.realm == @realm
|
29
|
+
end
|
30
|
+
|
31
|
+
def can_handle?(request)
|
32
|
+
Addressable::URI.parse(request.uri).host == @domain
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_credentials_to(request)
|
36
|
+
request.header['Authorization'] = credentials_for(request)
|
37
|
+
end
|
38
|
+
|
39
|
+
def credentials_for(request)
|
40
|
+
HTTPAuth::Digest::Credentials.from_challenge(@challenge,
|
41
|
+
:username => @username,
|
42
|
+
:password => @password,
|
43
|
+
:method => request.method.to_s.upcase,
|
44
|
+
:uri => Addressable::URI.parse(request.uri).path).to_header
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module DataMapper::Types
|
3
|
+
class Href < DataMapper::Type
|
4
|
+
primitive String
|
5
|
+
length 255
|
6
|
+
|
7
|
+
def self.load(value, property)
|
8
|
+
value
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.dump(value, property)
|
12
|
+
value
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.typecast(value, property)
|
16
|
+
value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/create_spec.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe 'creating' do
|
4
|
+
|
5
|
+
it 'should create' do
|
6
|
+
article = Article.create(:title => "Test Create",
|
7
|
+
:text => "Here's how you create an article.")
|
8
|
+
|
9
|
+
article.published_at.should == "2009-04-29T15:53:00-06:00"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
|
data/spec/models.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class Article
|
2
|
+
include DataMapper::Resource
|
3
|
+
def self.default_repository_name
|
4
|
+
:ssbe
|
5
|
+
end
|
6
|
+
|
7
|
+
service_name :AllArticles
|
8
|
+
|
9
|
+
property :href, String, :key => true
|
10
|
+
property :title, String
|
11
|
+
property :text, String
|
12
|
+
property :published_at, Time
|
13
|
+
|
14
|
+
property :comments_href, Href
|
15
|
+
|
16
|
+
refers_to_collection_of :comments
|
17
|
+
end
|
18
|
+
|
19
|
+
class Comment
|
20
|
+
include DataMapper::Resource
|
21
|
+
def self.default_repository_name
|
22
|
+
:ssbe
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
property :href, String, :key => true
|
27
|
+
property :author, String
|
28
|
+
property :text, String
|
29
|
+
|
30
|
+
property :article_href, Href
|
31
|
+
|
32
|
+
refers_to :article
|
33
|
+
end
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe 'reading' do
|
4
|
+
|
5
|
+
it 'should get something by service name' do
|
6
|
+
articles = Article.all
|
7
|
+
articles.size.should == 2
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should get something by its href' do
|
11
|
+
article = Article.get('http://localhost:5050/articles/1')
|
12
|
+
article.text.should == "Something different from the index, so we can get which GET we used"
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should get a collection from a reference' do
|
16
|
+
comments = Article.first.comments
|
17
|
+
|
18
|
+
comments.size.should == 2
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should get a record from a reference' do
|
22
|
+
article = Article.get("http://localhost:5050/articles/1")
|
23
|
+
comment = Comment.get("http://localhost:5050/articles/1/comments/1")
|
24
|
+
|
25
|
+
comment.article.should == article
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'attributes' do
|
29
|
+
before do
|
30
|
+
@article = Article.get("http://localhost:5050/articles/1")
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should get a string attribute' do
|
34
|
+
@article.title.should == "First Article"
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should get an href attribute' do
|
38
|
+
@article.comments_href.should == 'http://localhost:5050/articles/1/comments'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should get a time attribute' do
|
42
|
+
pending "dm-core needs to allow custom type parsing"
|
43
|
+
@article.published_at.should == Time.iso8601("2009-04-29T15:53:00-06:00")
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
|
2
|
+
require 'sinatra/base'
|
3
|
+
|
4
|
+
class App < Sinatra::Base
|
5
|
+
|
6
|
+
SSJ = 'application/vnd.absperf.ssbe+json'
|
7
|
+
|
8
|
+
use_in_file_templates!
|
9
|
+
|
10
|
+
before do
|
11
|
+
content_type SSJ
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/services' do
|
15
|
+
erb :services
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/articles' do
|
19
|
+
erb :articles
|
20
|
+
end
|
21
|
+
|
22
|
+
post '/articles' do
|
23
|
+
erb :article
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/articles/:id' do
|
27
|
+
erb :article
|
28
|
+
end
|
29
|
+
|
30
|
+
get '/articles/:article_id/comments' do
|
31
|
+
erb :comments
|
32
|
+
end
|
33
|
+
|
34
|
+
get '/articles/:article_id/comments/:id' do
|
35
|
+
erb :comment
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
__END__
|
41
|
+
|
42
|
+
@@services
|
43
|
+
{
|
44
|
+
"href": "http://localhost:5050/services",
|
45
|
+
"item_count": 2,
|
46
|
+
"items": [
|
47
|
+
{
|
48
|
+
"_type": "Service",
|
49
|
+
"href": "http://localhost:5050/services/AllServices",
|
50
|
+
"name": "AllServices",
|
51
|
+
"resource_href": "http://localhost:5050/services",
|
52
|
+
"created_at": "2009-04-29T15:53:00-06:00",
|
53
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
54
|
+
},
|
55
|
+
{
|
56
|
+
"_type": "Service",
|
57
|
+
"href": "http://localhost:5050/services/AllArticles",
|
58
|
+
"name": "AllArticles",
|
59
|
+
"resource_href": "http://localhost:5050/articles",
|
60
|
+
"created_at": "2009-04-29T15:53:00-06:00",
|
61
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
62
|
+
}
|
63
|
+
]
|
64
|
+
}
|
65
|
+
|
66
|
+
@@articles
|
67
|
+
{
|
68
|
+
"href": "http://localhost:5050/articles",
|
69
|
+
"item_count": 2,
|
70
|
+
"items": [
|
71
|
+
{
|
72
|
+
"_type": "Article",
|
73
|
+
"href": "http://localhost:5050/articles/1",
|
74
|
+
"title": "First Article",
|
75
|
+
"text": "This is the first article",
|
76
|
+
"comments_href": "http://localhost:5050/articles/1/comments",
|
77
|
+
"published_at": "2009-04-29T15:53:00-06:00",
|
78
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
79
|
+
},
|
80
|
+
{
|
81
|
+
"_type": "Article",
|
82
|
+
"href": "http://localhost:5050/articles/2",
|
83
|
+
"title": "Second Article",
|
84
|
+
"text": "This is the second article",
|
85
|
+
"comments_href": "http://localhost:5050/articles/2/comments",
|
86
|
+
"published_at": "2009-04-29T15:53:00-06:00",
|
87
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
88
|
+
}
|
89
|
+
]
|
90
|
+
}
|
91
|
+
|
92
|
+
@@article
|
93
|
+
{
|
94
|
+
"_type": "Article",
|
95
|
+
"href": "http://localhost:5050/articles/<%= params[:id] %>",
|
96
|
+
"title": "First Article",
|
97
|
+
"text": "Something different from the index, so we can get which GET we used",
|
98
|
+
"comments_href": "http://localhost:5050/articles/<%= params[:id] %>/comments",
|
99
|
+
"published_at": "2009-04-29T15:53:00-06:00",
|
100
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
101
|
+
}
|
102
|
+
|
103
|
+
@@comments
|
104
|
+
{
|
105
|
+
"href": "http://localhost:5050/articles/<%= params[:article_id] %>/comments",
|
106
|
+
"item_count": 2,
|
107
|
+
"items": [
|
108
|
+
{
|
109
|
+
"_type": "Comment",
|
110
|
+
"href": "http://localhost:5050/articles/<%= params[:article_id] %>/comments/1",
|
111
|
+
"author": "Paul",
|
112
|
+
"text": "This is the first comment on article <%= params[:article_id] %>",
|
113
|
+
"article_href": "http://localhost:5050/articles/<%= params[:article_id] %>",
|
114
|
+
"created_at": "2009-04-29T15:53:00-06:00",
|
115
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
116
|
+
},
|
117
|
+
{
|
118
|
+
"_type": "Comment",
|
119
|
+
"href": "http://localhost:5050/articles/<%= params[:article_id] %>/comments/2",
|
120
|
+
"author": "Erik",
|
121
|
+
"text": "This is the second comment on article <%= params[:article_id] %>",
|
122
|
+
"article_href": "http://localhost:5050/articles/<%= params[:article_id] %>",
|
123
|
+
"created_at": "2009-04-29T15:53:00-06:00",
|
124
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
125
|
+
}
|
126
|
+
]
|
127
|
+
}
|
128
|
+
|
129
|
+
@@comment
|
130
|
+
{
|
131
|
+
"_type": "Comment",
|
132
|
+
"href": "http://localhost:5050/articles/<%= params[:article_id] %>/comments/<%= params[:id] %>",
|
133
|
+
"author": "Erik",
|
134
|
+
"text": "This is the second comment on article <%= params[:article_id] %>",
|
135
|
+
"article_href": "http://localhost:5050/articles/<%= params[:article_id] %>",
|
136
|
+
"created_at": "2009-04-29T15:53:00-06:00",
|
137
|
+
"updated_at": "2009-04-29T15:53:00-06:00"
|
138
|
+
}
|
139
|
+
|
140
|
+
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'thin'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
require '../../dm-core/lib/dm-core'
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
10
|
+
require 'dm-ssbe-adapter'
|
11
|
+
require 'dm-ssbe-adapter/model_extensions'
|
12
|
+
|
13
|
+
DataMapper.setup(:ssbe, :adapter => :ssbe,
|
14
|
+
:username => 'admin',
|
15
|
+
:password => 'admin',
|
16
|
+
:services_uri => 'http://localhost:5050/services',
|
17
|
+
#:logger => Resourceful::StdOutLogger.new)
|
18
|
+
:logger => Resourceful::BitBucketLogger.new)
|
19
|
+
|
20
|
+
require 'models'
|
21
|
+
require 'simple_sinatra_server'
|
22
|
+
|
23
|
+
@server = Thread.new do
|
24
|
+
|
25
|
+
Thin::Server.start('0.0.0.0', 5050, App, :debug => false)
|
26
|
+
|
27
|
+
end unless @server
|
28
|
+
|
29
|
+
at_exit { @server.exit }
|
30
|
+
|
31
|
+
# Give the app a change to initialize
|
32
|
+
$stderr.puts "Waiting for thin to initialize..."
|
33
|
+
sleep 0.2
|
34
|
+
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: absperf-dm-ssbe-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.10.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Sadauskas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-10 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.10.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: resourceful
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.5.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: extlib
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.11
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: json
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.1.0
|
54
|
+
version:
|
55
|
+
description:
|
56
|
+
email: psadauskas@gmail.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- LICENSE
|
63
|
+
- README.mkd
|
64
|
+
files:
|
65
|
+
- .document
|
66
|
+
- .gitignore
|
67
|
+
- LICENSE
|
68
|
+
- README.mkd
|
69
|
+
- Rakefile
|
70
|
+
- VERSION.yml
|
71
|
+
- dm-ssbe-adapter.gemspec
|
72
|
+
- example.rb
|
73
|
+
- lib/dm-ssbe-adapter.rb
|
74
|
+
- lib/dm-ssbe-adapter/model_extensions.rb
|
75
|
+
- lib/dm-ssbe-adapter/service.rb
|
76
|
+
- lib/dm-ssbe-adapter/ssbe_authenticator.rb
|
77
|
+
- lib/dm-types/href.rb
|
78
|
+
- spec/create_spec.rb
|
79
|
+
- spec/models.rb
|
80
|
+
- spec/reading_spec.rb
|
81
|
+
- spec/simple_sinatra_server.rb
|
82
|
+
- spec/spec.opts
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
has_rdoc: false
|
85
|
+
homepage: http://github.com/absperf/dm-ssbe-adapter
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options:
|
88
|
+
- --charset=UTF-8
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "0"
|
96
|
+
version:
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: "0"
|
102
|
+
version:
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.2.0
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: A DataMapper adapter for System Shepherd flavored REST services
|
110
|
+
test_files:
|
111
|
+
- spec/simple_sinatra_server.rb
|
112
|
+
- spec/reading_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
- spec/create_spec.rb
|
115
|
+
- spec/models.rb
|