triannon 0.0.3 → 0.0.4

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.
@@ -0,0 +1,265 @@
1
+
2
+ module Triannon
3
+
4
+ # creates a new Annotation in the LDP server
5
+ class LdpCreator
6
+
7
+ # use LDP protocol to create the OpenAnnotation.Annotation in an RDF store
8
+ # @param [Triannon::Annotation] anno a Triannon::Annotation object, from which we use the graph
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
13
+
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
19
+
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
26
+
27
+ result.id
28
+ end
29
+
30
+ # given an RDF::Resource (an RDF::Node or RDF::URI), look for all the statements with that object
31
+ # as the subject, and recurse through the graph to find all descendant statements pertaining to the subject
32
+ # @param subject the RDF object to be used as the subject in the graph query. Should be an RDF::Node or RDF::URI
33
+ # @param [RDF::Graph] graph
34
+ # @return [Array[RDF::Statement]] all the triples with the given subject
35
+ def self.subject_statements(subject, graph)
36
+ result = []
37
+ graph.query([subject, nil, nil]).each { |stmt|
38
+ result << stmt
39
+ subject_statements(stmt.object, graph).each { |s| result << s }
40
+ }
41
+ result.uniq
42
+ end
43
+
44
+ attr_accessor :id
45
+
46
+ # @param [Triannon::Annotation] anno a Triannon::Annotation object
47
+ def initialize(anno)
48
+ @anno = anno
49
+ @base_uri = Triannon.config[:ldp_url]
50
+ end
51
+
52
+ # POSTS a ttl representation of the LDP Annotation container to the LDP store
53
+ def create_base
54
+ # TODO: we should error if the Annotation object already has an id
55
+
56
+ g = RDF::Graph.new
57
+ @anno.graph.each { |s|
58
+ g << s
59
+ }
60
+
61
+ # remove the hasBody statements and any other statements associated with them
62
+ bodies_stmts = g.query([nil, RDF::OpenAnnotation.hasBody, nil])
63
+ bodies_stmts.each { |has_body_stmt |
64
+ g.delete has_body_stmt
65
+ body_obj = has_body_stmt.object
66
+ Triannon::LdpCreator.subject_statements(body_obj, g).each { |s|
67
+ g.delete s
68
+ }
69
+ }
70
+
71
+ # remove the hasTarget statements and any other statements associated with them
72
+ targets_stmts = g.query([nil, RDF::OpenAnnotation.hasTarget, nil])
73
+ targets_stmts.each { |has_target_stmt |
74
+ g.delete has_target_stmt
75
+ target_obj = has_target_stmt.object
76
+ Triannon::LdpCreator.subject_statements(target_obj, g).each { |s|
77
+ g.delete s
78
+ }
79
+ }
80
+
81
+ # transform an outer blank node into a null relative URI
82
+ anno_stmts = g.query([nil, RDF.type, RDF::OpenAnnotation.Annotation])
83
+ anno_rdf_obj = anno_stmts.first.subject
84
+ if anno_rdf_obj.is_a?(RDF::Node)
85
+ # we need to use the null relative URI representation of blank nodes to write to LDP
86
+ anno_subject = RDF::URI.new
87
+ else # it's already a URI
88
+ anno_subject = anno_rdf_obj
89
+ end
90
+ Triannon::LdpCreator.subject_statements(anno_rdf_obj, g).each { |s|
91
+ if s.subject == anno_rdf_obj && anno_subject != anno_rdf_obj
92
+ g << RDF::Statement({:subject => anno_subject,
93
+ :predicate => s.predicate,
94
+ :object => s.object})
95
+ g.delete s
96
+ end
97
+ }
98
+
99
+ @id = create_resource g.to_ttl
100
+ end
101
+
102
+ # creates the LDP container for any and all bodies for this annotation
103
+ def create_body_container
104
+ create_direct_container RDF::OpenAnnotation.hasBody
105
+ end
106
+
107
+ # creates the LDP container for any and all targets for this annotation
108
+ def create_target_container
109
+ create_direct_container RDF::OpenAnnotation.hasTarget
110
+ end
111
+
112
+ # create the body resources inside the (already created) body container
113
+ 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
153
+ end
154
+
155
+ # create the target resources inside the (already created) target container
156
+ 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
228
+ end
229
+
230
+ def conn
231
+ @c ||= Faraday.new @base_uri
232
+ end
233
+
234
+ protected
235
+ def create_resource body, url = nil
236
+ response = conn.post do |req|
237
+ req.url url if url
238
+ req.headers['Content-Type'] = 'application/x-turtle'
239
+ req.body = body
240
+ end
241
+ new_url = response.headers['Location'] ? response.headers['Location'] : response.headers['location']
242
+ new_url.split('/').last if new_url
243
+ end
244
+
245
+ # Creates an empty LDP DirectContainer in LDP Storage that is a member of the base container and has the memberRelation per the oa_vocab_term
246
+ # The id of the created containter will be (base container id)b if hasBody or (base container id)/t if hasTarget
247
+ # @param [RDF::Vocabulary::Term] oa_vocab_term RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
248
+ def create_direct_container oa_vocab_term
249
+ null_rel_uri = RDF::URI.new
250
+ g = RDF::Graph.new
251
+ g << [null_rel_uri, RDF.type, RDF::LDP.DirectContainer]
252
+ g << [null_rel_uri, RDF::LDP.hasMemberRelation, oa_vocab_term]
253
+ g << [null_rel_uri, RDF::LDP.membershipResource, RDF::URI.new("#{@base_uri}/#{id}")]
254
+
255
+ response = conn.post do |req|
256
+ req.url "#{id}"
257
+ req.headers['Content-Type'] = 'application/x-turtle'
258
+ # OA vocab relationships all of form "hasXXX"
259
+ req.headers['Slug'] = oa_vocab_term.fragment.slice(3).downcase
260
+ req.body = g.to_ttl
261
+ end
262
+ end
263
+
264
+ end
265
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Triannon
3
+
4
+ class LdpDestroyer
5
+
6
+ def self.destroy key
7
+ conn = Faraday.new Triannon.config[:ldp_url]
8
+
9
+ resp = conn.delete { |req| req.url key }
10
+ if resp.status != 204
11
+ raise "Unable to delete Annotation: #{key}\nResponse Status: #{resp.status}\nResponse Body: #{resp.body}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,82 @@
1
+
2
+ module Triannon
3
+
4
+ # Loads an existing Annotation from the LDP server
5
+ class LdpLoader
6
+
7
+ def self.load key
8
+ l = Triannon::LdpLoader.new key
9
+ l.load_annotation
10
+ l.load_body
11
+ l.load_target
12
+
13
+ oa_graph = Triannon::LdpToOaMapper.ldp_to_oa l.annotation
14
+ oa_graph
15
+ end
16
+
17
+ def self.find_all
18
+ l = Triannon::LdpLoader.new
19
+ l.find_all
20
+ end
21
+
22
+ attr_accessor :annotation
23
+
24
+ def initialize key = nil
25
+ @key = key
26
+ @base_uri = Triannon.config[:ldp_url]
27
+ @annotation = Triannon::AnnotationLdp.new
28
+ end
29
+
30
+ def load_annotation
31
+ @annotation.load_data_into_graph get_ttl @key
32
+ end
33
+
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
38
+ }
39
+ end
40
+
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
45
+ }
46
+ end
47
+
48
+ # @return [Array<Triannon::Annotation>] an array of Triannon::Annotation objects with just the id set. Enough info to build the index page
49
+ def find_all
50
+ root_ttl = get_ttl
51
+ objs = []
52
+
53
+ g = RDF::Graph.new
54
+ g.from_ttl root_ttl
55
+ root_uri = RDF::URI.new @base_uri
56
+ results = g.query [root_uri, RDF::LDP.contains, nil]
57
+ results.each do |stmt|
58
+ id = stmt.object.to_s.split('/').last
59
+ objs << Triannon::Annotation.new(:id => id)
60
+ end
61
+
62
+ objs
63
+ end
64
+
65
+ protected
66
+
67
+ def get_ttl sub_path = nil
68
+ resp = conn.get do |req|
69
+ req.url "#{sub_path}" if sub_path
70
+ req.headers['Accept'] = 'text/turtle'
71
+ end
72
+ resp.body
73
+ end
74
+
75
+ def conn
76
+ @c ||= Faraday.new @base_uri
77
+ end
78
+
79
+ end
80
+
81
+
82
+ end
@@ -0,0 +1,61 @@
1
+ module Triannon
2
+ class LdpToOaMapper
3
+
4
+ # maps an AnnotationLdp to an OA RDF::Graph
5
+ def self.ldp_to_oa ldp_anno
6
+ mapper = Triannon::LdpToOaMapper.new ldp_anno
7
+ mapper.extract_base
8
+ mapper.extract_body
9
+ mapper.extract_target
10
+ mapper.oa_graph
11
+ end
12
+
13
+ attr_accessor :id, :oa_graph
14
+
15
+ def initialize ldp_anno
16
+ @ldp = ldp_anno
17
+ @oa_graph = RDF::Graph.new
18
+ end
19
+
20
+ def extract_base
21
+ @ldp.stripped_graph.each_statement do |stmnt|
22
+ if stmnt.predicate == RDF.type && stmnt.object == RDF::OpenAnnotation.Annotation
23
+ @id = stmnt.subject.to_s.split('/').last
24
+ @root_uri = RDF::URI.new(Triannon.config[:triannon_base_url] + "/#{@id}")
25
+ @oa_graph << [@root_uri, RDF.type, RDF::OpenAnnotation.Annotation]
26
+
27
+ elsif stmnt.predicate == RDF::OpenAnnotation.motivatedBy
28
+ @oa_graph << [@root_uri, stmnt.predicate, stmnt.object]
29
+ end
30
+ end
31
+ end
32
+
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
45
+ end
46
+ }
47
+ end
48
+
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]
55
+ end
56
+ }
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,36 @@
1
+ module Triannon
2
+
3
+ class RootAnnotationCreator
4
+
5
+ # Creates an LDP Container to hold all the annotations
6
+ # Called from config/initializers/root_annotation_container.rb during app bootup
7
+ # @return [Boolean] true if the root container was created, false if the container already exists or if there were issues
8
+ def self.create
9
+ conn = Faraday.new :url => Triannon.config[:ldp_url]
10
+ resp = conn.head
11
+ unless resp.status == 404 || resp.status == 410
12
+ Rails.logger.info "Root annotation resource already created."
13
+ return false
14
+ end
15
+
16
+ uri = RDF::URI.new Triannon.config[:ldp_url]
17
+ conn = Faraday.new :url => uri.parent.to_s
18
+ slug = uri.to_s.split('/').last
19
+
20
+ resp = conn.post do |req|
21
+ req.headers['Content-Type'] = 'text/turtle'
22
+ req.headers['Slug'] = slug
23
+ end
24
+
25
+ if resp.status == 201
26
+ Rails.logger.info "Created root annotation container #{Triannon.config[:ldp_url]}"
27
+ return true
28
+ else
29
+ Rails.logger.warn "Unable to create root annotation container #{Triannon.config[:ldp_url]}"
30
+ return false
31
+ # TODO raise an exception if we get here?
32
+ end
33
+ end
34
+
35
+ end
36
+ end