ldp 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ldp/resource.rb CHANGED
@@ -1,20 +1,15 @@
1
1
  module Ldp
2
2
  class Resource
3
+ require 'ldp/resource/binary_source'
4
+ require 'ldp/resource/rdf_source'
3
5
 
4
6
  attr_reader :client, :subject
7
+ attr_accessor :content
5
8
 
6
- ##
7
- # Create a new LDP resource with a blank RDF graph
8
- def self.create client, subject
9
- self.new client, subject, RDF::Graph.new
10
- end
11
-
12
- def initialize client, subject, graph_or_response = nil
9
+ def initialize client, subject, response = nil
13
10
  @client = client
14
11
  @subject = subject
15
-
16
- @graph = graph_or_response if graph_or_response.is_a? RDF::Graph
17
- @get = graph_or_response if graph_or_response.is_a? Ldp::Response
12
+ @get = response if response.is_a? Faraday::Response and current? response
18
13
  end
19
14
 
20
15
  ##
@@ -26,13 +21,15 @@ module Ldp
26
21
  ##
27
22
  # Reload the LDP resource
28
23
  def reload
29
- Ldp::Resource.new client, subject
24
+ self.class.new client, subject, @get
30
25
  end
31
26
 
32
27
  ##
33
28
  # Is the resource new, or does it exist in the LDP server?
34
29
  def new?
35
- get
30
+ subject.nil? || !head
31
+ rescue Ldp::NotFound
32
+ true
36
33
  end
37
34
 
38
35
  ##
@@ -44,9 +41,11 @@ module Ldp
44
41
  ##
45
42
  # Get the resource
46
43
  def get
47
- @get ||= client.get(subject).tap do |result|
48
- raise NotFound if result.status == 404
49
- end
44
+ @get ||= client.get(subject)
45
+ end
46
+
47
+ def head
48
+ @head ||= @get || client.head(subject)
50
49
  end
51
50
 
52
51
  ##
@@ -56,75 +55,45 @@ module Ldp
56
55
  req.headers['If-Match'] = get.etag if retrieved_content?
57
56
  end
58
57
  end
59
-
58
+
59
+ def save
60
+ new? ? create : update
61
+ end
62
+
60
63
  ##
61
64
  # Create a new resource at the URI
62
- def create
63
- raise "" if new?
64
- resp = client.post '', graph.dump(:ttl) do |req|
65
- req.headers['Slug'] = subject
65
+ # @return [RdfSource] the new representation
66
+ def create &block
67
+ raise "Can't call create on an existing resource" unless new?
68
+ resp = client.post((subject || ""), content) do |req|
69
+
70
+ yield req if block_given?
66
71
  end
67
72
 
68
73
  @subject = resp.headers['Location']
69
74
  @subject_uri = nil
75
+ reload
70
76
  end
71
-
77
+
72
78
  ##
73
79
  # Update the stored graph
74
- def update new_graph = nil
75
- new_graph ||= graph
76
- client.put subject, new_graph.dump(:ttl) do |req|
80
+ def update new_content = nil
81
+ new_content ||= content
82
+ client.put subject, new_content do |req|
77
83
  req.headers['If-Match'] = get.etag if retrieved_content?
78
84
  end
79
85
  end
80
86
 
81
- def graph
82
- @graph ||= begin
83
- original_graph = get.graph
84
-
85
- inlinedResources = get.graph.query(:predicate => Ldp.inlinedResource).map { |x| x.object }
86
-
87
- unless inlinedResources.empty?
88
- new_graph = RDF::Graph.new
89
-
90
- original_graph.each_statement do |s|
91
- unless inlinedResources.include? s.subject
92
- new_graph << s
93
- end
94
- end
87
+ def current? response = nil
88
+ response ||= @get
89
+ return true if new? and subject.nil?
90
+
91
+ new_response = client.head(subject)
95
92
 
