triannon 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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