roadforest 0.5 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/examples/file-management.rb +70 -58
  3. data/lib/roadforest/application.rb +9 -17
  4. data/lib/roadforest/application/dispatcher.rb +76 -9
  5. data/lib/roadforest/application/parameters.rb +9 -1
  6. data/lib/roadforest/application/path-provider.rb +30 -3
  7. data/lib/roadforest/application/route-adapter.rb +96 -14
  8. data/lib/roadforest/application/services-host.rb +21 -3
  9. data/lib/roadforest/augment/affordance.rb +82 -11
  10. data/lib/roadforest/augment/augmentation.rb +24 -6
  11. data/lib/roadforest/augment/augmenter.rb +12 -3
  12. data/lib/roadforest/authorization.rb +7 -229
  13. data/lib/roadforest/authorization/auth-entity.rb +26 -0
  14. data/lib/roadforest/authorization/authentication-chain.rb +79 -0
  15. data/lib/roadforest/authorization/default-authentication-store.rb +33 -0
  16. data/lib/roadforest/authorization/grant-builder.rb +23 -0
  17. data/lib/roadforest/authorization/grants-holder.rb +58 -0
  18. data/lib/roadforest/authorization/manager.rb +85 -0
  19. data/lib/roadforest/authorization/policy.rb +19 -0
  20. data/lib/roadforest/graph/access-manager.rb +25 -2
  21. data/lib/roadforest/graph/focus-list.rb +4 -0
  22. data/lib/roadforest/graph/graph-focus.rb +30 -13
  23. data/lib/roadforest/graph/nav-affordance-builder.rb +62 -0
  24. data/lib/roadforest/graph/normalization.rb +3 -3
  25. data/lib/roadforest/graph/path-vocabulary.rb +64 -0
  26. data/lib/roadforest/graph/post-focus.rb +5 -0
  27. data/lib/roadforest/graph/vocabulary.rb +4 -1
  28. data/lib/roadforest/http/adapters/excon.rb +4 -0
  29. data/lib/roadforest/http/graph-transfer.rb +17 -1
  30. data/lib/roadforest/http/keychain.rb +121 -33
  31. data/lib/roadforest/http/user-agent.rb +5 -3
  32. data/lib/roadforest/interface/application.rb +25 -8
  33. data/lib/roadforest/interface/rdf.rb +114 -15
  34. data/lib/roadforest/interface/utility.rb +3 -0
  35. data/lib/roadforest/interface/utility/backfill.rb +63 -0
  36. data/lib/roadforest/interface/utility/grant-list.rb +45 -0
  37. data/lib/roadforest/interface/utility/grant.rb +22 -0
  38. data/lib/roadforest/interfaces.rb +1 -0
  39. data/lib/roadforest/path-matcher.rb +471 -0
  40. data/lib/roadforest/remote-host.rb +159 -35
  41. data/lib/roadforest/resource/read-only.rb +23 -4
  42. data/lib/roadforest/server.rb +32 -3
  43. data/lib/roadforest/source-rigor/graph-store.rb +0 -2
  44. data/lib/roadforest/source-rigor/rigorous-access.rb +138 -21
  45. data/lib/roadforest/templates/affordance-property-values.haml +3 -0
  46. data/lib/roadforest/templates/rdfpost-curie.haml +1 -1
  47. data/lib/roadforest/test-support/matchers.rb +41 -12
  48. data/lib/roadforest/test-support/remote-host.rb +3 -3
  49. data/lib/roadforest/type-handlers/rdfa-writer/environment-decorator.rb +1 -1
  50. data/lib/roadforest/type-handlers/rdfa-writer/render-engine.rb +40 -27
  51. data/lib/roadforest/type-handlers/rdfa.rb +10 -3
  52. data/lib/roadforest/utility/class-registry.rb +44 -4
  53. data/spec/affordance-augmenter.rb +46 -19
  54. data/spec/affordances-flow.rb +46 -30
  55. data/spec/authorization.rb +16 -4
  56. data/spec/client.rb +22 -4
  57. data/spec/focus-list.rb +24 -0
  58. data/spec/full-integration.rb +8 -3
  59. data/spec/graph-store.rb +8 -0
  60. data/spec/keychain.rb +18 -14
  61. data/spec/rdf-normalization.rb +32 -6
  62. data/spec/update-focus.rb +36 -39
  63. metadata +19 -5
@@ -54,12 +54,14 @@ module RoadForest
54
54
  when 401
