triannon 0.0.3 → 0.0.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 +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
|