triannon 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9936254da42efbcc7d7225b4f684097a2ac84f0f
4
- data.tar.gz: b0d8e6bcba4019fc9cef4300f484f7a2d11774db
3
+ metadata.gz: 98b04d335bf8e2b24385cfeb4088d9c8db62c5c8
4
+ data.tar.gz: e10c1dc27cd105c7acb4fee6de77f8f51dddac9e
5
5
  SHA512:
6
- metadata.gz: 90a6538ea8c10582a4e06f1e159947b1013784d7f2b7c8474811f13534f3c4806ed559b2456b304c5d51ce688d0b5f2ff44b2a43b518a181827fd3a32b3276bc
7
- data.tar.gz: efda4aab06e7a5939d44e61ab6a860be835b20301c2f699291b1b246ae59e3820563720860c0ef307fbb1b9d4447b695ed911fbd7534df4f16419f77a2dce349
6
+ metadata.gz: 6f46652fb5c45b9b47be3374919eb4fd9bcf79ae50bf69059ee3896640990cedd11c499684185ea85d9befb6820c17ec8e6e84fc6425fbd7cef979dc57b6ef31
7
+ data.tar.gz: e3967493035299c17a6e7235f288e0b212738e94b9817806869fb4fb737bb6508e72c05607372718e6e84738ba85cca2d5597143ca723cf148457e2a003a6976
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/sul-dlss/triannon.svg?branch=master)](https://travis-ci.org/sul-dlss/triannon) [![Dependency Status](https://gemnasium.com/sul-dlss/triannon.svg)](https://gemnasium.com/sul-dlss/triannon) [![Gem Version](https://badge.fury.io/rb/triannon.svg)](http://badge.fury.io/rb/triannon)
1
+ [![Build Status](https://travis-ci.org/sul-dlss/triannon.svg?branch=master)](https://travis-ci.org/sul-dlss/triannon) [![Coverage Status](https://coveralls.io/repos/sul-dlss/triannon/badge.png)](https://coveralls.io/r/sul-dlss/triannon) [![Dependency Status](https://gemnasium.com/sul-dlss/triannon.svg)](https://gemnasium.com/sul-dlss/triannon) [![Gem Version](https://badge.fury.io/rb/triannon.svg)](http://badge.fury.io/rb/triannon)
2
2
 
3
3
  # Triannon
4
4
 
@@ -4,6 +4,7 @@ module Triannon
4
4
  class AnnotationsController < ApplicationController
5
5
  before_action :default_format_jsonld, only: [:show]
6
6
  before_action :set_annotation, only: [:show, :edit, :update, :destroy]
7
+ rescue_from Triannon::ExternalReferenceError, with: :ext_ref_error
7
8
 
8
9
  # GET /annotations/annotations
9
10
  def index
@@ -95,5 +96,10 @@ module Triannon
95
96
  end
96
97
  end
97
98
  end
99
+
100
+ # handle Triannon::ExternalReferenceError
101
+ def ext_ref_error(exception)
102
+ render plain: exception.message, status: 403
103
+ end
98
104
  end
99
105
  end
@@ -178,7 +178,12 @@ private
178
178
  end
179
179
 
180
180
  def json_oa_context
181
- @json_oa_context ||= File.read("lib/triannon/oa_context_20130208.json")
181
+ # FIXME: this is a terrible place to do this!!
182
+ if Rails.root.to_s.match(/internal/) # testing via engine_cart
183
+ @json_oa_context ||= File.read(Rails.root.join("..", "..", "lib", "triannon", "oa_context_20130208.json"))
184
+ else
185
+ @json_oa_context ||= File.read(Rails.root.join("lib", "triannon", "oa_context_20130208.json"))
186
+ end
182
187
  end
183
188
 
184
189
  def graph_exists?
@@ -1,4 +1,6 @@
1
1
  module Triannon
2
+ # an LDP aware model of an Annotation -- basically, a shim between the OA notion of an annotation
3
+ # and the LDP storage.
2
4
  class AnnotationLdp
3
5
 
4
6
  # RDF::Graph object with all triples, including back end (e.g. LDP, Fedora)
@@ -8,9 +10,7 @@ module Triannon
8
10
 
9
11
  # RDF::Graph without any back end (e.g. LDP, Fedora) triples
10
12
  def stripped_graph
11
- @stripped_graph ||= begin
12
- new_graph = RDF::LDP.remove_ldp_triples (RDF::FCRepo4.remove_fedora_triples(graph))
13
- end
13
+ RDF::LDP.remove_ldp_triples (RDF::FCRepo4.remove_fedora_triples(graph))
14
14
  end
15
15
 
16
16
  def base_uri
@@ -39,9 +39,11 @@ module Triannon
39
39
  }
40
40
  result
41
41
  end
42
-
43
- def load_data_into_graph ttl
44
- graph.from_ttl ttl
42
+
43
+ # add the passed statements to #graph
44
+ # @param [Array<RDF::Statement>] statements an array of RDF statements to be loaded into the graph
45
+ def load_statements_into_graph statements
46
+ graph.insert(statements) if statements && statements.size > 0
45
47
  end
46
48
 
47
49
  private
@@ -0,0 +1,33 @@
1
+ module Triannon
2
+ class GraphValidator < ActiveModel::Validator
3
+
4
+ def validate(anno)
5
+ graph_exists? anno
6
+ graph = anno.graph
7
+ basic_solution = graph.query(self.basic_query)
8
+ unless basic_solution && basic_solution.size == 1
9
+ anno.errors[:data] << 'The oa:Annotation class MUST be associated with each Annotation.'
10
+ anno.errors[:data] << 'each Annotation MUST have at least one target'
11
+ end
12
+ end
13
+
14
+ def graph_exists?(anno)
15
+ graph = anno.graph
16
+ unless graph && graph.size > 0
17
+ anno.errors[:data] << 'Unable to create non-null graph'
18
+ end
19
+ end
20
+
21
+ # query for a subject with
22
+ # predicate RDF::OpenAnnotation.hasTarget
23
+ # type of RDF::OpenAnnotation.Annotation
24
+ def self.basic_query
25
+ @@basic_query ||= begin
26
+ RDF::Query.new
27
+ basic_query << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
28
+ basic_query << [:s, RDF::OpenAnnotation.hasTarget, nil]
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -7,24 +7,27 @@ module Triannon
7
7
  # use LDP protocol to create the OpenAnnotation.Annotation in an RDF store
8
8
  # @param [Triannon::Annotation] anno a Triannon::Annotation object, from which we use the graph
9
9
  def self.create(anno)
10
- # TODO: we should not get here if the Annotation object already has an id
11
- result = Triannon::LdpCreator.new anno
12
- result.create_base
10
+ if anno && anno.graph
11
+ # TODO: special case if the Annotation object already has an id --
12
+ # see https://github.com/sul-dlss/triannon/issues/84
13
+ result = Triannon::LdpCreator.new anno
14
+ result.create_base
13
15
 
14
- bodies_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
15
- if bodies_solns.size > 0
16
- result.create_body_container
17
- result.create_body_resources
18
- end
16
+ bodies_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
17
+ if bodies_solns.size > 0
18
+ result.create_body_container
19
+ result.create_body_resources
20
+ end
19
21
 
20
- targets_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
21
- # NOTE: Annotation is invalid if there are no target statements
22
- if targets_solns.size > 0
23
- result.create_target_container
24
- result.create_target_resources
25
- end
22
+ targets_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
23
+ # NOTE: Annotation is invalid if there are no target statements
24
+ if targets_solns.size > 0
25
+ result.create_target_container
26
+ result.create_target_resources
27
+ end
26
28
 
27
- result.id
29
+ result.id
30
+ end
28
31
  end
29
32
 
30
33
  # given an RDF::Resource (an RDF::Node or RDF::URI), look for all the statements with that object
@@ -51,16 +54,20 @@ module Triannon
51
54
 
52
55
  # POSTS a ttl representation of the LDP Annotation container to the LDP store
53
56
  def create_base
54
- # TODO: we should error if the Annotation object already has an id
55
-
57
+ if @anno.graph.query([nil, RDF::Triannon.externalReference, nil]).count > 0
58
+ raise Triannon::ExternalReferenceError, "Incoming annotations may not have http://triannon.stanford.edu/ns/externalReference as a predicate."
59
+ end
60
+
61
+ # TODO: special case if the Annotation object already has an id --
62
+ # see https://github.com/sul-dlss/triannon/issues/84
56
63
  g = RDF::Graph.new
57
64
  @anno.graph.each { |s|
58
65
  g << s
59
66
  }
60
67
 
61
- # remove the hasBody statements and any other statements associated with them
68
+ # don't include the hasBody statements and any other statements associated with them
62
69
  bodies_stmts = g.query([nil, RDF::OpenAnnotation.hasBody, nil])
63
- bodies_stmts.each { |has_body_stmt |
70
+ bodies_stmts.each { |has_body_stmt|
64
71
  g.delete has_body_stmt
65
72
  body_obj = has_body_stmt.object
66
73
  Triannon::LdpCreator.subject_statements(body_obj, g).each { |s|
@@ -68,9 +75,9 @@ module Triannon
68
75
  }
69
76
  }
70
77
 
71
- # remove the hasTarget statements and any other statements associated with them
78
+ # don't include the hasTarget statements and any other statements associated with them
72
79
  targets_stmts = g.query([nil, RDF::OpenAnnotation.hasTarget, nil])
73
- targets_stmts.each { |has_target_stmt |
80
+ targets_stmts.each { |has_target_stmt|
74
81
  g.delete has_target_stmt
75
82
  target_obj = has_target_stmt.object
76
83
  Triannon::LdpCreator.subject_statements(target_obj, g).each { |s|
@@ -111,120 +118,12 @@ module Triannon
111
118
 
112
119
  # create the body resources inside the (already created) body container
113
120
  def create_body_resources
114
- bodies_solns = @anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
115
- body_ids = []
116
- bodies_solns.each { |has_body_stmt |
117
- graph_for_resource = RDF::Graph.new
118
- body_obj = has_body_stmt.object
119
- if body_obj.is_a?(RDF::Node)
120
- # we need to use the null relative URI representation of blank nodes to write to LDP
121
- body_subject = RDF::URI.new
122
- else
123
- # it's already a URI, but we need to use the null relative URI representation so we can
124
- # write out as a Triannon:externalRef property with the URL, and any addl props too.
125
- if body_obj.to_str
126
- body_subject = RDF::URI.new
127
- graph_for_resource << RDF::Statement({:subject => body_subject,
128
- :predicate => RDF::Triannon.externalReference,
129
- :object => RDF::URI.new(body_obj.to_str)})
130
- addl_stmts = @anno.graph.query([body_obj, nil, nil])
131
- addl_stmts.each { |s|
132
- graph_for_resource << RDF::Statement({:subject => body_subject,
133
- :predicate => s.predicate,
134
- :object => s.object})
135
- }
136
- else # it's already a null relative URI
137
- body_subject = body_obj
138
- end
139
- end
140
- # add statements with body_obj as the subject
141
- Triannon::LdpCreator.subject_statements(body_obj, @anno.graph).each { |s|
142
- if s.subject == body_obj
143
- graph_for_resource << RDF::Statement({:subject => body_subject,
144
- :predicate => s.predicate,
145
- :object => s.object})
146
- else
147
- graph_for_resource << s
148
- end
149
- }
150
- body_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/b")
151
- }
152
- body_ids
121
+ create_resources_in_container RDF::OpenAnnotation.hasBody
153
122
  end
154
-
123
+
155
124
  # create the target resources inside the (already created) target container
156
125
  def create_target_resources
157
- target_solns = @anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
158
- target_ids = []
159
- target_solns.each { |has_target_stmt |
160
- graph_for_resource = RDF::Graph.new
161
- target_obj = has_target_stmt.object
162
- if target_obj.is_a?(RDF::Node)
163
- # we need to use the null relative URI representation of blank nodes to write to LDP
164
- target_subject = RDF::URI.new
165
- else
166
- # it's already a URI, but we need to use the null relative URI representation so we can
167
- # write out as a Triannon:externalRef property with the URL, and any addl props too.
168
- if target_obj.to_str
169
- target_subject = RDF::URI.new
170
- graph_for_resource << RDF::Statement({:subject => target_subject,
171
- :predicate => RDF::Triannon.externalReference,
172
- :object => RDF::URI.new(target_obj.to_str)})
173
- addl_stmts = @anno.graph.query([target_obj, nil, nil])
174
- addl_stmts.each { |s|
175
- graph_for_resource << RDF::Statement({:subject => target_subject,
176
- :predicate => s.predicate,
177
- :object => s.object})
178
- }
179
- else # it's already a null relative URI
180
- target_subject = target_obj
181
- end
182
- end
183
- # add statements with target_obj as the subject
184
- orig_source_objects = [] # the object URI nodes from targetObject, OA.hasSource, (uri) statements
185
- Triannon::LdpCreator.subject_statements(target_obj, @anno.graph).each { |s|
186
- if s.subject == target_obj
187
- # deal with external references in hasSource statements (i.e. targetObject, OA.hasSource, (url) )
188
- if s.predicate == RDF::OpenAnnotation.hasSource && s.object.is_a?(RDF::URI) && s.object.to_str
189
- # we need to represent the source URL as an externalReference - use a hash URI
190
- source_object = RDF::URI.new("#source")
191
- orig_source_objects << s.object
192
- graph_for_resource << RDF::Statement({:subject => source_object,
193
- :predicate => RDF::Triannon.externalReference,
194
- :object => RDF::URI.new(s.object.to_str)})
195
- # and all of the source URL's addl props
196
- Triannon::LdpCreator.subject_statements(s.object, @anno.graph).each { |ss|
197
- if ss.subject == s.object
198
- graph_for_resource << RDF::Statement({:subject => source_object,
199
- :predicate => ss.predicate,
200
- :object => ss.object})
201
- else
202
- graph_for_resource << ss
203
- end
204
- }
205
- # add the targetObj, OA.hasSource, (hash URI representation) to graph
206
- graph_for_resource << RDF::Statement({:subject => target_subject,
207
- :predicate => s.predicate,
208
- :object => source_object})
209
- else
210
- graph_for_resource << RDF::Statement({:subject => target_subject,
211
- :predicate => s.predicate,
212
- :object => s.object})
213
- end
214
- else
215
- graph_for_resource << s
216
- end
217
- }
218
- # make sure the graph we will write contains no extraneous statements about source URI
219
- # now represented as hash URI (#source)
220
- orig_source_objects.each { |rdf_uri_node|
221
- Triannon::LdpCreator.subject_statements(rdf_uri_node, graph_for_resource).each { |s|
222
- graph_for_resource.delete(s)
223
- }
224
- }
225
- target_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/t")
226
- }
227
- target_ids
126
+ create_resources_in_container RDF::OpenAnnotation.hasTarget
228
127
  end
229
128
 
230
129
  def conn
@@ -261,5 +160,112 @@ module Triannon
261
160
  end
262
161
  end
263
162
 
163
+ # create the target/body resources inside the (already created) target/body container
164
+ # @param [RDF::URI] predicate either RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
165
+ def create_resources_in_container(predicate)
166
+ predicate_solns = @anno.graph.query([nil, predicate, nil])
167
+ resource_ids = []
168
+ predicate_solns.each { |predicate_stmt |
169
+ graph_for_resource = RDF::Graph.new
170
+ predicate_obj = predicate_stmt.object
171
+ if predicate_obj.is_a?(RDF::Node)
172
+ # we need to use the null relative URI representation of blank nodes to write to LDP
173
+ predicate_subject = RDF::URI.new
174
+ else
175
+ # it's already a URI, but we need to use the null relative URI representation so we can
176
+ # write out as a Triannon:externalRef property with the URL, and any addl props too.
177
+ if predicate_obj.to_str
178
+ predicate_subject = RDF::URI.new
179
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
180
+ :predicate => RDF::Triannon.externalReference,
181
+ :object => RDF::URI.new(predicate_obj.to_str)})
182
+ addl_stmts = @anno.graph.query([predicate_obj, nil, nil])
183
+ addl_stmts.each { |s|
184
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
185
+ :predicate => s.predicate,
186
+ :object => s.object})
187
+ }
188
+ else # it's already a null relative URI
189
+ predicate_subject = predicate_obj
190
+ end
191
+ end
192
+
193
+ # add statements with predicate_obj as the subject
194
+ orig_hash_uri_objs = [] # the orig URI objects from [targetObject, OA.hasSource/.default/.item, (uri)] statements
195
+ hash_uri_counter = 1
196
+ Triannon::LdpCreator.subject_statements(predicate_obj, @anno.graph).each { |s|
197
+ if s.subject == predicate_obj
198
+ # deal with any external URI references which may occur in:
199
+ # OA.hasSource (from SpecificResource), OA.default or OA.item (from Choice, Composite, List)
200
+ if s.object.is_a?(RDF::URI) && s.object.to_s
201
+ # do we need to represent the URL as an externalReference with hash URI
202
+ if s.predicate == RDF::OpenAnnotation.hasSource
203
+ hash_uri_str = "#source"
204
+ elsif s.predicate == RDF::OpenAnnotation.default
205
+ hash_uri_str = "#default"
206
+ elsif s.predicate == RDF::OpenAnnotation.item
207
+ hash_uri_str = "#item#{hash_uri_counter}"
208
+ hash_uri_counter = hash_uri_counter + 1
209
+ else
210
+ # we don't need to represent the object URI as an external ref
211
+ hash_uri_str = nil
212
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
213
+ :predicate => s.predicate,
214
+ :object => s.object})
215
+ end
216
+
217
+ if hash_uri_str
218
+ # represent the object URL as an external ref
219
+ new_hash_uri_obj = RDF::URI.new(hash_uri_str)
220
+ orig_hash_uri_obj = s.object
221
+ orig_hash_uri_objs << orig_hash_uri_obj
222
+ # add [targetObj, OA.hasSource/.default/.item, (hash URI)] triple to graph
223
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
224
+ :predicate => s.predicate,
225
+ :object => new_hash_uri_obj})
226
+
227
+ # add externalReference triple to graph
228
+ graph_for_resource << RDF::Statement({:subject => new_hash_uri_obj,
229
+ :predicate => RDF::Triannon.externalReference,
230
+ :object => RDF::URI.new(orig_hash_uri_obj.to_s)})
231
+ # and all of the orig URL's addl props
232
+ Triannon::LdpCreator.subject_statements(orig_hash_uri_obj, @anno.graph).each { |ss|
233
+ if ss.subject == orig_hash_uri_obj
234
+ graph_for_resource << RDF::Statement({:subject => new_hash_uri_obj,
235
+ :predicate => ss.predicate,
236
+ :object => ss.object})
237
+ else
238
+ graph_for_resource << ss
239
+ end
240
+ }
241
+ end
242
+ # NOTE: already dealt with case where there is no hash uri above
243
+ else
244
+ # s.object is not a URI, and subject = predicate_subject -- it may be a blank node
245
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
246
+ :predicate => s.predicate,
247
+ :object => s.object})
248
+ end
249
+ else
250
+ # s.subject != predicate_obj
251
+ graph_for_resource << s
252
+ end
253
+ }
254
+ # make sure the graph we will write contains no extraneous statements about URIs
255
+ # now represented as hash URIs
256
+ orig_hash_uri_objs.each { |uri_node|
257
+ Triannon::LdpCreator.subject_statements(uri_node, graph_for_resource).each { |s|
258
+ graph_for_resource.delete(s)
259
+ }
260
+ }
261
+ if (predicate == RDF::OpenAnnotation.hasTarget)
262
+ resource_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/t")
263
+ else
264
+ resource_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/b")
265
+ end
266
+ }
267
+ resource_ids
268
+ end
269
+
264
270
  end
