triannon 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2102031aa5a20dc77e3ba1956520a2f48f1d2753
4
- data.tar.gz: c99336415b461a6acec866a6555da19ef00b0c44
3
+ metadata.gz: f2328d724cb403823e2e7e9fbf5565a1244c1453
4
+ data.tar.gz: 2424be58316b9d29eb4f8e48eee1ea1914a9b07c
5
5
  SHA512:
6
- metadata.gz: 804d9258559bab83616b0d68e5b73d1404ff052eee62daeee17d1095a1bbd839c30972daabcb87e77834fbd3e6dd96b8913890936ea47793c3960c52b78fe8da
7
- data.tar.gz: 1a1e9f0aa7e61938cce576c854c6c23939d6753d60e3d35147d3e3f7800160043cec7fddf0381637782df1ca86cf823b44d4f1001386a5f1b10c24c2d403ecbe
6
+ metadata.gz: 7de9938713c92a1c0e654ee71f44bfe558429e7ab2907a8a89752c12520e22e7bece73db6a54106f70e944075b346eb66fefe19c7317f4d7c60198cb6f5ce1a6
7
+ data.tar.gz: dd44726e702079259c81c29ad89c19e8f9597e0a9493a44041e4021fbba90b4bd9c0901fbfdb57efc4907726472b01fdb802126d1420eaabcbf2ff6d002c6bac
data/README.md CHANGED
@@ -35,7 +35,7 @@ $ rails g triannon:install
35
35
  Edit the `config/triannon.yml` file:
36
36
 
37
37
  * `ldp_url:` Points to the root annotations container on your LDP server
38
- * `triannon_base_url:` Used as the base url for all annotations hosted by your Triannon server. Identifiers from the LDP server will be appended to this base-url.
38
+ * `triannon_base_url:` Used as the base url for all annotations hosted by your Triannon server. Identifiers from the LDP server will be appended to this base-url. Generally something like "https://your-triannon-rails-box/annotations", as "/annotations" is added to the path by the Triannon gem
39
39
 
40
40
  Generate the root annotations container on the LDP server
41
41
 
@@ -6,12 +6,12 @@ module Triannon
6
6
  before_action :set_annotation, only: [:show, :edit, :update, :destroy]
7
7
  rescue_from Triannon::ExternalReferenceError, with: :ext_ref_error
8
8
 
9
- # GET /annotations/annotations
9
+ # GET /annotations
10
10
  def index
11
11
  @annotations = Annotation.all
12
12
  end
13
13
 
14
- # GET /annotations/annotations/1
14
+ # GET /annotations/1
15
15
  def show
16
16
  respond_to do |format|
17
17
  format.jsonld { render_jsonld_per_context (params[:jsonld_context]) }
@@ -31,17 +31,17 @@ module Triannon
31
31
  end
32
32
  end
33
33
 
34
- # GET /annotations/annotations/new
34
+ # GET /annotations/new
35
35
  def new
36
36
  @annotation = Annotation.new
37
37
  end
38
38
 
39
39
  # NOT YET IMPLEMENTED
40
- # GET /annotations/annotations/1/edit
40
+ # GET /annotations/1/edit
41
41
  # def edit
42
42
  # end
43
43
 
44
- # POST /annotations/annotations
44
+ # POST /annotations
45
45
  def create
46
46
  # FIXME: this is probably a bad way of allowing app form to be used as well as direct post requests
47
47
  if params["annotation"]
@@ -63,7 +63,7 @@ module Triannon
63
63
  end
64
64
 
65
65
  # NOT YET IMPLEMENTED
66
- # PATCH/PUT /annotations/annotations/1
66
+ # PATCH/PUT /annotations/1
67
67
  # def update
68
68
  # if @annotation.update(params)
69
69
  # redirect_to @annotation, notice: 'Annotation was successfully updated.'
@@ -72,7 +72,7 @@ module Triannon
72
72
  # end
73
73
  # end
74
74
 
75
- # DELETE /annotations/annotations/1
75
+ # DELETE /annotations/1
76
76
  def destroy
77
77
  @annotation.destroy
78
78
  redirect_to annotations_url, status: 204, notice: 'Annotation was successfully destroyed.'
@@ -1,6 +1,10 @@
1
1
  module Triannon
2
2
  class Annotation
3
3
  include ActiveModel::Model
4
+
5
+ define_model_callbacks :save, :destroy
6
+ after_save :solr_save
7
+ after_destroy :solr_delete
4
8
 
5
9
  attr_accessor :id, :data
6
10
 
@@ -14,13 +18,75 @@ module Triannon
14
18
  # https://github.com/uq-eresearch/lorestore/blob/3e9aa1c69aafd3692c69aa39c64bfdc32b757892/src/main/resources/OAConstraintsSPARQL.json
15
19
 
16
20
 
21
+ # Class Methods ----------------------------------------------------------------
22
+
23
+ def self.create(attrs = {})
24
+ a = Triannon::Annotation.new attrs
25
+ a.save
26
+ a
27
+ end
28
+
29
+ def self.find(key)
30
+ oa_graph = Triannon::LdpLoader.load key
31
+ anno = Triannon::Annotation.new
32
+ anno.graph = oa_graph
33
+ anno.id = key
34
+ anno
35
+ end
36
+
37
+ def self.all
38
+ Triannon::LdpLoader.find_all
39
+ end
40
+
41
+ # Instance Methods ----------------------------------------------------------------
42
+
43
+ def save
44
+ _run_save_callbacks do
45
+ # check if valid?
46
+ graph
47
+ @id = Triannon::LdpWriter.create_anno self
48
+ end
49
+ end
50
+
51
+ def destroy
52
+ _run_destroy_callbacks do
53
+ Triannon::LdpWriter.delete_anno @id
54
+ end
55
+ end
56
+
17
57
  def persisted?
18
58
  self.id.present?
19
59
  end
20
60
 
61
+ def graph
62
+ @graph ||= data_to_graph
63
+ end
64
+
65
+ def graph= g
66
+ @graph = g
67
+ end
68
+
69
+ # @return json-ld representation of graph with OpenAnnotation context as a url
70
+ def jsonld_oa
71
+ inline_context = graph.dump(:jsonld, :context => Triannon::JsonldContext::OA_CONTEXT_URL)
72
+ hash_from_json = JSON.parse(inline_context)
73
+ hash_from_json["@context"] = Triannon::JsonldContext::OA_CONTEXT_URL
74
+ hash_from_json.to_json
75
+ end
76
+
77
+ # @return json-ld representation of graph with IIIF context as a url
78
+ def jsonld_iiif
79
+ inline_context = graph.dump(:jsonld, :context => Triannon::JsonldContext::IIIF_CONTEXT_URL)
80
+ hash_from_json = JSON.parse(inline_context)
81
+ hash_from_json["@context"] = Triannon::JsonldContext::IIIF_CONTEXT_URL
82
+ hash_from_json.to_json
83
+ end
84
+
85
+ # @return [String] the id of this annotation as a url
86
+ # TODO: re-usable as part of Triannon::Graph class?
21
87
  def url
