roadforest 0.0.3 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
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