55
55
  #XXX What if challenge matches existing Auth header? i.e. current
56
56
  #creds are wrong?
57
- request.headers["Authorization"] = keychain.challenge_response(url, response.headers["WWW-Authenticate"])
58
- raise Retryable
57
+ authn_response = keychain.challenge_response(request.url, response.headers["WWW-Authenticate"])
58
+ if !authn_response.nil? and authn_response != request.headers["Authorization"]
59
+ request.headers["Authorization"] = authn_response
60
+ raise Retryable
61
+ end
59
62
  end
60
63
  return response
61
64
  rescue Retryable
62
- raise unless (retry_limit -= 1) > 0
63
65
  retry
64
66
  end
65
67
 
@@ -9,29 +9,33 @@ module RoadForest
9
9
  end
10
10
 
11
11
  class Application
12
- def initialize(route_name, params, router, services)
12
+ def initialize(route_name, params, path_provider, services)
13
13
  @route_name = route_name
14
14
  @params = params
15
- @router = router
15
+ @path_provider = path_provider
16
16
  @services = services
17
17
  @data = nil
18
18
  @response_values = {}
19
19
  end
20
- attr_reader :route_name, :params, :services, :data, :router
20
+ attr_reader :route_name, :params, :services, :data, :path_provider
21
21
  attr_reader :response_values
22
22
 
23
23
  #@!group Utility methods
24
24
 
25
25
  def path_for(route_name = nil, params = nil)
26
- router.path_for(route_name, params || self.params)
26
+ path_provider.path_for(route_name, params || self.params)
27
27
  end
28
28
 
29
29
  def url_for(route_name, params = nil)
30
- Addressable::URI.parse(canonical_host.to_s).join(path_for(route_name, params))
30
+ ::RDF::URI.new(Addressable::URI.parse(canonical_host.to_s).join(path_for(route_name, params)))
31
+ end
32
+
33
+ def pattern_for(route_name, vals = nil, extra = nil)
34
+ path_provider.pattern_for(route_name, vals, extra)
31
35
  end
32
36
 
33
37
  def interface_for(route_name = nil, params = nil)
34
- router.interface_for(route_name, params || self.params)
38
+ path_provider.interface_for(route_name, params || self.params)
35
39
  end
36
40
 
37
41
  def canonical_uri
@@ -56,12 +60,13 @@ module RoadForest
56
60
  end
57
61
  end
58
62
 
59
- def authorization(method, header)
63
+ def authorization(request)
64
+ method = request.method
60
65
  required = required_grants(method)
61
66
  if required.empty?
62
67
  :public
63
68
  else
64
- services.authz.authorization(header, required_grants(method))
69
+ services.authz.authorization(request, required)
65
70
  end
66
71
  end
67
72
 
@@ -80,6 +85,10 @@ module RoadForest
80
85
 
81
86
  #group Resource interface
82
87
 
88
+ def error_data(status)
89
+ nil
90
+ end
91
+
83
92
  def exists?
84
93
  !data.nil?
85
94
  end
@@ -124,6 +133,14 @@ module RoadForest
124
133
  nil
125
134
  end
126
135
 
136
+ def update_payload
137
+ nil
138
+ end
139
+
140
+ def create_payload
141
+ nil
142
+ end
143
+
127
144
  def update(data)
128
145
  raise NotImplementedError
129
146
  end
@@ -1,12 +1,109 @@
1
1
  require 'roadforest/interface/application'
2
2
 
3
3
  module RoadForest
