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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c88a5ccc4b645bbb52d89183ed8fa42f49eda50
4
- data.tar.gz: eb58660e72be33aa86bc66f0feb676d9cf2ed4ea
3
+ metadata.gz: 9936254da42efbcc7d7225b4f684097a2ac84f0f
4
+ data.tar.gz: b0d8e6bcba4019fc9cef4300f484f7a2d11774db
5
5
  SHA512:
6
- metadata.gz: 54bc08d30db5e0fd1a4e1a93d70ed2da19a3359586c3f1c995b7a5713a5c30635736443193f18ec327db3a21eca3b9b3ee65deb5e59034bd57417960e1ec6710
7
- data.tar.gz: 5f9f0fa5b0384791e765719c989996782786bcfb019e90b62c31b84d0fe39cdb42d82198dc4fe750f36f8af4c5f0e5f10a0ce928ae4a85d06335c83bea1a96f5
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
- Demonstration Open Annotation to support the Linked Data for Libraries use cases.
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/projecthydra/hydra-jetty/archive/v8.0.0.rc2.zip"
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 the test rails app w triannon'
35
- task :server do
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 the test rails console w triannon'
45
- task :console do
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 the test rails console w triannon but no jetty'
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 < ActiveRecord::Base
3
-
4
- validates :data, presence: true,
5
- length: {minimum: 30}
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(self.class.basic_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
- query = RDF::Query.new
26
- query << [:s, RDF::OpenAnnotation.hasTarget, nil]
27
- query << [:s, RDF.type, :type]
28
- solution = graph.query(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
- stmt = rdf.find_all { |s|
40
- s.predicate.to_s == RDF::OpenAnnotation.hasTarget
41
- }.first
42
- stmt.object.to_str if stmt
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 have multiple bodies per spec
47
- stmt = rdf.find_all { |s|
48
- s.predicate.to_s == RDF::OpenAnnotation.hasBody
49
- }.first
50
-
51
- # FIXME: body can be other things
52
- # if body is blank node and has character content, then return it
53
- body = stmt.object if stmt
54
- if body && body.is_a?(RDF::Node)
55
- body_stmts = rdf.find_all { |s| s.subject == body }
56
- if body_stmts &&
57
- body_stmts.detect { |s|
58
- s.predicate.to_s == RDF.type &&
59
- s.object.to_s == RDF::Content.ContentAsText
60
- }
61
- chars_stmt = body_stmts.detect { |s| s.predicate.to_s == RDF::Content.chars}
62
- return chars_stmt.object.to_s if chars_stmt
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
- nil
84
+ bodies
66
85
  end
67
86
 
68
87
  def motivated_by
69
88
  if graph_exists?
70
- q = self.class.basic_query.clone
89
+ q = self.class.anno_query.dup
71
90
  q << [:s, RDF::OpenAnnotation.motivatedBy, :motivated_by]
72
- solution = graph.query(q)
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 rdf
85
- @rdf ||= JSON::LD::API.toRdf(json_ld) if json_ld
103
+ def graph
104
+ @graph ||= data_to_graph
105
+ end
106
+
107
+ def graph= g
108
+ @graph = g
86
109
  end
87
110
 
88
- def graph
89
- g = data_to_graph
90
- @graph ||= g if g
91
- end
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 /\{\s*\"@\w+\"/
111
- json ||= JSON.parse(data)
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
- #when /http/
115
- # g ||= RDF::Graph.load(data, :format => :ttl)
116
- else # assume turtle
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