roadforest 0.0.3 → 0.1

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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/examples/file-management.rb +7 -9
  3. data/lib/roadforest/application/dispatcher.rb +44 -31
  4. data/lib/roadforest/application/parameters.rb +5 -4
  5. data/lib/roadforest/application/path-provider.rb +14 -1
  6. data/lib/roadforest/application/route-adapter.rb +15 -4
  7. data/lib/roadforest/application/services-host.rb +41 -7
  8. data/lib/roadforest/application.rb +4 -8
  9. data/lib/roadforest/authorization.rb +231 -0
  10. data/lib/roadforest/blob-model.rb +2 -11
  11. data/lib/roadforest/content-handling/engine.rb +12 -6
  12. data/lib/roadforest/content-handling/handler-wrap.rb +45 -0
  13. data/lib/roadforest/content-handling/media-type.rb +20 -12
  14. data/lib/roadforest/content-handling/type-handler.rb +76 -0
  15. data/lib/roadforest/content-handling/type-handlers/jsonld.rb +2 -146
  16. data/lib/roadforest/content-handling/type-handlers/rdf-handler.rb +38 -0
  17. data/lib/roadforest/content-handling/type-handlers/rdfa-writer/document-environment.rb +34 -0
  18. data/lib/roadforest/content-handling/type-handlers/rdfa-writer/object-environment.rb +62 -0
  19. data/lib/roadforest/content-handling/type-handlers/rdfa-writer/property-environment.rb +46 -0
  20. data/lib/roadforest/content-handling/type-handlers/rdfa-writer/render-engine.rb +574 -0
  21. data/lib/roadforest/content-handling/type-handlers/rdfa-writer/render-environment.rb +144 -0
  22. data/lib/roadforest/content-handling/type-handlers/rdfa-writer/subject-environment.rb +80 -0
  23. data/lib/roadforest/content-handling/type-handlers/rdfa-writer.rb +163 -0
  24. data/lib/roadforest/content-handling/type-handlers/rdfa.rb +175 -0
  25. data/lib/roadforest/content-handling/type-handlers/rdfpost.rb +297 -0
  26. data/lib/roadforest/debug.rb +10 -0
  27. data/lib/roadforest/http/graph-response.rb +3 -7
  28. data/lib/roadforest/http/graph-transfer.rb +28 -106
  29. data/lib/roadforest/http/keychain.rb +79 -0
  30. data/lib/roadforest/http/message.rb +9 -1
  31. data/lib/roadforest/http/user-agent.rb +115 -0
  32. data/lib/roadforest/http.rb +8 -0
  33. data/lib/roadforest/model.rb +48 -3
  34. data/lib/roadforest/rdf/graph-focus.rb +5 -3
  35. data/lib/roadforest/rdf/graph-store.rb +4 -2
  36. data/lib/roadforest/rdf/normalization.rb +6 -1
  37. data/lib/roadforest/remote-host.rb +22 -7
  38. data/lib/roadforest/resource/rdf/read-only.rb +15 -1
  39. data/lib/roadforest/templates/base/doc.haml +13 -0
  40. data/lib/roadforest/templates/base/property_value.haml +12 -0
  41. data/lib/roadforest/templates/base/property_values.haml +6 -0
  42. data/lib/roadforest/templates/base/subject.haml +4 -0
  43. data/lib/roadforest/templates/distiller/doc.haml +20 -0
  44. data/lib/roadforest/templates/distiller/nil-object.haml +1 -0
  45. data/lib/roadforest/templates/distiller/property_value.haml +7 -0
  46. data/lib/roadforest/templates/distiller/property_values.haml +7 -0
  47. data/lib/roadforest/templates/distiller/subject.haml +5 -0
  48. data/lib/roadforest/templates/min/doc.haml +10 -0
  49. data/lib/roadforest/templates/min/property_values.haml +7 -0
  50. data/lib/roadforest/templates/min/subject.haml +2 -0
  51. data/lib/roadforest/templates/nil-object.haml +0 -0
  52. data/lib/roadforest/templates/node-object.haml +1 -0
  53. data/lib/roadforest/templates/object.haml +1 -0
  54. data/lib/roadforest/templates/uri-object.haml +1 -0
  55. data/lib/roadforest/templates/xml-literal-object.haml +1 -0
  56. data/lib/roadforest/utility/class-registry.rb +4 -0
  57. data/spec/client.rb +119 -77
  58. data/spec/{excon-adapater.rb → excon-adapter.rb} +4 -0
  59. data/spec/full-integration.rb +6 -2
  60. data/spec/graph-store.rb +1 -1
  61. data/spec/media-types.rb +29 -2
  62. metadata +102 -125
