triannon 0.4.4 → 0.5.0

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: f2328d724cb403823e2e7e9fbf5565a1244c1453
4
- data.tar.gz: 2424be58316b9d29eb4f8e48eee1ea1914a9b07c
3
+ metadata.gz: 9061e8fc2e0127540ae750118289a5f79a7f6969
4
+ data.tar.gz: 2785857023cdd9a5a2e5caa3ecb7810f613430cb
5
5
  SHA512:
6
- metadata.gz: 7de9938713c92a1c0e654ee71f44bfe558429e7ab2907a8a89752c12520e22e7bece73db6a54106f70e944075b346eb66fefe19c7317f4d7c60198cb6f5ce1a6
7
- data.tar.gz: dd44726e702079259c81c29ad89c19e8f9597e0a9493a44041e4021fbba90b4bd9c0901fbfdb57efc4907726472b01fdb802126d1420eaabcbf2ff6d002c6bac
6
+ metadata.gz: e149fc61982e7323f0a2401a316737f0dc0132fc2f3374f167317b149c0ec9541c47e5ec5364596c977c3d55f96bb006b213d5c78e966d091b770098d18fd5d4
7
+ data.tar.gz: edac1a83ba367c2064a0ba1c17662948531634873924c0740a947e272bf90004a8d2128edc9b2e73589fae1522b3d24dcedccec458459a7ee3d062de860d974d
data/README.md CHANGED
@@ -35,6 +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
+ * `solr_url:` Points to the baseurl of Solr instance configured for Triannon
38
39
  * `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
40
 
40
41
  Generate the root annotations container on the LDP server
@@ -45,20 +46,39 @@ $ rake triannon:create_root_container
45
46
 
46
47
  ## Running the application in development
47
48
 
48
- There is a bundled rake task for running the test app:
49
+ There is a bundled rake task for running the test app, but there is some one-time set up.
49
50
 
51
+ ### One time setup
52
+
53
+ ##### Set up a local instance of Fedora4
50
54
  ```console
51
- # One time setup: run the following 3 commands
52
55
  $ rake jetty:download
53
56
  $ rake jetty:unzip
57
+ ```
58
+ ##### Set up a Triannon flavored Solr
59
+ ```console
60
+ $ cp config/solr/solr.xml jetty/solr
61
+ $ cp config/solr/triannon-core jetty/solr
62
+ ```
63
+
64
+ ##### Set up a runnable Rails app that uses triannon gem
65
+ ```console
54
66
  $ rake engine_cart:generate # (first run only)
67
+ ```
55
68
 
56
- # Configure config/triannon.yml as specified above
57
- $ vi config/triannon.yml
69
+ ##### Configure spec/internal/config/triannon.yml as specified above
70
+ ```console
71
+ $ vi spec/internal/config/triannon.yml
72
+ ```
58
73
 
59
- # Generate root annotations container
74
+ ##### Generate root annotations container
75
+ ```console
60
76
  $ rake triannon:create_root_container
77
+ ```
61
78
 
62
79
  # Run the test app
80
+ ```console
81
+ $ rake jetty:start
63
82
  $ rake triannon:server