96
- new_graph
97
- else
98
- original_graph
99
- end
100
- end
101
- end
102
-
103
- def self.check_for_differences_and_reload_resource old_object
104
- new_object = old_object.reload
105
-
106
- bijection = new_object.graph.bijection_to(old_object.graph)
107
- diff = RDF::Graph.new
108
-
109
- old_object.graph.each do |statement|
110
- if statement.has_blank_nodes?
111
- subject = bijection.fetch(statement.subject, false) if statement.subject.node?
112
- object = bijection.fetch(statement.object, false) if statement.object.node?
113
- bijection_statement = RDF::Statement.new :subject => subject || statemnet.subject, :predicate => statement.predicate, :object => object || statement.object
114
-
115
- diff << statement if subject === false or object === false or new_object.graph.has_statement?(bijection_statement)
116
- elsif !new_object.graph.has_statement? statement
117
- diff << statement
118
- end
119
- end
120
-
121
- diff
122
- end
123
-
124
- ##
125
- # Reload this resource as an LDP container
126
- def as_container
127
- Ldp::Container.new client, subject, @graph || @get
93
+ response.headers['ETag'] &&
94
+ response.headers['Last-Modified'] &&
95
+ new_response.headers['ETag'] == response.headers['ETag'] &&
96
+ new_response.headers['Last-Modified'] == response.headers['Last-Modified']
128
97
  end
129
98
  end
130
99
  end
@@ -0,0 +1,57 @@
1
+ module Ldp
2
+ module Response::Paging
3
+ ##
4
+ # Statements about the page
5
+ def page
6
+ @page_graph ||= begin
7
+ g = RDF::Graph.new
8
+
9
+ if resource?
10
+ res = graph.query RDF::Statement.new(page_subject, nil, nil)
11
+
12
+ res.each_statement do |s|
13
+ g << s
14
+ end
15
+ end
16
+
17
+ g
18
+ end
19
+ end
20
+
21
+ ##
22
+ # Get the subject for the response
23
+ def subject
24
+ @subject ||= if has_page?
25
+ graph.first_object [page_subject, Ldp.page_of, nil]
26
+ else
27
+ page_subject
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Is there a next page?
33
+ def has_next?
34
+ next_page != nil
35
+ end
36
+
37
+ ##
38
+ # Get the URI for the next page
39
+ def next_page
40
+ graph.first_object [page_subject, Ldp.nextPage, nil]
41
+ end
42
+
43
+ ##
44
+ # Get the URI to the first page
45
+ def first_page
46
+ if links['first']
47
+ RDF::URI.new links['first']
48
+ elsif graph.has_statement? RDf::Statement.new(page_subject, Ldp.nextPage, nil)
49
+ subject
50
+ end
51
+ end
52
+
53
+ def sort
54
+
55
+ end
56
+ end
57
+ end
data/lib/ldp/response.rb CHANGED
@@ -1,33 +1,62 @@
1
1
  module Ldp
2
2
  module Response
3
+ require 'ldp/response/paging'
3
4
 
4
5
  ##
5
6
  # Wrap the raw Faraday respone with our LDP extensions
6
7
  def self.wrap client, raw_resp
7
8
  raw_resp.send(:extend, Ldp::Response)
8
- raw_resp.ldp_client = client
9
+ raw_resp.send(:extend, Ldp::Response::Paging) if raw_resp.has_page?
9
10
  raw_resp
10
11
  end
11
12
 
12
13
  ##
13
14
  # Extract the Link: headers from the HTTP resource
14
- def self.links raw_resp
15
- h = Hash.new { |hash, key| hash[key] = [] }
16
- Array(raw_resp.headers["Link"]).map { |x| x.split(", ") }.flatten.inject(h) do |memo, header|
17
- v = header.scan(/(.*);\s?rel="([^"]+)"/)
18
-
19
- if v.length == 1
20
- memo[v.first.last] << v.first.first
15
+ def self.links response
16
+ h = {}
17
+ Array(response.headers["Link"]).map { |x| x.split(", ") }.flatten.inject(h) do |memo, header|
18
+ m = header.match(/(?<link>.*);\s?rel="(?<rel>[^"]+)"/)
19
+ if m
20
+ memo[m[:rel]] ||= []
21
+ memo[m[:rel]] << m[:link]
21
22
  end
22
23
 
23
24
  memo
24
25
  end
25
26
  end
27
+
28
+ def self.applied_preferences headers
29
+ h = {}
30
+
31
+ Array(headers).map { |x| x.split(",") }.flatten.inject(h) do |memo, header|
32
+ m = header.match(/(?<key>[^=;]*)(=(?<value>[^;,]*))?(;\s*(?<params>[^,]*))?/)
33
+ includes = (m[:params].match(/include="(?<include>[^"]+)"/)[:include] || "").split(" ")
34
+ omits = (m[:params].match(/omit="(?<omit>[^"]+)"/)[:omit] || "").split(" ")
35
+ memo[m[:key]] = { value: m[:value], includes: includes, omits: omits }
36
+ end
37
+ end
26
38
 