22
88
  if graph_exists?
23
- solution = graph.query self.class.anno_query
89
+ solution = graph.query Triannon::Graph.anno_query
24
90
  if solution && solution.size == 1
25
91
  solution.first.s.to_s
26
92
  # TODO: raise exception if no URL?
@@ -29,6 +95,7 @@ module Triannon
29
95
  end
30
96
 
31
97
  # FIXME: this should be part of validation: RDF.type should be RDF::OpenAnnotation.Annotation
98
+ # @return [String] should always be a string representation of RDF::OpenAnnotation.Annotation
32
99
  def type
33
100
  if graph_exists?
34
101
  q = RDF::Query.new
@@ -43,9 +110,11 @@ module Triannon
43
110
  end
44
111
  end
45
112
 
113
+ # @return [Array] of uris expressing the OA motivated_by values
114
+ # TODO: re-usable as part of Triannon::Graph class?
46
115
  def motivated_by
47
116
  if graph_exists?
48
- q = self.class.anno_query.dup
117
+ q = Triannon::Graph.anno_query.dup
49
118
  q << [:s, RDF::OpenAnnotation.motivatedBy, :motivated_by]
50
119
  solution = graph.query q
51
120
  if solution && solution.size > 0
@@ -59,67 +128,74 @@ module Triannon
59
128
  end
60
129
  end
61
130
 
62
- def graph
63
- @graph ||= data_to_graph
131
+ protected
132
+
133
+ # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
134
+ def solr_save
135
+ # puts "TO DO: send add to Solr (after save)"
136
+ # pp solr_hash
64
137
  end
65
138
 
66
- def graph= g
67
- @graph = g
139
+ # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
140
+ def solr_delete
141
+ # puts "TO DO: send delete to Solr (after destroy)"
68
142
  end
69
143
 
