roadforest 0.5 → 0.7

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 (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