@@ -0,0 +1,297 @@
1
+ require 'roadforest/content-handling/type-handlers/rdf-handler'
2
+
3
+ module RoadForest
4
+ module MediaType
5
+ module Handlers
6
+ #application/x-www-form-urlencoded
7
+ class RDFPost < RDFHandler
8
+
9
+ #c.f. http://www.lsrn.org/semweb/rdfpost.html
10
+ class Reader < ::RDF::Reader
11
+ module St
12
+ class State
13
+ def initialize(reader)
14
+ @reader = reader
15
+ @accept_hash = cleanup(accept_list)
16
+ end
17
+
18
+ def cleanup(accept_list)
19
+ hash = Hash.new{ accept_list[nil] }
20
+ accept_list.each_key do |key|
21
+ next if key.nil?
22
+ hash[key.to_s] = accept_list[key]
23
+ end
24
+ hash
25
+ end
26
+
27
+ def blank_node(name)
28
+ ::RDF::Node.new(name)
29
+ end
30
+
31
+ def base_uri
32
+ ::RDF::URI.intern(@reader.options[:base_uri])
33
+ end
34
+
35
+ def uri(string)
36
+ base_uri.join(string)
37
+ end
38
+
39
+ def prefix_uri(name)
40
+ ::RDF::URI.intern(@reader.options[:prefixes][name])
41
+ end
42
+
43
+ def clear_subject
44
+ @reader.subject = nil
45
+ @reader.subject_prefix = nil
46
+ end
47
+
48
+ def clear_predicate
49
+ @reader.predicate = nil
50
+ @reader.predicate_prefix = nil
51
+ end
52
+
53
+ def clear_object
54
+ @reader.object = nil
55
+ @reader.object_prefix = nil
56
+ end
57
+
58
+ def consume_next(name)
59
+ consume
60
+ next_state(name)
61
+ end
62
+
63
+ def consume
64
+ @reader.consume_pair
65
+ end
66
+
67
+ def triple_complete
68
+ @reader.new_triple = true
69
+ end
70
+
71
+ def next_state(name)
72
+ @reader.current_state = @reader.states.fetch(name)
73
+ end
74
+
75
+ def accept(key, value)
76
+ #puts "#{[self.class.to_s.sub(/.*:/,''), key,
77
+ #value.sub(/\s*\Z/,'')].inspect}"
78
+ @accept_hash[key][value.sub(/\s*\Z/,'')]
79
+ end
80
+ end
81
+
82
+ class RDF < State
83
+ def accept_list
84
+ { rdf: proc{|v| consume_next(:def_ns_decl) },
85
+ nil => proc{ consume } }
86
+ end
87
+ end
88
+
89
+ class DefNsDecl < State
90
+ def accept_list
91
+ { v: proc {|v| consume_next(:ns_decl); @reader.options[:prefixes][nil] = v},
92
+ nil => proc{|v| next_state(:ns_decl)}}
93
+ end
94
+ end
95
+
96
+ class NsDecl < State
97
+ def accept_list
98
+ { n: proc{|v| consume_next(:ns_decl_suffix); @reader.namespace_prefix = v},
99
+ nil => proc{|v| next_state(:subject)}}
100
+ end
101
+ end
102
+
103
+ class NsDeclSuffix < State
104
+ def accept_list
105
+ { v: proc{|v| consume_next(:ns_decl); @reader.options[:prefixes][@reader.namespace_prefix] = v},
106
+ nil => proc{ next_state(:ns_decl)}}
107
+ end
108
+ end
109
+
110
+ class SkipToSubject < State
111
+ def accept_list
112
+ next_is_subject = proc{ next_state(:subject); clear_subject; clear_predicate; clear_object }
113
+ {
114
+ sb: next_is_subject,
115
+ su: next_is_subject,
116
+ sv: next_is_subject,
117
+ sn: next_is_subject,
118
+ nil => proc{ consume }
119
+ }
120
+ end
121
+ end
122
+
123
+ class SkipToSubjectOrPred < SkipToSubject
124
+ def accept_list
125
+ next_is_pred = proc{ next_state(:predicate); clear_predicate; clear_object }
126
+ super.merge( pu: next_is_pred, pv: next_is_pred, pn: next_is_pred )
127
+ end
128
+ end
129
+
130
+ class Subject < State
131
+ def accept_list
132
+ {
133
+ sb: proc{|v| consume_next(:predicate); @reader.subject = blank_node(v)},
134
+ su: proc{|v| consume_next(:predicate); @reader.subject = uri(v)},
135
+ sv: proc{|v| consume_next(:predicate); @reader.subject = prefix_url(nil) / v},
136
+ sn: proc{|v| consume_next(:subject_suffix); @reader.subject_prefix = prefix_uri(v)},
137
+ nil => proc{ consume }
138
+ }
139
+ end
140
+ end
141
+
142
+ class SubjectSuffix < State
143
+ def accept_list
144
+ {
145
+ sv: proc{|v| consume_next(:predicate); @reader.subject = @reader.subject_prefix/v},
146
+ nil => proc{ next_state(:skip_to_subject)}
147
+ }
148
+ end
149
+ end
150
+
151
+ class Predicate < State
152
+ def accept_list
153
+ {
154
+ pu: proc {|v| @reader.predicate = uri(v); consume_next(:object)},
155
+ pv: proc {|v| @reader.predicate = prefix_uri(nil) / v; consume_next(:object)},
156
+ pn: proc {|v| @reader.predicate_prefix = prefix_uri(v); consume_next(:predicate_suffix)},
157
+ nil => proc { next_state(:skip_to_subject)}
158
+ }
159
+ end
160
+ end
161
+
162
+ class PredicateSuffix < State
163
+ def accept_list
164
+ {
165
+ pv: proc{|v| consume_next(:object); @reader.predicate = @reader.predicate_prefix/v},
166
+ nil => proc{ next_state(:skip_to_subject)}
167
+ }
168
+ end
169
+ end
170
+
171
+ class Object < State
172
+ def accept_list
173
+ {
174
+ ob: proc{|v| consume; triple_complete; @reader.object = blank_node(v)},
175
+ ou: proc{|v| consume; triple_complete; @reader.object = uri(v)},
176
+ ov: proc{|v| consume; triple_complete; @reader.object = prefix_uri(nil) / v},
177
+ on: proc{|v| consume_next(:object_suffix); @reader.object_prefix = prefix_uri(v)},
178
+ ol: proc{|v| consume_next(:type_or_lang); @reader.object = v},
179
+ ll: proc{|v| consume_next(:object_literal); @reader.object_lang = v},
180
+ lt: proc{|v| consume_next(:object_literal); @reader.object_type = v},
181
+ nil => proc{ next_state(:skip_to_subj_or_pred) }
182
+ }
183
+ end
184
+ end
185
+
186
+ class ObjectSuffix < State
187
+ def accept_list
188
+ {
189
+ ov: proc{|v| consume_next(:object); triple_complete; @reader.object = @reader.object_prefix/v},
190
+ nil => proc{ next_state(:skip_to_subj_or_pred)}
191
+ }
192
+ end
193
+ end
194
+
195
+ class ObjectLiteral < State
196
+ def accept_list
197
+ {
198
+ ol: proc{|v| consume_next(:type_or_lang); @reader.object = v},
199
+ ll: proc{|v| consume; @reader.object_lang = v},
200
+ lt: proc{|v| consume; @reader.object_type = v},
201
+ nil => proc{ next_state(:skip_to_subj_or_pred)}
202
+ }
203
+ end
204
+ end
205
+
206
+ class TypeOrLang < State
207
+ def accept_list
208
+ {
209
+ ll: proc{|v| consume; @reader.object_lang = v},
210
+ lt: proc{|v| consume; @reader.object_type = v},
211
+ nil => proc{ next_state(:object); triple_complete }
212
+ }
213
+ end
214
+ end
215
+ end
216
+
217
+ def initialize(input, options=nil, &block)
218
+ super(input, options||{}, &block)
219
+
220
+ @lineno = 0
221
+ @new_triple = false
222
+
223
+ @states = {
224
+ :rdf => St::RDF.new(self),
225
+ :def_ns_decl => St::DefNsDecl.new(self),
226
+ :ns_decl => St::NsDecl.new(self),
227
+ :ns_decl_suffix => St::NsDeclSuffix.new(self),
228
+ :skip_to_subject => St::SkipToSubject.new(self),
229
+ :skip_to_subj_or_pred => St::SkipToSubjectOrPred.new(self),
230
+ :subject => St::Subject.new(self),
231
+ :subject_suffix => St::SubjectSuffix.new(self),
232
+ :predicate => St::Predicate.new(self),
233
+ :predicate_suffix => St::PredicateSuffix.new(self),
234
+ :object => St::Object.new(self),
235
+ :object_suffix => St::ObjectSuffix.new(self),
236
+ :object_literal => St::ObjectLiteral.new(self),
237
+ :type_or_lang => St::TypeOrLang.new(self)
238
+ }
239
+
240
+ @current_state = @states.fetch(:rdf)
241
+ end
242
+ attr_reader :lineno, :states
243
+ attr_accessor :current_state
244
+ attr_accessor :new_triple, :subject, :predicate, :object
245
+ attr_accessor :object_type, :object_lang
246
+ attr_accessor :namespace_prefix, :subject_prefix, :predicate_prefix, :object_prefix
247
+
248
+ def read_triple
249
+ if @lineno >= @input.length
250
+ raise EOFError
251
+ end
252
+ while @lineno < @input.length
253
+ @current_state.accept(*@input[@lineno])
254
+ if @new_triple
255
+ @new_triple = false
256
+ return build_triple
257
+ end
258
+ end
259
+ return build_triple
260
+ end
261
+
262
+ def build_triple
263
+ object = @object
264
+ if object.is_a? String
265
+ object = ::RDF::Literal.new(object, :datatype => object_type, :language => object_lang)
266
+ end
267
+ @object_type = nil
268
+ @object_lang = nil
269
+
270
+ [@subject, @predicate, object]
271
+ end
272
+
273
+ def rewind
274
+ @lineno = 0
275
+ end
276
+
277
+ def consume_pair
278
+ @lineno += 1
279
+ end
280
+ end
281
+
282
+ def local_to_network(base_uri, graph)
283
+ end
284
+
285
+ def network_to_local(base_uri, list)
286
+ raise "Invalid base uri: #{base_uri.inspect}" if base_uri.nil?
287
+ graph = ::RDF::Graph.new
288
+ reader = Reader.new(list, :base_uri => base_uri.to_s)
289
+ reader.each_statement do |statement|
290
+ graph.insert(statement)
291
+ end
292
+ graph
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,10 @@
1
+ module RoadForest
2
+ class << self
3
+ attr_accessor :debug_io
4
+
5
+ def debug(message)
6
+ return if @debug_io.nil?
7
+ @debug_io.puts(message)
8
+ end
9
+ end
10
+ end
@@ -1,14 +1,10 @@
1
1
  module RoadForest