265
271
  end
@@ -6,11 +6,11 @@ module Triannon
6
6
 
7
7
  def self.load key
8
8
  l = Triannon::LdpLoader.new key
9
- l.load_annotation
10
- l.load_body
11
- l.load_target
9
+ l.load_anno_container
10
+ l.load_bodies
11
+ l.load_targets
12
12
 
13
- oa_graph = Triannon::LdpToOaMapper.ldp_to_oa l.annotation
13
+ oa_graph = Triannon::LdpToOaMapper.ldp_to_oa l.ldp_annotation
14
14
  oa_graph
15
15
  end
16
16
 
@@ -19,29 +19,32 @@ module Triannon
19
19
  l.find_all
20
20
  end
21
21
 
22
- attr_accessor :annotation
22
+ attr_accessor :ldp_annotation
23
23
 
24
24
  def initialize key = nil
25
25
  @key = key
26
26
  @base_uri = Triannon.config[:ldp_url]
27
- @annotation = Triannon::AnnotationLdp.new
27
+ @ldp_annotation = Triannon::AnnotationLdp.new
28
28
  end
29
29
 
30
- def load_annotation
31
- @annotation.load_data_into_graph get_ttl @key
30
+ # load annotation container object into @ldp_annotation's (our Triannon::AnnotationLdp object) graph
31
+ def load_anno_container
32
+ load_object_into_annotation_graph(@key)
32
33
  end
