active_cmis 0.1.0
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/.gitignore +3 -0
- data/LICENSE +26 -0
- data/README.md +30 -0
- data/Rakefile +36 -0
- data/TODO +8 -0
- data/VERSION +1 -0
- data/lib/active_cmis.rb +27 -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 +74 -0
- data/lib/active_cmis/atomic_types.rb +232 -0
- data/lib/active_cmis/attribute_prefix.rb +35 -0
- data/lib/active_cmis/collection.rb +175 -0
- data/lib/active_cmis/document.rb +314 -0
- data/lib/active_cmis/exceptions.rb +82 -0
- data/lib/active_cmis/folder.rb +21 -0
- data/lib/active_cmis/internal/caching.rb +86 -0
- data/lib/active_cmis/internal/connection.rb +171 -0
- data/lib/active_cmis/internal/utils.rb +69 -0
- data/lib/active_cmis/ns.rb +18 -0
- data/lib/active_cmis/object.rb +543 -0
- data/lib/active_cmis/policy.rb +13 -0
- data/lib/active_cmis/property_definition.rb +175 -0
- data/lib/active_cmis/rel.rb +17 -0
- data/lib/active_cmis/relationship.rb +47 -0
- data/lib/active_cmis/rendition.rb +65 -0
- data/lib/active_cmis/repository.rb +258 -0
- data/lib/active_cmis/server.rb +88 -0
- data/lib/active_cmis/type.rb +193 -0
- metadata +110 -0
@@ -0,0 +1,175 @@
|
|
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 && 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
|
+
|
59
|
+
@property_type = case property_type.downcase
|
60
|
+
when "string"
|
61
|
+
max_length = params["maxLength"] ? params["maxLength"].to_i : nil
|
62
|
+
AtomicType::String.new(max_length)
|
63
|
+
when "decimal"
|
64
|
+
min_value = params["minValue"] ? params["minValue"].to_f : nil
|
65
|
+
max_value = params["maxValue"] ? params["maxValue"].to_f : nil
|
66
|
+
AtomicType::Decimal.new(params["precision"].to_i, min_value, max_value)
|
67
|
+
when "integer"
|
68
|
+
min_value = params["minValue"] ? params["minValue"].to_i : nil
|
69
|
+
max_value = params["maxValue"] ? params["maxValue"].to_i : nil
|
70
|
+
AtomicType::Integer.new(min_value, max_value)
|
71
|
+
when "datetime"
|
72
|
+
AtomicType::DateTime.new(params["resolution"] || (logger.warn "No resolution for DateTime #{@id}"; "time") )
|
73
|
+
when "html"
|
74
|
+
AtomicType::HTML.new
|
75
|
+
when "id"
|
76
|
+
AtomicType::ID.new
|
77
|
+
when "boolean"
|
78
|
+
AtomicType::Boolean.new
|
79
|
+
when "uri"
|
80
|
+
AtomicType::URI.new
|
81
|
+
else
|
82
|
+
raise "Unknown property type #{property_type}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [Boolean] Returns true if the attribute can have multiple values
|
87
|
+
def repeating
|
88
|
+
cardinality == "multi"
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String]
|
92
|
+
def inspect
|
93
|
+
"#{object_type.display_name}:#{id} => #{property_type}#{"[]" if repeating}"
|
94
|
+
end
|
95
|
+
alias to_s inspect
|
96
|
+
|
97
|
+
# @return [String]
|
98
|
+
def property_name
|
99
|
+
"property#{property_type}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# @private
|
103
|
+
def render_property(xml, value)
|
104
|
+
xml["c"].send(property_name, "propertyDefinitionId" => id) {
|
105
|
+
if repeating
|
106
|
+
value.each do |v|
|
107
|
+
property_type.rb2cmis(xml, v)
|
108
|
+
end
|
109
|
+
else
|
110
|
+
property_type.rb2cmis(xml, value)
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
# @private
|
116
|
+
# FIXME: should probably also raise error for out of bounds case
|
117
|
+
def validate_ruby_value(value)
|
118
|
+
if updatability == "readonly" # FIXME: what about oncreate?
|
119
|
+
raise "You are trying to update a readonly attribute (#{self})"
|
120
|
+
elsif required && value.nil?
|
121
|
+
raise "You are trying to unset a required attribute (#{self})"
|
122
|
+
elsif repeating != (Array === value)
|
123
|
+
raise "You are ignoring the cardinality for an attribute (#{self})"
|
124
|
+
else
|
125
|
+
if repeating && z = value.detect {|v| !property_type.can_handle?(v)}
|
126
|
+
raise "Can't assign attribute with type #{z.class} to attribute with type #{property_type}"
|
127
|
+
elsif !repeating && !property_type.can_handle?(value)
|
128
|
+
raise "Can't assign attribute with type #{value.class} to attribute with type #{property_type}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# @private
|
134
|
+
def extract_property(properties)
|
135
|
+
elements = properties.children.select do |n|
|
136
|
+
n.node_name == property_name &&
|
137
|
+
n["propertyDefinitionId"] == id &&
|
138
|
+
n.namespace.href == NS::CMIS_CORE
|
139
|
+
end
|
140
|
+
if elements.empty?
|
141
|
+
if required
|
142
|
+
logger.warn "The server behaved strange: attribute #{self.inspect} required but not present among properties"
|
143
|
+
# raise ActiveCMIS::Error.new("The server behaved strange: attribute #{self.inspect} required but not present among properties")
|
144
|
+
end
|
145
|
+
if repeating
|
146
|
+
[]
|
147
|
+
else
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
elsif elements.length == 1
|
151
|
+
values = elements.first.children.select {|node| node.name == 'value' && node.namespace && node.namespace.href == ActiveCMIS::NS::CMIS_CORE}
|
152
|
+
if required && values.empty?
|
153
|
+
logger.warn "The server behaved strange: attribute #{self.inspect} required but not present among properties"
|
154
|
+
#raise ActiveCMIS::Error.new("The server behaved strange: attribute #{self.inspect} required but no values specified")
|
155
|
+
end
|
156
|
+
if !repeating && values.length > 1
|
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} not repeating but multiple values given")
|
159
|
+
end
|
160
|
+
values
|
161
|
+
else
|
162
|
+
raise "Property is not unique"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return [Logger] The logger of the repository
|
167
|
+
def logger
|
168
|
+
repository.logger
|
169
|
+
end
|
170
|
+
# @return [Repository] The repository that the CMIS type is defined in
|
171
|
+
def repository
|
172
|
+
object_type.repository
|
173
|
+
end
|
174
|
+
end
|
175
|
+
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,47 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
class Relationship < ::ActiveCMIS::Object
|
3
|
+
# @return [Object]
|
4
|
+
def source
|
5
|
+
Internal::Utils.string_or_id_to_object(attribute("cmis:sourceId"))
|
6
|
+
end
|
7
|
+
cache :source
|
8
|
+
|
9
|
+
# @return [Object]
|
10
|
+
def target
|
11
|
+
Internal::Utils.string_or_id_to_object(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
|
+
if source = updates["cmis:sourceId"]
|
29
|
+
remove_instance_variable "@source"
|
30
|
+
end
|
31
|
+
if updates["cmis:targetId"]
|
32
|
+
remove_instance_variable "@target"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return [], a relationship is not fileable
|
37
|
+
# @return [Array()]
|
38
|
+
def parent_folders
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def create_url
|
44
|
+
source.source_relations.url
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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
|
+
|
12
|
+
# @private
|
13
|
+
def initialize(repository, link)
|
14
|
+
@repository = repository
|
15
|
+
|
16
|
+
@rel = link['rel'] == "alternate"
|
17
|
+
@rendition_kind = link['renditionKind'] if rendition?
|
18
|
+
@format = link['type']
|
19
|
+
if link['href']
|
20
|
+
@url = URI(link['href'])
|
21
|
+
else # For inline content streams
|
22
|
+
@data = link['data']
|
23
|
+
end
|
24
|
+
@size = link['length'] ? link['length'].to_i : nil
|
25
|
+
|
26
|
+
|
27
|
+
@link = link # FIXME: For debugging purposes only, remove
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used to differentiate between rendition and primary content
|
31
|
+
def rendition?
|
32
|
+
@rel == "alternate"
|
33
|
+
end
|
34
|
+
# Used to differentiate between rendition and primary content
|
35
|
+
def primary?
|
36
|
+
@rel.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a hash with the name of the file to which was written, the lenthe, and the content type
|
40
|
+
#
|
41
|
+
# *WARNING*: this loads the complete file in memory and dumps it at once, this should be fixed
|
42
|
+
# @param [String] filename Location to store the content.
|
43
|
+
# @return [Hash]
|
44
|
+
def get_file(file_name)
|
45
|
+
if @url
|
46
|
+
response = repository.conn.get_response(@url)
|
47
|
+
status = response.code.to_i
|
48
|
+
if 200 <= status && status < 300
|
49
|
+
data = response.body
|
50
|
+
else
|
51
|
+
raise HTTPError.new("Problem downloading rendition: status: #{status}, message: #{response.body}")
|
52
|
+
end
|
53
|
+
content_type = response.content_type
|
54
|
+
content_lenth = response.content_length || response.body.length # In case content encoding is chunked? ??
|
55
|
+
else
|
56
|
+
data = @data
|
57
|
+
content_type = @format
|
58
|
+
content_length = @data.length
|
59
|
+
end
|
60
|
+
File.open(file_name, "w") {|f| f.syswrite data }
|
61
|
+
|
62
|
+
{:file => file_name, :content_type => content_type, :content_length => content_length}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,258 @@
|
|
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
|
+
# @private
|
7
|
+
def initialize(connection, logger, initial_data) #:nodoc:
|
8
|
+
@conn = connection
|
9
|
+
@data = initial_data
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
# Use authentication to access the CMIS repository
|
14
|
+
#
|
15
|
+
# e.g.: repo.authenticate(:basic, "username", "password")
|
16
|
+
# @return [void]
|
17
|
+
def authenticate(method, *params)
|
18
|
+
conn.authenticate(method, *params)
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# The identifier of the repository
|
23
|
+
# @return [String]
|
24
|
+
def key
|
25
|
+
@key ||= data.xpath('cra:repositoryInfo/c:repositoryId', NS::COMBINED).text
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String]
|
29
|
+
def inspect
|
30
|
+
"<#ActiveCMIS::Repository #{key}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
# The version of the CMIS standard supported by this repository
|
34
|
+
# @return [String]
|
35
|
+
def cmis_version
|
36
|
+
# NOTE: we might want to "version" our xml namespaces depending on the CMIS version
|
37
|
+
# If we do that we need to make this method capable of not using the predefined namespaces
|
38
|
+
#
|
39
|
+
# 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
|
40
|
+
@cmis_version ||= data.xpath("cra:repositoryInfo/c:cmisVersionSupported", NS::COMBINED).text
|
41
|
+
end
|
42
|
+
|
43
|
+
# Finds the object with a given ID in the repository
|
44
|
+
#
|
45
|
+
# @param [String] id
|
46
|
+
# @param parameters A list of parameters used to get (defaults are what you should use)
|
47
|
+
# @return [Object]
|
48
|
+
def object_by_id(id, parameters = {"renditionFilter" => "*", "includeAllowableActions" => "true", "includeACL" => true})
|
49
|
+
ActiveCMIS::Object.from_parameters(self, parameters.merge("id" => id))
|
50
|
+
end
|
51
|
+
|
52
|
+
# @private
|
53
|
+
def object_by_id_url(parameters)
|
54
|
+
template = pick_template("objectbyid")
|
55
|
+
raise "Repository does not define required URI-template 'objectbyid'" unless template
|
56
|
+
url = fill_in_template(template, parameters)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Finds the type with a given ID in the repository
|
60
|
+
# @return [Class]
|
61
|
+
def type_by_id(id)
|
62
|
+
@type_by_id ||= {}
|
63
|
+
if result = @type_by_id[id]
|
64
|
+
result
|
65
|
+
else
|
66
|
+
template = pick_template("typebyid")
|
67
|
+
raise "Repository does not define required URI-template 'typebyid'" unless template
|
68
|
+
url = fill_in_template(template, "id" => id)
|
69
|
+
|
70
|
+
@type_by_id[id] = Type.create(conn, self, conn.get_atom_entry(url))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
%w[root checkedout unfiled].each do |coll_name|
|
75
|
+
define_method coll_name do
|
76
|
+
iv = :"@#{coll_name}"
|
77
|
+
if instance_variable_defined?(iv)
|
78
|
+
instance_variable_get(iv)
|
79
|
+
else
|
80
|
+
href = data.xpath("app:collection[cra:collectionType[child::text() = '#{coll_name}']]/@href", NS::COMBINED)
|
81
|
+
if href.first
|
82
|
+
result = Collection.new(self, href.first)
|
83
|
+
else
|
84
|
+
result = nil
|
85
|
+
end
|
86
|
+
instance_variable_set(iv, result)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# A collection containing the CMIS base types supported by this repository
|
92
|
+
# @return [Collection<Class>]
|
93
|
+
def base_types
|
94
|
+
@base_types ||= begin
|
95
|
+
query = "app:collection[cra:collectionType[child::text() = 'types']]/@href"
|
96
|
+
href = data.xpath(query, NS::COMBINED)
|
97
|
+
if href.first
|
98
|
+
url = href.first.text
|
99
|
+
Collection.new(self, url) do |entry|
|
100
|
+
id = entry.xpath("cra:type/c:id", NS::COMBINED).text
|
101
|
+
type_by_id id
|
102
|
+
end
|
103
|
+
else
|
104
|
+
raise "Repository has no types collection, this is strange and wrong"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# An array containing all the types used by this repository
|
110
|
+
# @return [<Class>]
|
111
|
+
def types
|
112
|
+
@types ||= base_types.map do |t|
|
113
|
+
t.all_subtypes
|
114
|
+
end.flatten
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns a collection with the changes since the given changeLogToken.
|
118
|
+
#
|
119
|
+
# Completely uncached so use with care
|
120
|
+
#
|
121
|
+
# @param options Keys can be Symbol or String, all options are optional
|
122
|
+
# @option options [String] filter
|
123
|
+
# @option options [String] changeLogToken A token indicating which changes you already know about
|
124
|
+
# @option options [Integer] maxItems For paging
|
125
|
+
# @option options [Boolean] includeAcl
|
126
|
+
# @option options [Boolean] includePolicyIds
|
127
|
+
# @option options [Boolean] includeProperties
|
128
|
+
# @return [Collection]
|
129
|
+
def changes(options = {})
|
130
|
+
query = "at:link[@rel = '#{Rel[cmis_version][:changes]}']/@href"
|
131
|
+
link = data.xpath(query, NS::COMBINED)
|
132
|
+
if link = link.first
|
133
|
+
link = Internal::Utils.append_parameters(link.to_s, options)
|
134
|
+
Collection.new(self, link)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns a collection with the results of a query (if supported by the repository)
|
139
|
+
#
|
140
|
+
# @param [#to_s] query_string A query in the CMIS SQL format (unescaped in any way)
|
141
|
+
# @param [{Symbol => ::Object}] options Optional configuration for the query
|
142
|
+
# @option options [Boolean] :searchAllVersions (false)
|
143
|
+
# @option options [Boolean] :includeAllowableActions (false)
|
144
|
+
# @option options ["none","source","target","both"] :includeRelationships
|
145
|
+
# @option options [String] :renditionFilter ('cmis:none') Possible values: 'cmis:none', '*' (all), comma-separated list of rendition kinds or mimetypes
|
146
|
+
# @option options [Integer] :maxItems used for paging
|
147
|
+
# @option options [Integer] :skipCount (0) used for paging
|
148
|
+
# @return [Collection]
|
149
|
+
def query(query_string, options = {})
|
150
|
+
raise "This repository does not support queries" if capabilities["Query"] == "none"
|
151
|
+
# For the moment we make no difference between metadataonly,fulltextonly,bothseparate and bothcombined
|
152
|
+
# Nor do we look at capabilities["Join"] (none, inneronly, innerandouter)
|
153
|
+
|
154
|
+
# For searchAllVersions need to check capabilities["AllVersionsSearchable"]
|
155
|
+
# includeRelationships, includeAllowableActions and renditionFilter only work if SELECT only contains attributes from 1 object
|
156
|
+
valid_params = ["searchAllVersions", "includeAllowableActions", "includeRelationships", "renditionFilter", "maxItems", "skipCount"]
|
157
|
+
invalid_params = options.keys - valid_params
|
158
|
+
unless invalid_params.empty?
|
159
|
+
raise "Invalid parameters for query: #{invalid_params.join ', '}"
|
160
|
+
end
|
161
|
+
|
162
|
+
# FIXME: options are not respected yet by pick_template
|
163
|
+
url = pick_template("query", :mimetype => "application/atom+xml", :type => "feed")
|
164
|
+
url = fill_in_template(url, options.merge("q" => query_string))
|
165
|
+
Collection.new(self, url)
|
166
|
+
end
|
167
|
+
|
168
|
+
# The root folder of the repository (as defined in the CMIS standard)
|
169
|
+
# @return [Folder]
|
170
|
+
def root_folder
|
171
|
+
@root_folder ||= object_by_id(data.xpath("cra:repositoryInfo/c:rootFolderId", NS::COMBINED).text)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns an Internal::Connection object, normally you should not use this directly
|
175
|
+
# @return [Internal::Connection]
|
176
|
+
def conn
|
177
|
+
@conn ||= Internal::Connection.new
|
178
|
+
end
|
179
|
+
|
180
|
+
# Describes the capabilities of the repository
|
181
|
+
# @return [Hash{String => String,Boolean}] The hash keys have capability cut of their name
|
182
|
+
def capabilities
|
183
|
+
@capabilities ||= begin
|
184
|
+
capa = {}
|
185
|
+
data.xpath("cra:repositoryInfo/c:capabilities/*", NS::COMBINED).map do |node|
|
186
|
+
# FIXME: conversion should be based on knowledge about data model + transforming bool code should not be duplicated
|
187
|
+
capa[node.name.sub("capability", "")] = case t = node.text
|
188
|
+
when "true", "1"; true
|
189
|
+
when "false", "0"; false
|
190
|
+
else t
|
191
|
+
end
|
192
|
+
end
|
193
|
+
capa
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Responds with true if Private Working Copies are updateable, false otherwise
|
198
|
+
# (if false the PWC object can only be updated during the checkin)
|
199
|
+
def pwc_updatable?
|
200
|
+
capabilities["PWCUpdatable"]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Responds with true if different versions of the same document can
|
204
|
+
# be filed in different folders
|
205
|
+
def version_specific_filing?
|
206
|
+
capabilities["VersionSpecificFiling"]
|
207
|
+
end
|
208
|
+
|
209
|
+
# returns true if ACLs can at least be viewed
|
210
|
+
def acls_readable?
|
211
|
+
["manage", "discover"].include? capabilities["ACL"]
|
212
|
+
end
|
213
|
+
|
214
|
+
# You should probably not use this directly, use :anonymous instead where a user name is required
|
215
|
+
# @return [String]
|
216
|
+
def anonymous_user
|
217
|
+
if acls_readable?
|
218
|
+
data.xpath('cra:repositoryInfo/c:principalAnonymous', NS::COMBINED).text
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# You should probably not use this directly, use :world instead where a user name is required
|
223
|
+
# @return [String]
|
224
|
+
def world_user
|
225
|
+
if acls_readable?
|
226
|
+
data.xpath('cra:repositoryInfo/c:principalAnyone', NS::COMBINED).text
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
# @private
|
232
|
+
attr_reader :data
|
233
|
+
|
234
|
+
def pick_template(name, options = {})
|
235
|
+
# FIXME: we can have more than 1 template with differing media types
|
236
|
+
# I'm not sure how to pick the right one in the most generic/portable way though
|
237
|
+
# So for the moment we pick the 1st and hope for the best
|
238
|
+
# Options are ignored for the moment
|
239
|
+
data.xpath("n:uritemplate[n:type[child::text() = '#{name}']][1]/n:template", "n" => NS::CMIS_REST).text
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
# The type parameter should contain the type of the uri-template
|
244
|
+
#
|
245
|
+
# The keys of the values hash should be strings,
|
246
|
+
# if a key is not in the hash it is presumed to be equal to the empty string
|
247
|
+
# The values will be percent-encoded in the fill_in_template method
|
248
|
+
# If a given key is not present in the template it will be ignored silently
|
249
|
+
#
|
250
|
+
# e.g. fill_in_template("objectbyid", "id" => "@root@", "includeACL" => true)
|
251
|
+
# -> 'http://example.org/repo/%40root%40?includeRelationships&includeACL=true'
|
252
|
+
def fill_in_template(template, values)
|
253
|
+
result = template.gsub /\{([^}]+)\}/ do |match|
|
254
|
+
Internal::Utils.percent_encode(values[$1].to_s)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|