2
2
  module HTTP
3
3
  class BaseResponse
4
- attr_reader :request, :response
4
+ attr_reader :url, :response
5
5
 
6
- def initialize(request, response)
7
- @request, @response = request, response
8
- end
9
-
10
- def url
11
- request.url
6
+ def initialize(url, response)
7
+ @url, @response = url, response
12
8
  end
13
9
 
14
10
  def etag
@@ -1,26 +1,15 @@
1
- require 'roadforest/http/message'
2
- require 'roadforest/http/graph-response'
1
+ require 'roadforest/http'
3
2
  require 'roadforest/content-handling/engine'
4
3
 
5
4
  module RoadForest
6
5
  module HTTP
7
6
  class GraphTransfer
8
- class Retryable < StandardError; end
9
-
10
- attr_accessor :http_client, :trace
11
7
  attr_writer :type_handling
12
- attr_reader :graph_cache
8
+ attr_accessor :user_agent
13
9
 
14
- def initialize
15
- @trace = nil
10
+ def initialize(user_agent)
11
+ @user_agent = user_agent
16
12
  @type_preferences = Hash.new{|h,k| k.nil? ? "*/*" : h[nil]}
17
- @graph_cache = Hash.new do |cache, url|
18
- cache[url] = {}
19
- end
20
- end
21
-
22
- def type_handling
23
- @type_handling ||= ContentHandling::Engine.default
24
13
  end
