timfel-active_cmis 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|