4
+ Payload = Struct.new(:root, :graph)
5
+
6
+ module Graph
7
+ module Helpers
8
+ module Focus
9
+ def start_focus(graph = nil, resource_url=nil)
10
+ graph ||= ::RDF::Graph.new
11
+ access = RoadForest::Graph::WriteManager.new
12
+ access.source_graph = graph
13
+ focus = RoadForest::Graph::GraphFocus.new(access, resource_url || my_url)
14
+
15
+ yield focus if block_given?
16
+ return graph
17
+ end
18
+ end
19
+
20
+ module Payloads
21
+ include Focus
22
+
23
+ def payload_blocks
24
+ @payload_blocks ||= {}
25
+ end
26
+
27
+ def payload_block(domain, type, &block)
28
+ payload_blocks[[domain, type]] = block
29
+ end
30
+
31
+ def backfill_payload(domain, type, root)
32
+ if payload_blocks.has_key?([domain, type])
33
+ start_focus(nil, root) do |focus|
34
+ payload_blocks[[domain, type]][focus]
35
+ end
36
+ end
37
+ end
38
+
39
+ def payload_method(method_name, domain, type, &block)
40
+ payload_block(domain, type, &block)
41
+ define_method method_name do
42
+ backfill_route = path_provider.find_route do |route|
43
+ klass = route.interface_class
44
+ next if klass.nil?
45
+ next unless klass.respond_to? :domains
46
+ next unless klass.respond_to? :types
47
+ klass.domains.include?(domain) and klass.types.include?(type)
48
+ end
49
+ return nil if backfill_route.nil?
50
+
51
+ klass = backfill_route.interface_class
52
+
53
+ root_node = url_for(backfill_route.name) + klass.fragment_for(route_name, type)
54
+ return Payload.new(root_node, nil)
55
+ end
56
+ end
57
+
58
+ def payload_for_update(domain = nil, &block)
59
+ payload_method(:update_payload, domain || :general, :update, &block)
60
+ end
61
+
62
+ def payload_for_create(domain = nil, &block)
63
+ payload_method(:create_payload, domain || :general, :create, &block)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
4
69
  module Interface
5
70
  class RDF < Application
6
- include RoadForest::Graph::Etagging
71
+ include Graph::Etagging
72
+ include Graph::Helpers::Focus
73
+
74
+ # Utility method, useful for overriding #update_payload and
75
+ # #create_payload
76
+ def payload_pair
77
+ root_node = ::RDF::Node.new
78
+ graph = ::RDF::Graph.new
79
+ graph << [root_node, ::RDF.type, Graph::Path.Root]
80
+ yield root_node, graph
81
+ return Payload.new(root_node, graph)
82
+ end
7
83
 
8
84
  def update(graph)
9
- graph_update(start_focus(graph))
85
+ start_focus(graph) do |focus|
86
+ graph_update(focus)
87
+ end
88
+ end
89
+
90
+ def error_data(status)
91
+ case status
92
+ when 401
93
+ begin
94
+ perm_list_pattern = pattern_for(:perm_list, {}, [:username])
95
+
96
+ graph = ::RDF::Graph.new
97
+ perm_aff = ::RDF::Node.new
98
+ perm_pattern = ::RDF::Node.new
99
+ graph << [perm_aff, ::RDF.type, Graph::Af.Navigate]
100
+ graph << [perm_aff, Graph::Af.target, perm_pattern]
101
+ graph << [perm_pattern, Graph::Af.pattern, perm_list_pattern]
102
+
103
+ rescue KeyError
104
+ nil
105
+ end
106
+ end
10
107
  end
11
108
 
12
109
  def graph_update(focus)
@@ -14,8 +111,10 @@ module RoadForest
14
111
  end
15
112
 
16
113
  def add_graph_child(graph)
17
- add_child(start_focus(graph))
18
- new_graph
114
+ start_focus(graph) do |focus|
115
+ add_child(focus)
116
+ end
117
+ new_graph #XXX?
19
118
  end
20
119
 
21
120
  def add_child(focus)
@@ -29,13 +128,10 @@ module RoadForest
29
128
  def fill_graph(graph)
30
129
  end
31
130
 
32
- def start_focus(graph, resource_url=nil)
33
- access = RoadForest::Graph::WriteManager.new
34
- access.source_graph = graph
35
- focus = RoadForest::Graph::GraphFocus.new(access, resource_url || my_url)
36
-
37
- yield focus if block_given?
38
- return focus
131
+ def payload_focus(&block)
132
+ payload_pair do |root, graph|
133
+ start_focus(graph, root, &block)
134
+ end
39
135
  end
40
136
 
41
137
  def copy_interface(node, route_name, params=nil)
@@ -61,16 +157,19 @@ module RoadForest
61
157
  current_graph
62
158
  end
63
159
 
160
+ def augment_graph(graph)
161
+ services.augmenter.augment(graph)
162
+ end
163
+
64
164
  def current_graph
65
165
  return response_data if response_values.has_key?(:data)
66
166
  new_graph
67
167
  end
68
168
 
69
169
  def new_graph
70
- graph = ::RDF::Graph.new
71
- focus = start_focus(graph, my_url)
72
- fill_graph(focus)
73
- self.response_data = graph
170
+ self.response_data = augment_graph(start_focus do |focus|
171
+ fill_graph(focus)
172
+ end)
74
173
  end
