Class: ActiveCMIS::Object

Inherits:
Object
  • Object
show all
Includes:
Internal::Caching
Defined in:
lib/active_cmis/object.rb

Direct Known Subclasses

Document, Folder, Policy, Relationship

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Internal::Caching

included

Constructor Details

#initialize(repository, data, parameters) ⇒ Object

Creates a representation of an CMIS Object in the repository

Not meant for direct use, use Repository#object_by_id instead. To create a new object use the new method on the type that you want the new object to have.


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/active_cmis/object.rb', line 21

def initialize(repository, data, parameters)
  @repository = repository
  @data = data

  @updated_attributes = []

  if @data.nil?
    # Creating a new type from scratch
    raise Error::Constraint.new("This type is not creatable") unless self.class.creatable
    @key = parameters["id"]
    @allowable_actions = {}
    @parent_folders = [] # start unlinked
  else
    @key = parameters["id"] || attribute('cmis:objectId')
    @self_link = data.xpath("at:link[@rel = 'self']/@href", NS::COMBINED).first
    @self_link = @self_link.text
  end
  @used_parameters = parameters
  # FIXME: decide? parameters to use?? always same ? or parameter with reload ?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *parameters) ⇒ Object

Via method missing attribute accessors and setters are provided for the CMIS attributes of an object. If attributes have a colon in their name you can access them by changing the colon in a dot

Examples:

Set an attribute named DateTimePropMV

my_object.DateTimePropMV = Time.now #=> "Wed Apr 07 14:34:19 0200 2010"

Read the attribute named DateTimePropMV

my_object.DateTimePropMV #=> "Wed Apr 07 14:34:19 0200 2010"

Get the cmis:name of an object

my_object.cmis.name #=> "My object 25"

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/active_cmis/object.rb', line 51

def method_missing(method, *parameters)
  string = method.to_s
  if string[-1] == ?=
    assignment = true
    string = string[0..-2]
  end
  if attributes.keys.include? string
    if assignment
      update(string => parameters.first)
    else
      attribute(string)
    end
  elsif self.class.attribute_prefixes.include? string
    if assignment
      raise NotImplementedError.new("Mass assignment not yet supported to prefix")
    else
      @attribute_prefix ||= {}
      @attribute_prefix[method] ||= AttributePrefix.new(self, string)
    end
  else
    super
  end
end

Class Attribute Details

.repositoryRepository (readonly)

The repository this type is defined in


522
523
524
# File 'lib/active_cmis/object.rb', line 522

def repository
  @repository
end

Instance Attribute Details

#keyString? (readonly)

The cmis:objectId of the object, or nil if the document does not yet exist in the repository


11
12
13
# File 'lib/active_cmis/object.rb', line 11

def key
  @key
end

#repositoryRepository (readonly)

The repository that contains this object


7
8
9
# File 'lib/active_cmis/object.rb', line 7

def repository
  @repository
end

#updated_attributesArray<String>

A list of all attributes that have changed locally


89
90
91
# File 'lib/active_cmis/object.rb', line 89

def updated_attributes
  @updated_attributes
end

Class Method Details

.attributes(inherited = false) ⇒ Hash{String => PropertyDefinition}

A list of all attributes defined on this object


550
551
552
# File 'lib/active_cmis/object.rb', line 550

def attributes(inherited = false)
  {}
end

.from_atom_entry(repository, data, parameters = {}) ⇒ Object


525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/active_cmis/object.rb', line 525

def from_atom_entry(repository, data, parameters = {})
  query = "cra:object/c:properties/c:propertyId[@propertyDefinitionId = '%s']/c:value"
  type_id = data.xpath(query % "cmis:objectTypeId", NS::COMBINED).text
  klass = repository.type_by_id(type_id)
  if klass
    if klass <= self
      klass.new(repository, data, parameters)
    else
      raise "You tried to do from_atom_entry on a type which is not a supertype of the type of the document you identified"
    end
  else
    raise "The object #{extract_property(data, "String", 'cmis:name')} has an unrecognized type #{type_id}"
  end
end

.from_parameters(repository, parameters) ⇒ Object


541
542
543
544
545
# File 'lib/active_cmis/object.rb', line 541

def from_parameters(repository, parameters)
  url = repository.object_by_id_url(parameters)
  data = repository.conn.get_atom_entry(url)
  from_atom_entry(repository, data, parameters)
