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