25
14
 
26
15
  def put(url, graph)
@@ -35,121 +24,54 @@ module RoadForest
35
24
  make_request("POST", url, graph)
36
25
  end
37
26
 
38
- def make_request(method, url, graph=nil)
39
- method = method.to_s.upcase
27
+ def make_request(method, url, graph, retry_limit=5)
28
+ headers = {"Accept" => type_handling.parsers.types.accept_header}
29
+ body = nil
40
30
 
41
- validate(method, url, graph)
42
-
43
- request = setup_request(method, url)
31
+ if(%w{POST PUT PATCH}.include? method.upcase)
32
+ content_type = best_type_for(url)
33
+ renderer = type_handling.choose_renderer(content_type)
34
+ headers["Content-Type"] = renderer.content_type_header
35
+ body = renderer.from_graph(url, graph)
36
+ end
44
37
 
45
- response = send_request(request, graph)
38
+ response = user_agent.make_request(method, url, headers, body, retry_limit)
46
39
 
47
- case response
48
- when HTTP::Response
49
- response = build_response(request, response)
50
- cache_response(response)
51
- return response
52
- when GraphResponse
53
- return response
40
+ case response.status
41
+ when 415 #Type not accepted
42
+ record_accept_header(url, response.headers["Accept"])
43
+ raise Retryable
54
44
  end
