timfel-active_cmis 0.3.1
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.
- data/LICENSE +26 -0
- data/README.md +34 -0
- data/Rakefile +36 -0
- data/TODO +7 -0
- data/active_cmis.gemspec +77 -0
- data/lib/active_cmis/acl.rb +181 -0
- data/lib/active_cmis/acl_entry.rb +26 -0
- data/lib/active_cmis/active_cmis.rb +87 -0
- data/lib/active_cmis/atomic_types.rb +245 -0
- data/lib/active_cmis/attribute_prefix.rb +35 -0
- data/lib/active_cmis/collection.rb +206 -0
- data/lib/active_cmis/document.rb +299 -0
- data/lib/active_cmis/exceptions.rb +82 -0
- data/lib/active_cmis/folder.rb +36 -0
- data/lib/active_cmis/internal/caching.rb +86 -0
- data/lib/active_cmis/internal/connection.rb +222 -0
- data/lib/active_cmis/internal/utils.rb +82 -0
- data/lib/active_cmis/ns.rb +18 -0
- data/lib/active_cmis/object.rb +563 -0
- data/lib/active_cmis/policy.rb +13 -0
- data/lib/active_cmis/property_definition.rb +179 -0
- data/lib/active_cmis/query_result.rb +40 -0
- data/lib/active_cmis/rel.rb +17 -0
- data/lib/active_cmis/relationship.rb +49 -0
- data/lib/active_cmis/rendition.rb +80 -0
- data/lib/active_cmis/repository.rb +327 -0
- data/lib/active_cmis/server.rb +113 -0
- data/lib/active_cmis/type.rb +200 -0
- data/lib/active_cmis/version.rb +8 -0
- data/lib/active_cmis.rb +31 -0
- metadata +111 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
|
2
|
+
module ActiveCMIS
|
3
|
+
module Internal
|
4
|
+
class Connection
|
5
|
+
# @return [String, nil] The user that is used with the authentication to the server
|
6
|
+
attr_reader :user
|
7
|
+
# @return [Logger] A logger used to send debug and info messages
|
8
|
+
attr_reader :logger
|
9
|
+
|
10
|
+
# @param [Logger] Initialize with a logger of your choice
|
11
|
+
def initialize(logger)
|
12
|
+
@logger = logger || ActiveCMIS.default_logger
|
13
|
+
end
|
14
|
+
|
15
|
+
# Use authentication to access the CMIS repository
|
16
|
+
#
|
17
|
+
# @param method [Symbol] Currently only :basic is supported
|
18
|
+
# @param params The parameters that need to be sent to the Net::HTTP authentication method used, username and password for basic authentication
|
19
|
+
# @return [void]
|
20
|
+
# @example Basic authentication
|
21
|
+
# repo.authenticate(:basic, "username", "password")
|
22
|
+
# @example NTLM authentication
|
23
|
+
# repo.authenticate(:ntlm, "username", "password")
|
24
|
+
def authenticate(method, *params)
|
25
|
+
case method
|
26
|
+
when :basic, "basic"
|
27
|
+
@authentication = {:method => :basic_auth, :params => params}
|
28
|
+
@user = params.first
|
29
|
+
when :ntlm, "ntlm"
|
30
|
+
@authentication = {:method => :ntlm_auth, :params => params}
|
31
|
+
@user = params.first
|
32
|
+
else raise "Authentication method not supported"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# The return value is the unparsed body, unless an error occured
|
37
|
+
# If an error occurred, exceptions are thrown (see _ActiveCMIS::Exception
|
38
|
+
#
|
39
|
+
# @private
|
40
|
+
# @return [String] returns the body of the request, unless an error occurs
|
41
|
+
def get(url)
|
42
|
+
uri = normalize_url(url)
|
43
|
+
|
44
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
45
|
+
handle_request(uri, req)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Does not throw errors, returns the full response (includes status code and headers)
|
49
|
+
# @private
|
50
|
+
# @return [Net::HTTP::Response]
|
51
|
+
def get_response(url)
|
52
|
+
logger.debug "GET (response) #{url}"
|
53
|
+
uri = normalize_url(url)
|
54
|
+
|
55
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
56
|
+
http = authenticate_request(uri, req)
|
57
|
+
response = http.request(req)
|
58
|
+
logger.debug "GOT (#{response.code}) #{url}"
|
59
|
+
response
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the parsed body of the result
|
63
|
+
# @private
|
64
|
+
# @return [Nokogiri::XML::Document]
|
65
|
+
def get_xml(url)
|
66
|
+
Nokogiri::XML.parse(get(url), nil, nil, Nokogiri::XML::ParseOptions::STRICT)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private
|
70
|
+
# @return [Nokogiri::XML::Node]
|
71
|
+
def get_atom_entry(url)
|
72
|
+
# FIXME: add validation that first child is really an entry
|
73
|
+
get_xml(url).child
|
74
|
+
end
|
75
|
+
|
76
|
+
# @private
|
77
|
+
def put(url, body, headers = {})
|
78
|
+
uri = normalize_url(url)
|
79
|
+
|
80
|
+
req = Net::HTTP::Put.new(uri.request_uri)
|
81
|
+
headers.each {|k,v| req.add_field k, v}
|
82
|
+
assign_body(req, body)
|
83
|
+
handle_request(uri, req)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @private
|
87
|
+
def delete(url, headers = {})
|
88
|
+
uri = normalize_url(url)
|
89
|
+
|
90
|
+
req = Net::HTTP::Put.new(uri.request_uri)
|
91
|
+
headers.each {|k,v| req.add_field k, v}
|
92
|
+
handle_request(uri, req)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @private
|
96
|
+
def post(url, body, headers = {})
|
97
|
+
uri = normalize_url(url)
|
98
|
+
|
99
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
100
|
+
headers.each {|k,v| req.add_field k, v}
|
101
|
+
assign_body(req, body)
|
102
|
+
handle_request(uri, req)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Does not throw errors, returns the full response (includes status code and headers)
|
106
|
+
# @private
|
107
|
+
def post_response(url, body, headers = {})
|
108
|
+
logger.debug "POST (response) #{url}"
|
109
|
+
uri = normalize_url(url)
|
110
|
+
|
111
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
112
|
+
headers.each {|k,v| req.add_field k, v}
|
113
|
+
assign_body(req, body)
|
114
|
+
|
115
|
+
http = authenticate_request(uri, req)
|
116
|
+
response = http.request(req)
|
117
|
+
logger.debug "POSTED (#{response.code}) #{url}"
|
118
|
+
response
|
119
|
+
end
|
120
|
+
|
121
|
+
# @private
|
122
|
+
def delete(url)
|
123
|
+
uri = normalize_url(url)
|
124
|
+
|
125
|
+
req = Net::HTTP::Delete.new(uri.request_uri)
|
126
|
+
handle_request(uri, req)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def normalize_url(url)
|
131
|
+
case url
|
132
|
+
when URI; url
|
133
|
+
else URI.parse(url.to_s)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def http_class
|
138
|
+
@http_class ||= begin
|
139
|
+
if proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] then
|
140
|
+
p_uri = URI.parse(proxy)
|
141
|
+
p_user, p_pass = p_uri.user, p_uri.password if p_uri.user
|
142
|
+
Net::HTTP::Proxy(p_uri.host, p_uri.port, p_user, p_pass)
|
143
|
+
else
|
144
|
+
Net::HTTP
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def authenticate_request(uri, req)
|
150
|
+
http = http_class.new(uri.host, uri.port)
|
151
|
+
if uri.scheme == 'https'
|
152
|
+
http.use_ssl = true
|
153
|
+
end
|
154
|
+
if auth = @authentication
|
155
|
+
req.send(auth[:method], *auth[:params])
|
156
|
+
end
|
157
|
+
http
|
158
|
+
end
|
159
|
+
|
160
|
+
def assign_body(req, body)
|
161
|
+
if body.respond_to? :length
|
162
|
+
req.body = body
|
163
|
+
else
|
164
|
+
req.body_stream = body
|
165
|
+
if body.respond_to? :stat
|
166
|
+
req["Content-Length"] = body.stat.size.to_s
|
167
|
+
elsif req["Content-Size"].nil?
|
168
|
+
req["Transfer-Encoding"] = 'chunked'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def handle_request(uri, req, retry_count = 0)
|
174
|
+
logger.debug "#{req.method} #{uri}"
|
175
|
+
http = authenticate_request(uri, req)
|
176
|
+
|
177
|
+
status, body, headers = nil
|
178
|
+
http.request(req) { |resp|
|
179
|
+
status = resp.code.to_i
|
180
|
+
body = resp.body
|
181
|
+
headers = resp
|
182
|
+
}
|
183
|
+
|
184
|
+
logger.debug "RECEIVED #{status}"
|
185
|
+
|
186
|
+
if 200 <= status && status < 300
|
187
|
+
return body
|
188
|
+
elsif 300 <= status && status < 400
|
189
|
+
# follow the redirected a limited number of times
|
190
|
+
location = headers["location"]
|
191
|
+
logger.debug "REDIRECTING: #{location.inspect}"
|
192
|
+
if retry_count <= 3
|
193
|
+
new_uri = URI.parse(location)
|
194
|
+
if new_uri.relative?
|
195
|
+
new_uri = uri + location
|
196
|
+
end
|
197
|
+
new_req = req.class.new(uri.request_uri)
|
198
|
+
handle_request(new_uri, new_req, retry_count + 1)
|
199
|
+
else
|
200
|
+
raise HTTPError.new("Too many redirects")
|
201
|
+
end
|
202
|
+
elsif 400 <= status && status < 500
|
203
|
+
# Problem: some codes 400, 405, 403, 409, 500 have multiple meanings
|
204
|
+
logger.error "Error occurred when handling request:\n#{body}"
|
205
|
+
case status
|
206
|
+
when 400; raise Error::InvalidArgument.new(body)
|
207
|
+
# FIXME: can also be filterNotValid
|
208
|
+
when 401; raise HTTPError::AuthenticationError.new(body)
|
209
|
+
when 404; raise Error::ObjectNotFound.new(body)
|
210
|
+
when 403; raise Error::PermissionDenied.new(body)
|
211
|
+
# FIXME: can also be streamNotSupported (?? shouldn't that be 405??)
|
212
|
+
when 405; raise Error::NotSupported.new(body)
|
213
|
+
else
|
214
|
+
raise HTTPError::ClientError.new("A HTTP #{status} error occured, for more precision update the code:\n" + body)
|
215
|
+
end
|
216
|
+
elsif 500 <= status
|
217
|
+
raise HTTPError::ServerError.new("The server encountered an internal error #{status} (this could be a client error though):\n" + body)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
module Internal
|
3
|
+
# @private
|
4
|
+
module Utils
|
5
|
+
# @private
|
6
|
+
def self.escape_url_parameter(parameter)
|
7
|
+
control = "\x00-\x1F\x7F"
|
8
|
+
space = " "
|
9
|
+
delims = "<>#%\""
|
10
|
+
unwise = '{}|\\\\^\[\]`'
|
11
|
+
query = ";/?:@&=+,$"
|
12
|
+
escape(parameter, /[#{control+space+delims+unwise+query}]/o)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Given an url (string or URI) returns that url with the given parameters appended
|
16
|
+
#
|
17
|
+
# This method does not perform any encoding on the paramter or key values.
|
18
|
+
# This method does not check the existing parameters for duplication in keys
|
19
|
+
# @private
|
20
|
+
def self.append_parameters(uri, parameters)
|
21
|
+
uri = case uri
|
22
|
+
when String; string = true; URI.parse(uri)
|
23
|
+
when URI; uri.dup
|
24
|
+
end
|
25
|
+
uri.query = [uri.query, *parameters.map {|key, value| "#{key}=#{value}"} ].compact.join "&"
|
26
|
+
if string
|
27
|
+
uri.to_s
|
28
|
+
else
|
29
|
+
uri
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# FIXME?? percent_encode and escape_url_parameter serve nearly the same purpose, replace one?
|
34
|
+
# @private
|
35
|
+
def self.percent_encode(string)
|
36
|
+
escape(string, /[^#{URI::PATTERN::UNRESERVED}]/o)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.escape(string, pattern)
|
40
|
+
if defined?(URI::Parser)
|
41
|
+
parser = URI::Parser.new
|
42
|
+
parser.escape(string, pattern)
|
43
|
+
else
|
44
|
+
URI.escape(string, pattern)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns id if id is already an object, object_by_id if id is a string, nil otherwise
|
49
|
+
# @private
|
50
|
+
def self.string_or_id_to_object(repository, id)
|
51
|
+
# FIXME: only used in lib/activecmis/relationship.rb, the repository parameter
|
52
|
+
# would be unnecessary if included.
|
53
|
+
# Should this be a generic method, or should this be moved to the Relationship class?
|
54
|
+
# Or should I start including this module in every place that needs it?
|
55
|
+
case id
|
56
|
+
when String; repository.object_by_id(id)
|
57
|
+
when ::ActiveCMIS::Object; id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @private
|
62
|
+
def self.extract_links(xml, rel, type_main = nil, type_params = {})
|
63
|
+
links = xml.xpath("at:link[@rel = '#{rel}']", NS::COMBINED)
|
64
|
+
|
65
|
+
if type_main
|
66
|
+
type_main = Regexp.escape(type_main)
|
67
|
+
if type_params.empty?
|
68
|
+
regex = /#{type_main}/
|
69
|
+
else
|
70
|
+
parameters = type_params.map {|k,v| "#{Regexp.escape(k)}=#{Regexp.escape(v)}" }.join(";\s*")
|
71
|
+
regex = /#{type_main};\s*#{parameters}/
|
72
|
+
end
|
73
|
+
links = links.select do |node|
|
74
|
+
regex === node.attribute("type").to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
links.map {|l| l.attribute("href").to_s}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
# This module defines namespaces that often occur in the REST/Atompub API to CMIS
|
3
|
+
module NS
|
4
|
+
CMIS_CORE = "http://docs.oasis-open.org/ns/cmis/core/200908/"
|
5
|
+
CMIS_REST = "http://docs.oasis-open.org/ns/cmis/restatom/200908/"
|
6
|
+
CMIS_MESSAGING = "http://docs.oasis-open.org/ns/cmis/messaging/200908/"
|
7
|
+
APP = "http://www.w3.org/2007/app"
|
8
|
+
ATOM = "http://www.w3.org/2005/Atom"
|
9
|
+
|
10
|
+
COMBINED = {
|
11
|
+
"xmlns:c" => CMIS_CORE,
|
12
|
+
"xmlns:cra" => CMIS_REST,
|
13
|
+
"xmlns:cm" => CMIS_MESSAGING,
|
14
|
+
"xmlns:app" => APP,
|
15
|
+
"xmlns:at" => ATOM
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|