83
+ $ rake jetty:stop # at some point
64
84
  ```
@@ -59,143 +59,50 @@ module Triannon
59
59
  end
60
60
 
61
61
  def graph
62
- @graph ||= data_to_graph
62
+ @graph ||= begin
63
+ g = data_to_graph
64
+ Triannon::Graph.new g if g.kind_of? RDF::Graph
65
+ end
63
66
  end
64
67
 
68
+ # @param [RDF::Graph]
65
69
  def graph= g
66
- @graph = g
70
+ @graph = Triannon::Graph.new g if g.kind_of? RDF::Graph
67
71
  end
68
72
 
69
- # @return json-ld representation of graph with OpenAnnotation context as a url
73
+ # @return json-ld representation of anno with OpenAnnotation context as a url
70
74
  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
+ graph.jsonld_oa
75
76
  end
76
77
 
77
- # @return json-ld representation of graph with IIIF context as a url
78
+ # @return json-ld representation of anno with IIIF context as a url
78
79
  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?
87
- def url
88
- if graph_exists?
89
- solution = graph.query Triannon::Graph.anno_query
90
- if solution && solution.size == 1
91
- solution.first.s.to_s
92
- # TODO: raise exception if no URL?
93
- end
94
- end
80
+ graph.jsonld_iiif
95
81
  end
96
82
 
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
99
- def type
100
- if graph_exists?
101
- q = RDF::Query.new
102
- q << [:s, RDF::OpenAnnotation.hasTarget, nil] # must have a target
103
- q << [:s, RDF.type, :type]
104
- solution = graph.query q
105
- solution.distinct!
106
- if solution && solution.size == 1
107
- solution.first.type.to_s
108
- # TODO: raise exception if no type?
109
- end
110
- end
83
+ # @return [String] the id of this annotation as a url or nil if no graph
84
+ def id_as_url
85
+ graph.id_as_url if graph_exists?
111
86
  end
112
87
 
113
- # @return [Array] of uris expressing the OA motivated_by values
114
- # TODO: re-usable as part of Triannon::Graph class?
88
+ # @return [Array<String>] of urls expressing the OA motivated_by values or nil if no graph
115
89
  def motivated_by
116
- if graph_exists?
117
- q = Triannon::Graph.anno_query.dup
118
- q << [:s, RDF::OpenAnnotation.motivatedBy, :motivated_by]
119
- solution = graph.query q
120
- if solution && solution.size > 0
121
- motivations = []
122
- solution.each {|res|
123
- motivations << res.motivated_by.to_s
124
- }
125
- motivations
126
- # TODO: raise exception if none?
127
- end
128
- end
90
+ graph.motivated_by if graph_exists?
129
91
  end
130
92
 
131
93
  protected
132
94
 
133
- # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
95
+ # Add annotation to Solr as a Solr document
134
96
  def solr_save
135
- # puts "TO DO: send add to Solr (after save)"
136
- # pp solr_hash
97
+ # pass in id we got from LDP Store
98
+ solr_writer.add(graph.solr_hash(id))
137
99
  end
138
100
 
139
- # TODO: WRITE_COMMENTS_AND_TESTS_FOR_THIS_METHOD
101
+ # Delete annotation from Solr
140
102
  def solr_delete
141
- # puts "TO DO: send delete to Solr (after destroy)"
103
+ solr_writer.delete(id)
142
104
  end
143
105
 
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
167
- end
168
-
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
181
- end
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
197
- end
198
-
199
106
  private
200
107
 
201
108
  # loads RDF::Graph from data attribute. If data is in json-ld, converts it to turtle.
@@ -235,5 +142,9 @@ private
235
142
  graph && graph.size > 0
236
143
  end
237
144
 
145
+ def solr_writer
146
+ @sw ||= Triannon::SolrWriter.new
147
+ end
148
+
238
149
  end
239
150
  end
@@ -128,7 +128,7 @@ module Triannon
128
128
  req.body = ttl
129
129
  end
130
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}"
131
+ raise "Unable to create LDP resource in container #{parent_path}: Response Status: #{resp.status}\nResponse Body: #{resp.body}\nAnnotation sent: #{body}"
132
132
  end
133
133
  new_url = resp.headers['Location'] ? resp.headers['Location'] : resp.headers['location']
134
134
  new_url.split('/').last if new_url
@@ -0,0 +1,53 @@
1
+ module Triannon
2
+ class SolrWriter
3
+
4
+ def initialize
5
+ @rsolr_client = RSolr.connect :url => Triannon.config[:solr_url]
6
+ @logger = Rails.logger
7
+ @max_retries = Triannon.config[:max_solr_retries] || 5
8
+ @base_sleep_seconds = Triannon.config[:base_sleep_seconds] || 1
9
+ @max_sleep_seconds = Triannon.config[:max_sleep_seconds] || 5
10
+ end
11
+
12
+ # Add the document to Solr, retrying if an error occurs.
13
+ # See https://github.com/ooyala/retries for info on with_retries.
14
+ # @param [Hash] doc a Hash representation of the solr document to be added
15
+ def add(doc)
16
+ id = doc[:id]
17
+
18
+ handler = Proc.new do |exception, attempt_cnt, total_delay|
19
+ @logger.debug "#{exception.inspect} on Solr add attempt #{attempt_cnt} for #{id}"
20
+ end
21
+
22
+ with_retries(:handler => handler,
23
+ :max_tries => @max_retries,
24
+ :base_sleep_seconds => @base_sleep_seconds,
25
+ :max_sleep_seconds => @max_sleep_seconds) do |attempt|
26
+ @logger.debug "Solr add attempt #{attempt} for #{id}"
27
+ # add it and commit within 0.5 seconds
28
+ @rsolr_client.add(doc, :add_attributes => {:commitWithin => 500})
29
+ @logger.info "Successfully indexed #{id} to Solr on attempt #{attempt}"
30
+ end
31
+ end
32
+
33
+ # Delete the document from Solr, retrying if an error occurs.
34
+ # See https://github.com/ooyala/retries for info on with_retries.
35
+ # @param [String] the id of the solr document to be deleted
36
+ def delete(id)
37
+ handler = Proc.new do |exception, attempt_cnt, total_delay|
38
+ @logger.debug "#{exception.inspect} on Solr delete attempt #{attempt_cnt} for #{id}"
39
+ end
40
+
41
+ with_retries(:handler => handler,
42
+ :max_tries => @max_retries,
43
+ :base_sleep_seconds => @base_sleep_seconds,
44
+ :max_sleep_seconds => @max_sleep_seconds) do |attempt|
45
+ @logger.debug "Solr delete attempt #{attempt} for #{id}"
46
+ @rsolr_client.delete_by_id(id)
47
+ @rsolr_client.commit
48
+ @logger.info "Successfully deleted #{id} from Solr"
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -1,7 +1,7 @@
1
1
  <h1>Annotations</h1>
2
2
  <ul>
3
3
  <% @annotations.each do |a| %>
4
- <li><%= link_to a.url, annotation_path(a) %></li>
4
+ <li><%= link_to a.id_as_url, annotation_path(a) %></li>
5
5
  <% end %>
6
6
  </ul>
7
7
 
@@ -1,7 +1,7 @@
1
1
  <h1>Annotation</h1>
2
2
  <p>
3
3
  <strong>Url (@id):</strong>
4
- <%= @annotation.url %>
4
+ <%= @annotation.id_as_url %>
5
5
  </p>
6
6
  <p>
7
7
  <strong>Motivated By:</strong>
@@ -0,0 +1,36 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <!--
3
+ Licensed to the Apache Software Foundation (ASF) under one or more
4
+ contributor license agreements. See the NOTICE file distributed with
5
+ this work for additional information regarding copyright ownership.
6
+ The ASF licenses this file to You under the Apache License, Version 2.0
7
+ (the "License"); you may not use this file except in compliance with
8
+ the License. You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ -->
18
+
19
+ <!--
20
+ All (relative) paths are relative to the installation path
21
+
22
+ persistent: Save changes made via the API to this file
23
+ sharedLib: path to a lib directory that will be shared across all cores
24
+ -->
25
+ <solr persistent="false" sharedLib="lib">
26
+
27
+ <!--
28
+ adminPath: RequestHandler path to manage cores.
29
+ If 'null' (or absent), cores will not be manageable via REST
30
+ -->
31
+ <cores adminPath="/admin/cores" defaultCoreName="development">
32
+ <core name="development" instanceDir="development-core" />
33
+ <core name="test" instanceDir="test-core" />
34
+ <core name="triannon" instanceDir="triannon-core" />
35
+ </cores>
36
+ </solr>
@@ -190,7 +190,7 @@
190
190
  <requestHandler name="/doc" class="solr.SearchHandler" >
191
191
  <lst name="defaults">
192
192
  <str name="echoParams">explicit</str>
193
- <str name="fl">anno_jsonld</str>
193
+ <str name="fl">*</str>
194
194
  <int name="rows">1</int>
195
195
  <str name="q">{!raw f=id v=$id}</str> <!-- use id=666 instead of q=id:666 -->
196
196
  <str name="wt">ruby</str>
data/config/triannon.yml CHANGED
@@ -1,6 +1,15 @@
1
1
  development:
2
- ldp_url: yer_ldp_url_here
2
+ ldp_url: yer_ldp_store_url_here
3
+ solr_url: yer_triannon_solr_url_here
4
+ triannon_base_url: http://your.triannon-server.com/annotations/
5
+ max_solr_retries: 5
6
+ base_sleep_seconds: 1
7
+ max_sleep_seconds: 5
3
8
  test: &test
4
9
  ldp_url: yer_ldp_url_here
10
+ solr_url: yer_triannon_solr_url_here
11
+ triannon_base_url: http://your.triannon-server.com/annotations/
5
12
  production:
6
- ldp_url: yer_ldp_url_here
13
+ ldp_url: yer_ldp_url_here
14
+ solr_url: yer_triannon_solr_url_here
15
+ triannon_base_url: http://your.triannon-server.com/annotations/
@@ -10,12 +10,18 @@ module Triannon
10
10
  default_yml =<<-YML
11
11
  development:
12
12
  ldp_url: http://localhost:8983/fedora/rest/anno
13
- triannon_base_url: http://your.triannon-server.com
13
+ solr_url: http://localhost:8983/solr/triannon
14
+ triannon_base_url: http://your.triannon-server.com/annotations/
15
+ max_solr_retries: 5
16
+ base_sleep_seconds: 1
17
+ max_sleep_seconds: 5
14
18
  test: &test
15
19
  ldp_url: http://localhost:8983/fedora/rest/anno
16
- triannon_base_url: http://your.triannon-server.com
20
+ solr_url: http://localhost:8983/solr/triannon
21
+ triannon_base_url: http://your.triannon-server.com/annotations/
17
22
  production:
18
23
  ldp_url:
24
+ solr_url:
19
25
  triannon_base_url:
20
26
  YML
21
27
  create_file 'config/triannon.yml', default_yml
data/lib/triannon.rb CHANGED
@@ -5,7 +5,9 @@ require 'rdf/ldp'
5
5
  require 'rdf/fcrepo4'
6
6
  require 'rdf/triannon_vocab'
7
7
  require 'bootstrap-sass'
8
- require 'faraday'
8
+ require 'faraday' # for writing to LDP store
9
+ require 'rsolr'
10
+ require 'retries' # for writing to Solr
9
11
 
10
12
  module Triannon
11
13
  require "triannon/engine"
@@ -1,7 +1,10 @@
1
1
  module Triannon
2
2
  # a wrapper class for RDF::Graph that adds methods specific to Triannon
3
+ # this is intended to be used for an RDF::Graph of a single annotation
3
4
  class Graph
4
5
 
6
+ # Class Methods ----------------------------------------------------------------
7
+
5
8
  # given an RDF::Resource (an RDF::Node or RDF::URI), look for all the statements with that object
6
9
  # as the subject, and recurse through the graph to find all descendant statements pertaining to the subject
7
10
  # @param subject the RDF object to be used as the subject in the graph query. Should be an RDF::Node or RDF::URI
@@ -19,13 +22,139 @@ module Triannon
19
22
  # @return [RDF::Query] query for a subject :s with type of RDF::OpenAnnotation.Annotation
20
23
  def self.anno_query
21
24
  q = RDF::Query.new
22
- q << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
25
+ q << [:s, RDF.type, RDF::OpenAnnotation.Annotation]
23
26
  end
24
27
 
28
+
29
+ # Instance Methods ----------------------------------------------------------------
30
+
31
+ # instantiate this class for an RDF::Graph of a single annotation
25
32
  def initialize(rdf_graph)
26
33
  @graph = rdf_graph
27
34
  end
28
35
 
36
+ # @return json-ld representation of graph with OpenAnnotation context as a url
37
+ def jsonld_oa
38
+ inline_context = @graph.dump(:jsonld, :context => Triannon::JsonldContext::OA_CONTEXT_URL)
39
+ hash_from_json = JSON.parse(inline_context)
40
+ hash_from_json["@context"] = Triannon::JsonldContext::OA_CONTEXT_URL
41
+ hash_from_json.to_json
42
+ end
43
+
44
+ # @return json-ld representation of graph with IIIF context as a url
45
+ def jsonld_iiif
46
+ inline_context = @graph.dump(:jsonld, :context => Triannon::JsonldContext::IIIF_CONTEXT_URL)
47
+ hash_from_json = JSON.parse(inline_context)
48
+ hash_from_json["@context"] = Triannon::JsonldContext::IIIF_CONTEXT_URL
49
+ hash_from_json.to_json
50
+ end
51
+
52
+ # NOTE: NEVER get here before anno is stored
53
+ # the graph should have an assigned url for the @id of the root; it shouldn't be a blank node
54
+ # @param [String] the Triannon id for this anno. Defaults to nil, in which case id_as_url will be used.
55
+ # @return [Hash] a hash to be written to Solr, populated appropriately
56
+ def solr_hash(triannon_id=nil)
57
+ doc_hash = {}
58
+ # chars in Solr/Lucene query syntax are a big pain in Solr id fields, so we only use
59
+ # the uuid portion of the Triannon anno id, not the full url
60
+ triannon_id ||= id_as_url
61
+ solr_id = triannon_id.sub(Triannon.config[:ldp_url], "")
62
+ doc_hash[:id] = solr_id.sub(/^\//, "")
63
+
64
+ # use short strings for motivation field
65
+ doc_hash[:motivation] = motivated_by.map { |m| m.sub(RDF::OpenAnnotation.to_s, "") }
66
+
67
+ # date field format: 1995-12-31T23:59:59Z; or w fractional seconds: 1995-12-31T23:59:59.999Z
68
+ if annotated_at
69
+ begin
70
+ dt = Time.parse(annotated_at)
71
+ doc_hash[:annotated_at] = dt.iso8601 if dt
72
+ rescue ArgumentError
73
+ # ignore invalid datestamps
74
+ end
75
+ end
76
+ # doc_hash[:annotated_by_stem] # not yet implemented
77
+
78
+ doc_hash[:target_url] = predicate_urls RDF::OpenAnnotation.hasTarget
79
+ # TODO: recognize more target types
80
+ doc_hash[:target_type] = ['external_URI'] if doc_hash[:target_url].size > 0
81
+
82
+ doc_hash[:body_url] = predicate_urls RDF::OpenAnnotation.hasBody
83
+ doc_hash[:body_type] = []
84
+ doc_hash[:body_type] << 'external_URI' if doc_hash[:body_url].size > 0
85
+ doc_hash[:body_chars_exact] = body_chars.map {|bc| bc.strip}
86
+ doc_hash[:body_type] << 'content_as_text' if doc_hash[:body_chars_exact].size > 0
87
+ doc_hash[:body_type] << 'no_body' if doc_hash[:body_type].size == 0
88
+
89
+ doc_hash[:anno_jsonld] = jsonld_oa
90
+ doc_hash
91
+ end
92
+
93
+
94
+ # Canned Query methods ----------------------------------------------------------------
95
+
96
+ # @return [String] the id of this annotation as a url string
97
+ def id_as_url
98
+ solution = @graph.query self.class.anno_query
99
+ if solution && solution.size == 1
100
+ solution.first.s.to_s
101
+ # TODO: raise exception if not a URI or missing?
102
+ end
103
+ end
104
+
105
+ # @return [Array<String>] Array of urls expressing the OA motivated_by values
106
+ def motivated_by
107
+ motivations = []
108
+ q = self.class.anno_query.dup
109
+ q << [:s, RDF::OpenAnnotation.motivatedBy, :motivated_by]
110
+ solution = @graph.query q
111
+ if solution && solution.size > 0
112
+ solution.each {|res|
113
+ motivations << res.motivated_by.to_s
114
+ }
115
+ end
116
+ # TODO: raise exception if none? (validation)
117
+ motivations
118
+ end
119
+
120
+ # @param [RDF::URI] predicate either RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
121
+ # @return [Array<String>] urls for the predicate, as an Array of Strings
122
+ def predicate_urls(predicate)
123
+ urls = []
124
+ predicate_solns = @graph.query [nil, predicate, nil]
125
+ predicate_solns.each { |predicate_stmt |
126
+ predicate_obj = predicate_stmt.object
127
+ urls << predicate_obj.to_str.strip if predicate_obj.is_a?(RDF::URI)
128
+ }
129
+ urls
130
+ end
131
+
132
+ # For all bodies that are of type ContentAsText, get the characters as a single String in the returned Array.
133
+ # @return [Array<String>] body chars as Strings, in an Array (one element for each contentAsText body)
134
+ def body_chars
135
+ result = []
136
+ q = RDF::Query.new
137
+ q << [nil, RDF::OpenAnnotation.hasBody, :body]
138
+ q << [:body, RDF.type, RDF::Content.ContentAsText]
139
+ q << [:body, RDF::Content.chars, :body_chars]
140
+ solns = @graph.query q
141
+ solns.each { |soln|
142
+ result << soln.body_chars.value
143
+ }
144
+ result
145
+ end
146
+
147
+ # @return [String] The datetime from the annotatedAt property, or nil
148
+ def annotated_at
149
+ solution = @graph.query [nil, RDF::OpenAnnotation.annotatedAt, nil]
150
+ if solution && solution.size == 1
151
+ solution.first.object.to_s
152
+ end
153
+ end
154
+
155
+
156
+ # Changing the Graph ----------------------------------------------------------------
157
+
29
158
  # remove all RDF::OpenAnnotation.hasBody and .hasTarget statements
30
159
  # and any other statements associated with body and target objects,
31
160
  # leaving all statements to be stored as part of base object in LDP store
@@ -59,10 +188,9 @@ module Triannon
59
188
  # transform an outer blank node into a null relative URI
60
189
  def make_null_relative_uri_out_of_blank_node
61
190
  anno_stmts = @graph.query([nil, RDF.type, RDF::OpenAnnotation.Annotation])
62
- # FIXME: should actually look for subject with type of RDF::OpenAnnotation.Annotation
63
191
  anno_rdf_obj = anno_stmts.first.subject
64
192
  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
193
+ # use null relative URI representation of blank node
66
194
  anno_subject = RDF::URI.new
67
195
  else # it's already a URI
68
196
  anno_subject = anno_rdf_obj
@@ -1,3 +1,3 @@
1
1
  module Triannon
2
- VERSION = "0.4.4"
2
+ VERSION = "0.5.0"
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.4
4
+ version: 0.5.0
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-28 00:00:00.000000000 Z
13
+ date: 2015-01-31 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -124,6 +124,34 @@ dependencies:
124
124
  - - ">="
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: rsolr
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ - !ruby/object:Gem::Dependency
142
+ name: retries
143
+ requirement: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ type: :runtime
149
+ prerelease: false
150
+ version_requirements: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
127
155
  - !ruby/object:Gem::Dependency
128
156
  name: rspec
129
157
  requirement: !ruby/object:Gem::Requirement
@@ -261,6 +289,7 @@ files:
261
289
  - app/services/triannon/ldp_to_oa_mapper.rb
262
290
  - app/services/triannon/ldp_writer.rb
263
291
  - app/services/triannon/root_annotation_creator.rb
292
+ - app/services/triannon/solr_writer.rb
264
293
  - app/views/layouts/triannon/application.html.erb
265
294
  - app/views/triannon/annotations/_form.html.erb
266
295
  - app/views/triannon/annotations/edit.html.erb
@@ -269,8 +298,9 @@ files:
269
298
  - app/views/triannon/annotations/show.html.erb
270
299
  - config/initializers/mime_types.rb
271
300
  - config/routes.rb
272
- - config/solr/schema.xml
273
- - config/solr/solrconfig.xml
301
+ - config/solr/solr.xml
302
+ - config/solr/triannon-core/conf/schema.xml
303
+ - config/solr/triannon-core/conf/solrconfig.xml
274
304
  - config/triannon.yml
275
305
  - lib/generators/triannon/install_generator.rb
276
306
  - lib/rdf/triannon_vocab.rb