55
- end
56
45
 
57
- def cache_response(response)
58
- return if response.etag.nil?
59
- return if response.etag.empty?
60
- graph_cache[response.url][response.etag] = response
46
+ build_response(url, response)
47
+ rescue Retryable
48
+ raise unless (retry_limit -= 1) > 0
49
+ retry
61
50
  end
62
51
 
63
- def validate(method, url, graph)
64
- case method
65
- when "GET", "HEAD", "DELETE"
66
- raise "Method #{method} requires an empty body" unless graph.nil?
67
- when "POST", "PATCH", "PUT"
68
- raise "Method #{method} requires a body" if graph.nil?
69
- #when "OPTION", "TRACE" #Need to put verbs where they go
70
- else
71
- raise "Unrecognized method: #{method}"
72
- end
52
+ def type_handling
53
+ @type_handling ||= ContentHandling::Engine.default
73
54
  end
74
55
 
75
56
  def best_type_for(url)
76
57
  return @type_preferences[url]
77
58
  end
78
59
 
79
- def setup_request(method, url)
80
- request = Request.new(method, url)
81
- request.headers["Accept"] = type_handling.parsers.types.accept_header
82
- add_cache_headers(request)
83
- request
84
- end
85
-
86
- def add_cache_headers(request)
87
- return unless request.method == "GET"
88
- return unless graph_cache.has_key?(request.url)
89
- cached = graph_cache[request.url]
90
- return if cached.empty?
91
- request.headers["If-None-Match"] = cached.keys.join(", ")
92
- end
93
-
94
- def select_renderer(url)
95
- end
96
-
97
60
  def record_accept_header(url, types)
98
61
  return if types.nil? or types.empty?
99
62
  @type_preferences[nil] = types
100
63
  @type_preferences[url] = types
101
64
  end
102
65
 
103
- def render_graph(graph, request)
104
- return unless request.needs_body?
105
-
106
- content_type = best_type_for(request.url)
107
- renderer = type_handling.choose_renderer(content_type)
108
- request.headers["Content-Type"] = renderer.content_type_header
109
- request.body_string = renderer.from_graph(request.url, graph)
110
- end
111
-
112
- def trace_message(message)
113
- return unless @trace
114
- @trace = $stdout unless IO === @trace
115
- @trace.puts message.inspect
116
- end
117
-
118
- def send_request(request, graph)
119
- retry_limit ||= 5
120
- #Check expires headers on received
121
- render_graph(graph, request)
122
-
123
- trace_message(request)
124
- response = http_client.do_request(request)
125
- trace_message(response)
126
- case response.status
127
- when 304 #Not Modified
128
- response = graph_cache.fetch(request.url).fetch(response.etag)
129
- trace_message(response)
130
- return response
131
- when 415 #Type not accepted
132
- record_accept_header(request.url, response.headers["Accept"])
133
- raise Retryable
134
- end
135
- return response
136
- rescue Retryable
137
- raise unless (retry_limit -= 1) > 0
138
- retry
139
- end
140
-
141
- def parse_response(base_uri, response)
66
+ def build_response(url, response)
142
67
  parser = type_handling.choose_parser(response.headers["Content-Type"])
