cmis 0.1.0-java → 0.2.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +248 -13
- data/lib/cmis.rb +10 -0
- data/lib/cmis/version.rb +1 -1
- data/spec/cmis_spec.rb +57 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -42,6 +42,8 @@ server is http://cmis.alfresco.com/cmisatom.
|
|
42
42
|
|
43
43
|
Nuxeo also provides a demo server. More information can be found here: http://doc.nuxeo.com/display/NXDOC/CMIS+for+Nuxeo#CMISforNuxeo-Onlinedemo .
|
44
44
|
|
45
|
+
The documentation below is heavily based on the [OpenCMIS Client API Developer's Guide](http://chemistry.apache.org/java/developing/guide.html)
|
46
|
+
|
45
47
|
## Connecting to a CMIS repository
|
46
48
|
|
47
49
|
To be able to do anything useful on a CMIS repository, you must first find a repository, and create a session with it:
|
@@ -123,6 +125,8 @@ doc.download(file)
|
|
123
125
|
|
124
126
|
### Updating a document
|
125
127
|
|
128
|
+
Updating metadata:
|
129
|
+
|
126
130
|
```ruby
|
127
131
|
id = root.create_cmis_document("cmis_logo_original.png", "/Users/ricn/cmis_logo.png")
|
128
132
|
doc = @session.get_object(id)
|
@@ -133,6 +137,27 @@ doc = @session.get_object(id)
|
|
133
137
|
puts "New name: #{doc.name}"
|
134
138
|
```
|
135
139
|
|
140
|
+
Updating the actual content of a document (using check out / check in):
|
141
|
+
```ruby
|
142
|
+
root = @session.root_folder
|
143
|
+
doc = root.create_text_doc("my_file.txt", "content")
|
144
|
+
puts "Orginal version: " + doc.version_label
|
145
|
+
id = doc.check_out
|
146
|
+
working_copy = @session.get_object(id)
|
147
|
+
|
148
|
+
content_stream = CMIS::create_content_stream("/Users/ricn/updated_file.txt", @session)
|
149
|
+
|
150
|
+
# check_in parameters: boolean major, properties, contentStream, checkinComment
|
151
|
+
id = working_copy.check_in(false, nil, content_stream, "minor version")
|
152
|
+
doc = @session.get_object(id)
|
153
|
+
|
154
|
+
puts "New version: " + doc.version_label
|
155
|
+
```
|
156
|
+
|
157
|
+
Update the content of a document directly:
|
158
|
+
|
159
|
+
TODO
|
160
|
+
|
136
161
|
### Deleting a document
|
137
162
|
|
138
163
|
```ruby
|
@@ -147,8 +172,9 @@ doc.delete # Yay, that was easy!
|
|
147
172
|
folder1 = root.create_cmis_folder("folder1")
|
148
173
|
folder11 = folder1.create_cmis_folder("Folder11")
|
149
174
|
folder12 = folder1.create_cmis_folder("Folder12")
|
150
|
-
|
151
|
-
|
175
|
+
|
176
|
+
# Parameter: boolean allversions, UnfileObject unfile, boolean continueOnFailure
|
177
|
+
folder1.delete_tree(true, CMIS::UnfileObject::DELETE, true)
|
152
178
|
```
|
153
179
|
|
154
180
|
## Working with CMIS Properties
|
@@ -183,25 +209,234 @@ puts "Content stream file name: " + doc.content_stream_file_name
|
|
183
209
|
puts "Content stream mime type: " + doc.content_stream_mime_type
|
184
210
|
```
|
185
211
|
|
212
|
+
Get allowed actions for a document or folder:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
root = @session.root_folder
|
216
|
+
allowed_actions = root.allowed_actions
|
217
|
+
|
218
|
+
allowed_actions.each do |a|
|
219
|
+
puts a.to_s + " is an allowed action on " + root.name
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
A complete list of actions can be found here: http://chemistry.apache.org/java/0.8.0/maven/apidocs/org/apache/chemistry/opencmis/commons/enums/Action.html
|
224
|
+
|
186
225
|
## Working with CMIS Queries
|
187
226
|
|
188
227
|
```ruby
|
189
|
-
query = "SELECT * FROM cmis:
|
190
|
-
q = @session.query(query, false) #
|
228
|
+
query = "SELECT * FROM cmis:folder"
|
229
|
+
q = @session.query(query, false) # false means only latest versions
|
191
230
|
|
192
231
|
q.each do |result|
|
193
|
-
|
232
|
+
props = result.properties
|
233
|
+
props.each do |p|
|
234
|
+
disp_name = p.display_name
|
235
|
+
puts "Name: " + p.first_value if disp_name == "Name"
|
236
|
+
end
|
194
237
|
end
|
195
238
|
```
|
196
239
|
|
197
|
-
##
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
240
|
+
## Capabilities
|
241
|
+
CMIS repositories has different capabilities. Some are designed for a specific application domain and do not provide capabilities that are not needed for that domain. This means a repository implementation may not be able to support all the capabilities that the CMIS specification provides. To allow this, some capabilities can be optionally supported by a CMIS repository.
|
242
|
+
|
243
|
+
This is how you check the capabilites of the repository:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
rep_info = @session.repository_info
|
247
|
+
cap = rep_info.capabilities
|
248
|
+
|
249
|
+
puts "Navigation Capabilities"
|
250
|
+
puts "Get descendants supported: " + cap.is_get_descendants_supported.to_s
|
251
|
+
puts "Get folder tree supported: " + cap.is_get_folder_tree_supported.to_s
|
252
|
+
puts "=============================="
|
253
|
+
puts "Object Capabilities"
|
254
|
+
puts "Content Stream: " + cap.content_stream_updates_capability.value
|
255
|
+
puts "Changes: " + cap.changes_capability.value
|
256
|
+
puts "Renditions: " + cap.renditions_capability.value
|
257
|
+
puts "=============================="
|
258
|
+
puts "Filing Capabilities"
|
259
|
+
puts "Multifiling supported: " + cap.is_multifiling_supported.to_s
|
260
|
+
puts "Unfiling supported: " + cap.is_unfiling_supported.to_s
|
261
|
+
puts "Version specific filing supported: " + cap.is_version_specific_filing_supported.to_s
|
262
|
+
puts "=============================="
|
263
|
+
puts "Versioning Capabilities"
|
264
|
+
puts "PWC searchable: " + cap.is_pwc_searchable_supported.to_s
|
265
|
+
puts "PWC updatable: " + cap.is_pwc_updatable_supported.to_s
|
266
|
+
puts "All versions searchable: " + cap.is_all_versions_searchable_supported.to_s
|
267
|
+
puts "=============================="
|
268
|
+
puts "Query Capabilities"
|
269
|
+
puts "Query: " + cap.query_capability.value
|
270
|
+
puts "Join: " + cap.join_capability.value
|
271
|
+
puts "=============================="
|
272
|
+
puts "ACL Capabilities"
|
273
|
+
puts "ACL: " + cap.acl_capability.value
|
274
|
+
```
|
275
|
+
|
276
|
+
## Paging
|
277
|
+
When you retrieve the children of a CMIS object, the result set returned is of an arbitrary size. Retrieving a large result set synchronously could increase response times. To improve performance, you can use OpenCMIS's paging support to control the size of the result set retrieved from the repository. To use paging,
|
278
|
+
you must specify an [OperationContext](http://chemistry.apache.org/java/0.8.0/maven/apidocs/org/apache/chemistry/opencmis/client/api/OperationContext.html) when invoking children method call on the parent object. The OperationContext specifies the maximum number of items to retrieve in a page.
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
root = @session.root_folder
|
282
|
+
oc = CMIS::OperationContextImpl.new
|
283
|
+
oc.max_items_per_page = 3
|
284
|
+
|
285
|
+
# List all object in the root folder using paging
|
286
|
+
page1 = root.children(oc).skip_to(0).page.map(&:name)
|
287
|
+
page2 = root.children(oc).skip_to(1).page.map(&:name)
|
288
|
+
|
289
|
+
puts "Page 1:"
|
290
|
+
page1.each do |o|
|
291
|
+
puts o
|
292
|
+
end
|
293
|
+
|
294
|
+
puts "Page 2:"
|
295
|
+
page2.each do |o|
|
296
|
+
puts o
|
297
|
+
end
|
298
|
+
```
|
299
|
+
|
300
|
+
## Renditions
|
301
|
+
|
302
|
+
Some repositories provide a facility to retrieve alternative representations, or renditions of a document. An example is a preview thumbnail image of the content of a document, which could be presented to the user without needing to download the full document content. Another example is a PDF version of a word document.
|
303
|
+
|
304
|
+
A CMIS repository may have zero or more renditions for a document or folder in addition to the document's content stream.
|
305
|
+
At present the CMIS specification only allows renditions to be read. There are no facilities to create, update or delete renditions. Renditions are of a specific version of the document and may differ between document versions. Each rendition consists of a set of rendition attributes and a rendition stream. Rendition attributes are not object properties, and are not queryable. Renditions can be retrieved using the getRenditions service.
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
puts "Rendition support: " + @session.repository_info.capabilities.renditions_capability.to_s
|
309
|
+
|
310
|
+
id = @session.root_folder.create_text_doc("simple file.txt", "My content")
|
311
|
+
context = @session.create_operation_context
|
312
|
+
context.rendition_filter_string = "cmis:thumbnail"
|
313
|
+
doc = @session.get_object(id, context)
|
314
|
+
|
315
|
+
renditions = doc.renditions
|
316
|
+
|
317
|
+
puts "Renditions"
|
318
|
+
renditions.each do |r|
|
319
|
+
puts "Kind" + r.kind
|
320
|
+
puts "Mimetype: " + r.mime_type
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
Note: If you run the code above you might not get the renditions directly. Many repositories renders them asynchronously so it will take some time before you see them.
|
325
|
+
|
326
|
+
## Multi-filing
|
327
|
+
Multi-filing allows you to file a document object in more than one folder. This capability are optional, and your repository may not support them.
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
doc = @session.root_folder.create_text_doc("Multi-filing.txt", "Content")
|
331
|
+
folder = @session.root_folder.create_cmis_folder("multi-filing")
|
332
|
+
puts "Document parent count: " + doc.parents.size.to_s
|
333
|
+
doc.add_to_folder(folder, true) # true means all versions
|
334
|
+
puts "Document parent count: " + doc.parents.size.to_s
|
335
|
+
```
|
336
|
+
|
337
|
+
## Access control
|
338
|
+
|
339
|
+
Document or folder objects can have an access control list (ACL), which controls access to the object. An ACL is a list of Access Control Entries (ACEs). An ACE grants one or more permissions to a principal. A principal is a user, group, role, or something similar.
|
340
|
+
|
341
|
+
An ACE contains:
|
342
|
+
* One String with the principalid
|
343
|
+
* One or more Strings with the names of the permissions.
|
344
|
+
* A boolean flag direct, which is true if the ACE is directly assigned to the object, or false if the ACE is somehow derived
|
345
|
+
|
346
|
+
There are three basic permissions predefined by CMIS:
|
347
|
+
* cmis:read: permission for reading properties and reading content
|
348
|
+
* cmis:write: permission to write properties and the content of an object. A respository can defin the property to include cmis:read
|
349
|
+
* cmis:all: all the permissions of a repository. It includes all other basic CMIS permissions.
|
350
|
+
|
351
|
+
How these basic permissions are mapped to allowable actions is repository specific. You can discover the repository semantics for basic permissions with regard to allowable actions by examining the mappings parameter returned by session method repository_info. A repository can extend the basic permissions with its own repository-specific permissions. The folowing code snippet prints out the permissions available for a repository,
|
352
|
+
and the mappings of allowable actions to repository permissions:
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
acl_caps = @session.repository_info.acl_capabilities
|
356
|
+
|
357
|
+
puts "Propogation for this repository is " + acl_caps.acl_propagation.to_s
|
358
|
+
|
359
|
+
puts "Permissions for this repository are: "
|
360
|
+
acl_caps.permissions.each do |p|
|
361
|
+
puts "ID: " + p.id + " description: " + p.description
|
362
|
+
end
|
363
|
+
|
364
|
+
puts "Permission mappings for this repository are:"
|
365
|
+
repo_mapping = acl_caps.permission_mapping
|
366
|
+
|
367
|
+
repo_mapping.each do |key, value|
|
368
|
+
puts key + " maps to " + repo_mapping.get(key).permissions.to_s
|
369
|
+
end
|
370
|
+
```
|
371
|
+
|
372
|
+
You can specify how a repository should handle non-direct ACEs when you create an ACL, by specifying one of the following acl propogation values:
|
373
|
+
|
374
|
+
* OBJECTONLY: apply ACEs to a document or folder, without changing the ACLs of other objects
|
375
|
+
* PROPAGATE: apply ACEs to the given object and all inheriting objects
|
376
|
+
* REPOSITORYDETERMINED: allow the repository to use its own method of computing how changing an ACL for an object influences the non-direct ACEs of other objects.
|
377
|
+
|
378
|
+
The following example creates a folder object, and prints out the ACEs in the created folder's ACL. It then creates a new ACL with one ACE, adds it to the folder, retrieves the folder again, and prints out the ACEs now present in the folder's ACL:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
folder = @session.root_folder.create_cmis_folder("ACL test")
|
382
|
+
oc = CMIS::OperationContextImpl.new
|
383
|
+
oc.include_acls = true
|
384
|
+
folder = @session.get_object(folder, oc)
|
385
|
+
|
386
|
+
aces = folder.acl.aces
|
387
|
+
puts "Permissions before we add the guest user:"
|
388
|
+
aces.each do |a|
|
389
|
+
puts "Principal: " + a.principal.id
|
390
|
+
a.permissions.each do |p|
|
391
|
+
puts "Permission ID: " + p
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
permissions = ["cmis:read"]
|
396
|
+
principal = "guest" # Built in user in Alfresco
|
397
|
+
ace_in = @session.object_factory.create_ace(principal, permissions)
|
398
|
+
folder.add_acl([ace_in], CMIS::AclPropagation::REPOSITORYDETERMINED)
|
399
|
+
folder = @session.get_object(folder, oc)
|
400
|
+
|
401
|
+
aces = aces = folder.acl.aces
|
402
|
+
puts "Permissions after we added the guest user:"
|
403
|
+
aces.each do |a|
|
404
|
+
puts "Principal: " + a.principal.id
|
405
|
+
a.permissions.each do |p|
|
406
|
+
puts "Permission ID: " + p
|
407
|
+
end
|
408
|
+
end
|
409
|
+
```
|
410
|
+
|
411
|
+
## Relationships
|
412
|
+
|
413
|
+
A Relationship object is a relationship between a source object and a target object. The relationship has direction, from source to target. It is non-invasive, in that a relationship does not modify either the source or the target object. A relationship object has a type, like any other CMIS object. The source and target objects must be independent objects, such as a document, folder, or policy objects. A relationship object does not have a content-stream, and is not versionable, queryable, or fileable.
|
414
|
+
|
415
|
+
A repository does not have to support relationships. If it doesn't the relationship base object-type will not be returned by a "get types" call.
|
416
|
+
|
417
|
+
The following example creates a relationship between 2 objects. Alfresco supports relationships, but the base type cmis:relationship is not defined as creatable, so the example uses an existing type R:cmiscustom:assoc which is a creatable sub-type of cmis:relationship in Alfresco:
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
content_stream = CMIS::create_content_stream("/Users/ricn/source.txt", @session)
|
421
|
+
source_props = { CMIS::PropertyIds::OBJECT_TYPE_ID => "D:cmiscustom:document", CMIS::PropertyIds::NAME => "source.txt" }
|
422
|
+
source_doc = @session.root_folder.create_document(source_props, content_stream, CMIS::VersioningState::MAJOR)
|
423
|
+
|
424
|
+
content_stream = CMIS::create_content_stream("/Users/ricn/target.txt", @session)
|
425
|
+
target_props = { CMIS::PropertyIds::OBJECT_TYPE_ID => "D:cmiscustom:document", CMIS::PropertyIds::NAME => "target.txt" }
|
426
|
+
target_doc = @session.root_folder.create_document(target_props, content_stream, CMIS::VersioningState::MAJOR)
|
427
|
+
|
428
|
+
rel_props = {
|
429
|
+
"cmis:sourceId" => source_doc.id,
|
430
|
+
"cmis:targetId" => target_doc.id,
|
431
|
+
"cmis:objectTypeId" => "R:cmiscustom:assoc"
|
432
|
+
}
|
433
|
+
|
434
|
+
rel = @session.create_relationship(rel_props)
|
435
|
+
rel = @session.get_object(rel)
|
436
|
+
|
437
|
+
puts rel.source.id
|
438
|
+
puts rel.target.id
|
439
|
+
```
|
205
440
|
|
206
441
|
## Contributing
|
207
442
|
|
data/lib/cmis.rb
CHANGED
@@ -17,8 +17,10 @@ module CMIS
|
|
17
17
|
import org.apache.chemistry.opencmis.commons.SessionParameter
|
18
18
|
import org.apache.chemistry.opencmis.commons.PropertyIds
|
19
19
|
import org.apache.chemistry.opencmis.commons.impl.MimeTypes
|
20
|
+
import org.apache.chemistry.opencmis.commons.data.AclCapabilities
|
20
21
|
|
21
22
|
import org.apache.chemistry.opencmis.commons.enums.Action
|
23
|
+
import org.apache.chemistry.opencmis.commons.enums.AclPropagation
|
22
24
|
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId
|
23
25
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl
|
24
26
|
import org.apache.chemistry.opencmis.commons.enums.VersioningState
|
@@ -49,6 +51,10 @@ module CMIS
|
|
49
51
|
def download(destination_path)
|
50
52
|
FileUtils.download(self, destination_path)
|
51
53
|
end
|
54
|
+
|
55
|
+
def allowed_actions
|
56
|
+
self.allowable_actions.allowable_actions.to_a
|
57
|
+
end
|
52
58
|
end
|
53
59
|
|
54
60
|
class FolderImpl
|
@@ -72,6 +78,10 @@ module CMIS
|
|
72
78
|
def create_text_doc(name, content)
|
73
79
|
FileUtils.create_text_document(self.id, name, content, "cmis:document", CMIS::VersioningState::MAJOR, session)
|
74
80
|
end
|
81
|
+
|
82
|
+
def allowed_actions
|
83
|
+
self.allowable_actions.allowable_actions.to_a
|
84
|
+
end
|
75
85
|
end
|
76
86
|
|
77
87
|
def self.create_session(url, user, password, repo_id = nil)
|
data/lib/cmis/version.rb
CHANGED
data/spec/cmis_spec.rb
CHANGED
@@ -79,6 +79,16 @@ describe "CMIS" do
|
|
79
79
|
allowed_actions.to_a.should include(CMIS::Action::CAN_GET_PROPERTIES)
|
80
80
|
end
|
81
81
|
|
82
|
+
it "should retrieve allowed actions for an object using convenient method" do
|
83
|
+
# for a folder
|
84
|
+
allowed_actions = @session.root_folder.allowed_actions
|
85
|
+
allowed_actions.should include(CMIS::Action::CAN_GET_PROPERTIES)
|
86
|
+
|
87
|
+
doc = create_random_doc(@test_folder)
|
88
|
+
allowed_actions = doc.allowed_actions
|
89
|
+
allowed_actions.should include(CMIS::Action::CAN_GET_PROPERTIES)
|
90
|
+
end
|
91
|
+
|
82
92
|
it "should be possible to check if a document is versionable" do
|
83
93
|
doc = create_random_doc(@test_folder)
|
84
94
|
doc.type.is_versionable.should == true
|
@@ -158,6 +168,13 @@ describe "CMIS" do
|
|
158
168
|
p.value.should == content.size if p.definition.id == "cmis:contentStreamLength"
|
159
169
|
end
|
160
170
|
end
|
171
|
+
|
172
|
+
it "should be possible to use multi-filing" do
|
173
|
+
doc = create_random_doc(@test_folder)
|
174
|
+
folder = @test_folder.create_cmis_folder("multi-filing")
|
175
|
+
doc.add_to_folder(folder, true) # true means all versions
|
176
|
+
doc.parents.size.should == 2
|
177
|
+
end
|
161
178
|
end
|
162
179
|
|
163
180
|
describe "Updating objects" do
|
@@ -246,6 +263,46 @@ describe "CMIS" do
|
|
246
263
|
@sub_folder.children.map(&:name).size.should == 5
|
247
264
|
end
|
248
265
|
end
|
266
|
+
|
267
|
+
describe "Access control" do
|
268
|
+
it "should be possible to add acls to an object" do
|
269
|
+
folder = @test_folder.create_cmis_folder("ACL test")
|
270
|
+
oc = CMIS::OperationContextImpl.new
|
271
|
+
oc.include_acls = true
|
272
|
+
folder = @session.get_object(folder, oc)
|
273
|
+
original_acl_size = folder.acl.aces.size
|
274
|
+
|
275
|
+
permissions = ["cmis:read"]
|
276
|
+
principal = "guest"
|
277
|
+
ace_in = @session.object_factory.create_ace(principal, permissions)
|
278
|
+
folder.add_acl([ace_in], CMIS::AclPropagation::REPOSITORYDETERMINED)
|
279
|
+
folder = @session.get_object(folder, oc)
|
280
|
+
folder.acl.aces.size.should == original_acl_size + 2
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
describe "Relationships" do
|
285
|
+
|
286
|
+
it "should be possible to create relationships" do
|
287
|
+
content_stream = CMIS::create_content_stream(file_path("text_file.txt"), @session)
|
288
|
+
source_props = { CMIS::PropertyIds::OBJECT_TYPE_ID => "D:cmiscustom:document", CMIS::PropertyIds::NAME => "source.txt" }
|
289
|
+
source_doc = @test_folder.create_document(source_props, content_stream, CMIS::VersioningState::MAJOR)
|
290
|
+
|
291
|
+
target_props = { CMIS::PropertyIds::OBJECT_TYPE_ID => "D:cmiscustom:document", CMIS::PropertyIds::NAME => "target.txt" }
|
292
|
+
target_doc = @test_folder.create_document(target_props, content_stream, CMIS::VersioningState::MAJOR)
|
293
|
+
|
294
|
+
rel_props = {
|
295
|
+
"cmis:sourceId" => source_doc.id,
|
296
|
+
"cmis:targetId" => target_doc.id,
|
297
|
+
"cmis:objectTypeId" => "R:cmiscustom:assoc"
|
298
|
+
}
|
299
|
+
|
300
|
+
rel = @session.create_relationship(rel_props)
|
301
|
+
rel = @session.get_object(rel)
|
302
|
+
rel.source.id.should == source_doc.id
|
303
|
+
rel.target.id.should == target_doc.id
|
304
|
+
end
|
305
|
+
end
|
249
306
|
end
|
250
307
|
|
251
308
|
describe "Local Alfresco" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cmis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: java
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|