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
@@ -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 trace=(target)
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
- user_agent.keychain.add(url, username, password)
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 = build_graph_store
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
- def putting(&block)
104
+ class AuthorizationDecider
105
+ include Graph::Normalization
70
106
 
71
- graph = build_graph_store
72
- access = SourceRigor::UpdateManager.new
73
- access.rigor = source_rigor
74
- access.source_graph = graph
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
- annealer = SourceRigor::CredenceAnnealer.new(graph)
112
+ @resource = focus.subject
113
+ @keychain = remote_host.user_agent.keychain
114
+ end
78
115
 
79
- annealer.resolve do
80
- access.target_graph = ::RDF::Repository.new
81
- yield updater
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
- target_graph = access.target_graph
85
- target_graph.each_context do |context|
86
- graph = ::RDF::Graph.new(context, :data => target_graph)
87
- graph_transfer.put(context, graph)
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 posting(&block)
92
- require 'roadforest/graph/post-focus'
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 = SourceRigor::PostManager.new
213
+ access = manager_class.new
96
214
  access.rigor = source_rigor
97
215
  access.source_graph = graph
98
- poster = Graph::PostFocus.new(access, url)
216
+ focus = focus_class.new(access, url)
99
217
 
100
- graphs = {}
101
- poster.graphs = graphs
218
+ anneal(focus, &block)
102
219
 
103
- anneal(poster, &block)
220
+ return focus
221
+ end
104
222
 
105
- graphs.each_pair do |url, graph|
106
- graph_transfer.post(url, graph)
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 getting(&block)
233
+ def posting(&block)
234
+ poster = transaction(SourceRigor::PostManager, Graph::PostFocus, &block)
111
235
 
112
- graph = build_graph_store
113
- access = SourceRigor::RetrieveManager.new
114
- access.rigor = source_rigor
115
- access.source_graph = graph
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
- anneal(reader, &block)
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
- response = user_agent.make_request("PUT", destination, {"Content-Type" => type}, io)
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
- params = Application::Parameters.new do |params|
23
+ Application::Parameters.new do |params|
24
24
  params.path_info = @request.path_info
25
- params.query_params = @request.query_params
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 => ex
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.method, header)
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?
@@ -3,16 +3,17 @@ require 'roadforest/application'
3
3
  require 'roadforest/interfaces'
4
4
 
5
5
  module RoadForest
6
- def self.serve(application, services)
6
+ def self.serve(services)
7
7
  require 'webrick/accesslog'
8
- application.services = services
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
- pattern = ResourcePattern.from(pattern, {:context_roles => {:subject => resource}, :source_rigor => rigor})
35
- execute_search(pattern, &block)
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
- enum = search.execute(destination_graph)
65
- if enum.any?{ true }
66
- enum.each(&block)
67
- return enum
68
- end
69
- search.execute(origin_graph, &block)
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
- copy_context
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
- copy_context
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 = source_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