27
39
  ##
28
40
  # Is the response an LDP resource?
29
- def self.resource? raw_resp
30
- links(raw_resp).fetch("type", []).include? Ldp.resource.to_s
41
+
42
+ def self.resource? response
43
+ Array(links(response)["type"]).include? Ldp.resource.to_s
44
+ end
45
+
46
+ ##
47
+ # Is the response an LDP container?
48
+ def self.container? response
49
+ [
50
+ Ldp.basic_container,
51
+ Ldp.direct_container,
52
+ Ldp.indirect_container
53
+ ].any? { |x| Array(links(response)["type"]).include? x.to_s }
54
+ end
55
+
56
+ ##
57
+ # Link: headers from the HTTP response
58
+ def links
59
+ @links ||= Ldp::Response.links(self)
31
60
  end
32
61
 
33
62
  ##
@@ -39,17 +68,16 @@ module Ldp
39
68
  ##
40
69
  # Is the response an LDP container
41
70
  def container?
42
- graph.has_statement? RDF::Statement.new(subject, RDF.type, Ldp.container)
71
+ Ldp::Response.container?(self)
43
72
  end
44
73
 
74
+ def preferences
75
+ Ldp::Resource.applied_preferences(headers["Preference-Applied"])
76
+ end
45
77
  ##
46
78
  # Get the subject for the response
47
79
  def subject
48
- @subject ||= if has_page?
49
- graph.first_object [page_subject, Ldp.page_of, nil]
50
- else
51
- page_subject
52
- end
80
+ page_subject
53
81
  end
54
82
 
55
83
  ##
@@ -59,15 +87,9 @@ module Ldp
59
87
  end
60
88
 
61
89
  ##
62
- # Set the LDP client for this resource
63
- def ldp_client= client
64
- @ldp_client = client
65
- end
66
-
67
- ##
68
- # Get the LDP client
69
- def ldp_client
70
- @ldp_client
90
+ # Is the response paginated?
91
+ def has_page?
92
+ graph.has_statement? RDF::Statement.new(page_subject, RDF.type, Ldp.page)
71
93
  end
72
94
 
73
95
  ##
@@ -101,77 +123,19 @@ module Ldp
101
123
  end
102
124
 
103
125
  ##
104
- # Statements about the page
105
- def page
106
- @page_graph ||= begin
107
- g = RDF::Graph.new
108
-
109
- if resource?
110
- res = graph.query RDF::Statement.new(page_subject, nil, nil)
111
-
112
- res.each_statement do |s|
113
- g << s
114
- end
115
- end
116
-
117
- g
118
- end
126
+ # Extract the Link: rel="type" headers for the resource
127
+ def types
128
+ Array(links["type"])
119
129
  end
120
-
121
- ##
122
- # Is the response paginated?
123
- def has_page?
124
- graph.has_statement? RDF::Statement.new(page_subject, RDF.type, Ldp.page)
125
- end
126
-
127
- ##
128
- # Is there a next page?
129
- def has_next?
130
- next_page != nil
131
- end
132
-
133
- ##
134
- # Get the URI for the next page
135
- def next_page
136
- graph.first_object [page_subject, Ldp.nextPage, nil]
137
- end
138
-
139
- ##
140
- # Get the URI to the first page
141
- def first_page
142
- if links['first']
143
- RDF::URI.new links['first']
144
- elsif graph.has_statement? RDf::Statement.new(page_subject, Ldp.nextPage, nil)
145
- subject
146
- end
147
- end
148
-
149
- ##
150
- # Get a list of inlined resources
151
- def resources
152
- graph.query RDF::Statement.new(page_subject, Ldp.inlinedResource, nil)
153
- end
154
-
155
- ##
156
- # Get a list of member resources
157
- def members
158
- graph.query RDF::Statement.new(page_subject, membership_predicate, nil)
159
- end
160
-
161
- ##
162
- # Predicate to use to determine container membership
163
- def membership_predicate
164
- graph.first_object [page_subject, Ldp.membership_predicate, nil]
130
+
131
+ def includes? preference
132
+ key = Ldp.send("prefer_#{preference}") if Ldp.respond_to("prefer_#{preference}")
133
+ key ||= preference
134
+ preferences["return"][:includes].include?(key) || !preferences["return"][:omits].include?(key)
165
135
  end