75
174
  end
76
175
  end
@@ -0,0 +1,3 @@
1
+ require 'roadforest/interface/utility/backfill'
2
+ require 'roadforest/interface/utility/grant-list'
3
+ require 'roadforest/interface/utility/grant'
@@ -0,0 +1,63 @@
1
+ require 'roadforest/interface/rdf'
2
+
3
+ module RoadForest
4
+ module Utility
5
+ class Backfill < Interface::RDF
6
+ # A backfill domain is an opaque selector for backfills and their client
7
+ # resources to coordinate with. Use them to partition backfills by the
8
+ # stability of interface, or to group like resources or whatever.
9
+ # Backfill domain names will never be exposed outside of the server.
10
+ def self.domains
11
+ [:general]
12
+ end
13
+
14
+ def domains
15
+ self.class.domains
16
+ end
17
+
18
+ # Backfill types are used to select within a resource which backfill is
19
+ # being rendered - the defaults are :update and :create to correspond
20
+ # with PUT and POST methods and af:Update and af:Create.
21
+ def self.types
22
+ [:update, :create]
23
+ end
24
+
25
+ def types
26
+ self.class.types
27
+ end
28
+
29
+ def root_for(name, domain, type)
30
+ my_url + self.class.fragment_for(name, type)
31
+ end
32
+
33
+ def self.fragment_for(name, type)
34
+ "##{name}-#{type}"
35
+ end
36
+
37
+ def new_graph
38
+ graph = ::RDF::Graph.new
39
+ path_provider.each_name_and_route do |name, route|
40
+ interface_class = route.interface_class
41
+ next if interface_class.nil?
42
+ next unless interface_class.respond_to? :backfill_payload
43
+
44
+ domains.each do |domain|
45
+ types.each do |type|
46
+ payload_graph = interface_class.backfill_payload(domain, type, root_for(name, domain, type))
47
+
48
+ next if payload_graph.nil?
49
+
50
+ payload_graph.each_statement do |stmt|
51
+ graph << stmt
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ graph
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,45 @@
1
+ require 'roadforest/interface/rdf'
2
+
3
+ module RoadForest
4
+ module Utility
5
+ class GrantList < Interface::RDF
6
+ def self.path_params
7
+ [ :username ]
8
+ end
9
+
10
+ def required_grants(method)
11
+ if method == "GET"
12
+ services.authz.build_grants do |grants|
13
+ grants.add(:is, :name => params[:username])
14
+ grants.add(:admin)
15
+ end
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def data
22
+ entity = Authorization::AuthEntity.new
23
+ entity.username = params[:username]
24
+ services.authz.policy.grants_for(entity)
25
+ end
26
+
27
+ def new_graph
28
+ perm_route = nil
29
+ begin
30
+ perm_route = path_provider.route_for_name(:perm)
31
+ rescue KeyError
32
+ end
33
+ start_focus do |focus|
34
+ data.each do |grant|
35
+ if perm_route.nil?
36
+ focus.add(:af, :grants, grant)
37
+ else
38
+ focus.add(:af, :grants, path_provider.url_for(:perm, {:grant_name => grant}))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ module RoadForest
2
+ module Utility
3
+ class Grant < Interface::RDF
4
+ def path_params
5
+ [ :grant_name ]
6
+ end
7
+
8
+ def required_grants(method)
9
+ #except in the unlikely case that a grant hashes to "NSG"
10
+ [ params[:grant_name] || "no_such_grant" ]
11
+ end
12
+
13
+ def data
14
+ [ params[:grant_name] || "no_such_grant" ]
15
+ end
16
+
17
+ def new_graph
18
+ ::RDF::Graph.new
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,2 +1,3 @@
1
1
  require 'roadforest/interface/rdf'
2
2
  require 'roadforest/interface/blob'