70
- # @return json-ld representation of graph with OpenAnnotation context as a url
71
- def jsonld_oa
72
- inline_context = graph.dump(:jsonld, :context => Triannon::JsonldContext::OA_CONTEXT_URL)
73
- hash_from_json = JSON.parse(inline_context)
74
- hash_from_json["@context"] = Triannon::JsonldContext::OA_CONTEXT_URL
75
- hash_from_json.to_json
144
+ # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
145
+ # TODO: re-usable as part of Triannon::Graph class?
146
+ #
147
+ # @return [Hash] a hash to be written to Solr, populated appropriately
148
+ def solr_hash
149
+ doc_hash = {}
150
+ tid = url.sub(Triannon.config[:ldp_url], "")
151
+ tid.sub(/^\//, "")
152
+ doc_hash[:id] = tid
153
+ doc_hash[:motivation] = motivated_by.map { |m| m.sub(RDF::OpenAnnotation.to_s, "") }
154
+ # date field format: 1995-12-31T23:59:59Z; or w fractional seconds: 1995-12-31T23:59:59.999Z
155
+ # doc_hash[:annotated_at] =
156
+ # doc_hash[:annotated_by_stem]
157
+ doc_hash[:target_url] = predicate_urls RDF::OpenAnnotation.hasTarget
158
+ doc_hash[:target_type] = ['external_URI'] if doc_hash[:target_url].size > 0
159
+ doc_hash[:body_url] = predicate_urls RDF::OpenAnnotation.hasBody
160
+ doc_hash[:body_type] = []
161
+ doc_hash[:body_type] << 'external_URI' if doc_hash[:body_url].size > 0
162
+ doc_hash[:body_chars_exact] = body_chars
163
+ doc_hash[:body_type] << 'content_as_text' if doc_hash[:body_chars_exact].size > 0
164
+ doc_hash[:body_type] << 'no_body' if doc_hash[:body_type].size == 0
165
+ doc_hash[:anno_jsonld] = jsonld_oa
166
+ doc_hash
76
167
  end
77
168
 
78
- # @return json-ld representation of graph with IIIF context as a url
79
- def jsonld_iiif
80
- inline_context = graph.dump(:jsonld, :context => Triannon::JsonldContext::IIIF_CONTEXT_URL)
81
- hash_from_json = JSON.parse(inline_context)
82
- hash_from_json["@context"] = Triannon::JsonldContext::IIIF_CONTEXT_URL
83
- hash_from_json.to_json
84
- end
85
-
86
- # query for a subject with type of RDF::OpenAnnotation.Annotation
87
- def self.anno_query
88
- @anno_query ||= begin
89
- q = RDF::Query.new
90
- q << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
91
- end
169
+ # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
170
+ # TODO: re-usable as part of Triannon::Graph class?
171
+ # @param [RDF::URI] predicate either RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
172
+ # @return [Array<String>] urls for the predicate, as an Array of Strings
173
+ def predicate_urls(predicate)
174
+ predicate_solns = graph.query([nil, predicate, nil])
175
+ urls = []
176
+ predicate_solns.each { |predicate_stmt |
177
+ predicate_obj = predicate_stmt.object
178
+ urls << predicate_obj.to_str if predicate_obj.is_a?(RDF::URI)
179
+ }
180
+ urls
92
181
  end
93
-
94
- def self.create(attrs = {})
95
- a = Triannon::Annotation.new attrs
96
- a.save
97
- a
98
- end
99
-
100
- def save
101
- # check if valid?
102
- graph
103
- @id = Triannon::LdpCreator.create self
104
- end
105
-
106
- def destroy
107
- Triannon::LdpDestroyer.destroy @id
108
- end
109
-
110
- def self.find(key)
111
- oa_graph = Triannon::LdpLoader.load key
112
- anno = Triannon::Annotation.new
113
- anno.graph = oa_graph
114
- anno.id = key
115
- anno
116
- end
117
-
118
- def self.all
119
- Triannon::LdpLoader.find_all
182
+
183
+ # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
184
+ # TODO: re-usable as part of Triannon::Graph class?
185
+ # @return [Array<String>] body chars as Strings, in an Array (one element for each contentAsText body)
186
+ def body_chars
187
+ result = []
188
+ q = RDF::Query.new
189
+ q << [nil, RDF::OpenAnnotation.hasBody, :body]
190
+ q << [:body, RDF.type, RDF::Content.ContentAsText]
191
+ q << [:body, RDF::Content.chars, :body_chars]
192
+ solns = graph.query q
193
+ solns.each { |soln|
194
+ result << soln.body_chars.value.strip
195
+ }
196
+ result
120
197
  end
121
198
 
122
-
123
199
  private
124
200
 
125
201
  # loads RDF::Graph from data attribute. If data is in json-ld, converts it to turtle.
@@ -14,12 +14,13 @@ module Triannon
14
14
  end
15
15
 
16
16
  def base_uri
17
- res = graph.query anno_query
17
+ res = graph.query Triannon::Graph.anno_query
18
18
  res.first.s
19
19
  end
20
20
 
21
+ # @return [Array<String>] the uris of each LDP body resource
21
22
  def body_uris
22
- q = anno_query
23
+ q = Triannon::Graph.anno_query.dup
23
24
  q << [:s, RDF::OpenAnnotation.hasBody, :body_uri]
24
25
  solns = graph.query q
25
26
  result = []
@@ -29,8 +30,9 @@ module Triannon
29
30
  result
30
31
  end
31
32
 
33
+ # @return [Array<String>] the uris of each LDP target resource
32
34
  def target_uris
33
- q = anno_query
35
+ q = Triannon::Graph.anno_query.dup
34
36
  q << [:s, RDF::OpenAnnotation.hasTarget, :target_uri]
35
37
  solns = graph.query q
36
38
  result = []
@@ -46,11 +48,5 @@ module Triannon
46
48
  graph.insert(statements) if statements && statements.size > 0
47
49
  end
48
50
 
49
- private
50
- def anno_query
51
- q = RDF::Query.new
52
- q << [:s, RDF.type, RDF::OpenAnnotation.Annotation]
53
- end
54
-
55
51
  end
56
52
  end
@@ -24,9 +24,10 @@ module Triannon
24
24
  @id = stmnt.subject.to_s.split('/').last
25
25
  @root_uri = RDF::URI.new(Triannon.config[:triannon_base_url] + "/#{@id}")
26
26
  @oa_graph << [@root_uri, RDF.type, RDF::OpenAnnotation.Annotation]
27
-
28
27
  elsif stmnt.predicate == RDF::OpenAnnotation.motivatedBy
29
28
  @oa_graph << [@root_uri, stmnt.predicate, stmnt.object]
29
+ elsif stmnt.predicate == RDF::OpenAnnotation.annotatedAt
30
+ @oa_graph << [@root_uri, stmnt.predicate, stmnt.object]
30
31
  end
31
32
  end
32
33
  end
@@ -63,7 +64,7 @@ module Triannon
63
64
  external_uri = solns.first.object
64
65
  @oa_graph << [subject_obj, predicate, external_uri]
65
66
 
66
- Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
67
+ Triannon::Graph.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
67
68
  if stmt.subject == uri_obj && stmt.predicate != RDF::Triannon.externalReference
68
69
  @oa_graph << [external_uri, stmt.predicate, stmt.object]
69
70
  else
@@ -89,7 +90,7 @@ module Triannon
89
90
  blank_node = RDF::Node.new
90
91
  @oa_graph << [subject_obj, predicate, blank_node]
91
92
 
92
- Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
93
+ Triannon::Graph.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
93
94
  if stmt.subject == uri_obj
94
95
  @oa_graph << [blank_node, stmt.predicate, stmt.object]
95
96
  else
@@ -118,7 +119,7 @@ module Triannon
118
119
  source_obj = nil
119
120
  selector_obj = nil
120
121
  selector_blank_node = nil
121
- specific_res_stmts = Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph)
122
+ specific_res_stmts = Triannon::Graph.subject_statements(uri_obj, @ldp_anno_graph)
122
123
  specific_res_stmts.each { |stmt|
123
124
  if stmt.predicate == RDF::OpenAnnotation.hasSource
124
125
  # expecting a hash URI
@@ -170,7 +171,7 @@ module Triannon
170
171
 
171
172
  default_obj = nil
172
173
  item_objs = []
173
- choice_stmts = Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph)
174
+ choice_stmts = Triannon::Graph.subject_statements(uri_obj, @ldp_anno_graph)
174
175
  choice_stmts.each { |stmt|
175
176
  if stmt.predicate == RDF::OpenAnnotation.default
176
177
  default_obj = stmt.object
@@ -1,58 +1,58 @@
1
-
2
1
  module Triannon
3
2
 
4
- # creates a new Annotation in the LDP server
5
- class LdpCreator
3
+ # writes data/objects to the LDP server; also does deletes
4
+ class LdpWriter
6
5
 
7
6
  # use LDP protocol to create the OpenAnnotation.Annotation in an RDF store
8
7
  # @param [Triannon::Annotation] anno a Triannon::Annotation object, from which we use the graph
9
- def self.create(anno)
8
+ def self.create_anno(anno)
10
9
  if anno && anno.graph
11
10
  # TODO: special case if the Annotation object already has an id --
12
11
  # see https://github.com/sul-dlss/triannon/issues/84
13
- result = Triannon::LdpCreator.new anno
14
- result.create_base
12
+ ldp_writer = Triannon::LdpWriter.new anno
13
+ id = ldp_writer.create_base
15
14
 
16
15
  bodies_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
17
16
  if bodies_solns.size > 0
18
- result.create_body_container
19
- result.create_body_resources
17
+ ldp_writer.create_body_container
18
+ ldp_writer.create_body_resources
20
19
  end
21
20
 
22
21
  targets_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
23
22
  # NOTE: Annotation is invalid if there are no target statements
24
23
  if targets_solns.size > 0
25
- result.create_target_container
26
- result.create_target_resources
24
+ ldp_writer.create_target_container
25
+ ldp_writer.create_target_resources
27
26
  end
28
27
 
29
- result.id
28
+ id
30
29
  end
31
30
  end
32
31
 
33
- # given an RDF::Resource (an RDF::Node or RDF::URI), look for all the statements with that object
34
- # as the subject, and recurse through the graph to find all descendant statements pertaining to the subject
35
- # @param subject the RDF object to be used as the subject in the graph query. Should be an RDF::Node or RDF::URI
36
- # @param [RDF::Graph] graph
37
- # @return [Array[RDF::Statement]] all the triples with the given subject
38
- def self.subject_statements(subject, graph)
39
- result = []
40
- graph.query([subject, nil, nil]).each { |stmt|
41
- result << stmt
42
- subject_statements(stmt.object, graph).each { |s| result << s }
43
- }
44
- result.uniq
32
+ # deletes the indicated container and all its child containers from the LDP store
33
+ # @param [String] id the unique id for the LDP container for an annotation
34
+ # May be a compound id, such as uuid1/t/uuid2, in which case the LDP container object uuid2 and its children
35
+ # are deleted from the LDP store, but LDP containers uuid1/t and uuid1 are not deleted from the LDP store.
36
+ def self.delete_container id
37
+ ldpw = Triannon::LdpWriter.new nil
38
+ ldpw.delete_containers id
45
39
  end
46
-
47
- attr_accessor :id
48
-
40
+
41
+ class << self
42
+ alias_method :delete_anno, :delete_container
43
+ end
44
+
49
45
  # @param [Triannon::Annotation] anno a Triannon::Annotation object
50
- def initialize(anno)
46
+ # @param [String] id the unique id for the LDP container for the passed annotation; defaults to nil
47
+ def initialize(anno, id=nil)
51
48
  @anno = anno
49
+ @id = id
52
50
  @base_uri = Triannon.config[:ldp_url]
53
51
  end
54
52
 
55
- # POSTS a ttl representation of the LDP Annotation container to the LDP store
53
+ # creates a stored LDP container for this object's Annotation, without its targets or bodies (as those are put in descendant containers)
54
+ # SIDE EFFECT: assigns the uuid of the container created to @id
55
+ # @return [String] the unique id for the LDP container created for this annotation
56
56
  def create_base
57
57
  if @anno.graph.query([nil, RDF::Triannon.externalReference, nil]).count > 0
58
58
  raise Triannon::ExternalReferenceError, "Incoming annotations may not have http://triannon.stanford.edu/ns/externalReference as a predicate."
@@ -60,48 +60,15 @@ module Triannon
60
60
 
61
61
  # TODO: special case if the Annotation object already has an id --
62
62
  # see https://github.com/sul-dlss/triannon/issues/84
63
+
64
+ # we need to work with a copy of the graph so we don't change @anno.graph
63
65
  g = RDF::Graph.new
64
66
  @anno.graph.each { |s|
65
67
  g << s
66
68
  }
67
-
68
- # don't include the hasBody statements and any other statements associated with them
69
- bodies_stmts = g.query([nil, RDF::OpenAnnotation.hasBody, nil])
70
- bodies_stmts.each { |has_body_stmt|
71
- g.delete has_body_stmt
72
- body_obj = has_body_stmt.object
73
- Triannon::LdpCreator.subject_statements(body_obj, g).each { |s|
74
- g.delete s
75
- }
76
- }
77
-
78
- # don't include the hasTarget statements and any other statements associated with them
79
- targets_stmts = g.query([nil, RDF::OpenAnnotation.hasTarget, nil])
80
- targets_stmts.each { |has_target_stmt|
81
- g.delete has_target_stmt
82
- target_obj = has_target_stmt.object
83
- Triannon::LdpCreator.subject_statements(target_obj, g).each { |s|
84
- g.delete s
85
- }
86
- }
87
-
88
- # transform an outer blank node into a null relative URI
89
- anno_stmts = g.query([nil, RDF.type, RDF::OpenAnnotation.Annotation])
90
- anno_rdf_obj = anno_stmts.first.subject
91
- if anno_rdf_obj.is_a?(RDF::Node)
92
- # we need to use the null relative URI representation of blank nodes to write to LDP
93
- anno_subject = RDF::URI.new
94
- else # it's already a URI
95
- anno_subject = anno_rdf_obj
96
- end
97
- Triannon::LdpCreator.subject_statements(anno_rdf_obj, g).each { |s|
98
- if s.subject == anno_rdf_obj && anno_subject != anno_rdf_obj
99
- g << RDF::Statement({:subject => anno_subject,
100
- :predicate => s.predicate,
101
- :object => s.object})
102
- g.delete s
103
- end
104
- }
69
+ g = Triannon::Graph.new(g)
70
+ g.remove_non_base_statements
71
+ g.make_null_relative_uri_out_of_blank_node
105
72
 
106
73
  @id = create_resource g.to_ttl
107
74
  end
@@ -126,38 +93,68 @@ module Triannon
126
93
  create_resources_in_container RDF::OpenAnnotation.hasTarget
127
94
  end
128
95
 
129
- def conn
130
- @c ||= Faraday.new @base_uri
96
+ # @param [Array<String>] ldp_container_uris an Array of ids for LDP containers. (can also be a String)
97
+ # e.g. [@base_uri/(uuid1)/t/(uuid2), @base_uri/(uuid1)/t/(uuid3)] or [@base_uri/(uuid)] or (uuid)
98
+ # @return true if a resource was deleted; false otherwise
99
+ def delete_containers ldp_container_uris
100
+ if ldp_container_uris.kind_of? String
101
+ ldp_container_uris = [ldp_container_uris]
102
+ end
103
+ something_deleted = false
104
+ ldp_container_uris.each { |uri|
105
+ ldp_id = uri.to_s.split(@base_uri + '/').last
106
+ resp = conn.delete { |req| req.url ldp_id }
107
+ if resp.status != 204
108
+ raise "Unable to delete LDP container: #{ldp_id}\nResponse Status: #{resp.status}\nResponse Body: #{resp.body}"
109
+ end
110
+ something_deleted = true
111
+ }
112
+ something_deleted
131
113
  end
132
114
 
133
115
  protected
134
- def create_resource body, url = nil
135
- response = conn.post do |req|
136
- req.url url if url
116
+
117
+ # POSTS a ttl representation of a graph to a newly created LDP container in the LDP store
118
+ # @param [String] ttl a turtle representation of RDF data to be put in the new LDP container
119
+ # @param [String] parent_path the path portion of the url for the LDP parent container for this resource
120
+ # if no path is supplied, then the resource will be created as a child of the root annotation;
121
+ # expected paths would also be (anno_id)/t for a target resource (inside the target container of anno_id)
122
+ # or (anno_id)/b for a body resource (inside the body container of anno_id)
123
+ # @return [String] uuid representing the unique id of the newly created LDP container
124
+ def create_resource ttl, parent_path = nil
125
+ resp = conn.post do |req|
126
+ req.url parent_path if parent_path
137
127
  req.headers['Content-Type'] = 'application/x-turtle'
138
- req.body = body
128
+ req.body = ttl
129
+ end
130
+ if resp.status != 200 && resp.status != 201
131
+ raise "Unable to create LDP resource in container #{url}: Response Status: #{resp.status}\nResponse Body: #{resp.body}\nAnnotation sent: #{body}"
139
132
  end
140
- new_url = response.headers['Location'] ? response.headers['Location'] : response.headers['location']
133
+ new_url = resp.headers['Location'] ? resp.headers['Location'] : resp.headers['location']
141
134
  new_url.split('/').last if new_url
142
135
  end
143
-
136
+
144
137
  # 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
145
- # The id of the created containter will be (base container id)b if hasBody or (base container id)/t if hasTarget
138
+ # The id of the created containter will be (base container id)/b if hasBody or (base container id)/t if hasTarget
146
139
  # @param [RDF::Vocabulary::Term] oa_vocab_term RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
147
140
  def create_direct_container oa_vocab_term
148
141
  null_rel_uri = RDF::URI.new
149
142
  g = RDF::Graph.new
150
143
  g << [null_rel_uri, RDF.type, RDF::LDP.DirectContainer]
151
144
  g << [null_rel_uri, RDF::LDP.hasMemberRelation, oa_vocab_term]
152
- g << [null_rel_uri, RDF::LDP.membershipResource, RDF::URI.new("#{@base_uri}/#{id}")]
145
+ g << [null_rel_uri, RDF::LDP.membershipResource, RDF::URI.new("#{@base_uri}/#{@id}")]
153
146
 
154
- response = conn.post do |req|
155
- req.url "#{id}"
147
+ resp = conn.post do |req|
148
+ req.url "#{@id}"
156
149
  req.headers['Content-Type'] = 'application/x-turtle'
157
- # OA vocab relationships all of form "hasXXX"
150
+ # OA vocab relationships all of form "hasXXX" so this becomes 't' or 'b'
158
151
  req.headers['Slug'] = oa_vocab_term.fragment.slice(3).downcase
159
152
  req.body = g.to_ttl
160
153
  end
154
+ if resp.status != 201
155
+ raise "Unable to create #{oa_vocab_term.fragment.sub('has', '')} LDP container for anno: Response Status: #{resp.status}\nResponse Body: #{resp.body}"
156
+ end
157
+ resp
161
158
  end
162
159
 
163
160
  # create the target/body resources inside the (already created) target/body container
@@ -193,7 +190,7 @@ module Triannon
193
190
  # add statements with predicate_obj as the subject
194
191
  orig_hash_uri_objs = [] # the orig URI objects from [targetObject, OA.hasSource/.default/.item, (uri)] statements
195
192
  hash_uri_counter = 1
196
- Triannon::LdpCreator.subject_statements(predicate_obj, @anno.graph).each { |s|
193
+ Triannon::Graph.subject_statements(predicate_obj, @anno.graph).each { |s|
197
194
  if s.subject == predicate_obj
198
195
  # deal with any external URI references which may occur in:
199
196
  # OA.hasSource (from SpecificResource), OA.default or OA.item (from Choice, Composite, List)
@@ -229,7 +226,7 @@ module Triannon
229
226
  :predicate => RDF::Triannon.externalReference,
230
227
  :object => RDF::URI.new(orig_hash_uri_obj.to_s)})
231
228
  # and all of the orig URL's addl props
232
- Triannon::LdpCreator.subject_statements(orig_hash_uri_obj, @anno.graph).each { |ss|
229
+ Triannon::Graph.subject_statements(orig_hash_uri_obj, @anno.graph).each { |ss|
233
230
  if ss.subject == orig_hash_uri_obj
234
231
  graph_for_resource << RDF::Statement({:subject => new_hash_uri_obj,
235
232
  :predicate => ss.predicate,
@@ -254,7 +251,7 @@ module Triannon
254
251
  # make sure the graph we will write contains no extraneous statements about URIs
255
252
  # now represented as hash URIs
256
253
  orig_hash_uri_objs.each { |uri_node|
257
- Triannon::LdpCreator.subject_statements(uri_node, graph_for_resource).each { |s|
254
+ Triannon::Graph.subject_statements(uri_node, graph_for_resource).each { |s|
258
255
  graph_for_resource.delete(s)
259
256
  }
260
257
  }
@@ -266,6 +263,10 @@ module Triannon
266
263
  }
267
264
  resource_ids
268
265
  end
269
-
266
+
267
+ def conn
268
+ @c ||= Faraday.new @base_uri
269
+ end
270
+
270
271
  end
271
- end
272
+ end
@@ -0,0 +1,102 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <schema name="Triannon" version="1.5">
3
+ <uniqueKey>id</uniqueKey>
4
+ <solrQueryParser defaultOperator="AND"/>
5
+
6
+ <fields>
7
+ <!-- _version_ for SolrCloud -->
8
+ <field name="_version_" type="long" indexed="true" stored="true" multiValued="false"/>
9
+
10
+ <field name="id" type="string" stored="true" indexed="true" multiValued="false" required="true"/>
11
+ <field name="timestamp" type="date" stored="true" indexed="true" multiValued="false" default="NOW"/>
12
+
13
+ <field name="motivation" type="string" stored="true" indexed="true" multiValued="true"/>
14
+ <!-- date field format: 1995-12-31T23:59:59Z; or w fractional seconds: 1995-12-31T23:59:59.999Z -->
15
+ <field name="annotated_at" type="date" stored="true" indexed="true" multiValued="false"/>
16
+ <!-- trie field for range queries and faceting -->
17
+ <field name="annotated_at_tdate" type="tdate" stored="false" indexed="true" multiValued="false"/>
18
+ <field name="annotated_by_unstem" type="text_ws_lc" stored="false" indexed="true" multiValued="true"/>
19
+ <field name="annotated_by_stem" type="text_en" stored="true" indexed="true" multiValued="true"/>
20
+ <field name="target_url" type="url" stored="true" indexed="true" multiValued="true"/>
21
+ <field name="target_type" type="string" stored="true" indexed="true" multiValued="true"/>
22
+ <field name="body_url" type="url" stored="true" indexed="true" multiValued="true"/>
23
+ <field name="body_type" type="string" stored="true" indexed="true" multiValued="true"/>
24
+ <field name="body_chars_exact" type="alphaSort" stored="true" indexed="true" multiValued="true"/>
25
+ <field name="body_chars_unstem" type="text_ws_lc" stored="false" indexed="true" multiValued="true"/>
26
+ <field name="body_chars_stem" type="text_en" stored="false" indexed="true" multiValued="true"/>
27
+
28
+ <field name="anno_jsonld" type="string" stored="true" indexed="false" multiValued="false"/>
29
+ </fields>
30
+
31
+
32
+ <!-- unstemmed search fields -->
33
+ <copyField source="annotated_by_stem" dest="annotated_by_unstem" />
34
+ <copyField source="body_chars_exact" dest="body_chars_unstem" />
35
+ <copyField source="body_chars_exact" dest="body_chars_stem" />
36
+ <copyField source="annotated_at" dest="annotated_at_tdate" />
37
+
38
+
39
+ <types>
40
+ <fieldType name="string" class="solr.StrField" sortMissingLast="true" />
41
+ <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
42
+ <fieldType name="rand" class="solr.RandomSortField" omitNorms="true"/>
43
+
44
+ <!-- Default numeric field types. -->
45
+ <fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
46
+ <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/>
47
+ <fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/>
48
+ <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/>
49
+
50
+ <!-- trie numeric field types for faster range queries -->
51
+ <fieldType name="tint" class="solr.TrieIntField" precisionStep="8" positionIncrementGap="0"/>
52
+ <fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" positionIncrementGap="0"/>
53
+ <fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" positionIncrementGap="0"/>
54
+ <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" positionIncrementGap="0"/>
55
+
56
+ <!-- date field format: 1995-12-31T23:59:59Z; or w fractional seconds: 1995-12-31T23:59:59.999Z -->
57
+ <fieldType name="date" class="solr.TrieDateField" precisionStep="0" positionIncrementGap="0"/>
58
+ <!-- A Trie based date field for faster date range queries and date faceting. -->
59
+ <fieldType name="tdate" class="solr.TrieDateField" precisionStep="6" positionIncrementGap="0"/>
60
+
61
+ <!-- A text field that only splits on whitespace and case folding for exact matching of words -->
62
+ <fieldType name="text_ws_lc" class="solr.TextField" positionIncrementGap="100">
63
+ <analyzer>
64
+ <tokenizer class="solr.WhitespaceTokenizerFactory"/>
65
+ <filter class="solr.ICUFoldingFilterFactory"/>
66
+ </analyzer>
67
+ </fieldType>
68
+
69
+ <!-- single token analyzed text, for sorting. Punctuation is significant. -->
70
+ <fieldtype name="alphaSort" class="solr.TextField" sortMissingLast="true" omitNorms="true">
71
+ <analyzer>
72
+ <charFilter class="solr.PatternReplaceCharFilterFactory" pattern="\s+" replacement=" "/>
73
+ <tokenizer class="solr.KeywordTokenizerFactory" />
74
+ <filter class="solr.ICUFoldingFilterFactory"/>
75
+ <filter class="solr.TrimFilterFactory" />
76
+ </analyzer>
77
+ </fieldtype>
78
+
79
+ <!-- A text field with defaults appropriate for English -->
80
+ <fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
81
+ <analyzer>
82
+ <tokenizer class="solr.ICUTokenizerFactory"/>
83
+ <filter class="solr.ICUFoldingFilterFactory"/>
84
+ <filter class="solr.EnglishPossessiveFilterFactory"/>
85
+ <filter class="solr.EnglishMinimalStemFilterFactory"/>
86
+ </analyzer>
87
+ </fieldType>
88
+
89
+ <!-- for urls - ignore http:// and https:// prefixes and trailing slash -->
90
+ <fieldType name="url" class="solr.TextField" positionIncrementGap="100">
91
+ <analyzer>
92
+ <charFilter class="solr.PatternReplaceCharFilterFactory" pattern="^\s*https?\:\/\/" replacement=""/>
93
+ <charFilter class="solr.PatternReplaceCharFilterFactory" pattern="\/\s*$" replacement=""/>
94
+ <tokenizer class="solr.KeywordTokenizerFactory" />
95
+ <filter class="solr.ICUFoldingFilterFactory"/>
96
+ <filter class="solr.TrimFilterFactory" />
97
+ </analyzer>
98
+ </fieldType>
99
+
100
+ </types>
101
+
102
+ </schema>
@@ -0,0 +1,239 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <!--
3
+ For more details about configurations options that may appear in
4
+ this file, see http://wiki.apache.org/solr/SolrConfigXml.
5
+ -->
6
+ <config>
7
+ <luceneMatchVersion>4.7</luceneMatchVersion>
8
+
9
+ <dataDir>${solr.data.dir:}</dataDir>
10
+
11
+ <directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
12
+ <codecFactory class="solr.SchemaCodecFactory"/>
13
+ <schemaFactory class="ClassicIndexSchemaFactory"/>
14
+
15
+ <indexConfig>
16
+ <lockType>${solr.lock.type:native}</lockType>
17
+ <unlockOnStartup>true</unlockOnStartup>
18
+ </indexConfig>
19
+
20
+ <jmx />
21
+
22
+ <updateHandler class="solr.DirectUpdateHandler2">
23
+ <updateLog>
24
+ <str name="dir">${solr.data.dir:}</str>
25
+ </updateLog>
26
+ <autoCommit>
27
+ <maxTime>15000</maxTime>
28
+ <openSearcher>false</openSearcher>
29
+ </autoCommit>
30
+ </updateHandler>
31
+
32
+ <query>
33
+ <maxBooleanClauses>1024</maxBooleanClauses>
34
+ <filterCache class="solr.FastLRUCache" size="512" initialSize="512" autowarmCount="0"/>
35
+ <queryResultCache class="solr.LRUCache" size="512" initialSize="512" autowarmCount="0"/>
36
+ <documentCache class="solr.LRUCache" size="512" initialSize="512" autowarmCount="0"/>
37
+ <enableLazyFieldLoading>true</enableLazyFieldLoading>
38
+ <queryResultWindowSize>20</queryResultWindowSize>
39
+ <queryResultMaxDocsCached>200</queryResultMaxDocsCached>
40
+
41
+ <!-- A New Searcher is opened when a (current) Searcher already exists..
42
+ It should only have the most common facets -->
43
+ <listener event="newSearcher" class="solr.QuerySenderListener">
44
+ <arr name="queries">
45
+ <lst>
46
+ <!-- default query for all objects: populate facet caches -->
47
+ <int name="rows">0</int>
48
+ <str name="fl">score</str>
49
+ <bool name="facet">true</bool>
50
+ <int name="facet.mincount">1</int>
51
+ <str name="facet.field">motivation</str>
52
+ <str name="f.motivation.facet.method">enum</str>
53
+ <str name="facet.field">target_type</str>
54
+ <str name="f.target_type.facet.method">enum</str>
55
+ <str name="facet.field">body_type</str>
56
+ <str name="f.body_type.facet.method">enum</str>
57
+ <str name="facet.field">annotated_at_tdate</str>
58
+ </lst>
59
+ <lst>
60
+ <!-- single object query: populate filter and fieldValue caches -->
61
+ <str name="q">id:a*</str>
62
+ <str name="defType">lucene</str>
63
+ <int name="rows">0</int>
64
+ <str name="fl">score</str>
65
+ <bool name="facet">true</bool>
66
+ <int name="facet.mincount">1</int>
67
+ <str name="facet.field">motivation</str>
68
+ <str name="f.motivation.facet.method">enum</str>
69
+ <str name="facet.field">target_type</str>
70
+ <str name="f.target_type.facet.method">enum</str>
71
+ <str name="facet.field">body_type</str>
72
+ <str name="f.body_type.facet.method">enum</str>
73
+ <str name="facet.field">annotated_at_tdate</str>
74
+ </lst>
75
+ </arr>
76
+ </listener>
77
+
78
+ <!-- A First Searcher is opened when there is _no_ existing (current) Searcher. ("fast warmup") -->
79
+ <listener event="firstSearcher" class="solr.QuerySenderListener">
80
+ <arr name="queries">
81
+ <lst>
82
+ <!-- default query for all objects: populate facet caches -->
83
+ <int name="rows">0</int>
84
+ <str name="fl">score</str>
85
+ <bool name="facet">true</bool>
86
+ <int name="facet.mincount">1</int>
87
+ <str name="facet.field">motivation</str>
88
+ <str name="f.motivation.facet.method">enum</str>
89
+ <str name="facet.field">target_type</str>
90
+ <str name="f.target_type.facet.method">enum</str>
91
+ <str name="facet.field">body_type</str>
92
+ <str name="f.body_type.facet.method">enum</str>
93
+ </lst>
94
+ <lst>
95
+ <!-- single object query: populate filter and fieldValue caches -->
96
+ <str name="q">id:a*</str>
97
+ <str name="defType">lucene</str>
98
+ <int name="rows">0</int>
99
+ <str name="fl">score</str>
100
+ <bool name="facet">true</bool>
101
+ <int name="facet.mincount">1</int>
102
+ <str name="facet.field">motivation</str>
103
+ <str name="f.motivation.facet.method">enum</str>
104
+ <str name="facet.field">target_type</str>
105
+ <str name="f.target_type.facet.method">enum</str>
106
+ <str name="facet.field">body_type</str>
107
+ <str name="f.body_type.facet.method">enum</str>
108
+ </lst>
109
+ </arr>
110
+ </listener>
111
+
112
+ <useColdSearcher>true</useColdSearcher>
113
+ <maxWarmingSearchers>2</maxWarmingSearchers>
114
+ </query>
115
+
116
+ <requestDispatcher handleSelect="false" >
117
+ <requestParsers enableRemoteStreaming="true"
118
+ multipartUploadLimitInKB="2048000"
119
+ formdataUploadLimitInKB="2048"
120
+ addHttpRequestToContext="false"/>
121
+ </requestDispatcher>
122
+
123
+ <requestHandler name="/select" class="solr.SearchHandler" default="true">
124
+ <lst name="defaults">
125
+ <str name="echoParams">explicit</str>
126
+ <str name="sort">score desc, annotated_at_tdate desc</str>
127
+ <int name="rows">20</int>
128
+ <str name="fl">* score</str>
129
+ <str name="wt">ruby</str>
130
+ <str name="indent">true</str>
131
+
132
+ <str name="defType">edismax</str>
133
+ <str name="q.alt">*:*</str>
134
+ <int name="qs">1</int>
135
+ <int name="ps">0</int>
136
+ <str name="mm">75%</str>
137
+ <float name="tie">0.01</float>
138
+ <bool name="lowercaseOperators">false</bool>
139
+ <!-- in case lucene query parser -->
140
+ <str name="df">anno_jsonld</str>
141
+ <str name="q.op">AND</str>
142
+
143
+ <str name="qf">
144
+ body_chars_exact^3
145
+ body_chars_unstem^2
146
+ body_chars_stem
147
+ annotated_by_unstem^2
148
+ annotated_by_stem
149
+ target_url
150
+ body_url
151
+ motivation
152
+ id
153
+ </str>
154
+ <str name="pf"> <!-- (phrase boost within result set) -->
155
+ body_chars_exact^15
156
+ body_chars_unstem^10
157
+ body_chars_stem^5
158
+ annotated_by_unstem^10
159
+ annotated_by_stem^5
160
+ </str>
161
+ <str name="pf3"> <!-- (token trigrams boost within result set) -->
162
+ body_chars_exact^9
163
+ body_chars_unstem^6
164
+ body_chars_stem^3
165
+ annotated_by_unstem^6
166
+ annotated_by_stem^3
167
+ </str>
168
+ <str name="pf2"> <!--(token bigrams boost within result set) -->
169
+ body_chars_exact^6
170
+ body_chars_unstem^4
171
+ body_chars_stem^2
172
+ annotated_by_unstem^4
173
+ annotated_by_stem^2
174
+ </str>
175
+
176
+ <bool name="facet">true</bool>
177
+ <int name="facet.mincount">1</int>
178
+ <str name="facet.field">motivation</str>
179
+ <str name="f.motivation.facet.method">enum</str>
180
+ <str name="facet.field">target_type</str>
181
+ <str name="f.target_type.facet.method">enum</str>
182
+ <str name="facet.field">body_type</str>
183
+ <str name="f.body_type.facet.method">enum</str>
184
+ <str name="facet.field">annotated_at_tdate</str>
185
+
186
+ </lst>
187
+ </requestHandler>
188
+
189
+ <!-- single document requests; use id=666 instead of q=id:666 -->
190
+ <requestHandler name="/doc" class="solr.SearchHandler" >
191
+ <lst name="defaults">
192
+ <str name="echoParams">explicit</str>
193
+ <str name="fl">anno_jsonld</str>
194
+ <int name="rows">1</int>
195
+ <str name="q">{!raw f=id v=$id}</str> <!-- use id=666 instead of q=id:666 -->
196
+ <str name="wt">ruby</str>
197
+ <str name="indent">true</str>
198
+ </lst>
199
+ </requestHandler>
200
+
201
+ <requestHandler name="/update" class="solr.UpdateRequestHandler" />
202
+
203
+ <!-- required by SolrCloud -->
204
+ <requestHandler name="/replication" class="solr.ReplicationHandler" startup="lazy" />
205
+ <requestHandler name="/get" class="solr.RealTimeGetHandler">
206
+ <lst name="defaults">
207
+ <str name="omitHeader">true</str>
208
+ </lst>
209
+ </requestHandler>
210
+
211
+ <requestHandler name="/analysis/field" class="solr.FieldAnalysisRequestHandler" startup="lazy" />
212
+ <requestHandler name="/analysis/document" class="solr.DocumentAnalysisRequestHandler" startup="lazy" />
213
+ <requestHandler name="/admin/" class="solr.admin.AdminHandlers" />
214
+
215
+ <requestHandler name="/admin/ping" class="solr.PingRequestHandler">
216
+ <lst name="invariants">
217
+ <str name="q">solrpingquery</str>
218
+ </lst>
219
+ <lst name="defaults">
220
+ <str name="echoParams">all</str>
221
+ </lst>
222
+ <!-- <str name="healthcheckFile">server-enabled.txt</str> -->
223
+ </requestHandler>
224
+
225
+ <!-- Echo the request contents back to the client -->
226
+ <requestHandler name="/debug/dump" class="solr.DumpRequestHandler" startup="lazy">
227
+ <lst name="defaults">
228
+ <str name="echoParams">explicit</str>
229
+ <str name="echoHandler">true</str>
230
+ </lst>
231
+ </requestHandler>
232
+
233
+ <!-- Legacy config for the admin interface -->
234
+ <admin>
235
+ <defaultQuery>*:*</defaultQuery>
236
+ </admin>
237
+
238
+ </config>
239
+
@@ -3,7 +3,7 @@ require 'rails/generators'
3
3
  module Triannon
4
4
  class Install < Rails::Generators::Base
5
5
  def inject_Triannon_routes
6
- route "mount Triannon::Engine, at: 'annotations'"
6
+ route "mount Triannon::Engine, at: ''"
7
7
  end
8
8
 
9
9
  def create_triannon_yml_file
data/lib/triannon.rb CHANGED
@@ -10,6 +10,7 @@ require 'faraday'
10
10
  module Triannon
11
11
  require "triannon/engine"
12
12
  require "triannon/error"
13
+ require "triannon/graph"
13
14
  require "triannon/jsonld_context"
14
15
 
15
16
  class << self
@@ -0,0 +1,86 @@
1
+ module Triannon
2
+ # a wrapper class for RDF::Graph that adds methods specific to Triannon
3
+ class Graph
4
+
5
+ # given an RDF::Resource (an RDF::Node or RDF::URI), look for all the statements with that object
6
+ # as the subject, and recurse through the graph to find all descendant statements pertaining to the subject
7
+ # @param subject the RDF object to be used as the subject in the graph query. Should be an RDF::Node or RDF::URI
8
+ # @param [RDF::Graph] graph
9
+ # @return [Array[RDF::Statement]] all the triples with the given subject
10
+ def self.subject_statements(subject, graph)
11
+ result = []
12
+ graph.query([subject, nil, nil]).each { |stmt|
13
+ result << stmt
14
+ subject_statements(stmt.object, graph).each { |s| result << s }
15
+ }
16
+ result.uniq
17
+ end
18
+
19
+ # @return [RDF::Query] query for a subject :s with type of RDF::OpenAnnotation.Annotation
20
+ def self.anno_query
21
+ q = RDF::Query.new
22
+ q << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
23
+ end
24
+
25
+ def initialize(rdf_graph)
26
+ @graph = rdf_graph
27
+ end
28
+
29
+ # remove all RDF::OpenAnnotation.hasBody and .hasTarget statements
30
+ # and any other statements associated with body and target objects,
31
+ # leaving all statements to be stored as part of base object in LDP store
32
+ def remove_non_base_statements
33
+ remove_has_target_statements
34
+ remove_has_body_statements
35
+ end
36
+
37
+ # remove all RDF::OpenAnnotation.hasBody statements and any other statements associated with body objects
38
+ def remove_has_body_statements
39
+ remove_predicate_and_its_object_statements RDF::OpenAnnotation.hasBody
40
+ end
41
+
42
+ # remove all RDF::OpenAnnotation.hasTarget statements and any other statements associated with body objects
43
+ def remove_has_target_statements
44
+ remove_predicate_and_its_object_statements RDF::OpenAnnotation.hasTarget
45
+ end
46
+
47
+ # remove all such predicate statements and any other statements associated with predicates' objects
48
+ def remove_predicate_and_its_object_statements(predicate)
49
+ predicate_stmts = @graph.query([nil, predicate, nil])
50
+ predicate_stmts.each { |pstmt|
51
+ pred_obj = pstmt.object
52
+ Triannon::Graph.subject_statements(pred_obj, @graph).each { |s|
53
+ @graph.delete s
54
+ } unless !Triannon::Graph.subject_statements(pred_obj, @graph)
55
+ @graph.delete pstmt
56
+ }
57
+ end
58
+
59
+ # transform an outer blank node into a null relative URI
60
+ def make_null_relative_uri_out_of_blank_node
61
+ anno_stmts = @graph.query([nil, RDF.type, RDF::OpenAnnotation.Annotation])
62
+ # FIXME: should actually look for subject with type of RDF::OpenAnnotation.Annotation
63
+ anno_rdf_obj = anno_stmts.first.subject
64
+ if anno_rdf_obj.is_a?(RDF::Node)
65
+ # we need to use the null relative URI representation of blank nodes to write to LDP
66
+ anno_subject = RDF::URI.new
67
+ else # it's already a URI
68
+ anno_subject = anno_rdf_obj
69
+ end
70
+ Triannon::Graph.subject_statements(anno_rdf_obj, @graph).each { |s|
71
+ if s.subject == anno_rdf_obj && anno_subject != anno_rdf_obj
72
+ @graph << RDF::Statement({:subject => anno_subject,
73
+ :predicate => s.predicate,
74
+ :object => s.object})
75
+ @graph.delete s
76
+ end
77
+ }
78
+ end
79
+
80
+ # send unknown methods to RDF::Graph
81
+ def method_missing(sym, *args, &block)
82
+ @graph.send sym, *args, &block
83
+ end
84
+
85
+ end
86
+ end
@@ -1,3 +1,3 @@
1
1
  module Triannon
2
- VERSION = "0.4.3"
2
+ VERSION = "0.4.4"
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.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Beer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-01-08 00:00:00.000000000 Z
13
+ date: 2015-01-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -257,10 +257,9 @@ files:
257
257
  - app/helpers/triannon/application_helper.rb
258
258
  - app/models/triannon/annotation.rb
259
259
  - app/models/triannon/annotation_ldp.rb
260
- - app/services/triannon/ldp_creator.rb
261
- - app/services/triannon/ldp_destroyer.rb
262
260
  - app/services/triannon/ldp_loader.rb
263
261
  - app/services/triannon/ldp_to_oa_mapper.rb
262
+ - app/services/triannon/ldp_writer.rb
264
263
  - app/services/triannon/root_annotation_creator.rb
265
264
  - app/views/layouts/triannon/application.html.erb
266
265
  - app/views/triannon/annotations/_form.html.erb
@@ -270,6 +269,8 @@ files:
270
269
  - app/views/triannon/annotations/show.html.erb
271
270
  - config/initializers/mime_types.rb
272
271
  - config/routes.rb
272
+ - config/solr/schema.xml
273
+ - config/solr/solrconfig.xml
273
274
  - config/triannon.yml
274
275
  - lib/generators/triannon/install_generator.rb
275
276
  - lib/rdf/triannon_vocab.rb
@@ -277,6 +278,7 @@ files:
277
278
  - lib/triannon.rb
278
279
  - lib/triannon/engine.rb
279
280
  - lib/triannon/error.rb
281
+ - lib/triannon/graph.rb
280
282
  - lib/triannon/iiif_presentation_2_context.json
281
283
  - lib/triannon/jsonld_context.rb
282
284
  - lib/triannon/oa_context_20130208.json
@@ -1,15 +0,0 @@
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