triannon 0.0.3 → 0.0.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 +11 -1
- data/Rakefile +18 -6
- data/app/controllers/triannon/annotations_controller.rb +37 -0
- data/app/models/triannon/annotation.rb +115 -60
- data/app/models/triannon/annotation_ldp.rb +54 -0
- data/app/services/triannon/ldp_creator.rb +265 -0
- data/app/services/triannon/ldp_destroyer.rb +15 -0
- data/app/services/triannon/ldp_loader.rb +82 -0
- data/app/services/triannon/ldp_to_oa_mapper.rb +61 -0
- data/app/services/triannon/root_annotation_creator.rb +36 -0
- data/app/views/layouts/triannon/application.html.erb +2 -2
- data/app/views/triannon/annotations/_form.html.erb +1 -1
- data/app/views/triannon/annotations/show.html.erb +7 -7
- data/config/initializers/mime_types.rb +4 -0
- data/config/initializers/root_annotation_container.rb +8 -0
- data/config/triannon.yml +6 -0
- data/lib/generators/triannon/install_generator.rb +15 -0
- data/lib/rdf/triannon_vocab.rb +14 -0
- data/lib/triannon.rb +45 -1
- data/lib/triannon/oa_context_20130208.json +53 -0
- data/lib/triannon/version.rb +1 -1
- metadata +72 -7
- data/app/models/triannon/graph_validator.rb +0 -33
- data/db/migrate/20140912173046_create_triannon_annotations.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9936254da42efbcc7d7225b4f684097a2ac84f0f
|
4
|
+
data.tar.gz: b0d8e6bcba4019fc9cef4300f484f7a2d11774db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90a6538ea8c10582a4e06f1e159947b1013784d7f2b7c8474811f13534f3c4806ed559b2456b304c5d51ce688d0b5f2ff44b2a43b518a181827fd3a32b3276bc
|
7
|
+
data.tar.gz: efda4aab06e7a5939d44e61ab6a860be835b20301c2f699291b1b246ae59e3820563720860c0ef307fbb1b9d4447b695ed911fbd7534df4f16419f77a2dce349
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Triannon
|
4
4
|
|
5
|
-
|
5
|
+
Store Open Annotation in Fedora4 to support the Linked Data for Libraries use cases.
|
6
6
|
|
7
7
|
## Tests
|
8
8
|
|
@@ -32,11 +32,21 @@ Then run the triannon generator:
|
|
32
32
|
$ rails g triannon:install
|
33
33
|
```
|
34
34
|
|
35
|
+
Edit the `config/triannon.yml` file:
|
36
|
+
|
37
|
+
* `ldp_url:` Points to the root annotations container in 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.
|
39
|
+
|
35
40
|
## Running the application in development
|
36
41
|
|
37
42
|
There is a bundled rake task for running the test app:
|
38
43
|
|
39
44
|
```console
|
45
|
+
# One time setup: run the following 3 commands
|
46
|
+
$ rake jetty:download
|
47
|
+
$ rake jetty:unzip
|
40
48
|
$ rake engine_cart:generate # (first run only)
|
49
|
+
|
50
|
+
# Run the test app
|
41
51
|
$ rake triannon:server
|
42
52
|
```
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ rescue LoadError
|
|
4
4
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
5
|
end
|
6
6
|
|
7
|
-
ZIP_URL = "https://github.com/
|
7
|
+
ZIP_URL = "https://github.com/sul-dlss/hydra-jetty/archive/fedora-4/edge.zip"
|
8
8
|
|
9
9
|
require 'active_support/benchmarkable'
|
10
10
|
require 'jettywrapper'
|
@@ -31,8 +31,8 @@ task :ci => ['engine_cart:generate', 'jetty:clean'] do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
namespace :triannon do
|
34
|
-
desc 'run
|
35
|
-
task :
|
34
|
+
desc 'run test rails app w triannon and jetty'
|
35
|
+
task :server_jetty do
|
36
36
|
jetty_params = Jettywrapper.load_config.merge({:jetty_home => File.expand_path(File.dirname(__FILE__) + '/jetty')})
|
37
37
|
Jettywrapper.wrap(jetty_params) do
|
38
38
|
within_test_app do
|
@@ -41,8 +41,17 @@ namespace :triannon do
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
desc 'run
|
45
|
-
task :
|
44
|
+
desc 'run test rails app w triannon but no jetty'
|
45
|
+
task :server_no_jetty do
|
46
|
+
within_test_app do
|
47
|
+
system "rails s"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
desc 'run test rails app w triannon but no jetty'
|
51
|
+
task :server => :server_no_jetty
|
52
|
+
|
53
|
+
desc 'run test rails console w triannon'
|
54
|
+
task :console_jetty do
|
46
55
|
jetty_params = Jettywrapper.load_config.merge({:jetty_home => File.expand_path(File.dirname(__FILE__) + '/jetty')})
|
47
56
|
Jettywrapper.wrap(jetty_params) do
|
48
57
|
within_test_app do
|
@@ -51,12 +60,15 @@ namespace :triannon do
|
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
54
|
-
desc 'run
|
63
|
+
desc 'run test rails console w triannon but no jetty'
|
55
64
|
task :console_no_jetty do
|
56
65
|
within_test_app do
|
57
66
|
system "rails c"
|
58
67
|
end
|
59
68
|
end
|
69
|
+
desc 'run test rails console w triannon but no jetty'
|
70
|
+
task :console => :console_no_jetty
|
71
|
+
|
60
72
|
end
|
61
73
|
|
62
74
|
|
@@ -2,6 +2,7 @@ require_dependency "triannon/application_controller"
|
|
2
2
|
|
3
3
|
module Triannon
|
4
4
|
class AnnotationsController < ApplicationController
|
5
|
+
before_action :default_format_jsonld, only: [:show]
|
5
6
|
before_action :set_annotation, only: [:show, :edit, :update, :destroy]
|
6
7
|
|
7
8
|
# GET /annotations/annotations
|
@@ -11,6 +12,22 @@ module Triannon
|
|
11
12
|
|
12
13
|
# GET /annotations/annotations/1
|
13
14
|
def show
|
15
|
+
respond_to do |format|
|
16
|
+
format.jsonld { render :json => @annotation.graph.to_jsonld }
|
17
|
+
format.ttl {
|
18
|
+
accept_return_type = mime_type_from_accept(["application/x-turtle", "text/turtle"])
|
19
|
+
render :body => @annotation.graph.to_ttl, content_type: accept_return_type if accept_return_type }
|
20
|
+
format.rdfxml {
|
21
|
+
accept_return_type = mime_type_from_accept(["application/rdf+xml", "text/rdf+xml", "text/rdf"])
|
22
|
+
render :body => @annotation.graph.to_rdfxml, content_type: accept_return_type if accept_return_type }
|
23
|
+
format.json {
|
24
|
+
accept_return_type = mime_type_from_accept(["application/json", "text/x-json", "application/jsonrequest"])
|
25
|
+
render :json => @annotation.graph.to_jsonld, content_type: accept_return_type if accept_return_type }
|
26
|
+
format.xml {
|
27
|
+
accept_return_type = mime_type_from_accept(["application/xml", "text/xml", "application/x-xml"])
|
28
|
+
render :xml => @annotation.graph.to_rdfxml, content_type: accept_return_type if accept_return_type }
|
29
|
+
format.html { render :show }
|
30
|
+
end
|
14
31
|
end
|
15
32
|
|
16
33
|
# GET /annotations/annotations/new
|
@@ -58,5 +75,25 @@ module Triannon
|
|
58
75
|
def annotation_params
|
59
76
|
params.require(:annotation).permit(:data)
|
60
77
|
end
|
78
|
+
|
79
|
+
def default_format_jsonld
|
80
|
+
if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
|
81
|
+
request.format = "jsonld"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# find first mime type from request.accept that matches return mime type
|
86
|
+
def mime_type_from_accept(return_mime_types)
|
87
|
+
@mime_type_from_accept ||= begin
|
88
|
+
if request.accept && request.accept.is_a?(String)
|
89
|
+
accepted_formats = request.accept.split(',')
|
90
|
+
accepted_formats.each { |accepted_format|
|
91
|
+
if return_mime_types.include? accepted_format
|
92
|
+
return accepted_format
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
61
98
|
end
|
62
99
|
end
|
@@ -1,75 +1,94 @@
|
|
1
1
|
module Triannon
|
2
|
-
class Annotation
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
class Annotation
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor :id, :data
|
6
|
+
|
7
|
+
validates_each :data do |record, attr, value|
|
8
|
+
record.errors.add attr, 'less than 30 chars' if value.to_s.length < 30
|
9
|
+
end
|
6
10
|
|
7
11
|
# full validation should be optional?
|
8
12
|
# minimal: a subject with the right type and a hasTarget? (see url)
|
9
13
|
# and perhaps modeled on this:
|
10
14
|
# https://github.com/uq-eresearch/lorestore/blob/3e9aa1c69aafd3692c69aa39c64bfdc32b757892/src/main/resources/OAConstraintsSPARQL.json
|
11
15
|
|
16
|
+
|
17
|
+
def persisted?
|
18
|
+
self.id.present?
|
19
|
+
end
|
20
|
+
|
12
21
|
def url
|
13
22
|
if graph_exists?
|
14
|
-
solution = graph.query
|
23
|
+
solution = graph.query self.class.anno_query
|
15
24
|
if solution && solution.size == 1
|
16
25
|
solution.first.s.to_s
|
17
26
|
# TODO: raise exception if no URL?
|
18
27
|
end
|
19
28
|
end
|
20
29
|
end
|
21
|
-
|
30
|
+
|
22
31
|
# FIXME: this should be part of validation: RDF.type should be RDF::OpenAnnotation.Annotation
|
23
32
|
def type
|
24
33
|
if graph_exists?
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
solution = graph.query
|
34
|
+
q = RDF::Query.new
|
35
|
+
q << [:s, RDF::OpenAnnotation.hasTarget, nil] # must have a target
|
36
|
+
q << [:s, RDF.type, :type]
|
37
|
+
solution = graph.query q
|
38
|
+
solution.distinct!
|
29
39
|
if solution && solution.size == 1
|
30
40
|
solution.first.type.to_s
|
31
41
|
# TODO: raise exception if no type?
|
32
42
|
end
|
33
43
|
end
|
34
44
|
end
|
35
|
-
|
45
|
+
|
36
46
|
def has_target
|
37
|
-
# FIXME: can have multiple targets per spec (example 8)
|
38
47
|
# FIXME: target might be more than a string (examples 14-17)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
48
|
+
if graph_exists?
|
49
|
+
q = self.class.anno_query.dup
|
50
|
+
q << [:s, RDF::OpenAnnotation.hasTarget, :target]
|
51
|
+
solution = graph.query q
|
52
|
+
if solution && solution.size > 0
|
53
|
+
targets = []
|
54
|
+
solution.each {|res|
|
55
|
+
targets << res.target.to_s
|
56
|
+
}
|
57
|
+
targets
|
58
|
+
# TODO: raise exception if none?
|
59
|
+
end
|
60
|
+
end
|
43
61
|
end
|
44
|
-
|
62
|
+
|
45
63
|
def has_body
|
46
|
-
# FIXME: can
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
64
|
+
# FIXME: body can be other things besides blank node with chars
|
65
|
+
bodies = []
|
66
|
+
if graph_exists?
|
67
|
+
q = self.class.anno_query.dup
|
68
|
+
q << [:s, RDF::OpenAnnotation.hasBody, :body]
|
69
|
+
# for chars content
|
70
|
+
# the following two lines are equivalent in identifying inline chars content
|
71
|
+
# q << [:body, RDF.type, RDF::Content.ContentAsText]
|
72
|
+
q << [:body, RDF::Content.chars, :chars]
|
73
|
+
# for non-chars content
|
74
|
+
# // non-embedded Text resource
|
75
|
+
#?body a dctypes:Text ;
|
76
|
+
# dc:format "application/msword .
|
77
|
+
solution = graph.query q
|
78
|
+
if solution && solution.size > 0
|
79
|
+
solution.each {|res|
|
80
|
+
bodies << res.chars.to_s
|
81
|
+
}
|
63
82
|
end
|
64
83
|
end
|
65
|
-
|
84
|
+
bodies
|
66
85
|
end
|
67
86
|
|
68
87
|
def motivated_by
|
69
88
|
if graph_exists?
|
70
|
-
q = self.class.
|
89
|
+
q = self.class.anno_query.dup
|
71
90
|
q << [:s, RDF::OpenAnnotation.motivatedBy, :motivated_by]
|
72
|
-
solution = graph.query
|
91
|
+
solution = graph.query q
|
73
92
|
if solution && solution.size > 0
|
74
93
|
motivations = []
|
75
94
|
solution.each {|res|
|
@@ -81,39 +100,66 @@ module Triannon
|
|
81
100
|
end
|
82
101
|
end
|
83
102
|
|
84
|
-
def
|
85
|
-
@
|
103
|
+
def graph
|
104
|
+
@graph ||= data_to_graph
|
105
|
+
end
|
106
|
+
|
107
|
+
def graph= g
|
108
|
+
@graph = g
|
86
109
|
end
|
87
110
|
|
88
|
-
|
89
|
-
|
90
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
# query for a subject with
|
94
|
-
# predicate RDF::OpenAnnotation.hasTarget
|
95
|
-
# type of RDF::OpenAnnotation.Annotation
|
96
|
-
def self.basic_query
|
97
|
-
@basic_query ||= begin
|
98
|
-
basic_query = RDF::Query.new
|
99
|
-
basic_query << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
|
100
|
-
basic_query << [:s, RDF::OpenAnnotation.hasTarget, nil]
|
111
|
+
# query for a subject with type of RDF::OpenAnnotation.Annotation
|
112
|
+
def self.anno_query
|
113
|
+
@anno_query ||= begin
|
114
|
+
q = RDF::Query.new
|
115
|
+
q << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
|
101
116
|
end
|
102
117
|
end
|
103
|
-
|
118
|
+
|
119
|
+
def self.create(attrs = {})
|
120
|
+
a = Triannon::Annotation.new attrs
|
121
|
+
a.save
|
122
|
+
a
|
123
|
+
end
|
124
|
+
|
125
|
+
def save
|
126
|
+
# check if valid?
|
127
|
+
graph
|
128
|
+
@key = Triannon::LdpCreator.create self
|
129
|
+
end
|
130
|
+
|
131
|
+
def destroy
|
132
|
+
Triannon::LdpDestroyer.destroy @id
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.find(key)
|
136
|
+
oa_graph = Triannon::LdpLoader.load key
|
137
|
+
anno = Triannon::Annotation.new
|
138
|
+
anno.graph = oa_graph
|
139
|
+
anno
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.all
|
143
|
+
Triannon::LdpLoader.find_all
|
144
|
+
end
|
145
|
+
|
146
|
+
|
104
147
|
private
|
105
148
|
|
106
149
|
# loads RDF::Graph from data attribute. If data is in json-ld, converts it to turtle.
|
107
150
|
def data_to_graph
|
108
151
|
if data
|
152
|
+
data.strip!
|
109
153
|
case data
|
110
|
-
when /\{\
|
111
|
-
|
112
|
-
g ||= RDF::Graph.new << JSON::LD::API.toRdf(json_ld) if json
|
154
|
+
when /\A\{.+\}\Z/m
|
155
|
+
g ||= RDF::Graph.new << JSON::LD::API.toRdf(json_ld) if json_ld
|
113
156
|
self.data = g.dump(:ttl) if g
|
114
|
-
|
115
|
-
|
116
|
-
|
157
|
+
when /\A<.+>\Z/m # (Note: \A and \Z and m are needed instead of ^$ due to \n in data)
|
158
|
+
g = RDF::Graph.new
|
159
|
+
g.from_rdfxml(data)
|
160
|
+
g = nil if g.size == 0
|
161
|
+
when /\.\Z/ # (Note: \Z is needed instead of $ due to \n in data)
|
162
|
+
# turtle ends in period
|
117
163
|
g = RDF::Graph.new
|
118
164
|
g.from_ttl(data)
|
119
165
|
g = nil if g.size == 0
|
@@ -123,9 +169,18 @@ private
|
|
123
169
|
end
|
124
170
|
|
125
171
|
def json_ld
|
172
|
+
if data.match(/"@context"\s*\:\s*"http\:\/\/www\.w3\.org\/ns\/oa-context-20130208\.json"/)
|
173
|
+
data.sub!("\"http://www.w3.org/ns/oa-context-20130208.json\"", json_oa_context)
|
174
|
+
elsif data.match(/"@context"\s*\:\s*"http\:\/\/www\.w3\.org\/ns\/oa\.jsonld"/)
|
175
|
+
data.sub!("\"http://www.w3.org/ns/oa.jsonld\"", json_oa_context)
|
176
|
+
end
|
126
177
|
@json_ld ||= JSON.parse(data) rescue nil
|
127
178
|
end
|
128
|
-
|
179
|
+
|
180
|
+
def json_oa_context
|
181
|
+
@json_oa_context ||= File.read("lib/triannon/oa_context_20130208.json")
|
182
|
+
end
|
183
|
+
|
129
184
|
def graph_exists?
|
130
185
|
graph && graph.size > 0
|
131
186
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Triannon
|
2
|
+
class AnnotationLdp
|
3
|
+
|
4
|
+
# RDF::Graph object with all triples, including back end (e.g. LDP, Fedora)
|
5
|
+
def graph
|
6
|
+
@g ||= RDF::Graph.new
|
7
|
+
end
|
8
|
+
|
9
|
+
# RDF::Graph without any back end (e.g. LDP, Fedora) triples
|
10
|
+
def stripped_graph
|
11
|
+
@stripped_graph ||= begin
|
12
|
+
new_graph = RDF::LDP.remove_ldp_triples (RDF::FCRepo4.remove_fedora_triples(graph))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def base_uri
|
17
|
+
res = graph.query anno_query
|
18
|
+
res.first.s
|
19
|
+
end
|
20
|
+
|
21
|
+
def body_uris
|
22
|
+
q = anno_query
|
23
|
+
q << [:s, RDF::OpenAnnotation.hasBody, :body_uri]
|
24
|
+
solns = graph.query q
|
25
|
+
result = []
|
26
|
+
solns.distinct.each { |soln|
|
27
|
+
result << soln.body_uri
|
28
|
+
}
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
def target_uris
|
33
|
+
q = anno_query
|
34
|
+
q << [:s, RDF::OpenAnnotation.hasTarget, :target_uri]
|
35
|
+
solns = graph.query q
|
36
|
+
result = []
|
37
|
+
solns.distinct.each { |soln|
|
38
|
+
result << soln.target_uri
|
39
|
+
}
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_data_into_graph ttl
|
44
|
+
graph.from_ttl ttl
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def anno_query
|
49
|
+
q = RDF::Query.new
|
50
|
+
q << [:s, RDF.type, RDF::OpenAnnotation.Annotation]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|