33
34
 
34
- def load_body
35
- @annotation.body_uris.each { |body_uri|
36
- sub_path = body_uri.to_s.split(@base_uri + '/').last
37
- @annotation.load_data_into_graph get_ttl sub_path
35
+ # load body objects into @ldp_annotation's (our Triannon::AnnotationLdp object) graph
36
+ def load_bodies
37
+ @ldp_annotation.body_uris.each { |body_uri|
38
+ body_obj_path = body_uri.to_s.split(@base_uri + '/').last
39
+ load_object_into_annotation_graph(body_obj_path)
38
40
  }
39
41
  end
40
42
 
41
- def load_target
42
- @annotation.target_uris.each { |target_uri|
43
- sub_path = target_uri.to_s.split(@base_uri + '/').last
44
- @annotation.load_data_into_graph get_ttl sub_path
43
+ # load target objects into @ldp_annotation's (our Triannon::AnnotationLdp object) graph
44
+ def load_targets
45
+ @ldp_annotation.target_uris.each { |target_uri|
46
+ target_obj_path = target_uri.to_s.split(@base_uri + '/').last
47
+ load_object_into_annotation_graph(target_obj_path)
45
48
  }
46
49
  end
47
50
 
@@ -64,19 +67,36 @@ module Triannon
64
67
 
65
68
  protected
66
69
 
70
+ # given a path to the back end storage url, retrieve the object from storage and load
71
+ # the triples (except storage specific triples) into the graph for @ldp_annotation, our Triannon::AnnotationLdp object
72
+ # @param [String] path the path to the object, e.g. the pid, or pid/t/target_pid
73
+ def load_object_into_annotation_graph(path)
74
+ @ldp_annotation.load_statements_into_graph(statements_from_ttl_minus_fedora(get_ttl path))
75
+ end
76
+
77
+ # gets object from back end storage as turtle serialization
67
78
  def get_ttl sub_path = nil
68
79
  resp = conn.get do |req|
69
80
  req.url "#{sub_path}" if sub_path
70
- req.headers['Accept'] = 'text/turtle'
81
+ req.headers['Accept'] = 'application/x-turtle'
71
82
  end
72
83
  resp.body
73
84
  end
74
85
 
86
+ # turns turtle serialization into Array of RDF::Statements, removing fedora-specific triples
87
+ # (leaving LDP and OA triples)
88
+ # @param [String] ttl a String containing RDF serialized as turtle
89
+ # @return [Array<RDF::Statements>] the RDF statements represented in the ttl
90
+ def statements_from_ttl_minus_fedora ttl
91
+ # RDF::Turtle::Reader.new(ttl).statements.to_a
92
+ g = RDF::Graph.new.from_ttl(ttl)
93
+ RDF::FCRepo4.remove_fedora_triples(g).statements
94
+ end
95
+
75
96
  def conn
76
97
  @c ||= Faraday.new @base_uri
77
98
  end
78
99
 
79
100
  end
80
101
 
81
-
82
102
  end
@@ -5,20 +5,21 @@ module Triannon
5
5
  def self.ldp_to_oa ldp_anno
6
6
  mapper = Triannon::LdpToOaMapper.new ldp_anno
7
7
  mapper.extract_base
8
- mapper.extract_body
9
- mapper.extract_target
8
+ mapper.extract_bodies
9
+ mapper.extract_targets
10
10
  mapper.oa_graph
11
11
  end
12
12
 
13
13
  attr_accessor :id, :oa_graph
14
14
 
15
15
  def initialize ldp_anno
16
- @ldp = ldp_anno
16
+ @ldp_anno = ldp_anno
17
+ @ldp_anno_graph = ldp_anno.stripped_graph
17
18
  @oa_graph = RDF::Graph.new
18
19
  end
19
20
 
20
21
  def extract_base
21
- @ldp.stripped_graph.each_statement do |stmnt|
22
+ @ldp_anno_graph.each_statement do |stmnt|
22
23
  if stmnt.predicate == RDF.type && stmnt.object == RDF::OpenAnnotation.Annotation
23
24
  @id = stmnt.subject.to_s.split('/').last
24
25
  @root_uri = RDF::URI.new(Triannon.config[:triannon_base_url] + "/#{@id}")
@@ -30,31 +31,175 @@ module Triannon
30
31
  end
31
32
  end
32
33
 
33
- def extract_body
34
- @ldp.body_uris.each { |body_uri|
35
- res = @ldp.stripped_graph.query [body_uri, RDF.type, RDF::Content.ContentAsText]
36
- if res.count > 0 # TODO raise if this fails?
37
- body_node = RDF::Node.new
38
- @oa_graph << [@root_uri, RDF::OpenAnnotation.hasBody, body_node]
39
- @oa_graph << [body_node, RDF.type, RDF::Content.ContentAsText]
40
- @oa_graph << [body_node, RDF.type, RDF::DCMIType.Text]
41
- res_chars = @ldp.stripped_graph.query [body_uri, RDF::Content.chars, nil]
42
- if res_chars.count > 0
43
- @oa_graph << [body_node, RDF::Content.chars, res_chars.first.object]
44
- end
34
+ def extract_bodies
35
+ @ldp_anno.body_uris.each { |body_uri|
36
+ if !map_external_ref(body_uri, RDF::OpenAnnotation.hasBody) &&
37
+ !map_content_as_text(body_uri, RDF::OpenAnnotation.hasBody) &&
38
+ !map_specific_resource(body_uri, RDF::OpenAnnotation.hasBody)
39
+ map_choice(body_uri, RDF::OpenAnnotation.hasBody)
45
40
  end
46
41
  }
47
42
  end
48
43
 
49
- def extract_target
50
- @ldp.target_uris.each { |target_uri|
51
- res = @ldp.stripped_graph.query [target_uri, RDF::Triannon.externalReference, nil]
52
- if res.count > 0
53
- ext_uri = res.first.object
54
- @oa_graph << [@root_uri, RDF::OpenAnnotation.hasTarget, ext_uri]
44
+ def extract_targets
45
+ @ldp_anno.target_uris.each { |target_uri|
46
+ if !map_external_ref(target_uri, RDF::OpenAnnotation.hasTarget) &&
47
+ !map_specific_resource(target_uri, RDF::OpenAnnotation.hasTarget)
48
+ map_choice(target_uri, RDF::OpenAnnotation.hasTarget)
55
49
  end
56
50
  }
57
51
  end
52
+
53
+ # if uri_obj is the subject of a Triannon.externalReference then add appropriate
54
+ # statements to @oa_graph and return true
55
+ # @param [RDF::URI] uri_obj the object that may have RDF::Triannon.externalReference
56
+ # @param [RDF::URI] predicate the predicate for [subject_obj, predicate, (ext_url)] statement
57
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
58
+ # @param [RDF::URI] the subject object to get the predicate statement; defaults to @root_uri
59
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
60
+ def map_external_ref uri_obj, predicate, subject_obj = @root_uri
61
+ solns = @ldp_anno_graph.query [uri_obj, RDF::Triannon.externalReference, nil]
62
+ if solns.count > 0
63
+ external_uri = solns.first.object
64
+ @oa_graph << [subject_obj, predicate, external_uri]
65
+
66
+ Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
67
+ if stmt.subject == uri_obj && stmt.predicate != RDF::Triannon.externalReference
68
+ @oa_graph << [external_uri, stmt.predicate, stmt.object]
69
+ else
70
+ # we should never get here for external references ...
71
+ end
72
+ }
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ # if uri_obj has a type of RDF::Content.ContentAsText, then this is a skolemized blank node;
80
+ # add appropriate statements to @oa_graph to represent the blank node and its contents and return true
81
+ # @param [RDF::URI] uri_obj the object that may type RDF::Content.ContentAsText
82
+ # @param [RDF::URI] predicate the predicate for [subject_obj, predicate, (ext_url)] statement
83
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
84
+ # @param [RDF::URI] the subject object to get the predicate statement; defaults to @root_uri
85
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
86
+ def map_content_as_text uri_obj, predicate, subject_obj = @root_uri
87
+ solns = @ldp_anno_graph.query [uri_obj, RDF.type, RDF::Content.ContentAsText]
88
+ if solns.count > 0
89
+ blank_node = RDF::Node.new
90
+ @oa_graph << [subject_obj, predicate, blank_node]
91
+
92
+ Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
93
+ if stmt.subject == uri_obj
94
+ @oa_graph << [blank_node, stmt.predicate, stmt.object]
95
+ else
96
+ # it is a descendant statment - take as is
97
+ @oa_graph << stmt
98
+ end
99
+ }
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+
106
+ # if uri_obj has a type of RDF::OpenAnnotation.SpecificResource, then this is a skolemized blank node;
107
+ # add appropriate statements to @oa_graph to represent the blank node and its contents and return true
108
+ # @param [RDF::URI] uri_obj the object that may have type RDF::OpenAnnotation.SpecificResource
109
+ # @param [RDF::URI] predicate the predicate for [@root_uri, predicate, (sel_res)] statement
110
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
111
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
112
+ def map_specific_resource uri_obj, predicate
113
+ solns = @ldp_anno_graph.query [uri_obj, RDF.type, RDF::OpenAnnotation.SpecificResource]
114
+ if solns.count > 0
115
+ blank_node = RDF::Node.new
116
+ @oa_graph << [@root_uri, predicate, blank_node]
117
+
118
+ source_obj = nil
119
+ selector_obj = nil
120
+ selector_blank_node = nil
121
+ specific_res_stmts = Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph)
122
+ specific_res_stmts.each { |stmt|
123
+ if stmt.predicate == RDF::OpenAnnotation.hasSource
124
+ # expecting a hash URI
125
+ source_obj = stmt.object
126
+ if source_obj.to_s.match("#{uri_obj.to_s}#source")
127
+ source_has_ext_uri = map_external_ref source_obj, RDF::OpenAnnotation.hasSource, blank_node
128
+ end
129
+ elsif stmt.predicate == RDF::OpenAnnotation.hasSelector
130
+ # this becomes a blank node. Per http://www.openannotation.org/spec/core/specific.html#Selectors
131
+ # "Typically if all of the information needed to resolve the Selector (or other Specifier)
132
+ # is present within the graph, such as is the case for the
133
+ # FragmentSelector, TextQuoteSelector, TextPositionSelector and DataPositionSelector classes,
134
+ # then there is no need to have a resolvable resource that provides the same information."
135
+ selector_obj = stmt.object
136
+ selector_blank_node = RDF::Node.new
137
+ @oa_graph << [blank_node, RDF::OpenAnnotation.hasSelector, selector_blank_node]
138
+ end
139
+ }
140
+
141
+ # We can't know we'll hit hasSource and hasSelector statements in graph first,
142
+ # so we must do another pass through the statements to get that information
143
+ specific_res_stmts.each { |stmt|
144
+ if stmt.subject == uri_obj && stmt.object != source_obj && stmt.object != selector_obj
145
+ @oa_graph << [blank_node, stmt.predicate, stmt.object]
146
+ elsif stmt.subject != source_obj
147
+ if selector_blank_node && stmt.subject == selector_obj
148
+ @oa_graph << [selector_blank_node, stmt.predicate, stmt.object]
149
+ end
150
+ # there shouldn't be any other statements present
151
+ end
152
+ }
153
+ true
154
+ else
155
+ false
156
+ end
157
+ end
158
+
159
+ # if uri_obj has a type of RDF::OpenAnnotation.Choice, then this is a skolemized blank node;
160
+ # add appropriate statements to @oa_graph to represent the blank node and its contents and return true
161
+ # @param [RDF::URI] uri_obj the object that may have type RDF::OpenAnnotation.Choice
162
+ # @param [RDF::URI] predicate the predicate for [@root_uri, predicate, (choice)] statement
163
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
164
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
165
+ def map_choice uri_obj, predicate
166
+ solns = @ldp_anno_graph.query [uri_obj, RDF.type, RDF::OpenAnnotation.Choice]
167
+ if solns.count > 0
168
+ blank_node = RDF::Node.new
169
+ @oa_graph << [@root_uri, predicate, blank_node]
170
+
171
+ default_obj = nil
172
+ item_objs = []
173
+ choice_stmts = Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph)
174
+ choice_stmts.each { |stmt|
175
+ if stmt.predicate == RDF::OpenAnnotation.default
176
+ default_obj = stmt.object
177
+ # assume it is either ContentAsText or external ref
178
+ if !map_content_as_text(default_obj, RDF::OpenAnnotation.default, blank_node)
179
+ map_external_ref(default_obj, RDF::OpenAnnotation.default, blank_node)
180
+ end
181
+ elsif stmt.predicate == RDF::OpenAnnotation.item
182
+ item_objs << stmt.object
183
+ # assume it is either ContentAsText or external ref
184
+ if !map_content_as_text(stmt.object, RDF::OpenAnnotation.item, blank_node)
185
+ map_external_ref(stmt.object, RDF::OpenAnnotation.item, blank_node)
186
+ end
187
+ end
188
+ }
189
+
190
+ # We can't know we'll hit item and default statements in graph first,
191
+ # so we must do another pass through the statements to get that information
192
+ choice_stmts.each { |stmt|
193
+ if stmt.subject == uri_obj && stmt.object != default_obj && !item_objs.include?(stmt.object)
194
+ @oa_graph << [blank_node, stmt.predicate, stmt.object]
195
+ # there shouldn't be any other unmapped statements present
196
+ end
197
+ }
198
+ true
199
+ else
200
+ false
201
+ end
202
+ end
58
203
 
