roadforest 0.5 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/file-management.rb +70 -58
- data/lib/roadforest/application.rb +9 -17
- data/lib/roadforest/application/dispatcher.rb +76 -9
- data/lib/roadforest/application/parameters.rb +9 -1
- data/lib/roadforest/application/path-provider.rb +30 -3
- data/lib/roadforest/application/route-adapter.rb +96 -14
- data/lib/roadforest/application/services-host.rb +21 -3
- data/lib/roadforest/augment/affordance.rb +82 -11
- data/lib/roadforest/augment/augmentation.rb +24 -6
- data/lib/roadforest/augment/augmenter.rb +12 -3
- data/lib/roadforest/authorization.rb +7 -229
- data/lib/roadforest/authorization/auth-entity.rb +26 -0
- data/lib/roadforest/authorization/authentication-chain.rb +79 -0
- data/lib/roadforest/authorization/default-authentication-store.rb +33 -0
- data/lib/roadforest/authorization/grant-builder.rb +23 -0
- data/lib/roadforest/authorization/grants-holder.rb +58 -0
- data/lib/roadforest/authorization/manager.rb +85 -0
- data/lib/roadforest/authorization/policy.rb +19 -0
- data/lib/roadforest/graph/access-manager.rb +25 -2
- data/lib/roadforest/graph/focus-list.rb +4 -0
- data/lib/roadforest/graph/graph-focus.rb +30 -13
- data/lib/roadforest/graph/nav-affordance-builder.rb +62 -0
- data/lib/roadforest/graph/normalization.rb +3 -3
- data/lib/roadforest/graph/path-vocabulary.rb +64 -0
- data/lib/roadforest/graph/post-focus.rb +5 -0
- data/lib/roadforest/graph/vocabulary.rb +4 -1
- data/lib/roadforest/http/adapters/excon.rb +4 -0
- data/lib/roadforest/http/graph-transfer.rb +17 -1
- data/lib/roadforest/http/keychain.rb +121 -33
- data/lib/roadforest/http/user-agent.rb +5 -3
- data/lib/roadforest/interface/application.rb +25 -8
- data/lib/roadforest/interface/rdf.rb +114 -15
- data/lib/roadforest/interface/utility.rb +3 -0
- data/lib/roadforest/interface/utility/backfill.rb +63 -0
- data/lib/roadforest/interface/utility/grant-list.rb +45 -0
- data/lib/roadforest/interface/utility/grant.rb +22 -0
- data/lib/roadforest/interfaces.rb +1 -0
- data/lib/roadforest/path-matcher.rb +471 -0
- data/lib/roadforest/remote-host.rb +159 -35
- data/lib/roadforest/resource/read-only.rb +23 -4
- data/lib/roadforest/server.rb +32 -3
- data/lib/roadforest/source-rigor/graph-store.rb +0 -2
- data/lib/roadforest/source-rigor/rigorous-access.rb +138 -21
- data/lib/roadforest/templates/affordance-property-values.haml +3 -0
- data/lib/roadforest/templates/rdfpost-curie.haml +1 -1
- data/lib/roadforest/test-support/matchers.rb +41 -12
- data/lib/roadforest/test-support/remote-host.rb +3 -3
- data/lib/roadforest/type-handlers/rdfa-writer/environment-decorator.rb +1 -1
- data/lib/roadforest/type-handlers/rdfa-writer/render-engine.rb +40 -27
- data/lib/roadforest/type-handlers/rdfa.rb +10 -3
- data/lib/roadforest/utility/class-registry.rb +44 -4
- data/spec/affordance-augmenter.rb +46 -19
- data/spec/affordances-flow.rb +46 -30
- data/spec/authorization.rb +16 -4
- data/spec/client.rb +22 -4
- data/spec/focus-list.rb +24 -0
- data/spec/full-integration.rb +8 -3
- data/spec/graph-store.rb +8 -0
- data/spec/keychain.rb +18 -14
- data/spec/rdf-normalization.rb +32 -6
- data/spec/update-focus.rb +36 -39
- 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
|
-
|
58
|
-
|
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,
|
12
|
+
def initialize(route_name, params, path_provider, services)
|
13
13
|
@route_name = route_name
|
14
14
|
@params = params
|
15
|
-
@
|
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, :
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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
|
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
|
-
|
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
|
-
|
18
|
-
|
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
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
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,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
|
@@ -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
|