cmis_active 0.3.7

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