end

.keyString

The key of the CMIS Type

Raises:

  • (NotImplementedError)

    for Object/Folder/Document/Policy/Relationship


557
558
559
# File 'lib/active_cmis/object.rb', line 557

def key
  raise NotImplementedError
end

Instance Method Details

#aclAcl?


207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/active_cmis/object.rb', line 207

def acl
  if repository.acls_readable? && allowable_actions["GetACL"]
    # FIXME: actual query should perhaps look at CMIS version before deciding which relation is applicable?
    query = "at:link[@rel = '#{Rel[repository.cmis_version][:acl]}']/@href"
    link = data.xpath(query, NS::COMBINED)
    if link.length == 1
      Acl.new(repository, self, link.first.text, data.xpath("cra:object/c:acl", NS::COMBINED))
    else
      raise "Expected exactly 1 acl for #{key}, got #{link.length}"
    end
  end
end

#allowable_actionsHash{String => Boolean,String}


165
166
167
168
169
170
171
172
173
174
175
# File 'lib/active_cmis/object.rb', line 165

def allowable_actions
  actions = {}
  _allowable_actions.children.map do |node|
    actions[node.name.sub("can", "")] = case t = node.text
                                        when "true", "1"; true
                                        when "false", "0"; false
                                        else t
                                        end
  end
  actions
end

#attribute(name) ⇒ Object

Attribute getter for the CMIS attributes of an object


93
94
95
# File 'lib/active_cmis/object.rb', line 93

def attribute(name)
  attributes[name]
end

#attributesHash{String => ::Object}

Attribute getter for the CMIS attributes of an object


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/active_cmis/object.rb', line 99

def attributes
  self.class.attributes.inject({}) do |hash, (key, attr)|
    if data.nil?
      if key == "cmis:objectTypeId"
        hash[key] = self.class.id
      else
        hash[key] = nil
      end
    else
      properties = data.xpath("cra:object/c:properties", NS::COMBINED)
      values = attr.extract_property(properties)
      hash[key] = if values.nil? || values.empty?
                    if attr.repeating
                      []
                    else
                      nil
                    end
                  elsif attr.repeating
                    values.map do |value|
                      attr.property_type.cmis2rb(value)
                    end
                  else
                    attr.property_type.cmis2rb(values.first)
                  end
    end
    hash
  end
end

#destroyObject

Tries to delete the object To delete all versions of a Document try #all_versions.delete

For policies this may just remove the policy from the policy group of a document, this depends on how you retrieved the policy. Be careful


296
297
298
# File 'lib/active_cmis/object.rb', line 296

def destroy
  conn.delete(self_link)
end

#file(folder) ⇒ void

This method returns an undefined value.

Files an object in a folder, if the repository supports multi-filing this will be an additional folder, else it will replace the previous folder

Raises:


244
245
246
247
248
249
250
251
252
# File 'lib/active_cmis/object.rb', line 244

def file(folder)
  raise Error::Constraint.new("Filing not supported for objects of type: #{self.class.id}") unless self.class.fileable
  @original_parent_folders ||= parent_folders.dup
  if repository.capabilities["MultiFiling"]
    @parent_folders << folder unless @parent_folders.detect {|f| f.id == folder.id }
  else
    @parent_folders = [folder]
  end
end

#idString?

The cmis:objectId of the object, or nil if the document does not yet exist in the repository


12
13
14
# File 'lib/active_cmis/object.rb', line 12

def key
  @key
end

#inspectString


76
77
78
# File 'lib/active_cmis/object.rb', line 76

def inspect
  "#<#{self.class.inspect} @key=#{key}>"
end

#nameString

Shorthand for the cmis:name of an object


82
83
84
# File 'lib/active_cmis/object.rb', line 82

def name
  attribute('cmis:name')
end

#parent_foldersArray<Folder>, Collection

Depending on the repository there can be more than 1 parent folder Always returns [] for relationships, policies may also return []


224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/active_cmis/object.rb', line 224

def parent_folders
  parent_feed = Internal::Utils.extract_links(data, 'up', 'application/atom+xml','type' => 'feed')
  unless parent_feed.empty?
    Collection.new(repository, parent_feed.first)
  else
    parent_entry = Internal::Utils.extract_links(data, 'up', 'application/atom+xml','type' => 'entry')
    unless parent_entry.empty?
      e = conn.get_atom_entry(parent_entry.first)
      [ActiveCMIS::Object.from_atom_entry(repository, e)]
    else
      []
    end
  end
