triannon 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/controllers/concerns/rdf_response_formats.rb +72 -0
- data/app/controllers/triannon/annotations_controller.rb +6 -69
- data/app/controllers/triannon/search_controller.rb +46 -0
- data/app/models/triannon/annotation.rb +1 -1
- data/app/services/triannon/solr_searcher.rb +164 -0
- data/app/views/triannon/search/find.html.erb +3 -0
- data/config/routes.rb +19 -14
- data/lib/generators/triannon/install_generator.rb +1 -1
- data/lib/triannon.rb +1 -0
- data/lib/triannon/iiif_anno_list.rb +59 -0
- data/lib/triannon/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d962055a1eab01f2506013e1b964c52c4e8f53b8
|
4
|
+
data.tar.gz: 2015fcbfba7f07fed353a59250a044376863aad0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a267c5b3a4c2fd76e386ca1b9f6e196dd5905a94c9590c9698ec906add9614df8c512d6382ce2c17f7aad619dc608435904dfe28fbd22748b2df87532310340e
|
7
|
+
data.tar.gz: c22b8ddb6e7b659598f879aceefa008345b35995b8945b8aa57a218162f5b29a5cc2e2bc420034727e02af14624fc113949165acfb11ab7331d7c9f6b7073d03
|
data/README.md
CHANGED
@@ -50,7 +50,7 @@ Set up caching for jsonld context documents:
|
|
50
50
|
** add to Gemfile:
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
gem 'rest-client', '~> 1.7.
|
53
|
+
gem 'rest-client', '~> 1.7.3' # problem with rest-client 1.8.0 and rest-client-components
|
54
54
|
gem 'rack-cache'
|
55
55
|
gem 'rest-client-components'
|
56
56
|
```
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# methods to support RDF response formats
|
2
|
+
module RdfResponseFormats
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# find first mime type from request.accept that matches return mime type
|
6
|
+
def mime_type_from_accept(return_mime_types)
|
7
|
+
@mime_type_from_accept ||= begin
|
8
|
+
if request.accept && request.accept.is_a?(String)
|
9
|
+
accept_mime_types = request.accept.split(',')
|
10
|
+
accept_mime_types.each { |mime_type|
|
11
|
+
mime_str = mime_type.split("; profile=").first.strip
|
12
|
+
if return_mime_types.include? mime_str
|
13
|
+
return mime_str
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# set format to jsonld if it isn't already set
|
21
|
+
def default_format_jsonld
|
22
|
+
if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
|
23
|
+
request.format = "jsonld"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# parse the Accept HTTP header for the value of profile if it is a request for jsonld or json
|
28
|
+
# e.g. Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
|
29
|
+
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
30
|
+
def context_url_from_accept
|
31
|
+
if request.format == "jsonld" || request.format == "json"
|
32
|
+
accept_str = request.accept
|
33
|
+
if accept_str && accept_str.split("profile=") && accept_str.split("profile=").last
|
34
|
+
context_url = accept_str.split("profile=").last.strip
|
35
|
+
context_url = context_url[1, context_url.size] if context_url.start_with?('"')
|
36
|
+
context_url = context_url[0, context_url.size-1] if context_url.end_with?('"')
|
37
|
+
case context_url
|
38
|
+
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
39
|
+
Triannon::JsonldContext::OA_CONTEXT_URL,
|
40
|
+
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
41
|
+
context_url
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# parse the Accept HTTP Link for the value of rel if it is a request for jsonld or json
|
50
|
+
# e.g. Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
|
51
|
+
# note that the "type" part is optional
|
52
|
+
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
53
|
+
def context_url_from_link
|
54
|
+
if request.format == "jsonld" || request.format == "json"
|
55
|
+
link_str = request.headers["Link"]
|
56
|
+
if link_str && link_str.split("; rel=") && link_str.split("; rel=").first
|
57
|
+
context_url = link_str.split("; rel=").first.strip
|
58
|
+
case context_url
|
59
|
+
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
60
|
+
Triannon::JsonldContext::OA_CONTEXT_URL,
|
61
|
+
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
62
|
+
context_url
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
end
|
@@ -2,6 +2,8 @@ require_dependency "triannon/application_controller"
|
|
2
2
|
|
3
3
|
module Triannon
|
4
4
|
class AnnotationsController < ApplicationController
|
5
|
+
include RdfResponseFormats
|
6
|
+
|
5
7
|
before_action :default_format_jsonld, only: [:show]
|
6
8
|
before_action :set_annotation, only: [:show, :update, :destroy]
|
7
9
|
rescue_from Triannon::ExternalReferenceError, with: :ext_ref_error
|
@@ -51,7 +53,7 @@ module Triannon
|
|
51
53
|
end
|
52
54
|
|
53
55
|
# NOT YET IMPLEMENTED
|
54
|
-
# GET /annotations/1/edit
|
56
|
+
# GET /annotations/1/edit
|
55
57
|
# def edit
|
56
58
|
# end
|
57
59
|
|
@@ -71,7 +73,7 @@ module Triannon
|
|
71
73
|
content_type = request.headers["Content-Type"]
|
72
74
|
@annotation = Annotation.new({:data => request.body.read, :expected_content_type => content_type})
|
73
75
|
end
|
74
|
-
|
76
|
+
|
75
77
|
if @annotation.save
|
76
78
|
default_format_jsonld # NOTE: this must be here and not in before_filter or we get Missing template errors
|
77
79
|
flash[:notice] = "Annotation #{@annotation.id} was successfully created."
|
@@ -124,83 +126,18 @@ module Triannon
|
|
124
126
|
@annotation.destroy
|
125
127
|
redirect_to annotations_url, status: 204, notice: 'Annotation was successfully destroyed.'
|
126
128
|
end
|
127
|
-
|
129
|
+
|
128
130
|
private
|
129
131
|
|
130
132
|
def set_annotation
|
131
133
|
@annotation = Annotation.find(params[:id])
|
132
134
|
end
|
133
|
-
|
134
|
-
# set format to jsonld if it isn't already set
|
135
|
-
def default_format_jsonld
|
136
|
-
if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
|
137
|
-
request.format = "jsonld"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
# find first mime type from request.accept that matches return mime type
|
142
|
-
def mime_type_from_accept(return_mime_types)
|
143
|
-
@mime_type_from_accept ||= begin
|
144
|
-
if request.accept && request.accept.is_a?(String)
|
145
|
-
accept_mime_types = request.accept.split(',')
|
146
|
-
accept_mime_types.each { |mime_type|
|
147
|
-
mime_str = mime_type.split("; profile=").first.strip
|
148
|
-
if return_mime_types.include? mime_str
|
149
|
-
return mime_str
|
150
|
-
end
|
151
|
-
}
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# parse the Accept HTTP header for the value of profile if it is a request for jsonld or json
|
157
|
-
# e.g. Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
|
158
|
-
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
159
|
-
def context_url_from_accept
|
160
|
-
if request.format == "jsonld" || request.format == "json"
|
161
|
-
accept_str = request.accept
|
162
|
-
if accept_str && accept_str.split("profile=") && accept_str.split("profile=").last
|
163
|
-
context_url = accept_str.split("profile=").last.strip
|
164
|
-
context_url = context_url[1, context_url.size] if context_url.start_with?('"')
|
165
|
-
context_url = context_url[0, context_url.size-1] if context_url.end_with?('"')
|
166
|
-
case context_url
|
167
|
-
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
168
|
-
Triannon::JsonldContext::OA_CONTEXT_URL,
|
169
|
-
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
170
|
-
context_url
|
171
|
-
else
|
172
|
-
nil
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# parse the Accept HTTP Link for the value of rel if it is a request for jsonld or json
|
179
|
-
# e.g. Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
|
180
|
-
# note that the "type" part is optional
|
181
|
-
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
182
|
-
def context_url_from_link
|
183
|
-
if request.format == "jsonld" || request.format == "json"
|
184
|
-
link_str = request.headers["Link"]
|
185
|
-
if link_str && link_str.split("; rel=") && link_str.split("; rel=").first
|
186
|
-
context_url = link_str.split("; rel=").first.strip
|
187
|
-
case context_url
|
188
|
-
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
189
|
-
Triannon::JsonldContext::OA_CONTEXT_URL,
|
190
|
-
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
191
|
-
context_url
|
192
|
-
else
|
193
|
-
nil
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
135
|
|
199
136
|
# handle Triannon::ExternalReferenceError
|
200
137
|
def ext_ref_error(exception)
|
201
138
|
render plain: exception.message, status: 403
|
202
139
|
end
|
203
|
-
|
140
|
+
|
204
141
|
# render json_ld respecting requested context
|
205
142
|
# @param [String] req_context set to "iiif" or "oa". Default is oa
|
206
143
|
# @param [String] mime_type the mime type to be set in the Content-Type header of the HTTP response
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_dependency "triannon/application_controller"
|
2
|
+
|
3
|
+
module Triannon
|
4
|
+
class SearchController < ApplicationController
|
5
|
+
include RdfResponseFormats
|
6
|
+
|
7
|
+
before_action :default_format_jsonld, only: [:find]
|
8
|
+
|
9
|
+
def find
|
10
|
+
anno_graphs_array = solr_searcher.find(params)
|
11
|
+
|
12
|
+
# add id to iiif_anno_list
|
13
|
+
@list_hash = Triannon::IIIFAnnoList.anno_list(anno_graphs_array)
|
14
|
+
@list_hash["@id"] = request.original_url if @list_hash
|
15
|
+
|
16
|
+
respond_to do |format|
|
17
|
+
format.jsonld { render :json => @list_hash.to_json, content_type: "application/ld+json" }
|
18
|
+
format.ttl {
|
19
|
+
accept_return_type = mime_type_from_accept(["application/x-turtle", "text/turtle"])
|
20
|
+
render :body => RDF::Graph.new.from_jsonld(@list_hash.to_json).to_ttl, content_type: accept_return_type if accept_return_type
|
21
|
+
}
|
22
|
+
format.rdfxml {
|
23
|
+
accept_return_type = mime_type_from_accept(["application/rdf+xml", "text/rdf+xml", "text/rdf"])
|
24
|
+
render :body => RDF::Graph.new.from_jsonld(@list_hash.to_json).to_rdfxml, content_type: accept_return_type if accept_return_type }
|
25
|
+
format.json {
|
26
|
+
accept_return_type = mime_type_from_accept(["application/json", "text/x-json", "application/jsonrequest"])
|
27
|
+
render :json => @list_hash.to_json, content_type: accept_return_type
|
28
|
+
}
|
29
|
+
format.xml {
|
30
|
+
accept_return_type = mime_type_from_accept(["application/xml", "text/xml", "application/x-xml"])
|
31
|
+
render :xml => RDF::Graph.new.from_jsonld(@list_hash.to_json).to_rdfxml, content_type: accept_return_type if accept_return_type }
|
32
|
+
format.html { render :find }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def solr_searcher
|
40
|
+
@ss ||= Triannon::SolrSearcher.new
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end # SearchController
|
45
|
+
|
46
|
+
end # Triannon
|
@@ -102,7 +102,7 @@ protected
|
|
102
102
|
|
103
103
|
# Add annotation to Solr as a Solr document
|
104
104
|
def solr_save
|
105
|
-
solr_writer.write(graph) if
|
105
|
+
solr_writer.write(graph) if id_as_url && !id_as_url.empty?
|
106
106
|
end
|
107
107
|
|
108
108
|
# Delete annotation from Solr
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Triannon
|
2
|
+
class SolrSearcher
|
3
|
+
|
4
|
+
# convert RSolr::Response object into an array of Triannon::Graph objects,
|
5
|
+
# where each graph object contains a single annotation returned in the response docs
|
6
|
+
# @param [Hash] rsolr_response an RSolr response to a query. It's actually an
|
7
|
+
# RSolr::HashWithResponse but let's not quibble
|
8
|
+
# @return [Array<Triannon::Graph>]
|
9
|
+
def self.anno_graphs_array(rsolr_response)
|
10
|
+
result = []
|
11
|
+
# TODO: deal with Solr pagination
|
12
|
+
rsolr_response['response']['docs'].each { |solr_doc_hash|
|
13
|
+
result << Triannon::Graph.new(RDF::Graph.new.from_jsonld(solr_doc_hash['anno_jsonld']))
|
14
|
+
}
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
# @note hardcoded Solr search service expectation in generated search params
|
19
|
+
# @note hardcoded mapping of REST params for /search to Solr params
|
20
|
+
#
|
21
|
+
# Convert action request params to appropriate params
|
22
|
+
# to be sent to the search service as part of a search request
|
23
|
+
#
|
24
|
+
# request params are given in "Annotation Lists in Triannon" by Robert Sanderson
|
25
|
+
# in Google Docs:
|
26
|
+
#
|
27
|
+
# - targetUri, value is a URI
|
28
|
+
# - bodyUri, value is a URI
|
29
|
+
# - bodyExact, value is a string
|
30
|
+
# - bodyKeyword, value is a string
|
31
|
+
# - bodyType, value is a URI
|
32
|
+
# - motivatedBy, value is a URI (or just the fragment portion)
|
33
|
+
# - annotatedBy, value is a URI
|
34
|
+
# - annotatedAt, value is a datetime
|
35
|
+
#
|
36
|
+
# @param [Hash<String => String>] controller_params params from Controller
|
37
|
+
# @return [Hash] params to send to Solr as a Hash
|
38
|
+
def self.solr_params(controller_params)
|
39
|
+
solr_params_hash = {}
|
40
|
+
q_terms_array = []
|
41
|
+
fq_terms_array = []
|
42
|
+
|
43
|
+
controller_params.each_pair { |k, v|
|
44
|
+
case k.downcase
|
45
|
+
when 'targeturi'
|
46
|
+
q_terms_array << q_terms_for_url("target_url", v)
|
47
|
+
when 'bodyuri'
|
48
|
+
q_terms_array << q_terms_for_url("body_url", v)
|
49
|
+
when 'bodyexact'
|
50
|
+
# no need to Solr escape value because it's in quotes
|
51
|
+
q_terms_array << "body_chars_exact:\"#{v}\""
|
52
|
+
when 'motivatedby'
|
53
|
+
case
|
54
|
+
when v.include?('#')
|
55
|
+
# we want fragment portion of URL value only, as that
|
56
|
+
# is what is in Solr
|
57
|
+
fq_terms_array << "motivation:#{RSolr.solr_escape(v.sub(/^.*#/, ''))}"
|
58
|
+
when v == "http://www.shared-canvas.org/ns/painting", v == "sc:painting"
|
59
|
+
fq_terms_array << "motivation:painting"
|
60
|
+
else
|
61
|
+
fq_terms_array << "motivation:#{RSolr.solr_escape(v)}"
|
62
|
+
end
|
63
|
+
when 'bodykeyword'
|
64
|
+
solr_params_hash[:kqf] = 'body_chars_exact^3 body_chars_unstem^2 body_chars_stem'
|
65
|
+
solr_params_hash[:kpf] = 'body_chars_exact^15 body_chars_unstem^10 body_chars_stem^5'
|
66
|
+
solr_params_hash[:kpf3] = 'body_chars_exact^9 body_chars_unstem^6 body_chars_stem^3'
|
67
|
+
solr_params_hash[:kpf2] = 'body_chars_exact^6 body_chars_unstem^4 body_chars_stem^2'
|
68
|
+
q_terms_array << '_query_:"{!dismax qf=$kqf pf=$kpf pf3=$kpf3 pf2=$kpf2}' + RSolr.solr_escape(v) + '"'
|
69
|
+
|
70
|
+
# TODO: add'l params to implement:
|
71
|
+
# targetType - fq
|
72
|
+
# bodyType - fq
|
73
|
+
# annotatedAt - fq (deal with time format and wildcard for specificity)
|
74
|
+
# annotatedBy - q (may be incomplete string)
|
75
|
+
end
|
76
|
+
}
|
77
|
+
|
78
|
+
q_terms_array.flatten
|
79
|
+
if q_terms_array.size > 0
|
80
|
+
solr_params_hash[:q] = q_terms_array.join(' AND ')
|
81
|
+
solr_params_hash[:defType] = "lucene"
|
82
|
+
end
|
83
|
+
if fq_terms_array.size > 0
|
84
|
+
solr_params_hash[:fq] = fq_terms_array
|
85
|
+
end
|
86
|
+
|
87
|
+
solr_params_hash
|
88
|
+
|
89
|
+
# TODO: integration tests for
|
90
|
+
# target_url with and without the scheme prefix
|
91
|
+
# target_url with and without fragment
|
92
|
+
# bodykeyword single terms, multiple terms, quoted strings ...
|
93
|
+
|
94
|
+
end # solr_params
|
95
|
+
|
96
|
+
|
97
|
+
# If the url contains a fragment, query terms should only match the exact
|
98
|
+
# url given (with the specific fragment). (i.e. foo.org#bar does not
|
99
|
+
# match foo.org)
|
100
|
+
# If the url does NOT contain a fragment, query terms should match the
|
101
|
+
# url given (no fragment) AND any urls that are the same with a fragment
|
102
|
+
# added. (i.e. foo.org matches foo.org#bar)
|
103
|
+
# @param [String] fieldname the name of the Solr field to be searched with url as a value
|
104
|
+
# @param [String] url the url value sought in the Solr field
|
105
|
+
# @return [Array<String>] an array of query terms to be added to the Solr q argument
|
106
|
+
def self.q_terms_for_url(fieldname, url)
|
107
|
+
q_terms = []
|
108
|
+
q_terms << "#{fieldname}:#{RSolr.solr_escape(url)}"
|
109
|
+
if !url.include? '#'
|
110
|
+
# Note: do NOT Solr escape the # (unnec) or the * (want Solr to view it as wildcard)
|
111
|
+
q_terms << "#{fieldname}:#{RSolr.solr_escape(url)}#*"
|
112
|
+
end
|
113
|
+
q_terms
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def initialize
|
118
|
+
@rsolr_client = RSolr.connect :url => Triannon.config[:solr_url]
|
119
|
+
@logger = Rails.logger
|
120
|
+
@max_retries = Triannon.config[:max_solr_retries] || 5
|
121
|
+
@base_sleep_seconds = Triannon.config[:base_sleep_seconds] || 1
|
122
|
+
@max_sleep_seconds = Triannon.config[:max_sleep_seconds] || 5
|
123
|
+
end
|
124
|
+
|
125
|
+
# to be called from controller:
|
126
|
+
# 1. converts controller params to solr params
|
127
|
+
# 2. sends request to Solr
|
128
|
+
# 3. converts Solr response object to array of anno graphs
|
129
|
+
# @param [Hash<String => String>] controller_params params from Controller
|
130
|
+
# @return [Array<Triannon::Graph>] array of Triannon::Graph objects,
|
131
|
+
# where each graph object contains a single annotation returned in the response docs
|
132
|
+
def find(controller_params)
|
133
|
+
solr_params = self.class.solr_params(controller_params)
|
134
|
+
solr_response = search(solr_params)
|
135
|
+
anno_graphs_array = self.class.anno_graphs_array(solr_response)
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
# send params to Solr 'select' with POST, retrying if an error occurs.
|
142
|
+
# See https://github.com/ooyala/retries for info on with_retries.
|
143
|
+
# @param [Hash] solr_params the params to send to Solr
|
144
|
+
# @return RSolr::Response object
|
145
|
+
def search(solr_params = {})
|
146
|
+
handler = Proc.new do |exception, attempt_cnt, total_delay|
|
147
|
+
@logger.debug "#{exception.inspect} on Solr search attempt #{attempt_cnt} for #{solr_params.inspect}"
|
148
|
+
end
|
149
|
+
|
150
|
+
response = nil
|
151
|
+
with_retries(:handler => handler,
|
152
|
+
:max_tries => @max_retries,
|
153
|
+
:base_sleep_seconds => @base_sleep_seconds,
|
154
|
+
:max_sleep_seconds => @max_sleep_seconds) do |attempt|
|
155
|
+
@logger.debug "Solr search attempt #{attempt} for #{solr_params.inspect}"
|
156
|
+
# use POST in case of long params
|
157
|
+
response = @rsolr_client.post 'select', :params => solr_params
|
158
|
+
@logger.info "Successfully searched Solr on attempt #{attempt}"
|
159
|
+
end
|
160
|
+
response
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
1
|
Triannon::Engine.routes.draw do
|
2
|
+
|
2
3
|
root to: 'annotations#index'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
constraints: lambda { |request|
|
5
|
+
resources :annotations, except: [:update, :edit],
|
6
|
+
# show action must explicitly forbid "new", "iiif" and "oa" as id values; couldn't
|
7
|
+
# figure out how to do it with regexp constraint since beginning and end regex
|
8
|
+
# matchers aren't allowed when enforcing formats for segment (e.g. :id)
|
9
|
+
constraints: lambda { |request|
|
9
10
|
id = request.env["action_dispatch.request.path_parameters"][:id]
|
10
|
-
id !~ /^
|
11
|
-
}
|
12
|
-
|
13
|
-
|
11
|
+
id !~ /^iiif$/ && id !~ /^oa$/ && id !~ /^search$/
|
12
|
+
} do
|
13
|
+
collection do
|
14
|
+
get 'search', to: 'search#find'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/search', to: 'search#find'
|
14
19
|
|
15
20
|
# allow jsonld context in path (only allow iiif or oa as values)
|
16
21
|
# must explicitly forbid "new" as id values; couldn't
|
17
|
-
#
|
18
|
-
#
|
19
|
-
get '/annotations/:jsonld_context/:id(.:format)', to: 'annotations#show',
|
20
|
-
constraints: lambda { |request|
|
22
|
+
# figure out how to do it with regexp constraint since beginning and end regex
|
23
|
+
# matchers aren't allowed when enforcing formats for segment (e.g. :id)
|
24
|
+
get '/annotations/:jsonld_context/:id(.:format)', to: 'annotations#show',
|
25
|
+
constraints: lambda { |request|
|
21
26
|
jsonld_context = request.env["action_dispatch.request.path_parameters"][:jsonld_context]
|
22
27
|
id = request.env["action_dispatch.request.path_parameters"][:id]
|
23
28
|
(jsonld_context =~ /^iiif$/ || jsonld_context =~ /^oa$/ ) && id !~ /^new$/
|
24
29
|
}
|
25
|
-
|
30
|
+
|
26
31
|
end
|
data/lib/triannon.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Triannon
|
2
|
+
|
3
|
+
class IIIFAnnoList
|
4
|
+
|
5
|
+
# from http://iiif.io/api/presentation/2/annotationList_frame.json
|
6
|
+
ANNO_LIST_FRAME = JSON.parse('
|
7
|
+
{
|
8
|
+
"@context" : "http://iiif.io/api/presentation/2/context.json",
|
9
|
+
"@type": "sc:AnnotationList",
|
10
|
+
"resources": [{
|
11
|
+
"@type": "oa:Annotation",
|
12
|
+
"on" : [{
|
13
|
+
"@embed" : false
|
14
|
+
}]
|
15
|
+
}]
|
16
|
+
}')
|
17
|
+
|
18
|
+
# from http://iiif.io/api/presentation/2/annotation_frame.json
|
19
|
+
ANNO_FRAME = JSON.parse('
|
20
|
+
{
|
21
|
+
"@context" : "http://iiif.io/api/presentation/2/context.json",
|
22
|
+
"@type": "oa:Annotation",
|
23
|
+
"on" : [{
|
24
|
+
"@embed" : false
|
25
|
+
}]
|
26
|
+
}')
|
27
|
+
|
28
|
+
# Class Methods ----------------------------------------------------------------
|
29
|
+
|
30
|
+
# take an Array of annos as Triannon::Graph objects and return a Hash representation
|
31
|
+
# of IIIF Annotation List
|
32
|
+
# @param [Array<Triannon::Graph>] tgraph_array annotations as Triannon::Graph objects
|
33
|
+
# @return [Hash] IIIF Annotation List as a Hash, containing the annotations in the array
|
34
|
+
def self.anno_list(tgraph_array)
|
35
|
+
if tgraph_array
|
36
|
+
result = {
|
37
|
+
"@context" => Triannon::JsonldContext::IIIF_CONTEXT_URL,
|
38
|
+
"@type" => "sc:AnnotationList",
|
39
|
+
"within" => {"@type" => "sc:Layer", "total" => tgraph_array.size },
|
40
|
+
"resources" => tgraph_array.map { |g|
|
41
|
+
embedded_body = JSON::LD::API.frame(JSON.parse(g.jsonld_iiif), ANNO_FRAME)
|
42
|
+
resource = embedded_body["@graph"]
|
43
|
+
if resource.is_a?(Array) && resource.size == 1
|
44
|
+
resource = resource.first
|
45
|
+
end
|
46
|
+
resource
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
# remove context from each anno as it is redundant
|
51
|
+
result["resources"].each { |anno_hash|
|
52
|
+
anno_hash.delete("@context")
|
53
|
+
}
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/triannon/version.rb
CHANGED
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
|
+
version: 0.7.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-
|
13
|
+
date: 2015-04-06 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -284,14 +284,14 @@ dependencies:
|
|
284
284
|
requirements:
|
285
285
|
- - "~>"
|
286
286
|
- !ruby/object:Gem::Version
|
287
|
-
version: 1.7.
|
287
|
+
version: 1.7.3
|
288
288
|
type: :development
|
289
289
|
prerelease: false
|
290
290
|
version_requirements: !ruby/object:Gem::Requirement
|
291
291
|
requirements:
|
292
292
|
- - "~>"
|
293
293
|
- !ruby/object:Gem::Version
|
294
|
-
version: 1.7.
|
294
|
+
version: 1.7.3
|
295
295
|
- !ruby/object:Gem::Dependency
|
296
296
|
name: rest-client-components
|
297
297
|
requirement: !ruby/object:Gem::Requirement
|
@@ -335,8 +335,10 @@ files:
|
|
335
335
|
- app/assets/javascripts/triannon/application.js
|
336
336
|
- app/assets/stylesheets/triannon/annotations.css
|
337
337
|
- app/assets/stylesheets/triannon/application.css.scss
|
338
|
+
- app/controllers/concerns/rdf_response_formats.rb
|
338
339
|
- app/controllers/triannon/annotations_controller.rb
|
339
340
|
- app/controllers/triannon/application_controller.rb
|
341
|
+
- app/controllers/triannon/search_controller.rb
|
340
342
|
- app/helpers/triannon/application_helper.rb
|
341
343
|
- app/models/triannon/annotation.rb
|
342
344
|
- app/models/triannon/annotation_ldp.rb
|
@@ -344,6 +346,7 @@ files:
|
|
344
346
|
- app/services/triannon/ldp_to_oa_mapper.rb
|
345
347
|
- app/services/triannon/ldp_writer.rb
|
346
348
|
- app/services/triannon/root_annotation_creator.rb
|
349
|
+
- app/services/triannon/solr_searcher.rb
|
347
350
|
- app/services/triannon/solr_writer.rb
|
348
351
|
- app/views/layouts/triannon/application.html.erb
|
349
352
|
- app/views/triannon/annotations/_form.html.erb
|
@@ -351,6 +354,7 @@ files:
|
|
351
354
|
- app/views/triannon/annotations/index.html.erb
|
352
355
|
- app/views/triannon/annotations/new.html.erb
|
353
356
|
- app/views/triannon/annotations/show.html.erb
|
357
|
+
- app/views/triannon/search/find.html.erb
|
354
358
|
- config/initializers/mime_types.rb
|
355
359
|
- config/routes.rb
|
356
360
|
- config/solr/solr.xml
|
@@ -365,6 +369,7 @@ files:
|
|
365
369
|
- lib/triannon/engine.rb
|
366
370
|
- lib/triannon/error.rb
|
367
371
|
- lib/triannon/graph.rb
|
372
|
+
- lib/triannon/iiif_anno_list.rb
|
368
373
|
- lib/triannon/jsonld_context.rb
|
369
374
|
- lib/triannon/version.rb
|
370
375
|
homepage:
|