triannon 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +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
|