triannon 0.4.3 → 0.4.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.
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