3
+ require 'roadforest/interface/utility'
@@ -0,0 +1,471 @@
1
+ require 'rdf'
2
+ require 'roadforest/graph/vocabulary'
3
+
4
+ module RoadForest
5
+ class PathMatcher
6
+ class Failure < ::StandardError; end
7
+ class NoMatch < Failure; end
8
+
9
+ class Match
10
+ def initialize(matcher)
11
+ @success = matcher.completed_child.accepting?
12
+ @graph = if @success
13
+ statements = matcher.completed_child.matched_statements.keys
14
+ ::RDF::Graph.new.tap do |graph|
15
+ statements.each do |stmt|
16
+ graph << stmt
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def success?
23
+ @success
24
+ end
25
+ alias successful? success?
26
+ alias succeed? success?
27
+
28
+ def graph
29
+ if success?
30
+ @graph
31
+ else
32
+ raise NoMatch, "Pattern doesn't match graph"
33
+ end
34
+ end
35
+ end
36
+
37
+ class MatchStep
38
+ attr_accessor :parent
39
+ attr_accessor :stem
40
+ attr_accessor :repeats
41
+ attr_accessor :satified
42
+ attr_accessor :pattern
43
+ attr_accessor :graph
44
+ attr_accessor :graph_term
45
+ attr_accessor :pattern_step
46
+
47
+ attr_accessor :exact_value, :before, :after, :order, :type
48
+
49
+ attr_reader :children
50
+
51
+ def initialize
52
+ @children = nil
53
+ reset
54
+ yield self
55
+ @satisfied ||= {}
56
+ @stem ||= {}
57
+ @repeats ||= {}
58
+ end
59
+
60
+ def reset
61
+ end
62
+
63
+ def pretty_print_instance_variables
64
+ instance_variables.reject do |var|
65
+ var == :@parent
66
+ end
67
+ end
68
+
69
+ def immediate_match
70
+ {}
71
+ end
72
+
73
+ def open
74
+ if excluded?
75
+ return @children = []
76
+ end
77
+
78
+ @children ||= build_children
79
+
80
+ return children
81
+ end
82
+
83
+ def matched_statements
84
+ return {} unless accepting?
85
+ @matched_statements ||=
86
+ begin
87
+ children.map do |child|
88
+ child.matched_statements
89
+ end.inject(immediate_match) do |set, matched|
90
+ set.merge(matched)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ class Edge < MatchStep
97
+ attr_accessor :predicate
98
+
99
+ attr_reader :accepting_count, :rejecting_count
100
+ attr_accessor :min_multi, :max_multi, :min_repeat, :max_repeat
101
+
102
+ alias child_nodes children
103
+
104
+ class << self
105
+ def find_child_edges(node)
106
+ node.pattern.query(edge_query_pattern(node.pattern_step)).map do |solution|
107
+ new do |edge|
108
+ edge.from(node, solution)
109
+ end
110
+ end
111
+ end
112
+
113
+ def edge_query_pattern(pattern_node)
114
+ path_direction = self.path_direction
115
+ RDF::Query.new do
116
+ pattern [ pattern_node, path_direction, :next ]
117
+ pattern [ :next, Graph::Path.predicate, :predicate ]
118
+ pattern [ :next, Graph::Path.minMulti, :min_multi ], :optional => true
119
+ pattern [ :next, Graph::Path.maxMulti, :max_multi ], :optional => true
120
+ pattern [ :next, Graph::Path.minRepeat, :min_repeat ], :optional => true
121
+ pattern [ :next, Graph::Path.maxRepeat, :max_repeat ], :optional => true
122
+ pattern [ :next, Graph::Path.is, :exact_value ], :optional => true
123
+ pattern [ :next, Graph::Path.after, :after ], :optional => true
124
+ pattern [ :next, Graph::Path.before, :before ], :optional => true
125
+ pattern [ :next, Graph::Path.order, :order ], :optional => true
126
+ pattern [ :next, Graph::Path.type, :type ], :optional => true
127
+ end
128
+ end
129
+ end
130
+
131
+ def from(node, solution)
132
+ self.parent = node
133
+
134
+ self.pattern = node.pattern
135
+ self.graph = node.graph
136
+
137
+ self.stem = node.stem.merge(node.statement => true)
138
+ self.repeats = node.repeats
139
+ self.graph_term = node.graph_term
140
+
141
+ self.predicate = solution[:predicate]
142
+ unless solution[:min_multi].nil? and solution[:max_multi].nil?
143
+ self.min_multi = solution[:min_multi].nil? ? 0 : solution[:min_multi].object
144
+ self.max_multi = solution[:max_multi].nil? ? nil : solution[:max_multi].object
145
+ end
146
+ unless solution[:min_repeat].nil? and solution[:max_repeat].nil?
147
+ self.min_repeat = solution[:max_repeat].nil? ? 0 : solution[:min_repeat].object
148
+ self.max_repeat = solution[:max_repeat].nil? ? nil : solution[:max_repeat].object
149
+ end
150
+
151
+ self.exact_value = solution[:exact_value]
152
+
153
+ self.pattern_step = solution[:next]
154
+ self.after = solution[:after]
155
+ self.before = solution[:before]
156
+ self.order = solution[:order]
157
+ self.type = solution[:type]
158
+ end
159
+
160
+ def to_s
161
+ state = case
162
+ when !resolved?
163
+ "?"
164
+ when accepting?
165
+ "Acpt"
166
+ when rejecting?
167
+ "Rjct"
168
+ end
169
+ "<#{self.class.name.sub(/.*::/,'')} #{predicate}*M:#{min_multi}(<?#{available_count rescue "-"})-(#{accepting_count rescue "-"}<?)#{max_multi} R:#{min_repeat}-#{max_repeat}:#{step_count} #{state} >"
170
+ end
171
+
172
+ def step_count
173
+ repeats.fetch(pattern_step, 0)
174
+ end
175
+
176
+ def excluded?
177
+ step_count >= max_repeat
178
+ end
179
+
180
+ def satisfied?
181
+ step_count >= min_repeat
182
+ end
183
+
184
+ def reset
185
+ @accepting_count = 0
186
+ @rejecting_count = 0
187
+ @min_multi = 1
188
+ @max_multi = 1
189
+ @min_repeat = 1
190
+ @max_repeat = 1
191
+ end
192
+
193
+ def notify_resolved(child)
194
+ @accepting_count += 1 if child.accepting?
195
+ @rejecting_count += 1 if child.rejecting?
196
+ end
197
+
198
+ def resolved?
199
+ return false if children.nil?
200
+ @resolved ||=
201
+ begin
202
+ if rejecting?
203
+ true
204
+ else
205
+ not children.any? do |node|
206
+ not node.resolved?
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ def rejecting?
213
+ return false if children.nil?
214
+ return false if excluded?
215
+ return false if satisfied?
216
+ (not max_multi.nil? and accepting_count > max_multi) or available_count < min_multi
217
+ end
218
+
219
+ def accepting?
220
+ return false if children.nil?
221
+ resolved? and not rejecting?
222
+ end
223
+
224
+ def available_count
225
+ child_nodes.length - rejecting_count
226
+ end
227
+
228
+ def build_children
229
+ Node.find_child_nodes(self)
230
+ end
231
+ end
232
+
233
+ class ForwardEdge < Edge
234
+ def self.path_direction
235
+ Graph::Path.forward
236
+ end
237
+
238
+ def pattern_hash
239
+ { :subject => graph_term, :predicate => predicate, :object => exact_value}
240
+ end
241
+
242
+ def graph_node(statement)
243
+ statement.object
244
+ end
245
+ end
246
+
247
+ class ReverseEdge < Edge
248
+ def self.path_direction
249
+ Graph::Path.reverse
250
+ end
251
+
252
+ def pattern_hash
253
+ { :subject => exact_value, :predicate => predicate, :object => graph_term }
254
+ end
255
+
256
+ def graph_node(statement)
257
+ statement.subject
258
+ end
259
+ end
260
+
261
+ class Node < MatchStep
262
+ attr_accessor :statement #the RDF statement that got here from parent
263
+
264
+ def self.find_child_nodes(edge)
265
+ edge.graph.query(edge.pattern_hash).map do |statement|
266
+ next if edge.stem.has_key?(statement)
267
+
268
+ Node.new do |node|
269
+ node.from(edge, statement)
270
+ end
271
+ end
272
+ end
273
+
274
+ def from(edge, statement)
275
+ self.parent = edge
276
+
277
+ self.pattern = edge.pattern
278
+ self.graph = edge.graph
279
+
280
+ self.stem = edge.stem
281
+ self.repeats = edge.repeats.merge({edge.pattern_step => edge.step_count + 1})
282
+ self.graph_term = edge.graph_node(statement)
283
+
284
+ self.statement = statement
285
+
286
+ self.pattern_step = edge.pattern_step
287
+ self.after = edge.after
288
+ self.before = edge.before
289
+ self.order = edge.order
290
+ self.type = edge.type
291
+ end
292
+
293
+ alias child_edges children
294
+
295
+ def to_s
296
+ state = case
297
+ when !resolved?
298
+ "?"
299
+ when accepting?
300
+ "Acpt"
301
+ when rejecting?
302
+ "Rjct"
303
+ end
304
+ "[#{self.class.name.sub(/.*::/,'')} #{statement} #{graph_term}/#{pattern_step} #{state} ]"
305
+ end
306
+
307
+ def immediate_match
308
+ statement.nil? ? {} : { statement => true }
309
+ end
310
+
311
+ def excluded?
312
+ stem.has_key?(statement)
313
+ end
314
+
315
+ def notify_resolved(child)
316
+
317
+ end
318
+
319
+ def reject_value?
320
+ unless before.nil? and after.nil?
321
+ return true if not (before.nil? or before > graph_term)
322
+ return true if not (after.nil? and after < graph_term)
323
+ end
324
+
325
+ unless type.nil?
326
+ return true if graph_term.datatype != type
327
+ end
328
+
329
+ return false
330
+ end
331
+
332
+ def resolved?
333
+ @resolved ||= accepting? or rejecting?
334
+ end
335
+
336
+ def accepting?
337
+ @accepting ||=
338
+ if excluded?
339
+ false
340
+ elsif children.nil?
341
+ false
342
+ elsif reject_value?
343
+ false
344
+ else
345
+ child_edges.all? do |edge|
346
+ edge.accepting?
347
+ end
348
+ end
349
+ end
350
+
351
+ def rejecting?
352
+ @rejecting ||=
353
+ begin
354
+ if excluded?
355
+ true
356
+ elsif children.nil?
357
+ false
358
+ elsif reject_value?
359
+ true
360
+ else
361
+ child_edges.any? do |edge|
362
+ edge.rejecting?
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ def build_children
369
+ ForwardEdge.find_child_edges(self) + ReverseEdge.find_child_edges(self)
370
+ end
371
+ end
372
+
373
+ def initialize()
374
+ @logging = false
375
+ reset
376
+ end
377
+
378
+ attr_accessor :pattern, :logging
379
+ attr_reader :completed_child
380
+ attr_writer :pattern_root
381
+
382
+ def match(root, graph)
383
+ reset
384
+ setup(root, graph)
385
+ search_iteration until complete?
386
+ return Match.new(self)
387
+ end
388
+
389
+ def reset
390
+ @search_queue = []
391
+ @completed_child = nil
392
+ end
393
+
394
+ def setup(root, graph)
395
+ add_matching_nodes([Node.new do |node|
396
+ node.parent = self
397
+ node.stem = {}
398
+ node.repeats = {}
399
+ node.pattern = pattern
400
+ node.graph = graph
401
+
402
+ node.statement = nil
403
+ node.graph_term = root
404
+ node.pattern_step = pattern_root
405
+ end
406
+ ])
407
+ end
408
+
409
+ def pattern_root
410
+ @pattern_root ||=
411
+ begin
412
+ roots = pattern.query(:predicate => ::RDF.type, :object => Graph::Path.Root).to_a
413
+ if roots.length != 1
414
+ raise "A pattern should have exactly one root, has: #{roots.length}\n#{roots.map(&:inspect).join("\n")}"
415
+ end
416
+ roots.first.subject
417
+ end
418
+ end
419
+
420
+ def complete?
421
+ !@completed_child.nil? or @search_queue.empty?
422
+ end
423
+
424
+ def notify_resolved(matching)
425
+ log "Resolved:", matching
426
+ @completed_child = matching
427
+ end
428
+
429
+ def search_iteration
430
+ matching = next_matching_node
431
+ unless matching.nil?
432
+ matching.open
433
+ if matching.children.empty?
434
+ log "No match:", matching
435
+ else
436
+ log "Matches for:", matching
437
+ end
438
+ add_matching_nodes(matching.children)
439
+
440
+ check_complete(matching)
441
+ end
442
+ end
443
+
444
+ def next_matching_node
445
+ @search_queue.pop #simple depth first
446
+ end
447
+
448
+ def add_matching_nodes(list)
449
+ list.each do |node|
450
+ log " Adding step:", node
451
+ end
452
+ @search_queue += list
453
+ end
454
+
455
+ def log(*args)
456
+ puts args.join(" ") if @logging
457
+ end
458
+
459
+ def resolved?
460
+ false
461
+ end
462
+
463
+ def check_complete(matching)
464
+ while matching.resolved?
465
+ log "Checking:", matching
466
+ matching.parent.notify_resolved(matching)
467
+ matching = matching.parent
468
+ end
469
+ end
470
+ end
471
+ end