end

#reloadvoid

This method returns an undefined value.

Empties the locally cached and updated values, updated data is asked from the server the next time a value is requested.

Raises:

  • (RuntimeError)

    if the object is not yet created on the server


281
282
283
284
285
286
287
288
289
# File 'lib/active_cmis/object.rb', line 281

def reload
  if @self_link.nil?
    raise "Can't reload unsaved object"
  else
    __reload
    @updated_attributes = []
    @original_parent_folders = nil
  end
end

#saveObject

Saves all changes to the object in the repository.

WARNING: because of the way CMIS is constructed the save operation is not atomic if updates happen to different aspects of the object (parent folders, attributes, content stream, acl), we can't work around this because CMIS lacks transactions


154
155
156
157
158
159
160
161
162
# File 'lib/active_cmis/object.rb', line 154

def save
  # FIXME: find a way to handle errors?
  # FIXME: what if multiple objects are created in the course of a save operation?
  result = self
  updated_aspects.each do |hash|
    result = result.send(hash[:message], *hash[:parameters])
  end
  result
end

#source_relationsCollection

Returns all relationships where this object is the source


194
195
196
197
198
199
200
201
202
203
# File 'lib/active_cmis/object.rb', line 194

def source_relations
  query = "at:link[@rel = '#{Rel[repository.cmis_version][:relationships]}']/@href"
  link = data.xpath(query, NS::COMBINED)
  if link.length == 1
    link = Internal::Utils.append_parameters(link.text, "relationshipDirection" => "source", "includeSubRelationshipTypes" => true)
    Collection.new(repository, link)
  else
    raise "Expected exactly 1 relationships link for #{key}, got #{link.length}, are you sure this is a document/folder?"
  end
end

#target_relationsCollection

Returns all relationships where this object is the target


180
181
182
183
184
185
186
187
188
189
# File 'lib/active_cmis/object.rb', line 180

def target_relations
  query = "at:link[@rel = '#{Rel[repository.cmis_version][:relationships]}']/@href"
  link = data.xpath(query, NS::COMBINED)
  if link.length == 1
    link = Internal::Utils.append_parameters(link.text, "relationshipDirection" => "target", "includeSubRelationshipTypes" => true)
    Collection.new(repository, link)
  else
    raise "Expected exactly 1 relationships link for #{key}, got #{link.length}, are you sure this is a document/folder?"
  end
end

#unfile(folder = nil) ⇒ void

This method returns an undefined value.

Removes an object from a given folder or all folders. If the repository does not support unfiling this method throws an error if the document would have no folders left after unfiling.

Raises:


258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/active_cmis/object.rb', line 258

def unfile(folder = nil)
  # Conundrum: should this throw exception if folder is not actually among parent_folders?
  raise Error::Constraint.new("Filing not supported for objects of type: #{self.class.id}") unless self.class.fileable
  @original_parent_folders ||= parent_folders.dup
  if repository.capabilities["UnFiling"]
    if folder.nil?
      @parent_folders = []
    else
      @parent_folders.delete_if {|f| f.id == folder.id}
    end
  else
    @parent_folders.delete_if {|f| f.id == folder.id}
    if @parent_folders.empty?
      @parent_folders = @original_parent_folders
      @original_parent_folders = nil
      raise Error::NotSupported.new("Unfiling not supported for this repository")
    end
  end
end

#update(attributes) ⇒ {String => ::Object}

Attribute setter for all CMIS attributes. This only updates this copy of the object. Use save to make these changes permanent and visible in the repositorhy. (use #reload after save on other instances of this document to reflect these changes)

Raises:

  • (Error::Constraint)

    if a readonly attribute is set

  • if a value can't be converted to the necessary type or falls outside the constraints


137
138
139
140
141
142
143
144
145
146
147
# File 'lib/active_cmis/object.rb', line 137

def update(attributes)
  attributes.each do |key, value|
    if (property = self.class.attributes[key.to_s]).nil?
      raise Error::Constraint.new("You are trying to add an unknown attribute (#{key})")
    else
      property.validate_ruby_value(value)
    end
  end
  self.updated_attributes.concat(attributes.keys).uniq!
  self.attributes.merge!(attributes)
end