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 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