cmis_active 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ module ActiveCMIS
2
+ class Policy < ActiveCMIS::Object
3
+ private
4
+ def create_url
5
+ if f = parent_folders.first
6
+ f.items.url
7
+ else
8
+ raise "not yet"
9
+ # Policy collection of containing document?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,179 @@
1
+ module ActiveCMIS
2
+ class PropertyDefinition
3
+ # @return [String]
4
+ attr_reader :object_type, :id, :local_name, :local_namespace, :query_name,
5
+ :display_name, :description, :cardinality, :property_type, :updatability,
6
+ :default_value
7
+ # @return [Boolean]
8
+ attr_reader :inherited, :required, :queryable, :orderable, :choices, :open_choice
9
+
10
+ # @private
11
+ def initialize(object_type, property_definition)
12
+ @object_type = object_type
13
+ @property_definition = property_definition
14
+ params = {}
15
+ property_type = nil
16
+ property_definition.map do |node|
17
+ next unless node.namespace
18
+ next unless node.namespace.href == NS::CMIS_CORE
19
+
20
+ # FIXME: add support for "choices"
21
+ case node.node_name
22
+ when "id"
23
+ @id = node.text
24
+ when "localName"
25
+ @local_name = node.text
26
+ when "localNamespace"
27
+ @local_namespace = node.text
28
+ when "displayName"
29
+ @display_name = node.text
30
+ when "queryName"
31
+ @query_name = node.text
32
+ when "propertyType"
33
+ # Will be post processed, but we need to know all the parameters before we can pick an atomic type
34
+ property_type = node.text
35
+ when "cardinality"
36
+ @cardinality = node.text
37
+ when "updatability"
38
+ @updatability = node.text
39
+ when "inherited"
40
+ @inherited = AtomicType::Boolean.xml_to_bool(node.text)
41
+ when "required"
42
+ @required = AtomicType::Boolean.xml_to_bool(node.text)
43
+ when "queryable"
44
+ @queryable = AtomicType::Boolean.xml_to_bool(node.text)
45
+ when "orderable"
46
+ @orderable = AtomicType::Boolean.xml_to_bool(node.text)
47
+ when "openChoice"
48
+ @open_choice = AtomicType::Boolean.xml_to_bool(node.text)
49
+ when "maxValue", "minValue", "resolution", "precision", "maxLength"
50
+ params[node.node_name] = node.text
51
+ end
52
+ end
53
+
54
+ if required and updatability == "readonly"
55
+ logger.warn "The server behaved strange: attribute #{self.inspect} required but readonly, will set required to false"
56
+ @required = false
57
+ end
58
+ if id == "cmis:objectTypeId" and updatability != "oncreate"
59
+ logger.warn "The server behaved strange: cmis:objectTypeId should be updatable on create but #{updatability}"
60
+ @updatability = "oncreate"
61
+ end
62
+
63
+ @property_type = case property_type.downcase
64
+ when "string"
65
+ max_length = params["maxLength"] ? params["maxLength"].to_i : nil
66
+ AtomicType::String.new(max_length)
67
+ when "decimal"
68
+ min_value = params["minValue"] ? params["minValue"].to_f : nil
69
+ max_value = params["maxValue"] ? params["maxValue"].to_f : nil
70
+ AtomicType::Decimal.new(params["precision"].to_i, min_value, max_value)
71
+ when "integer"
72
+ min_value = params["minValue"] ? params["minValue"].to_i : nil
73
+ max_value = params["maxValue"] ? params["maxValue"].to_i : nil
74
+ AtomicType::Integer.new(min_value, max_value)
75
+ when "datetime"
76
+ AtomicType::DateTime.new(params["resolution"] || (logger.warn "No resolution for DateTime #{@id}"; "time") )
77
+ when "html"
78
+ AtomicType::HTML.new
79
+ when "id"
80
+ AtomicType::ID.new
81
+ when "boolean"
82
+ AtomicType::Boolean.new
83
+ when "uri"
84
+ AtomicType::URI.new
85
+ else
86
+ raise "Unknown property type #{property_type}"
87
+ end
88
+ end
89
+
90
+ # @return [Boolean] Returns true if the attribute can have multiple values
91
+ def repeating
92
+ cardinality == "multi"
93
+ end
94
+
95
+ # @return [String]
96
+ def inspect
97
+ "#{object_type.display_name}:#{id} => #{property_type}#{"[]" if repeating}"
98
+ end
99
+ alias to_s inspect
100
+
101
+ # @return [String]
102
+ def property_name
103
+ "property#{property_type}"
104
+ end
105
+
106
+ # @private
107
+ def render_property(xml, value)
108
+ xml["c"].send(property_name, "propertyDefinitionId" => id) {
109
+ if repeating
110
+ value.each do |v|
111
+ property_type.rb2cmis(xml, v)
112
+ end
113
+ else
114
+ property_type.rb2cmis(xml, value)
115
+ end
116
+ }
117
+ end
118
+
119
+ # @private
120
+ # FIXME: should probably also raise error for out of bounds case
121
+ def validate_ruby_value(value)
122
+ if updatability == "readonly" # FIXME: what about oncreate?
123
+ raise "You are trying to update a readonly attribute (#{self})"
124
+ elsif required && value.nil?
125
+ raise "You are trying to unset a required attribute (#{self})"
126
+ elsif repeating != (Array === value)
127
+ raise "You are ignoring the cardinality for an attribute (#{self})"
128
+ else
129
+ if repeating && z = value.detect {|v| !property_type.can_handle?(v)}
130
+ raise "Can't assign attribute with type #{z.class} to attribute with type #{property_type}"
131
+ elsif !repeating && !property_type.can_handle?(value)
132
+ raise "Can't assign attribute with type #{value.class} to attribute with type #{property_type}"
133
+ end
134
+ end
135
+ end
136
+
137
+ # @private
138
+ def extract_property(properties)
139
+ elements = properties.children.select do |n|
140
+ n.node_name == property_name &&
141
+ n["propertyDefinitionId"] == id &&
142
+ n.namespace.href == NS::CMIS_CORE
143
+ end
144
+ if elements.empty?
145
+ if required
146
+ logger.warn "The server behaved strange: attribute #{self.inspect} required but not present among properties"
147
+ # raise ActiveCMIS::Error.new("The server behaved strange: attribute #{self.inspect} required but not present among properties")
148
+ end
149
+ if repeating
150
+ []
151
+ else
152
+ nil
153
+ end
154
+ elsif elements.length == 1
155
+ values = elements.first.children.select {|node| node.name == 'value' && node.namespace && node.namespace.href == ActiveCMIS::NS::CMIS_CORE}
156
+ if required && values.empty?
157
+ logger.warn "The server behaved strange: attribute #{self.inspect} required but not present among properties"
158
+ #raise ActiveCMIS::Error.new("The server behaved strange: attribute #{self.inspect} required but no values specified")
159
+ end
160
+ if !repeating && values.length > 1
161
+ logger.warn "The server behaved strange: attribute #{self.inspect} required but not present among properties"
162
+ #raise ActiveCMIS::Error.new("The server behaved strange: attribute #{self.inspect} not repeating but multiple values given")
163
+ end
164
+ values
165
+ else
166
+ raise "Property is not unique"
167
+ end
168
+ end
169
+
170
+ # @return [Logger] The logger of the repository
171
+ def logger
172
+ repository.logger
173
+ end
174
+ # @return [Repository] The repository that the CMIS type is defined in
175
+ def repository
176
+ object_type.repository
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveCMIS
2
+ # QueryResults are returned when doing a query
3
+ # You can retrieve the values they contain either by the properties ID or by the query name.
4
+ #
5
+ # Since it is possible that a JOIN is being requested it is not guaranteed that either the query name or the object ID are unique.
6
+ # If that's not the case then it is impossible to retrieve one of the two values. It is therefore important to choose unique queryNames
7
+ # Furthermore, it is not possible to guess which type a query is returning, and therefore it is also not possible to know whether a property is repeating or not, it is possible that a repeating property contains 0 or 1 values, in which case nil or that single value are returned. If multiple values are found for a property then an Array with those properties will be returned
8
+ class QueryResult
9
+ def initialize(atom_entry)
10
+ @atom_entry = atom_entry
11
+ properties = atom_entry.xpath("cra:object/c:properties/c:*", NS::COMBINED)
12
+ @properties_by_id = {}
13
+ @properties_by_query_name = {}
14
+ properties.each do |property|
15
+ type = ActiveCMIS::AtomicType::MAPPING[property.node_name]
16
+ converter = type.new
17
+
18
+ values = property.xpath("c:value", NS::COMBINED)
19
+ # FIXME: If attributes are repeating, but have 0-1 value they won't be in array
20
+ if values.length > 1
21
+ value = values.map {|v| converter.cmis2rb(v)}
22
+ elsif !values.empty?
23
+ value = converter.cmis2rb(values)
24
+ else
25
+ value = nil
26
+ end
27
+ @properties_by_id[property["propertyDefinitionId"]] = value
28
+ @properties_by_query_name[property["queryName"]] = value
29
+ end
30
+ end
31
+
32
+ def property_by_id(name)
33
+ @properties_by_id[name]
34
+ end
35
+
36
+ def property_by_query_name(name)
37
+ @properties_by_query_name[name]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveCMIS
2
+ module Rel
3
+ def self.[](version)
4
+ if version == '1.0'
5
+ prefix = "http://docs.oasis-open.org/ns/cmis/link/200908/"
6
+ {
7
+ :allowableactions => "#{prefix}allowableactions",
8
+ :acl => "#{prefix}acl",
9
+ :relationships => "#{prefix}relationships",
10
+ :changes => "#{prefix}changes",
11
+ }
12
+ else
13
+ raise ActiveCMIS::Error.new("ActiveCMIS only works with CMIS 1.0, requested version was #{version}")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveCMIS
2
+ class Relationship < ::ActiveCMIS::Object
3
+ # @return [Object]
4
+ def source
5
+ Internal::Utils.string_or_id_to_object(repository, attribute("cmis:sourceId"))
6
+ end
7
+ cache :source
8
+
9
+ # @return [Object]
10
+ def target
11
+ Internal::Utils.string_or_id_to_object(repository, attribute("cmis:targetId"))
12
+ end
13
+ cache :target
14
+
15
+ # Remove the relationship
16
+ # @return [void]
17
+ def delete
18
+ conn.delete(self_link)
19
+ end
20
+
21
+ # @see Object#update
22
+ # @param (see ActiveCMIS::Object#update)
23
+ # @return [void]
24
+ def update(updates = {})
25
+ super
26
+ # Potentially necessary if repositories support it
27
+ # Probably not though
28
+
29
+ # Note: we use remove_instance_variable because of the way I implemented the caching
30
+ if updates["cmis:sourceId"] && instance_variable_defined?("@source")
31
+ remove_instance_variable "@source"
32
+ end
33
+ if updates["cmis:targetId"] && instance_variable_defined?("@target")
34
+ remove_instance_variable "@target"
35
+ end
36
+ end
37
+
38
+ # Return [], a relationship is not fileable
39
+ # @return [Array()]
40
+ def parent_folders
41
+ []
42
+ end
43
+
44
+ private
45
+ def create_url
46
+ source.source_relations.url
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,86 @@
1
+ module ActiveCMIS
2
+ class Rendition
3
+ # @return [Repository]
4
+ attr_reader :repository
5
+ # @return [Numeric,nil] Size of the rendition, may not be given or misleading
6
+ attr_reader :size
7
+ # @return [String,nil]
8
+ attr_reader :rendition_kind
9
+ # @return [String,nil] The format is equal to the mime type, but may be unset or misleading
10
+ attr_reader :format
11
+ # @return [ActiveCMIS::Document] The document to which the rendition belongs
12
+ attr_reader :document
13
+
14
+ # @private
15
+ def initialize(repository, document, link)
16
+ @repository = repository
17
+ @document = document
18
+
19
+ @rel = link['rel'] == "alternate"
20
+ @rendition_kind = link['renditionKind'] if rendition?
21
+ @format = link['type']
22
+ if link['href']
23
+ @url = URI(link['href'])
24
+ else # For inline content streams
25
+ @data = link['data']
26
+ end
27
+ @size = link['length'] ? link['length'].to_i : nil
28
+
29
+
30
+ @link = link # FIXME: For debugging purposes only, remove
31
+ end
32
+
33
+ # Used to differentiate between rendition and primary content
34
+ def rendition?
35
+ @rel == "alternate"
36
+ end
37
+ # Used to differentiate between rendition and primary content
38
+ def primary?
39
+ @rel.nil?
40
+ end
41
+
42
+ # Returns a hash with the name of the file to which was written, the length, and the content type
43
+ #
44
+ # *WARNING*: this loads the complete file in memory and dumps it at once, this should be fixed
45
+ # @param [String] file_name Location to store the content.
46
+ # @return [Hash]
47
+ def get_file(file_name)
48
+ response = get_data
49
+ File.open(file_name, "w") {|f| f.syswrite response.delete(:data) }
50
+ response.merge!(:file_name => file_name)
51
+ end
52
+
53
+ # Returns a hash with the data of te rendition, the length and the content type
54
+ #
55
+ # *WARNING*: this loads all the data in memory
56
+ # Possible future enhancement could be to allow a block to which data is passed in chunks?r
57
+ # Not sure that is possible with Net::HTTP though.
58
+ # @param [String] filename Location to store the content.
59
+ # @return [Hash]
60
+ def get_data
61
+ if @url
62
+ response = repository.conn.get_response(@url)
63
+ status = response.code.to_i
64
+ if 200 <= status && status < 300
65
+ data = response.body
66
+ elsif 300 <= status && status < 400
67
+ location = response["location"]
68
+ new_url = URI.parse(location)
69
+ new_url = @url + location if new_url.relative?
70
+ @url = new_url
71
+ get_data
72
+ else
73
+ raise HTTPError.new("Problem downloading rendition: status: #{status}, message: #{response.body}")
74
+ end
75
+ content_type = response.content_type
76
+ content_length = response.content_length || response.body.length # In case content encoding is chunked? ??
77
+ else
78
+ data = @data
79
+ content_type = @format
80
+ content_length = @data.length
81
+ end
82
+
83
+ {:data => data, :content_type => content_type, :content_length => content_length}
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,327 @@
1
+ module ActiveCMIS
2
+ class Repository
3
+ # @return [Logger] A logger to which debug output and so on is sent
4
+ attr_reader :logger
5
+
6
+ # @return [ActiveCMIS::Server] The server from which the repository was
7
+ # requested
8
+ attr_reader :server
9
+
10
+ # @private
11
+ def initialize(server, connection, logger, initial_data, authentication_info) #:nodoc:
12
+ @server = server
13
+ @conn = connection
14
+ @data = initial_data
15
+ @logger = logger
16
+ method, *params = authentication_info
17
+ if method
18
+ conn.authenticate(method, *params)
19
+ end
20
+ end
21
+
22
+ # Use authentication to access the CMIS repository
23
+ # This returns a new Repository object, the existing repository will still
24
+ # use the previous authentication info.
25
+ # If the used authentication info (method, username, password) is the same
26
+ # as for the current Repository object, then self will be returned (unless
27
+ # the server repository cache is cleared first)
28
+ #
29
+ # e.g.: authenticated = repo.authenticate(:basic, "username", "password")
30
+ # @param method [:basic, :ntlm]
31
+ # @param username [String]
32
+ # @param password [String]
33
+ # @return [Repository]
34
+ def authenticate(*authentication_info)
35
+ server.repository(key, authentication_info)
36
+ end
37
+
38
+ # The identifier of the repository
39
+ # @return [String]
40
+ def key
41
+ @key ||= data.xpath('cra:repositoryInfo/c:repositoryId', NS::COMBINED).text
42
+ end
43
+
44
+ # @return [String]
45
+ def inspect
46
+ "<#ActiveCMIS::Repository #{key}>"
47
+ end
48
+
49
+ # The version of the CMIS standard supported by this repository
50
+ # @return [String]
51
+ def cmis_version
52
+ # NOTE: we might want to "version" our xml namespaces depending on the CMIS version
53
+ # If we do that we need to make this method capable of not using the predefined namespaces
54
+ #
55
+ # On the other hand breaking the XML namespace is probably going to break other applications too so the might not change them even when the spec is updated
56
+ @cmis_version ||= data.xpath("cra:repositoryInfo/c:cmisVersionSupported", NS::COMBINED).text
57
+ end
58
+
59
+ # The name of the repository, meant for display purposes
60
+ # @return [String]
61
+ def name
62
+ @name ||= data.xpath("cra:repositoryInfo/c:repositoryName", NS::COMBINED).text
63
+ end
64
+
65
+ # A description of the repository
66
+ # @return [String]
67
+ def description
68
+ @name ||= data.xpath("cra:repositoryInfo/c:repositoryDescription", NS::COMBINED).text
69
+ end
70
+
71
+ # The name of the vendor of this Repository
72
+ # @return [String]
73
+ def vendor
74
+ @vendor ||= data.xpath("cra:repositoryInfo/c:vendorName", NS::COMBINED).text
75
+ end
76
+
77
+ # The name of the product behind this Repository
78
+ # @return [String]
79
+ def product_name
80
+ @product_name ||= data.xpath("cra:repositoryInfo/c:productName", NS::COMBINED).text
81
+ end
82
+
83
+ # The version of the product behind this Repository
84
+ # @return [String]
85
+ def product_version
86
+ @product_version ||= data.xpath("cra:repositoryInfo/c:productVersion", NS::COMBINED).text
87
+ end
88
+
89
+ # Changelog token representing the most recent change to this repository (will
90
+ # represent the most recent change at the time that this ActiveCMIS::Repository
91
+ # was created
92
+ # @return [String]
93
+ def latest_changelog_token
94
+ @changelog_token ||= data.xpath("cra:repositoryInfo/c:latestChangeLogToken", NS::COMBINED).text
95
+ end
96
+
97
+ # A URI that points to a web service for this repository. May not be present
98
+ # @return [URI, nil]
99
+ def thin_client_uri
100
+ @thin_client_uri ||= begin
101
+ string = data.xpath("cra:repositoryInfo/c:thinClientURI", NS::COMBINED).text
102
+ URI.parse(string) if string && string != ""
103
+ end
104
+ end
105
+
106
+ # Finds the object with a given ID in the repository
107
+ #
108
+ # @param [String] id
109
+ # @param parameters A list of parameters used to get (defaults are what you should use)
110
+ # @return [Object]
111
+ def object_by_id(id, parameters = {"renditionFilter" => "*", "includeAllowableActions" => "true", "includeACL" => true})
112
+ ActiveCMIS::Object.from_parameters(self, parameters.merge("id" => id))
113
+ end
114
+
115
+ # @private
116
+ def object_by_id_url(parameters)
117
+ template = pick_template("objectbyid")
118
+ raise "Repository does not define required URI-template 'objectbyid'" unless template
119
+ url = fill_in_template(template, parameters)
120
+ end
121
+
122
+ # Finds the type with a given ID in the repository
123
+ # @return [Class]
124
+ def type_by_id(id)
125
+ @type_by_id ||= {}
126
+ if result = @type_by_id[id]
127
+ result
128
+ else
129
+ template = pick_template("typebyid")
130
+ raise "Repository does not define required URI-template 'typebyid'" unless template
131
+ url = fill_in_template(template, "id" => id)
132
+
133
+ @type_by_id[id] = Type.create(conn, self, conn.get_atom_entry(url))
134
+ end
135
+ end
136
+
137
+ %w[root checkedout unfiled].each do |coll_name|
138
+ define_method coll_name do
139
+ iv = :"@#{coll_name}"
140
+ if instance_variable_defined?(iv)
141
+ instance_variable_get(iv)
142
+ else
143
+ href = data.xpath("app:collection[cra:collectionType[child::text() = '#{coll_name}']]/@href", NS::COMBINED)
144
+ if href.first
145
+ result = Collection.new(self, href.first)
146
+ else
147
+ result = nil
148
+ end
149
+ instance_variable_set(iv, result)
150
+ end
151
+ end
152
+ end
153
+
154
+ # A collection containing the CMIS base types supported by this repository
155
+ # @return [Collection<Class>]
156
+ def base_types
157
+ @base_types ||= begin
158
+ query = "app:collection[cra:collectionType[child::text() = 'types']]/@href"
159
+ href = data.xpath(query, NS::COMBINED)
160
+ if href.first
161
+ url = href.first.text
162
+ Collection.new(self, url) do |entry|
163
+ id = entry.xpath("cra:type/c:id", NS::COMBINED).text
164
+ type_by_id id
165
+ end
166
+ else
167
+ raise "Repository has no types collection, this is strange and wrong"
168
+ end
169
+ end
170
+ end
171
+
172
+ # An array containing all the types used by this repository
173
+ # @return [<Class>]
174
+ def types
175
+ @types ||= base_types.map do |t|
176
+ t.all_subtypes
177
+ end.flatten
178
+ end
179
+
180
+ # Returns a collection with the changes since the given changeLogToken.
181
+ #
182
+ # Completely uncached so use with care
183
+ #
184
+ # @param options Keys can be Symbol or String, all options are optional
185
+ # @option options [String] filter
186
+ # @option options [String] changeLogToken A token indicating which changes you already know about
187
+ # @option options [Integer] maxItems For paging
188
+ # @option options [Boolean] includeAcl
189
+ # @option options [Boolean] includePolicyIds
190
+ # @option options [Boolean] includeProperties
191
+ # @return [Collection]
192
+ def changes(options = {})
193
+ query = "at:link[@rel = '#{Rel[cmis_version][:changes]}']/@href"
194
+ link = data.xpath(query, NS::COMBINED)
195
+ if link = link.first
196
+ link = Internal::Utils.append_parameters(link.to_s, options)
197
+ Collection.new(self, link)
198
+ end
199
+ end
200
+
201
+ # Returns a collection with the results of a query (if supported by the repository)
202
+ #
203
+ # @param [#to_s] query_string A query in the CMIS SQL format (unescaped in any way)
204
+ # @param [{Symbol => ::Object}] options Optional configuration for the query
205
+ # @option options [Boolean] :searchAllVersions (false)
206
+ # @option options [Boolean] :includeAllowableActions (false)
207
+ # @option options ["none","source","target","both"] :includeRelationships
208
+ # @option options [String] :renditionFilter ('cmis:none') Possible values: 'cmis:none', '*' (all), comma-separated list of rendition kinds or mimetypes
209
+ # @option options [Integer] :maxItems used for paging
210
+ # @option options [Integer] :skipCount (0) used for paging
211
+ # @return [Collection] A collection with each return value wrapped in a QueryResult
212
+ def query(query_string, options = {})
213
+ raise "This repository does not support queries" if capabilities["Query"] == "none"
214
+ # For the moment we make no difference between metadataonly,fulltextonly,bothseparate and bothcombined
215
+ # Nor do we look at capabilities["Join"] (none, inneronly, innerandouter)
216
+
217
+ # For searchAllVersions need to check capabilities["AllVersionsSearchable"]
218
+ # includeRelationships, includeAllowableActions and renditionFilter only work if SELECT only contains attributes from 1 object
219
+ valid_params = ["searchAllVersions", "includeAllowableActions", "includeRelationships", "renditionFilter", "maxItems", "skipCount"]
220
+ invalid_params = options.keys - valid_params
221
+ unless invalid_params.empty?
222
+ raise "Invalid parameters for query: #{invalid_params.join ', '}"
223
+ end
224
+
225
+ # FIXME: options are not respected yet by pick_template
226
+ url = pick_template("query", :mimetype => "application/atom+xml", :type => "feed")
227
+ url = fill_in_template(url, options.merge("q" => query_string))
228
+ Collection.new(self, url) do |entry|
229
+ QueryResult.new(entry)
230
+ end
231
+ end
232
+
233
+ # The root folder of the repository (as defined in the CMIS standard)
234
+ # @return [Folder]
235
+ def root_folder(reload = false)
236
+ if reload
237
+ @root_folder = object_by_id(data.xpath("cra:repositoryInfo/c:rootFolderId", NS::COMBINED).text)
238
+ else
239
+ @root_folder ||= object_by_id(data.xpath("cra:repositoryInfo/c:rootFolderId", NS::COMBINED).text)
240
+ end
241
+ end
242
+
243
+ # Returns an Internal::Connection object, normally you should not use this directly
244
+ # @return [Internal::Connection]
245
+ def conn
246
+ @conn ||= Internal::Connection.new
247
+ end
248
+
249
+ # Describes the capabilities of the repository
250
+ # @return [Hash{String => String,Boolean}] The hash keys have capability cut of their name
251
+ def capabilities
252
+ @capabilities ||= begin
253
+ capa = {}
254
+ data.xpath("cra:repositoryInfo/c:capabilities/*", NS::COMBINED).map do |node|
255
+ # FIXME: conversion should be based on knowledge about data model + transforming bool code should not be duplicated
256
+ capa[node.name.sub("capability", "")] = case t = node.text
257
+ when "true", "1"; true
258
+ when "false", "0"; false
259
+ else t
260
+ end
261
+ end
262
+ capa
263
+ end
264
+ end
265
+
266
+ # Responds with true if Private Working Copies are updateable, false otherwise
267
+ # (if false the PWC object can only be updated during the checkin)
268
+ def pwc_updatable?
269
+ capabilities["PWCUpdatable"]
270
+ end
271
+
272
+ # Responds with true if different versions of the same document can
273
+ # be filed in different folders
274
+ def version_specific_filing?
275
+ capabilities["VersionSpecificFiling"]
276
+ end
277
+
278
+ # returns true if ACLs can at least be viewed
279
+ def acls_readable?
280
+ ["manage", "discover"].include? capabilities["ACL"]
281
+ end
282
+
283
+ # You should probably not use this directly, use :anonymous instead where a user name is required
284
+ # @return [String]
285
+ def anonymous_user
286
+ if acls_readable?
287
+ data.xpath('cra:repositoryInfo/c:principalAnonymous', NS::COMBINED).text
288
+ end
289
+ end
290
+
291
+ # You should probably not use this directly, use :world instead where a user name is required
292
+ # @return [String]
293
+ def world_user
294
+ if acls_readable?
295
+ data.xpath('cra:repositoryInfo/c:principalAnyone', NS::COMBINED).text
296
+ end
297
+ end
298
+
299
+ private
300
+ # @private
301
+ attr_reader :data
302
+
303
+ def pick_template(name, options = {})
304
+ # FIXME: we can have more than 1 template with differing media types
305
+ # I'm not sure how to pick the right one in the most generic/portable way though
306
+ # So for the moment we pick the 1st and hope for the best
307
+ # Options are ignored for the moment
308
+ data.xpath("n:uritemplate[n:type[child::text() = '#{name}']][1]/n:template", "n" => NS::CMIS_REST).text
309
+ end
310
+
311
+
312
+ # The type parameter should contain the type of the uri-template
313
+ #
314
+ # The keys of the values hash should be strings,
315
+ # if a key is not in the hash it is presumed to be equal to the empty string
316
+ # The values will be percent-encoded in the fill_in_template method
317
+ # If a given key is not present in the template it will be ignored silently
318
+ #
319
+ # e.g. fill_in_template("objectbyid", "id" => "@root@", "includeACL" => true)
320
+ # -> 'http://example.org/repo/%40root%40?includeRelationships&includeACL=true'
321
+ def fill_in_template(template, values)
322
+ result = template.gsub(/\{([^}]+)\}/) do |match|
323
+ Internal::Utils.percent_encode(values[$1].to_s)
324
+ end
325
+ end
326
+ end
327
+ end