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
@@ -3,11 +3,22 @@ require 'roadforest/source-rigor/credence-annealer'
|
|
3
3
|
require 'roadforest/source-rigor/rigorous-access'
|
4
4
|
require 'roadforest/source-rigor/graph-store' #XXX
|
5
5
|
require 'roadforest/graph/graph-focus'
|
6
|
+
require 'roadforest/graph/post-focus'
|
6
7
|
require 'roadforest/http/user-agent'
|
7
8
|
require 'roadforest/http/graph-transfer'
|
8
9
|
require 'roadforest/http/adapters/excon'
|
10
|
+
require 'addressable/template'
|
9
11
|
|
10
12
|
module RoadForest
|
13
|
+
# This is a client's main entry point in RoadForest - we instantiate a
|
14
|
+
# RemoteHost to represent the server in the local program and interact with
|
15
|
+
# it. The design goal is that, having created a RemoteHost object, you should
|
16
|
+
# be able to forget that it isn't, in fact, part of your program. So, the
|
17
|
+
# details of TCP (or indeed HTTP, or whatever the network is doing) become
|
18
|
+
# incidental to the abstraction.
|
19
|
+
#
|
20
|
+
# One consequence being that you should be able to use a mock host for
|
21
|
+
# testing.
|
11
22
|
class RemoteHost
|
12
23
|
include Graph::Normalization
|
13
24
|
|
@@ -15,6 +26,8 @@ module RoadForest
|
|
15
26
|
self.url = well_known_url
|
16
27
|
end
|
17
28
|
attr_reader :url
|
29
|
+
attr_accessor :grant_list_pattern
|
30
|
+
attr_writer :http_client
|
18
31
|
|
19
32
|
def url=(string)
|
20
33
|
@url = normalize_resource(string)
|
@@ -24,14 +37,18 @@ module RoadForest
|
|
24
37
|
SourceRigor::GraphStore.new
|
25
38
|
end
|
26
39
|
|
27
|
-
attr_writer :http_client
|
28
40
|
def http_client
|
29
41
|
@http_client ||= HTTP::ExconAdapter.new(url)
|
30
42
|
end
|
31
43
|
|
32
|
-
def
|
44
|
+
def http_trace=(target)
|
33
45
|
user_agent.trace = target
|
34
46
|
end
|
47
|
+
alias trace= http_trace=
|
48
|
+
|
49
|
+
def graph_trace=(target)
|
50
|
+
graph_transfer.trace = target
|
51
|
+
end
|
35
52
|
|
36
53
|
def user_agent
|
37
54
|
@user_agent ||= HTTP::UserAgent.new(http_client)
|
@@ -41,8 +58,25 @@ module RoadForest
|
|
41
58
|
@graph_transfer ||= HTTP::GraphTransfer.new(user_agent)
|
42
59
|
end
|
43
60
|
|
61
|
+
def use_ca_cert(cert)
|
62
|
+
http_client.connection_defaults.merge!(:ssl_ca_file => cert)
|
63
|
+
http_client.reset_connections
|
64
|
+
end
|
65
|
+
|
66
|
+
def use_client_tls(key, cert)
|
67
|
+
http_client.connection_defaults.merge!(:client_key => key, :client_cert => cert)
|
68
|
+
http_client.reset_connections
|
69
|
+
end
|
70
|
+
|
71
|
+
def prepared_credential_source
|
72
|
+
@prepared_credential_source ||=
|
73
|
+
HTTP::PreparedCredentialSource.new.tap do |prepd|
|
74
|
+
user_agent.keychain.add_source(prepd)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
44
78
|
def add_credentials(username, password)
|
45
|
-
|
79
|
+
prepared_credential_source.add(url, username, password)
|
46
80
|
end
|
47
81
|
|
48
82
|
def source_rigor
|
@@ -59,63 +93,153 @@ module RoadForest
|
|
59
93
|
end
|
60
94
|
|
61
95
|
def anneal(focus)
|
62
|
-
graph =
|
96
|
+
graph = focus.access_manager.source_graph
|
63
97
|
annealer = SourceRigor::CredenceAnnealer.new(graph)
|
64
98
|
annealer.resolve do
|
99
|
+
focus.reset
|
65
100
|
yield focus
|
66
101
|
end
|
67
102
|
end
|
68
103
|
|
69
|
-
|
104
|
+
class AuthorizationDecider
|
105
|
+
include Graph::Normalization
|
70
106
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
updater = Graph::GraphFocus.new(access, url)
|
107
|
+
def initialize(remote_host, focus)
|
108
|
+
@graph = SourceRigor::RetrieveManager.new
|
109
|
+
graph.rigor = remote_host.source_rigor
|
110
|
+
graph.source_graph = focus.access_manager.source_graph
|
76
111
|
|
77
|
-
|
112
|
+
@resource = focus.subject
|
113
|
+
@keychain = remote_host.user_agent.keychain
|
114
|
+
end
|
78
115
|
|
79
|
-
|
80
|
-
|
81
|
-
|
116
|
+
attr_reader :graph, :resource, :keychain, :grant_list_pattern
|
117
|
+
|
118
|
+
def forbidden?(method)
|
119
|
+
annealer = SourceRigor::CredenceAnnealer.new(graph.source_graph)
|
120
|
+
|
121
|
+
permissions = []
|
122
|
+
annealer.resolve do
|
123
|
+
permissions.clear
|
124
|
+
@grant_list_pattern = nil
|
125
|
+
|
126
|
+
graph.query(authby_query(method)) do |solution|
|
127
|
+
permissions << solution[:authz]
|
128
|
+
end
|
129
|
+
permissions.each do |grant|
|
130
|
+
return false if have_grant?(grant)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
return false if permissions.empty?
|
135
|
+
return true
|
82
136
|
end
|
83
137
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
138
|
+
def have_grant?(url)
|
139
|
+
creds = keychain.credentials_for(url)
|
140
|
+
if grant_list_pattern.nil? or creds.nil?
|
141
|
+
direct_check(url)
|
142
|
+
else
|
143
|
+
grant_list(creds).include?(url)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def direct_check(url)
|
148
|
+
statements = graph.query(:subject => url)
|
149
|
+
if !statements.empty?
|
150
|
+
return true
|
151
|
+
else
|
152
|
+
annealer = SourceRigor::CredenceAnnealer.new(graph.source_graph)
|
153
|
+
annealer.resolve do
|
154
|
+
graph.query(list_pattern_query(url)) do |solution|
|
155
|
+
@grant_list_pattern = solution[:pattern].value
|
156
|
+
end
|
157
|
+
end
|
158
|
+
return false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def grant_list(creds)
|
163
|
+
return [] if grant_list_pattern.nil?
|
164
|
+
template = Addressable::Template.new(grant_list_pattern)
|
165
|
+
grant_list_url = uri(template.expand( :username => creds.user.to_s ).to_s)
|
166
|
+
graph.query_resource_pattern(grant_list_url, :subject => grant_list_url, :predicate => Graph::Af.grants).map do |stmt|
|
167
|
+
stmt.object
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def list_pattern_query(url)
|
172
|
+
SourceRigor::ResourceQuery.new([], :subject_context => url) do
|
173
|
+
pattern [:af, ::RDF.type, Graph::Af.Navigate]
|
174
|
+
pattern [:af, Graph::Af.target, :pnode]
|
175
|
+
pattern [:pnode, Graph::Af.pattern, :pattern]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def affordance_type(method)
|
180
|
+
case method.downcase
|
181
|
+
when "get"
|
182
|
+
Graph::Af.Navigate
|
183
|
+
when "post"
|
184
|
+
Graph::Af.Create
|
185
|
+
when "put"
|
186
|
+
Graph::Af.Update
|
187
|
+
when "delete"
|
188
|
+
Graph::Af.Destroy
|
189
|
+
else
|
190
|
+
Graph::Af[method] #allow passthrough
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def authby_query(method)
|
195
|
+
af_type = affordance_type(method)
|
196
|
+
resource = self.resource
|
197
|
+
SourceRigor::ResourceQuery.new([], {:subject_context => resource}) do
|
198
|
+
pattern [:aff, Graph::Af.target, resource]
|
199
|
+
pattern [:aff, ::RDF.type, af_type]
|
200
|
+
pattern [:aff, Graph::Af.authorizedBy, :authz]
|
201
|
+
end
|
88
202
|
end
|
89
203
|
end
|
90
204
|
|
91
|
-
def
|
92
|
-
|
205
|
+
def forbidden?(method, focus)
|
206
|
+
decider = AuthorizationDecider.new(self, focus)
|
93
207
|
|
208
|
+
decider.forbidden?(method)
|
209
|
+
end
|
210
|
+
|
211
|
+
def transaction(manager_class, focus_class, &block)
|
94
212
|
graph = build_graph_store
|
95
|
-
access =
|
213
|
+
access = manager_class.new
|
96
214
|
access.rigor = source_rigor
|
97
215
|
access.source_graph = graph
|
98
|
-
|
216
|
+
focus = focus_class.new(access, url)
|
99
217
|
|
100
|
-
|
101
|
-
poster.graphs = graphs
|
218
|
+
anneal(focus, &block)
|
102
219
|
|
103
|
-
|
220
|
+
return focus
|
221
|
+
end
|
104
222
|
|
105
|
-
|
106
|
-
|
223
|
+
def putting(&block)
|
224
|
+
update = transaction(SourceRigor::UpdateManager, Graph::GraphFocus, &block)
|
225
|
+
|
226
|
+
access = update.access_manager
|
227
|
+
|
228
|
+
access.each_target do |context, graph|
|
229
|
+
graph_transfer.put(context, graph)
|
107
230
|
end
|
108
231
|
end
|
109
232
|
|
110
|
-
def
|
233
|
+
def posting(&block)
|
234
|
+
poster = transaction(SourceRigor::PostManager, Graph::PostFocus, &block)
|
111
235
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
reader = Graph::GraphFocus.new(access, url)
|
236
|
+
poster.graphs.each_pair do |url, graph|
|
237
|
+
graph_transfer.post(url, graph)
|
238
|
+
end
|
239
|
+
end
|
117
240
|
|
118
|
-
|
241
|
+
def getting(&block)
|
242
|
+
transaction(SourceRigor::RetrieveManager, Graph::GraphFocus, &block)
|
119
243
|
end
|
120
244
|
|
121
245
|
def put_file(destination, type, io)
|
@@ -124,7 +248,7 @@ module RoadForest
|
|
124
248
|
elsif destination.respond_to?(:to_s)
|
125
249
|
destination = destination.to_s
|
126
250
|
end
|
127
|
-
|
251
|
+
user_agent.make_request("PUT", destination, {"Content-Type" => type}, io)
|
128
252
|
end
|
129
253
|
|
130
254
|
#TODO:
|
@@ -20,9 +20,9 @@ module RoadForest
|
|
20
20
|
### RoadForest interface
|
21
21
|
|
22
22
|
def params
|
23
|
-
|
23
|
+
Application::Parameters.new do |params|
|
24
24
|
params.path_info = @request.path_info
|
25
|
-
params.query_params = @request.
|
25
|
+
params.query_params = @request.query
|
26
26
|
params.path_tokens = @request.path_tokens
|
27
27
|
end
|
28
28
|
end
|
@@ -84,12 +84,16 @@ module RoadForest
|
|
84
84
|
|
85
85
|
def content_types_provided
|
86
86
|
content_engine.renderers.type_map
|
87
|
-
rescue
|
87
|
+
rescue
|
88
88
|
super
|
89
89
|
end
|
90
90
|
|
91
|
+
def required_grants(method)
|
92
|
+
@interface.required_grants(method)
|
93
|
+
end
|
94
|
+
|
91
95
|
def is_authorized?(header)
|
92
|
-
@authorization = @interface.authorization(request
|
96
|
+
@authorization = @interface.authorization(request)
|
93
97
|
if(@authorization == :public || @authorization == :granted)
|
94
98
|
return true
|
95
99
|
end
|
@@ -98,6 +102,21 @@ module RoadForest
|
|
98
102
|
|
99
103
|
#XXX Add cache-control headers here
|
100
104
|
def finish_request
|
105
|
+
if (400..499).include? response.code
|
106
|
+
set_error_body(response.code)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def error_data(status)
|
111
|
+
@interface.error_data(status)
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_error_body(status)
|
115
|
+
data= error_data(status)
|
116
|
+
return "" if data.nil?
|
117
|
+
renderer = content_engine.choose_renderer(request.headers["Accept"])
|
118
|
+
response.headers["Content-Type"] = renderer.type.content_type_header
|
119
|
+
response.body = renderer.local_to_network(request_uri, data)
|
101
120
|
end
|
102
121
|
|
103
122
|
def resource_exists?
|
data/lib/roadforest/server.rb
CHANGED
@@ -3,16 +3,17 @@ require 'roadforest/application'
|
|
3
3
|
require 'roadforest/interfaces'
|
4
4
|
|
5
5
|
module RoadForest
|
6
|
-
def self.serve(
|
6
|
+
def self.serve(services)
|
7
7
|
require 'webrick/accesslog'
|
8
|
-
|
8
|
+
|
9
|
+
application = RoadForest::Application.new(services)
|
9
10
|
|
10
11
|
logfile = services.logger
|
11
12
|
logfile.info("#{Time.now.to_s}: Starting Roadforest server")
|
12
13
|
|
13
14
|
application.configure do |config|
|
14
15
|
config.adapter_options = {
|
15
|
-
:Logger => WEBrick::Log.new(logfile),
|
16
|
+
:Logger => WEBrick::Log.new(logfile, WEBrick::BasicLog::DEBUG ),
|
16
17
|
:AccessLog => [
|
17
18
|
[logfile, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
|
18
19
|
[logfile, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
|
@@ -22,4 +23,32 @@ module RoadForest
|
|
22
23
|
end
|
23
24
|
application.run
|
24
25
|
end
|
26
|
+
|
27
|
+
module SSL
|
28
|
+
class << self
|
29
|
+
def enable(config, key, cert)
|
30
|
+
require 'webrick/https'
|
31
|
+
key = OpenSSL::PKey::RSA.new(File.read(key))
|
32
|
+
cert = OpenSSL::X509::Certificate.new(File.read(cert))
|
33
|
+
config.adapter_options.merge!( :SSLEnable => true, :SSLPrivateKey => key, :SSLCertificate => cert,
|
34
|
+
:SSLCertName => [["CN", WEBrick::Utils::getservername]]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
def add_ca_cert(config, cert_file)
|
38
|
+
config.adapter_options.merge!( :SSLCACertificateFile => cert_file)
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClientCert
|
42
|
+
def client_cert
|
43
|
+
wreq = @body.instance_variable_get("@request")
|
44
|
+
wreq.client_cert
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_client_verify(config)
|
49
|
+
Webmachine::Request.instance_eval{include(ClientCert)}
|
50
|
+
config.adapter_options.merge!( :SSLEnable => true, :SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
25
54
|
end
|
@@ -122,8 +122,6 @@ module RoadForest
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def insert_statement(statement)
|
125
|
-
#puts "\n#{__FILE__}:#{__LINE__} => #{[self.object_id,
|
126
|
-
#statement].inspect}"
|
127
125
|
repository.insert(statement)
|
128
126
|
|
129
127
|
repository.delete([statement.context, expand_curie([:rf, "impulse"]), nil])
|
@@ -2,10 +2,13 @@ require 'roadforest/graph/access-manager'
|
|
2
2
|
require 'roadforest/source-rigor/resource-query'
|
3
3
|
require 'roadforest/source-rigor/resource-pattern'
|
4
4
|
require 'roadforest/source-rigor/parcel'
|
5
|
+
require 'roadforest/path-matcher'
|
5
6
|
|
6
7
|
module RoadForest
|
7
8
|
module SourceRigor
|
8
9
|
module Rigorous
|
10
|
+
Af = Graph::Af
|
11
|
+
|
9
12
|
attr_accessor :rigor
|
10
13
|
|
11
14
|
def dup
|
@@ -30,9 +33,21 @@ module RoadForest
|
|
30
33
|
execute_search(query, &block)
|
31
34
|
end
|
32
35
|
|
36
|
+
def resource_pattern_from(pattern, resource = nil)
|
37
|
+
ResourcePattern.from(pattern, {:context_roles => {:subject => (resource || self.resource)}, :source_rigor => rigor})
|
38
|
+
end
|
39
|
+
|
40
|
+
def query_resource_pattern(resource, pattern, &block)
|
41
|
+
execute_search(resource_pattern_from(pattern, resource), &block)
|
42
|
+
end
|
43
|
+
|
33
44
|
def query_pattern(pattern, &block)
|
34
|
-
|
35
|
-
|
45
|
+
execute_search(resource_pattern_from(pattern), &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete(statement)
|
49
|
+
statement = resource_pattern_from(statement)
|
50
|
+
destination_graph.delete(statement)
|
36
51
|
end
|
37
52
|
end
|
38
53
|
|
@@ -49,53 +64,155 @@ module RoadForest
|
|
49
64
|
|
50
65
|
def initialize
|
51
66
|
@copied_contexts = {}
|
67
|
+
@inserts = Hash.new{|h,k| h[k] = []}
|
68
|
+
@deletes = Hash.new{|h,k| h[k] = []}
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset
|
72
|
+
super
|
73
|
+
|
74
|
+
@copied_contexts.clear
|
75
|
+
@inserts.clear
|
76
|
+
@deletes.clear
|
77
|
+
|
78
|
+
source_graph.each_statement do |stmt|
|
79
|
+
target_graph << stmt
|
80
|
+
end
|
52
81
|
end
|
53
82
|
|
54
|
-
attr_accessor :copied_contexts
|
83
|
+
attr_accessor :copied_contexts, :inserts, :deletes
|
55
84
|
|
56
85
|
def dup
|
57
86
|
other = super
|
58
87
|
other.copied_contexts = self.copied_contexts
|
88
|
+
other.inserts = self.inserts
|
89
|
+
other.deletes = self.deletes
|
59
90
|
other.target_graph = self.target_graph
|
60
91
|
return other
|
61
92
|
end
|
62
93
|
|
63
94
|
def execute_search(search, &block)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
95
|
+
destination_enum = search.execute(destination_graph)
|
96
|
+
source_enum = search.execute(origin_graph)
|
97
|
+
|
98
|
+
enum = destination_enum.any?{ true } ? destination_enum : source_enum
|
99
|
+
|
100
|
+
enum.each(&block)
|
101
|
+
return enum
|
102
|
+
end
|
103
|
+
|
104
|
+
def record_insert(statement)
|
105
|
+
@inserts[statement] << resource
|
106
|
+
end
|
107
|
+
|
108
|
+
def record_delete(statement)
|
109
|
+
@deletes[statement] << resource
|
110
|
+
end
|
111
|
+
|
112
|
+
def statement_from(statement)
|
113
|
+
::RDF::Statement.from(statement)
|
70
114
|
end
|
71
115
|
|
72
116
|
def insert(statement)
|
73
|
-
|
117
|
+
statement = statement_from(statement)
|
118
|
+
record_insert(statement)
|
119
|
+
statement.context ||= ::RDF::URI.intern("urn:local-insert")
|
74
120
|
super
|
75
121
|
end
|
76
122
|
|
77
123
|
def delete(statement)
|
78
|
-
|
124
|
+
statement = statement_from(statement)
|
125
|
+
record_delete(statement)
|
79
126
|
super
|
80
127
|
end
|
81
128
|
|
129
|
+
def each_payload
|
130
|
+
update_payload_query = ::RDF::Query.new do
|
131
|
+
pattern [ :affordance, Af.target, :resource ]
|
132
|
+
pattern [ :affordance, Af.payload, :pattern_root ]
|
133
|
+
pattern [ :affordance, RDF.type, Af.Update ]
|
134
|
+
end
|
135
|
+
query(update_payload_query).each do |solution|
|
136
|
+
yield(solution[:resource], solution[:pattern_root], parceller.graph_for(solution[:pattern_root]))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def each_target
|
141
|
+
all_subjects = Hash[destination_graph.subjects.map{|s| [s,true]}]
|
142
|
+
source_graph.subjects.each do |s|
|
143
|
+
all_subjects[s] = true
|
144
|
+
end
|
145
|
+
check_inserts = inserts.dup
|
146
|
+
check_deletes = deletes.dup
|
147
|
+
marked_inserts = []
|
148
|
+
marked_deletes = []
|
149
|
+
|
150
|
+
each_payload do |root, pattern_root, graph_pattern|
|
151
|
+
next unless all_subjects.has_key?(root)
|
152
|
+
|
153
|
+
marked_inserts.clear
|
154
|
+
marked_deletes.clear
|
155
|
+
|
156
|
+
matcher = PathMatcher.new
|
157
|
+
matcher.pattern = graph_pattern
|
158
|
+
matcher.pattern_root = pattern_root
|
159
|
+
|
160
|
+
source_match = matcher.match(root, source_graph)
|
161
|
+
|
162
|
+
dest_match = matcher.match(root, destination_graph)
|
163
|
+
|
164
|
+
if dest_match.successful?
|
165
|
+
check_inserts.each_key do |stmt|
|
166
|
+
if dest_match.graph.has_statement?(stmt)
|
167
|
+
marked_inserts << stmt
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
if source_match.successful?
|
173
|
+
check_deletes.each_key do |stmt|
|
174
|
+
if source_match.graph.has_statement?(stmt)
|
175
|
+
marked_deletes << stmt
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
if dest_match.successful?
|
181
|
+
if !source_match.successful? || (source_match.graph != dest_match.graph)
|
182
|
+
|
183
|
+
yield(root, dest_match.graph)
|
184
|
+
|
185
|
+
marked_inserts.each do |stmt|
|
186
|
+
check_inserts.delete(stmt)
|
187
|
+
end
|
188
|
+
marked_deletes.each do |stmt|
|
189
|
+
check_deletes.delete(stmt)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
fallback_needed = {}
|
196
|
+
check_inserts.each_value do|resources|
|
197
|
+
resources.each {|resource| fallback_needed[resource] = true }
|
198
|
+
end
|
199
|
+
check_deletes.each_value do|resources|
|
200
|
+
resources.each {|resource| fallback_needed[resource] = true }
|
201
|
+
end
|
202
|
+
|
203
|
+
fallback_needed.each_key do |key|
|
204
|
+
yield(key, parceller.graph_for(key))
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
82
208
|
def parceller
|
83
209
|
@parceller ||=
|
84
210
|
begin
|
85
211
|
parceller = Parcel.new
|
86
|
-
parceller.graph =
|
212
|
+
parceller.graph = destination_graph
|
87
213
|
parceller
|
88
214
|
end
|
89
215
|
end
|
90
|
-
|
91
|
-
def copy_context
|
92
|
-
return if copied_contexts[resource]
|
93
|
-
parceller.graph_for(resource).each_statement do |statement|
|
94
|
-
statement.context = resource
|
95
|
-
destination_graph << statement
|
96
|
-
end
|
97
|
-
copied_contexts[resource] = true
|
98
|
-
end
|
99
216
|
end
|
100
217
|
end
|
101
218
|
end
|