143
- parser.to_graph(base_uri, response.body_string)
144
- end
68
+ graph = parser.to_graph(url, response.body_string)
145
69
 
146
- def build_response(request, response)
147
- graph = parse_response(request.url, response)
148
- response = GraphResponse.new(request, response)
70
+ response = GraphResponse.new(url, response)
149
71
  response.graph = graph
150
72
  return response
151
73
  rescue ContentHandling::UnrecognizedType
152
- return UnparseableResponse.new(request, response)
74
+ return UnparseableResponse.new(url, response)
153
75
  end
154
76
  end
155
77
  end
@@ -0,0 +1,79 @@
1
+ require 'base64'
2
+ require 'addressable/uri'
3
+
4
+ module RoadForest
5
+ module HTTP
6
+ #Manages user credentials for HTTP Basic auth
7
+ class Keychain
8
+ class Credentials < Struct.new(:user, :secret)
9
+ def header_value
10
+ "Basic #{Base64.strict_encode64("#{user}:#{secret}")}"
11
+ end
12
+ end
13
+
14
+ def initialize
15
+ @realm_for_url = {}
16
+ @with_realm = {}
17
+ end
18
+
19
+ def add(url, user, secret, realm=nil)
20
+ creds = Credentials.new(user, secret)
21
+ add_credentials(url, creds, realm || :default)
22
+ end
23
+
24
+ def add_credentials(url, creds, realm)
25
+ if url.to_s[-1] != "/"
26
+ url << "/"
27
+ end
28
+ @realm_for_url[url.to_s] = realm
29
+
30
+ url = Addressable::URI.parse(url)
31
+ url.path = "/"
32
+ @with_realm[[url.to_s,realm]] = creds
33
+ end
34
+
35
+ BASIC_SCHEME = /basic\s+realm=(?<q>['"])(?<realm>(?:(?!['"]).)*)\k<q>/i
36
+
37
+ def challenge_response(url, challenge)
38
+ if (match = BASIC_SCHEME.match(challenge)).nil?
39
+ return nil
40
+ end
41
+ realm = match[:realm]
42
+
43
+ response(url, realm)
44
+ end
45
+
46
+ def response(url, realm)
47
+ lookup_url = Addressable::URI.parse(url)
48
+ lookup_url.path = "/"
49
+ creds = @with_realm[[lookup_url.to_s,realm]]
50
+ if creds.nil? and not realm.nil?
51
+ creds = missing_credentials(url, realm)
52
+ unless creds.nil?
53
+ add_credentials(url, creds, realm)
54
+ end
55
+ end
56
+
57
+ return nil if creds.nil?
58
+
59
+ return creds.header_value
60
+ end
61
+
62
+ def missing_credentials(url, realm)
63
+ nil
64
+ end
65
+
66
+ def preemptive_response(url)
67
+ url = Addressable::URI.parse(url)
68
+
69
+ while (realm = @realm_for_url[url.to_s]).nil?
70
+ new_url = url.join("..")
71
+ break if new_url == url
72
+ url = new_url
73
+ end
74
+
75
+ return response(url, realm)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -47,9 +47,17 @@ module RoadForest
47
47
  end
48
48
 
49
49
  def inspection_payload
50
+ if body.respond_to? :pos
51
+ [headers.inspect, inspection_stream]
52
+ else
53
+ [headers.inspect, body]
54
+ end
55
+ end
56
+
57
+ def inspection_stream
50
58
  old_pos = body.pos
51
59
  body.rewind
52
- [headers.inspect, body.read]
60
+ body.read
53
61
  ensure
54
62
  body.pos = old_pos
55
63
  end