59
204
  end
60
205
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  <div class="field">
15
15
  <%= f.label 'data (as json-ld or turtle)' %><br>
16
- <%= f.text_area :data %>
16
+ <%= f.text_area :data, size: "80x20" %>
17
17
  </div>
18
18
  <div class="actions">
19
19
  <%= f.submit %>
data/lib/triannon.rb CHANGED
@@ -8,6 +8,7 @@ require 'faraday'
8
8
 
9
9
  module Triannon
10
10
  require "triannon/engine"
11
+ require "triannon/error"
11
12
 
12
13
  class << self
13
14
  attr_accessor :config
@@ -0,0 +1,9 @@
1
+ module Triannon
2
+ # generic Triannon error allowing rescue to catch all Triannon exceptions
3
+ class Error < RuntimeError
4
+ end
5
+
6
+ class ExternalReferenceError < Triannon::Error
7
+ end
8
+
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Triannon
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: triannon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Beer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-29 00:00:00.000000000 Z
12
+ date: 2014-11-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -221,6 +221,20 @@ dependencies:
221
221
  - - ">="
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
+ - !ruby/object:Gem::Dependency
225
+ name: coveralls
226
+ requirement: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ type: :development
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
224
238
  description:
225
239
  email:
226
240
  - cabeer@stanford.edu
@@ -241,6 +255,7 @@ files:
241
255
  - app/helpers/triannon/application_helper.rb
242
256
  - app/models/triannon/annotation.rb
243
257
  - app/models/triannon/annotation_ldp.rb
258
+ - app/models/triannon/graph_validator.rb
244
259
  - app/services/triannon/ldp_creator.rb
245
260
  - app/services/triannon/ldp_destroyer.rb
246
261
  - app/services/triannon/ldp_loader.rb
@@ -261,6 +276,7 @@ files:
261
276
  - lib/tasks/triannon_tasks.rake
262
277
  - lib/triannon.rb
263
278
  - lib/triannon/engine.rb
279
+ - lib/triannon/error.rb
264
280
  - lib/triannon/oa_context_20130208.json
265
281
  - lib/triannon/version.rb
266
282
  homepage:
@@ -283,7 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
283
299
  version: '0'
284
300
  requirements: []
285
301
  rubyforge_project:
286
- rubygems_version: 2.2.2
302
+ rubygems_version: 2.4.3
287
303
  signing_key:
288
304
  specification_version: 4
289
305
  summary: Rails engine for working with storage of OpenAnnotations stored in Fedora4