166
-
167
- def sort
168
-
169
- end
170
-
171
- ##
172
- # Link: headers from the HTTP response
173
- def links
174
- Ldp::Response.links(self)
136
+
137
+ def minimal?
138
+ preferences["return"][:value] == "minimal"
175
139
  end
176
140
  end
177
- end
141
+ end
data/lib/ldp/uri.rb CHANGED
@@ -12,6 +12,22 @@ module Ldp::Uri
12
12
  uri("Container")
13
13
  end
14
14
 
15
+ def basic_container
16
+ uri("BasicContainer")
17
+ end
18
+
19
+ def direct_container
20
+ uri("DirectContainer")
21
+ end
22
+
23
+ def indirect_container
24
+ uri("IndirectContainer")
25
+ end
26
+
27
+ def contains
28
+ uri("contains")
29
+ end
30
+
15
31
  def page
16
32
  uri("Page")
17
33
  end
@@ -24,12 +40,28 @@ module Ldp::Uri
24
40
  uri("nextPage")
25
41
  end
26
42
 
27
- def inlinedResource
28
- uri("inlinedResource")
29
- end
30
-
31
43
  def membership_predicate
32
44
  uri("membershipPredicate")
33
45
  end
46
+
47
+ def prefer_empty_container
48
+ uri("PreferEmptyContainer")
49
+ end
50
+
51
+ def prefer_membership
52
+ uri("PreferMembership")
53
+ end
54
+
55
+ def prefer_containment
56
+ uri("PreferContainment")
57
+ end
58
+
59
+ def has_member_relation
60
+ uri("hasMemberRelation")
61
+ end
62
+
63
+ def member
64
+ uri("member")
65
+ end
34
66
 
35
- end
67
+ end
data/lib/ldp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ldp
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/ldp.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'ldp/version'
2
2
  require 'linkeddata'
3
3
  require 'logger'
4
+ require 'http_logger'
4
5
 
5
6
  module Ldp
6
7
  RDF::Graph.send(:include, RDF::Isomorphic)
@@ -16,7 +17,8 @@ module Ldp
16
17
 
17
18
  autoload :Orm, 'ldp/orm'
18
19
 
19
- class NotFound < StandardError; end
20
+ class HttpError < RuntimeError; end
21
+ class NotFound < HttpError; end
20
22
 
21
23
  class << self
22
24
  def logger
@@ -24,6 +26,14 @@ module Ldp
24
26
  log.level = Logger::WARN
25
27
  end
26
28
  end
29
+
30
+ def instrument *args, &block
31
+ if defined?(::ActiveSupport) && defined?(::ActiveSupport::Notifications)
32
+ ActiveSupport::Notifications.instrument *args, &block
33
+ else
34
+ yield
35
+ end
36
+ end
27
37
 
28
38
  attr_writer :logger
29
39
  end
@@ -1,45 +1,51 @@
1
1
  require 'spec_helper'
2
2
  describe "Ldp::Client" do
3
3
 
4
- let(:simple_graph) do
5
- graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
6
- graph.dump(:ttl)
7
- end
4
+ let(:simple_graph) do
5
+ graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
6
+ graph.dump(:ttl)
7
+ end
8
8
 
9
9
 
10
- let(:paginatedGraph) do
11
- graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
12
- graph << [RDF::URI.new("?firstPage"), RDF.type, Ldp.page]
13
- graph << [RDF::URI.new("?firstPage"), Ldp.page_of, RDF::URI.new("")]
14
- graph.dump(:ttl)
15
- end
10
+ let(:paginatedGraph) do
11
+ graph = RDF::Graph.new << [RDF::URI.new(""), RDF::DC.title, "Hello, world!"]
12
+ graph << [RDF::URI.new("?firstPage"), RDF.type, Ldp.page]
13
+ graph << [RDF::URI.new("?firstPage"), Ldp.page_of, RDF::URI.new("")]
14
+ graph.dump(:ttl)
15
+ end
16
16
 
17
- let(:simple_container_graph) do
18
- graph = RDF::Graph.new << [RDF::URI.new(""), RDF.type, Ldp.container]
19
- graph.dump(:ttl)
20
- end
17
+ let(:simple_container_graph) do
18
+ graph = RDF::Graph.new << [RDF::URI.new(""), RDF.type, Ldp.container]
19
+ graph.dump(:ttl)
20
+ end
21
21
 
