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 +4 -4
- data/README.md +1 -1
- data/app/controllers/triannon/annotations_controller.rb +7 -7
- data/app/models/triannon/annotation.rb +130 -54
- data/app/models/triannon/annotation_ldp.rb +5 -9
- data/app/services/triannon/ldp_to_oa_mapper.rb +6 -5
- data/app/services/triannon/{ldp_creator.rb → ldp_writer.rb} +85 -84
- data/config/solr/schema.xml +102 -0
- data/config/solr/solrconfig.xml +239 -0
- data/lib/generators/triannon/install_generator.rb +1 -1
- data/lib/triannon.rb +1 -0
- data/lib/triannon/graph.rb +86 -0
- data/lib/triannon/version.rb +1 -1
- metadata +6 -4
- data/app/services/triannon/ldp_destroyer.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2328d724cb403823e2e7e9fbf5565a1244c1453
|
4
|
+
data.tar.gz: 2424be58316b9d29eb4f8e48eee1ea1914a9b07c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
9
|
+
# GET /annotations
|
10
10
|
def index
|
11
11
|
@annotations = Annotation.all
|
12
12
|
end
|
13
13
|
|
14
|
-
# GET /annotations/
|
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/
|
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/
|
40
|
+
# GET /annotations/1/edit
|
41
41
|
# def edit
|
42
42
|
# end
|
43
43
|
|
44
|
-
# POST /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/
|
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/
|
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
|
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 =
|
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
|
-
|
63
|
-
|
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
|
-
|
67
|
-
|
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
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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::
|
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::
|
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::
|
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::
|
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
|
-
#
|
5
|
-
class
|
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.
|
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
|
-
|
14
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
26
|
-
|
24
|
+
ldp_writer.create_target_container
|
25
|
+
ldp_writer.create_target_resources
|
27
26
|
end
|
28
27
|
|
29
|
-
|
28
|
+
id
|
30
29
|
end
|
31
30
|
end
|
32
31
|
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
69
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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 =
|
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 =
|
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
|
-
|
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::
|
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::
|
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::
|
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
|
+
|
data/lib/triannon.rb
CHANGED
@@ -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
|
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.
|
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-
|
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
|