22
- let(:conn_stubs) do
23
- stubs = Faraday::Adapter::Test::Stubs.new do |stub|
24
- stub.get('/a_resource') {[ 200, {"Link" => "http://www.w3.org/ns/ldp#Resource;rel=\"type\""}, simple_graph ]}
25
- stub.get('/a_container') {[ 200, {"Link" => "http://www.w3.org/ns/ldp#Resource;rel=\"type\""}, simple_container_graph ]}
26
- stub.put("/a_resource") { [204]}
27
- stub.delete("/a_resource") { [204]}
28
- stub.post("/a_container") { [201, {"Location" => "http://example.com/a_container/subresource"}]}
29
- end
22
+ let(:conn_stubs) do
23
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
24
+ stub.head('/a_resource') {[ 200 ]}
25
+ stub.get('/a_resource') {[ 200, {"Link" => "http://www.w3.org/ns/ldp#Resource;rel=\"type\""}, simple_graph ]}
26
+ stub.get('/a_container') {[ 200, {"Link" => ["http://www.w3.org/ns/ldp#Resource;rel=\"type\"","http://www.w3.org/ns/ldp#BasicContainer;rel=\"type\""]}, simple_container_graph ]}
27
+ stub.head('/a_binary_resource') { [200]}
28
+ stub.get('/a_binary_resource') { [200, {}, ""]}
29
+ stub.put("/a_resource") { [204]}
30
+ stub.delete("/a_resource") { [204]}
31
+ stub.head('/a_container') {[ 200 ]}
32
+ stub.post("/a_container") { [201, {"Location" => "http://example.com/a_container/subresource"}]}
33
+ stub.get("/test:1") { [200] }
34
+ stub.get("http://test:8080/abc") { [200] }
30
35
  end
36
+ end
31
37
 
32
- let(:mock_conn) do
33
- test = Faraday.new do |builder|
34
- builder.adapter :test, conn_stubs do |stub|
35
- end
38
+ let(:mock_conn) do
39
+ test = Faraday.new do |builder|
40
+ builder.adapter :test, conn_stubs do |stub|
36
41
  end
37
-
38
42
  end
39
43
 
40
- subject do
41
- Ldp::Client.new mock_conn
42
- end
44
+ end
45
+
46
+ subject do
47
+ Ldp::Client.new mock_conn
48
+ end
43
49
 
44
50
  describe "initialize" do
45
51
  it "should accept an existing Faraday connection" do
@@ -66,6 +72,36 @@ describe "Ldp::Client" do
66
72
  it "should accept a block to change the HTTP request" do
67
73
  expect { |b| subject.get "a_resource", &b }.to yield_control
68
74
  end
75
+
76
+ context "should provide convenient accessors for LDP Prefer headers" do
77
+ it "should set the minimal header" do
78
+ subject.get "a_resource", minimal: true do |req|
79
+ expect(req.headers["Prefer"]).to eq "return=minimal"
80
+ end
81
+ end
82
+ it "should set the include parameter" do
83
+ subject.get "a_resource", include: "membership" do |req|
84
+ expect(req.headers["Prefer"]).to match "include=\"#{Ldp.prefer_membership}\""
85
+ end
86
+ end
87
+ it "should set the omit parameter" do
88
+ subject.get "a_resource", omit: "containment" do |req|
89
+ expect(req.headers["Prefer"]).to match "omit=\"#{Ldp.prefer_containment}\""
90
+ end
91
+ end
92
+ end
93
+
94
+ context "with an invalid relative uri" do
95
+ it "should work" do
96
+ subject.get "test:1"
97
+ end
98
+ end
99
+
100
+ context "with an absolute uri" do
101
+ it "should work" do
102
+ subject.get "http://test:8080/abc"
103
+ end
104
+ end
69
105
  end
70
106
 
71
107
  describe "delete" do
@@ -143,6 +179,11 @@ describe "Ldp::Client" do
143
179
  expect(resource).to be_a_kind_of(Ldp::Resource)
144
180
  expect(resource).to be_a_kind_of(Ldp::Container)
145
181
  end
182
+
183
+ it "should be a binary resource" do
184
+ resource = subject.find_or_initialize "a_binary_resource"
185
+ expect(resource).to be_a_kind_of(Ldp::Resource::BinarySource)
186
+ end
146
187
 
147
188
  end
148
189
  end