Title: OpenCMIS Client API Developer's Guide
This guide is for software engineers who want to use the Apache Chemistry OpenCMIS client to access CMIS-compliant content repositories from Java code. The examples included in the guide assume the code is running in a standalone Java application, but the OpenCMIS client can be used by on any Java platform, such as Web applications and phone apps.
The guide is divided into 5 parts :-
The guide assumes you are using the Eclipse IDE for code development, and have Maven and SVN installed.
This section gives a high-level overview of the OpenCMIS library, and introduces a supplied “Hello World” sample to get an OpenCMIS client up and running against a repository and accessing CMIS objects.
CMIS (Content Management Interoperability Services) is a vendor-neutral OASIS Web services interface specification that enables interoperability between Enterprise Content Management (ECM) systems. CMIS allows rich information to be shared across Internet protocols in vendor-neutral formats, among document systems, publishers and repositories, in a single enterprise and between companies.
OpenCMIS is a collection of Java libraries frameworks and tools based on the CMIS specification. It is available under the open source Apache License from the Apache Chemistry project. The OpenCMIS Client API is a Java client-side library that implements the CMIS specification. You can see the Javadoc for the OpenCMIS Client API here. CMIS defines a domain model that describes a vendor-neutral ECM repository and its contents, a set of services to work with the repository and its content, and a set of bindings which a client uses to access the services.
CMIS defines a domain model. A client will access a CMIS service endpoint described by a URL. A service endpoint must have at least one repository. A repository is a data store and contains content. Each item of content is an object such as a folder, or a document. A repository is identified by its ID, and has a set of capabilities which describe what optional CMIS functionality the repository supports.
An object has an ID, has a type, and has set of properties for that type. There are four base types for a CMIS object :-
There can be other object types in the repository, defined in a repository as subtypes of these base types. CMIS services are provided for the discovery of object-types that are defined in a repository, but for the moment folders and documents are the main objects you will be concerned with for the first 2 parts of this document.
The CMIS specification describes a set of services that act on repositories. The OpenCMIS client API uses these services, but by using a client binding, presents the user of the API a simple set of classes rather than the services described by CMIS. If you want to access a CMIS repository at the service level, you can do that by using the OpenCMIS client bindings layer directly.
The CMIS services are:-
How does an OpenCMIS client communicate over the wire with a CMIS service endpoint? A CMIS repository can communicate over two protocols, SOAP and AtomPub, and provide two corresponding bindings, a web services binding, and an AtomPub binding. A CMIS service endpoint will provide a URL for both types of binding, and in OpenCMIS you specify the binding type when calling the getRepositories()
method on the SessionFactory
object.
The workbench is a GUI repository browser that lets you quickly view the contents of a repository, create documents and folders, look at metadata, and perform queries. It's really useful for debugging your applications, and for quickly testing out queries before you commit them to code. You can download the latest version here. The workbench is a Java Swing application, and comes with both Windows and UNIX starter scripts.
You can download, install, and test the Apache Chemistry client code using the Hello World
sample. On a command line, type the following command :-
svn co https://svn.apache.org/repos/asf/chemistry/opencmis/trunk/chemistry-opencmis-samples/chemistry-opencmis-hello
You can then build the sample and run the org.apache.chemistry.opencmis.doc.Hello
sample using maven, by typing the following command
mvn clean install
You will see some output towards the end of the build that looks similar to the following lines:-
org.apache.chemistry.opencmis.doc.Hello started Found repository: InMemory Repository Got a connection to repository: InMemory Repository, with id: A1 Found the following objects in the root folder:- MasterClass My_Document-0-0 My_Document-0-1 My_Document-0-2 My_Folder-0-0 My_Folder-0-1 org.apache.chemistry.opencmis.doc.Hello ended
The Hello World sample has connected to a public CMIS repository and retrieved all the objects in the root folder of that repository. The simplest way to view the sample code is to use eclipse
Add the maven repository to your eclipse workspace classpath with the following command. You only need to perform this step once.
mvn eclipse:configure-workspace -Declipse.workspace=<path-to-your-eclipse-workspace>
Create an Eclipse project from the code, and import it into your eclipse. Type the following command :-
mvn eclipse:eclipse
Import the project into eclipse:-
File->Import
and choose General->Existing projects into workspace
in the Import wizard.Next
and browse to the folder you checked out the project to.chemistry-opencmis-hello
project. The project will be imported into your workspace.You can run the main
method in the Hello
class as a Java application.
Once you have successfully run the Hello World
sample, you can start to get familiar with the core parts of the OpenCMIS API in the next section.
This section introduces the most commonly used parts of the OpenCMIS client API. The code snippets are taken from the GettingStarted sample class supplied with Apache chemistry.
On a command line, type the following command :-
svn co https://svn.apache.org/repos/asf/chemistry/opencmis/trunk/chemistry-opencmis-samples/chemistry-opencmis-getting-started
You can then build the sample and run the org.apache.chemistry.opencmis.doc.GettingStarted
sample using maven, by typing the following command
mvn clean install
This will pull in any dependencies for the sample, build it, and run it against a public repository.
The simplest way to work with the getting started sample is to use Maven to create an eclipse project from the code, and import it into your eclipse. To create the eclipse project type the following command :-
mvn eclipse:eclipse
To import the project into eclipse:-
File->Import
and choose General->Existing projects into workspace
in the Import wizard.Next
and browse to the folder you checked out the project to.chemistry-opencmis-gettingstarted
project. The project will be imported into your workspace.You can run the main
method in the GettingStarted
class as a Java application.
To do any work on a repository, you must first find your repository, and create a session with it. To do that, you create a SessionFactory, and set up a parameter map that describes the credentials and connection settings for the target URL. This code snippet uses the publicly accessible OpenCMIS InMemory Repository which you will be sharing with everyone. There are other publicly accessible CMIS repositories which you can try your code out on, or you can change the code to use a repository local to you. Note that:-
This repository does not require credentials, but the code snippet sets a user id and password to show how this is done
The code uses the ATOMPUB
binding for connection.
SessionFactory sessionFactory = SessionFactoryImpl.newInstance(); Map<String, String> parameter = new HashMap<String, String>(); parameter.put(SessionParameter.USER, "admin"); parameter.put(SessionParameter.PASSWORD, "admin"); parameter.put(SessionParameter.ATOMPUB_URL, "http://repo.opencmis.org/inmemory/atom/"); parameter.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
In a production environment, the client code will probably know the ID of the repository that it wants to connect to. The following code snippet shows how to connect to a repository using its ID.
parameter.put(SessionParameter.REPOSITORY_ID, "A1"); Session session = sessionFactory.createSession(parameter);
That's it. Any work you want to do against the repository uses this session.
The CMIS specification allows a CMIS service endpoint to advertise one or more repositories, so this code snippet retrieves a list of repositories. In this case there the code chooses the first repository in list:-
List<Repository> repositories = sessionFactory.getRepositories(parameter); for (Repository r : repositories) { System.out.println("Found repository: " + r.getName()); }
Now you can create your session with the first and only repository:-
Repository repository = repositories.get(0); Session session = repository.createSession(); System.out.println("Got a connection to repository: " + repository.getName() + ", with id: " + repository.getId());
That's it. Any work you want to do against the repository uses this session.
Of the four base objects described by the CMIS domain model, the most commonly used are the folder and the document.
A folder is a container for other objects. Folders exist in a hierarchical structure as you would expect, but a CMIS repository can optionally store objects in multiple folders, and an object can exist but not be contained in a folder. CMIS uses the terminology multifiling and unfiling for these capabilities.
The Document is the only object that can have content, described by a content stream, and has properties such as the author and modification date.
Now you have a session you can start examining the repository. All CMIS repositories have a root folder, which is the single top level folder in the hierarchy. To list the objects in the root folder of your repository you use the getRootFolder()
method on the session.
Folder root = session.getRootFolder(); ItemIterable<CmisObject> children = root.getChildren(); System.out.println("Found the following objects in the root folder:-"); for (CmisObject o : children) { System.out.println(o.getName() + " which is of type " + o.getType().getDisplayName()); }
To create a folder, you use the createFolder()
method on the parent folder. As with most CMIS methods, it takes a map of properties which define the Object to be created. In this code snippet a folder with the name ADGNewFolder
is created in the root folder:-
System.out.println("Creating 'ADGNewFolder' in the root folder"); Map<String, String> newFolderProps = new HashMap<String, String>(); newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); newFolderProps.put(PropertyIds.NAME, "ADGNewFolder"); Folder newFolder = root.createFolder(newFolderProps); // Did it work? ItemIterable<CmisObject> children = root.getChildren(); System.out.println("Now finding the following objects in the root folder:-"); for (CmisObject o : children) { System.out.println(o.getName()); }
Note that the properties set for in the above code snippet are the minimum set of name and object type id. There are many other properties you can set on creation, depending on the object type.
Document objects describe entities in the CMIS repository. A document object with content contains a content stream that is the actual file contents, and a mimetype for the content stream. So, to create a document Object, first you need a content stream
final String textFileName = "test.txt"; System.out.println("creating a simple text file, " + textFileName); String mimetype = "text/plain; charset=UTF-8"; String content = "This is some test content."; String filename = textFileName; byte[] buf = content.getBytes("UTF-8"); ByteArrayInputStream input = new ByteArrayInputStream(buf); ContentStream contentStream = session.getObjectFactory().createContentStream(filename, buf.length, mimetype, input);
To add a document with some content to the repository, you use the createDocument()
method on the parent folder, and provide the content stream and a map of properties which define the object to be created.
Map<String, Object> properties = new HashMap<String, Object>(); properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); properties.put(PropertyIds.NAME, filename); Document doc = newFolder.createDocument(properties, contentStream, VersioningState.MAJOR); System.out.println("Document ID: " + doc.getId());
Note the following points :-
createDocument()
method returns the newly created document object.You can retrieve a CMIS object by path, or by using the object ID.
The following code snippet retrieves the newly created object using the object ID:-
// Get the contents of the file Document doc = (Document) session.getObject(id); ContentStream contentStream = doc.getContentStream(); // returns null if the document has no content if (contentStream != null) { String content = getContentAsString(contentStream); System.out.println("Contents of " + filename + " are: " + content); } else { System.out.println("No content."); } ... /** * Helper method to get the contents of a stream * * @param stream * @return * @throws IOException */ private static String getContentAsString(ContentStream stream) throws IOException { StringBuilder sb = new StringBuilder(); Reader reader = new InputStreamReader(stream.getStream(), "UTF-8"); try { final char[] buffer = new char[4 * 1024]; int b; while (true) { b = reader.read(buffer, 0, buffer.length); if (b > 0) { sb.append(buffer, 0, b); } else if (b == -1) { break; } } } finally { reader.close(); } return sb.toString(); }
The following code snippet retrieves the newly created object using a path:-
String path = newFolder.getPath() + "/" + textFileName; System.out.println("Getting object by path " + path); Document doc = (Document) session.getObjectByPath(path); ContentStream contentStream = doc.getContentStream(); if (contentStream != null) { String content = getContentAsString(contentStream); }
Note the following points :-
/
.Document
class does not have a getPath()
method, since multifiling means a document can have more than one parent folder. It does implement the getPaths()
method from the FileableCmisObject
interface. This returns a list of the current paths to the document object.You can update the properties of a document object, and you update the contents of the document by overwriting the document's content stream with a new content stream. Note that the object ID returned when updating a document is not guaranteed to remain the same, some repository implementations return the same object ID, and some may not.
The following code snippet updates the name property of the test2.txt
document:-
Document doc2 = (Document) session.getObject(id2); System.out.println("renaming " + doc2.getName() + " to test3.txt"); Map<String, String> properties = new HashMap<String, Object>(); properties.put(PropertyIds.NAME, "test3.txt"); doc2.updateProperties(properties, true); System.out.println("renamed to " + doc2.getName());
You can update the contents of a document by setting a new ContentStream using the setContentStream()
method. You must set the overwrite flag if the document has existing content, otherwise an exception will be thrown. The following code snippet updated the content of the test3.txt
document:-
if (!session.getRepositoryInfo().getCapabilities().getContentStreamUpdatesCapability() .equals(CapabilityContentStreamUpdates.ANYTIME)) { System.out.println("update without checkout not supported in this repository"); } else { System.out.println("updating content stream"); String mimetype = "text/plain; charset=UTF-8"; String content = "This is some updated test content for our renamed second document."; byte[] buf = content.getBytes("UTF-8"); input = new ByteArrayInputStream(buf); ContentStream contentStream = session.getObjectFactory().createContentStream("test3.txt", buf.length, mimetype, input); doc2.setContentStream(contentStream, true); // did it work? contentStream = doc2.getContentStream(); if (contentStream != null) { content = getContentAsString(contentStream); System.out.println("Contents of " + doc2.getName() + " are: " + content); } else { System.out.println("Something went wrong."); } }
Note that this code snippet will not work for repositories that require a checkout to set a content stream. For more information on checkout see the versioning section in this guide.
You can delete any CMIS object using the delete
method. The following code snippet lists the contents of the test folder before and after a delete of one of the two documents in the folder.
ItemIterable<CmisObject> children = newFolder.getChildren(); System.out.println("Now finding the following objects in our folder:-"); for (CmisObject o : children) { System.out.println(o.getName()); } System.out.println("Deleting document " + doc2.getName()); doc2.delete(true); System.out.println("Now finding the following objects in our folder:-"); for (CmisObject o : children) { System.out.println(o.getName()); }
Note that the allVersions
flag is set to true
. If you set it to false then the delete will be of this version only.
You can delete a folder and all its contents using just one method. The following code snippet creates a new folder tree and then deletes it, ignoring any failures to delete individual objects, and deleting all versions of any documents in the tree.
System.out.println("Creating 'ADGFolder1' in the root folder"); Map<String, String> newFolderProps = new HashMap<String, String>(); newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); newFolderProps.put(PropertyIds.NAME, "ADGFolder1"); Folder folder1 = root.createFolder(newFolderProps); newFolderProps.put(PropertyIds.NAME, "ADGFolder11"); Folder folder11 = folder1.createFolder(newFolderProps); newFolderProps.put(PropertyIds.NAME, "ADGFolder12"); Folder folder12 = folder1.createFolder(newFolderProps); System.out.println("delete the 'ADGFolder1' tree"); folder1.deleteTree(true, UnfileObject.DELETE, true);
Note that with the continueOnFailure parameter set to true, folders and documents are deleted individually. If a document or folder cannot be deleted, the method moves to the next document or folder in the list. When the method completes, it returns a list of the document IDs and folder IDs that were not deleted.
With the continueOnFailure parameter set to false, all of the folders and documents can be deleted in a single batch, which, depending on the repository design, may improve performance. If a document or folder cannot be deleted, an exception is raised. Some repository implementations will attempt the delete transactionally, so if it fails, no objects are deleted. In other repositories a failed delete may have deleted some, but not all, objects in the tree.
CMIS provides several concepts for navigating through the tree of objects in the repository. For any folder, you can get its children, get its descendants, and get the folder tree. The getting started sample application sets up a tree in the repository that looks like this:-
The following snippet from the getting started sample will print out the children of a folder :-
ItemIterable<CmisObject> children = folder1.getChildren(); System.out.println("Children of " + folder1.getName() + ":-"); for (CmisObject o : children) { System.out.println(o.getName()); }
Note that the children of a Folder object can be of any type supported by the repository. Running the sample produces the following output :-
Children of ADGFolder1:- ADGFolder11 ADGFolder12 ADGFile1f1
If your repository supports getDescendants
, you can navigate the whole subtree of descendants of any folder. This code snippet uses a recursive method to print out the entire folder subtree:-
if (!session.getRepositoryInfo().getCapabilities().isGetDescendantsSupported()) { System.out.println("getDescendants not supported in this repository"); } else { System.out.println("Descendants of " + folder1.getName() + ":-"); for (Tree<FileableCmisObject> t : folder1.getDescendants(-1)) { printTree(t); } } ... private static void printTree(Tree<FileableCmisObject> tree) { System.out.println("Descendant " + tree.getItem().getName()); for (Tree<FileableCmisObject> t : tree.getChildren()) { printTree(t); } }
Note the following points:-
Tree
are themselves of type Tree
.getDescendants
is the depth or level of the tree that you want to go to. A depth of 1 will give you just the root of the tree. A depth of -1 will return the full tree.Running the sample produces the following output :-
Descendants of ADGFolder1:- Descendant ADGFolder11 Descendant ADGFolder111 Descendant ADGFolder112 Descendant ADGFile11f1 Descendant ADGFile11f2 Descendant ADGFolder12 Descendant ADGFolder121 Descendant ADGFile121f1 Descendant ADGFolder122 Descendant ADGFile1f1
If your repository supports getFolderTree
, you can navigate the whole subtree of folders from any folder in the repository. This code snippet uses a recursive method to print out the entire folder subtree:-
if (!session.getRepositoryInfo().getCapabilities().isGetFolderTreeSupported()) { System.out.println("getFolderTree not supported in this repository"); } else { System.out.println("Foldertree for " + folder1.getName() + ":-"); for (Tree<FileableCmisObject> t : folder1.getFolderTree(-1)) { printFolderTree(t); } } ... private static void printFolderTree(Tree<FileableCmisObject> tree) { System.out.println("Folder " + tree.getItem().getName()); for (Tree<FileableCmisObject> t : tree.getChildren()) { printFolderTree(t); } }
Running the sample produces the following output :-
Foldertree for ADGFolder1:- Folder ADGFolder11 Folder ADGFolder111 Folder ADGFolder112 Folder ADGFolder12 Folder ADGFolder121 Folder ADGFolder122
Note that getDescendants
and getFolderTree
are similar, but getDescendants
finds objects of any fileable type, getFolderTree
finds only folder objects.
CMIS Objects have an object type. There are four base types, two of which must be supported by a repository
cmis:document
cmis:folder
The other two base types may be supported by a repository :-
cmis:relationship
cmis:policy
Any other types in a repository must be defined as sub-types of a base type. An object type definition includes a set of attributes (for example Fileable, Queryable) and a property schema that will apply to Objects of that type.
Every object type has a parent type, except a base type.
CMIS 1.0 does not allow you to create types, but it does have allow you to navigate the object type tree, and look at type definitions.
So, we can retrieve information in the type definition for our doc
object :-
System.out.println("Getting type definition for doc type"); ObjectType objectType = session.getTypeDefinition(doc.getType().getId()); System.out.println("isBaseType() returns " + (objectType.isBaseType() ? "true" : "false")); ObjectType baseType = objectType.getBaseType(); if (baseType == null) { System.out.println("getBaseType() returns null"); } else { System.out.println("getBaseType() returns " + baseType.getDisplayName()); } ObjectType parentType = objectType.getParentType(); if (parentType == null) { System.out.println("getParentType() returns null"); } else { System.out.println("getParentType() returns " + parentType.getDisplayName()); } System.out.println("Listing child types of " + objectType.getDisplayName()); for (ObjectType o : objectType.getChildren()) { System.out.println("\t" + o.getDisplayName()); } System.out.println("Getting immediate descendant types of " + objectType.getDisplayName()); for (Tree<ObjectType> o : objectType.getDescendants(1)) { System.out.println("\t" + o.getItem().getDisplayName()); }
The following lines are typical output from this code:-
Getting type definition for doc type isBaseType() returns true getBaseType() returns null getParentType() returns null Listing child types of CMIS Document My Type 1 Level 1 My Type 2 Level 1 Complex type with properties, Level 1 Document type with properties, Level 1 VersionedType Getting immediate descendant types of CMIS Document My Type 1 Level 1 My Type 2 Level 1 Complex type with properties, Level 1 Document type with properties, Level 1 VersionedType
You will see that, not surprisingly, doc has a type of Document
, which is a base type, so has no parent. Depending on the repository you are running this code against, there may be a number of children of the Document
type, which are types derived from this base type.
All objects of the CMIS base types implement the interface CmisObjectProperties
which provides accessors to CMIS object properties. The following code snippet uses the getProperties()
method to print out all the available properties on a newly created Document object, doc.
List<Property<?>> props = doc.getProperties(); for (Property<?> p : props) { System.out.println(p.getDefinition().getDisplayName() + "=" + p.getValuesAsString()); }
The output should look something like this :-
Is Latest Major Version=[false] Content Stream Length=[26] Content Stream Id=[store://2011/5/20/12/54/0d69ee48-ef03-4715-ae23-b84b4342315c.bin] Version Series Checked Out By=[] Object Type Id=[cmis:document] Version Series Checked Out Id=[] Name=[test.txt] Content Stream MIME Type=[text/plain; charset=UTF-8] Version series id=[workspace://SpacesStore/3cd2cbbf-1a00-4653-8ea8-1e91515c6092] Creation Date=[Fri May 20 12:54:47 BST 2011] Change token=[] Version Label=[0.0] Is Latest Version=[true] Is Version Series Checked Out=[false] Last Modified By=[admin] Created by=[admin] Checkin Comment=[] Object Id=[workspace://SpacesStore/3cd2cbbf-1a00-4653-8ea8-1e91515c6092] Is Immutable=[false] Is Major Version=[false] Base Type Id=[cmis:document] Content Stream Filename=[test.txt] Last Modified Date=[Fri May 20 12:54:47 BST 2011]
Each object type has a known set of properties, and you can retrieve these explicitly. For example, the document type has a set of properties described by the DocumentProperties
interface, and you can use the methods on this interface to retrieve the value a property:-
System.out.println("VersionLabel property on " + doc.getName() + " is " + doc.getVersionLabel()); System.out.println("Is this the latest version of " + doc.getName() + " ?: " + (doc.isLatestVersion() ? "yes" : "no"));
Note that properties can be of simple types, Boolean
, String, long, or more complex types, such as GregorianCalendar
. The following code snippet prints out the CreationDate
property:-
GregorianCalendar calendar = doc.getCreationDate(); String DATE_FORMAT = "yyyyMMdd"; SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); System.out.println("Creation date of " + doc.getName() + " is " + sdf.format(calendar.getTime()));
You can get the value of any property using the getPropertyValue()
method. This is particularly useful for retrieving properties that are not part of the CMIS specification. The following code snippet takes a property, finds the Id from the property definition, and gets the property's value using the queryName
:-
List<Property<?>> props = doc.getProperties(); Property<?> someProperty = props.get(0); System.out.println(someProperty.getDisplayName() + " property on " + doc.getName() + " (by getPropertValue()) is " + doc.getPropertyValue(someProperty.getId()));
Given a query result, you can always use the queryName
to get a property value. The following code snippet performs a query and gets a property's value using the queryName
:-
String query = "SELECT * FROM cmis:document WHERE cmis:name = 'test.txt'"; ItemIterable<QueryResult> queryResult = session.query(query, false); for (QueryResult item : queryResult) { System.out.println("property cmis:createdBy on test.txt is " + item.getPropertyByQueryName("cmis:createdBy").getFirstValue()); }
CMIS provides a type-based query service for discovering objects that match specified criteria. To allow this, CMIS provides a relational view of the CMIS data model. Through this relational view, queries may be performed using a simplified SQL SELECT statement. The query language is based on a subset of the SQL-92 grammar, with some extensions to enhance filtering capability for the CMIS data model. You can see an explanation of relational view and the full query language grammar in the CMIS Specification.
Let's look at a query that will find all document objects that have a name that starts with the string test
:-
SELECT * FROM cmis:document WHERE cmis:name LIKE 'test%'
This query specifies the virtual table cmis:document
in the FROM clause. In the CMIS relational view, a virtual table is implicitly defined for each queryable object type defined in the repository. In each virtual table, virtual columns are implicitly defined for:-
In this case we have selected all columns from the cmis:document virtual table, by specifying the asterisk.
The statement contains a WHERE clause that specifies constraints that must be satisfied. In this case the value in the virtual column cmis:name
in the table cmis:document
must start with the string test
. This is specified using a LIKE predicate.
You can run the query against any repository using the OpenCMIS workbench.
Let's look at the code required for this query when using the OpenCMIS client library:-
String query = "SELECT * FROM cmis:document WHERE cmis:name LIKE 'test%'"; ItemIterable<QueryResult> q = session.query(query, false); // Did it work? System.out.println("***results from query " + query); int i = 1; for (QueryResult qr : q) { System.out.println("--------------------------------------------\n" + i + " , " + qr.getPropertyByQueryName("cmis:objectTypeId").getFirstValue() + " , " + qr.getPropertyByQueryName("cmis:name").getFirstValue() + " , " + qr.getPropertyByQueryName("cmis:createdBy").getFirstValue() + " , " + qr.getPropertyByQueryName("cmis:objectId").getFirstValue() + " , " + qr.getPropertyByQueryName("cmis:contentStreamFileName").getFirstValue() + " , " + qr.getPropertyByQueryName("cmis:contentStreamMimeType").getFirstValue() + " , " + qr.getPropertyByQueryName("cmis:contentStreamLength").getFirstValue()); i++; }
So, you make query on the session object, and the results set is returned. You can iterate through each row of the results set to see the search results. Note that each row of the results set includes values for every column in the table in this particular query. This code snippet just displays a subset of them.
If something goes wrong in an OpenCMIS method, an exception will be thrown. All OpenCMIS exceptions extend CmisBaseException
which is a Java runtime exception. Because all exceptions are runtime, you do not have to catch or specify the exceptions in your own code. It does make sense to explicitly catch them in robust code. This code snippet forces an CmisInvalidArgumentException
by supplying a null content stream.
try { doc2.setContentStream(null, false); } catch (CmisInvalidArgumentException e) { System.err.println("caught an " + e.getClass().getName() + " exception with message " + e.getMessage()); }
If you are using the ATOMPUB binding, CmisBaseException
provides a getErrorContent()
method which returns the content of the error page returned from the server, if there is one. This can be extremely useful debugging, as the server side is normally able to provide far more information that the client.
In the following code snippet, a CmisObjectNotFoundException
exception is forced by trying to create a document with an invalid type. The catch block prints the server's error page and the client stack trace.
Folder root = session.getRootFolder(); HashMap<String, String> newFolderProps = new HashMap<String, String>(); newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); newFolderProps.put(PropertyIds.NAME, "ADGFolderTest"); Folder folderTest = root.createFolder(newFolderProps); Map<String, String> newFileProps = new HashMap<String, String>(); newFileProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:badtype"); newFileProps.put(PropertyIds.NAME, "ADGFileTest"); try { folderTest.createDocument(newFileProps, null, VersioningState.MAJOR); } catch (CmisObjectNotFoundException e) { System.err.println("server error page :\n" + e.getErrorContent()); System.err.println("\nClient stack trace :\n"); e1.printStackTrace(); }
The output from this snippet looks like this:-
server error page : Apache Chemistry OpenCMIS - objectNotFound error HTTP Status 404 - objectNotFound [...] Client stack trace : org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException: Not Found [...] at org.apache.chemistry.opencmis.client.runtime.SessionImpl.createDocument(SessionImpl.java:651) at org.apache.chemistry.opencmis.client.runtime.FolderImpl.createDocument(FolderImpl.java:87) at org.apache.chemistry.opencmis.client.runtime.FolderImpl.createDocument(FolderImpl.java:409) at org.apache.chemistry.opencmis.doc.TestingException.main(TestingException.java:104)
The amount of metadata and associated information retrieved during an OpenCMIS operation could be large, so certain OpenCMIS methods return a sensible subset of the information by default, and provide additional methods that take an OperationContext
. An OperationContext allows you to tune the amount of information returned by setting property filters, renditions filters, or by setting flags to include path segments, ACLs, Allowable Actions, Policies, and Relationships. The OperationContext is also used to control paging and caching in an operation.
Caching of objects is turned on by default in OpenCMIS, but if you want to guarantee that getObject()
or getObjectByPath()
will not return stale objects, you can turn it off using an OperationContext
:-
OperationContext operationContext = session.createOperationContext(); operationContext.setCacheEnabled(false); CmisObject object = session.getObject(id, operationContext);
####Rendition Filter For example the following code snippet looks like it retrieves a list of renditions for a document object.
CmisObject oo = session.getObject(o.getId()); List<Rendition> rl = oo.getRenditions();
The client will now have access to the default set of information, but this code will return an empty list, even when there are renditions associated with the document object. This is because renditions are not included in the default set of information.
You can use an OperationContext
with a rendition filter to extend the information retrieved:-
OperationContext operationContext = session.createOperationContext(); operationContext.setRenditionFilterString("cmis:thumbnail"); CmisObject oo = session.getObject(o.getId(), operationContext); List<Rendition> rl = oo.getRenditions();
This code will retrieve any renditions of type cmis:thumbnail
for this document object.
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, you must specify an OperationContext
in the getChildren()
method call on the parent object. The OperationContext
specifies the maximum number of items to retrieve in a page.
The following code snippet retrieves a page of three objects from the result set, starting at the fifth item.
OperationContext operationContext = session.createOperationContext(); operationContext.setMaxItemsPerPage(3); ItemIterable<CmisObject> children1 = folderPaging.getChildren(operationContext); int count = 0; for (CmisObject child : children1.skipTo(5).getPage()) { System.out.println("object " + count + " in page of " + children1.getPageNumItems() + " is " + child.getName()); count++; }
Note that skipTo()
method returns a an iterable over the complete collection. The getPage()
method is then be used to create an iterable over the page.
The following code snippet retrieves all objects in the result set, using the getHasMoreItems()
method to end the getPage()
loop.
System.out.println("Getting complete result set in pages of 3"); OperationContext operationContext = session.createOperationContext(); operationContext.setMaxItemsPerPage(3); children1 = folderPaging.getChildren(operationContext); int pageNumber = 0; boolean finished = false; count= 0; while (!finished) { ItemIterable<CmisObject> currentPage = children1.skipTo(count).getPage(); System.out.println("page " + pageNumber + " has " + currentPage.getPageNumItems() + " items"); for (CmisObject item : currentPage) { System.out.println("object " + count + " is " + item.getName()); count++; } pageNumber++; if (!currentPage.getHasMoreItems()) finished = true; }
Existing ECM repositories vary in their 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. You can discover which optional capabilities your repository supports using the getRepositoryInfo service:-
System.out.println("Printing repository capabilities..."); final RepositoryInfo repInfo = session.getRepositoryInfo(); RepositoryCapabilities cap = repInfo.getCapabilities(); System.out.println("\nNavigation Capabilities"); System.out.println("-----------------------"); System.out.println("Get descendants supported: " + (cap.isGetDescendantsSupported()?"true":"false")); System.out.println("Get folder tree supported: " + (cap.isGetFolderTreeSupported()?"true":"false")); System.out.println("\nObject Capabilities"); System.out.println("-----------------------"); System.out.println("Content Stream: " + cap.getContentStreamUpdatesCapability().value()); System.out.println("Changes: " + cap.getChangesCapability().value()); System.out.println("Renditions: " + cap.getRenditionsCapability().value()); System.out.println("\nFiling Capabilities"); System.out.println("-----------------------"); System.out.println("Multifiling supported: " + (cap.isMultifilingSupported()?"true":"false")); System.out.println("Unfiling supported: " + (cap.isUnfilingSupported()?"true":"false")); System.out.println("Version specific filing supported: " + (cap.isVersionSpecificFilingSupported()?"true":"false")); System.out.println("\nVersioning Capabilities"); System.out.println("-----------------------"); System.out.println("PWC searchable: " + (cap.isPwcSearchableSupported()?"true":"false")); System.out.println("PWC updatable: " + (cap.isPwcUpdatableSupported()?"true":"false")); System.out.println("All versions searchable: " + (cap.isAllVersionsSearchableSupported()?"true":"false")); System.out.println("\nQuery Capabilities"); System.out.println("-----------------------"); System.out.println("Query: " + cap.getQueryCapability().value()); System.out.println("Join: " + cap.getJoinCapability().value()); System.out.println("\nACL Capabilities"); System.out.println("-----------------------"); System.out.println("ACL: " + cap.getAclCapability().value()); System.out.println("End of repository capabilities");
The sample code above prints the capabilities of the repository in the same groups as shown in the CMIS specification, which has a full explanation of the meaning of each capability in the section on optional capabilities. ###Allowable Actions The CMIS specification allows an application to discover the set of operations that can currently be performed on a particular object, without having to perform the action itself. The set of allowable actions for a particular object are not fixed, but are influenced by ACLs, constraints on the object's base type, and policies or other mechanisms not specified by CMIS. The following code snippet gets the current allowable actions for the test.txt document object:-
System.out.println("Getting the current allowable actions for the " + doc.getName() + " document object..."); for (Action a: doc.getAllowableActions().getAllowableActions()) { System.out.println("\t" + a.value()); }
In practice, you would check a single allowable action before executing that action. So, the following code snippet shows how to check if your application is currently allowed to check out the document test.txt
if (doc.getAllowableActions().getAllowableActions().contains(Action.CAN_CHECK_OUT)) { System.out.println("can check out " + doc.getName()); } else { System.out.println("can not check out " + doc.getName()); }
Multi-filing allows you to file a document object in more than one folder. Unfiling allows you to leave a document without parent folder. Both these capabilities are optional, and your repository may not support them.
The following code snippet checks if multi-filing is supported by this repository. If it is, creates a second folder in the root folder, and then adds our document to the new folder. Since multi-filing is supported, the document should be seen in both folders, and the code checks for this.
if (!(cap.isMultifilingSupported())) { System.out.println("Multifiling not supported by this repository"); } else { // Add a new folder to the root folder System.out.println("Creating 'ADGNewFolder 2' in the root folder"); Map<String, Object> newFolderProps = new HashMap<String, Object>(); newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); newFolderProps.put(PropertyIds.NAME, "ADGNewFolder 2"); newFolder2 = root.createFolder(newFolderProps); System.out.println("Adding " + textFileName + "to 'ADGNewFolder 2' in the root folder"); doc.addToFolder(newFolder2, true); // Did it work? ItemIterable<CmisObject>children = newFolder.getChildren(); System.out.println("Now finding the following objects in the 'ADGNewFolder' folder:-"); for (CmisObject o : children) { System.out.println(o.getName()); } children = newFolder2.getChildren(); System.out.println("Now finding the following objects in the 'ADGNewFolder 2' folder:-"); for (CmisObject o : children) { System.out.println(o.getName()); } }
The following code snippet checks if unfiling is supported by this repository. If it is, it removes our document from both folders that contain it. Since unfiling is supported, the document should still be accessible by its ID, and the code checks for this.
java if (!(cap.isUnfilingSupported())) { System.out.println(“Unfiling not supported by this repository”); } else { // remove our document from both folders System.out.println(“removing :” + doc.getName() + “from ‘ADGNewFolder’:-”); doc.removeFromFolder(newFolder);
System.out.println("removing :" + doc.getName() + "from 'ADGNewFolder 2':-"); doc.removeFromFolder(newFolder2); // Did it work? Document docTest = (Document) session.getObject(id); if (docTest != null) { System.out.println(docTest.getName() + " still exists"); } }
<a name="Versioning"></a> ### Versioning Only document objects can be versioned. Whether or not a document object is versionable is specified by the `versionable` attribute on its Object-type. A version is a copy of the object, preserving its state at a certain point in time. Each version is itself a document object, with its own ObjectId. The CMIS specification uses the following terminology when discussing versioning: * *Version Series* - all the document objects that have been created from an original document in a repository. Each version series has a unique version series ID. * *Latest Version* - the latest version of any document object in the series. If the latest version in a version series is deleted, the previous version (if there is one) becomes the latest version. * *Major Versions* - you can specify that a version is a major or minor version on check-in. The specification does not define any differences between Major and non-Major versions in a Version Series. Repositories can apply additional constraints or semantics for Major versions, if the effect on CMIS services does not violate the CMIS model. If a Version Series contains one or more Major versions, the one that has the most recent LastModificationDate is the Latest Major Version of the version series. A Version Series need not have a Latest Major Version. When the latest major version is deleted, the previous major version (if there is one) becomes the latest major version. The following code snippet checks if the test.txt document object is versionable. ```java if (((DocumentType)(doc.getType())).isVersionable()) { System.out.println(doc.getName() + " is versionable"); } else { System.out.println(doc.getName() + " is NOT versionable"); }
You create new version of a document by checking-in a Private Working copy (PWC) of the object. A PWC is created by checking-out a document. Repositories may allow any version of a document to be checked out, or may only allow the latest version to be checked out. The following code snippet checks out the latest version of the test.txt document, updates the content stream, and checks in the new version of the document as a minor version.
if (versionable) { Document pwc = (Document) session.getObject(doc.checkOut()); // default values if the document has no content String filename = "version.txt"; String mimetype = "text/plain; charset=UTF-8"; String content = ""; // get the orginal content ContentStream contentStream = doc.getContentStream(); if (contentStream != null) { filename = contentStream.getFileName(); mimetype = contentStream.getMimeType(); content = getContentAsString(contentStream); } String updatedContents = content + "\nLine added in new version"; byte[] buf = updatedContents.getBytes("UTF-8"); ByteArrayInputStream input = new ByteArrayInputStream(buf); contentStream = session.getObjectFactory().createContentStream( filename, buf.length, mimetype, input); // Check in the pwc try { pwc.checkIn(false, null, contentStream, "minor version"); } catch (CmisBaseException e) { e.printStackTrace(); System.out.println("checkin failed, trying to cancel the checkout"); pwc.cancelCheckOut(); } System.out.println("Document version history"); { List<Document> versions = doc.getAllVersions(); for (Document version : versions) { System.out.println("\tname: " + version.getName()); System.out.println("\tversion label: " + version.getVersionLabel()); System.out.println("\tversion series id: " + version.getVersionSeriesId()); System.out.println("\tchecked out by: " + version.getVersionSeriesCheckedOutBy()); System.out.println("\tchecked out id: " + version.getVersionSeriesCheckedOutId()); System.out.println("\tmajor version: " + version.isMajorVersion()); System.out.println("\tlatest version: " + version.isLatestVersion()); System.out.println("\tlatest major version: " + version.isLatestMajorVersion()); System.out.println("\tcheckin comment: " + version.getCheckinComment()); System.out.println("\tcontent length: " + version.getContentStreamLength() + "\n"); } } }
The code snippet uses the getAllVersions()
method to retrieve all the versions in the version series. The output from this code snippet will look something like this:-
Document version history name: test.txt version label: 1.0 version series id: workspace://SpacesStore/72522204-016b-4bab-817c-bf6deb155d0a checked out by: null checked out id: null major version: false latest version: true latest major version: false checkin comment: minor version content length: 52
Note the following points:-
checkin()
call is true
, you are specifying a major version.getAllversions()
returns the all the versions with the latest version first.cancelCheckout()
method to discard the PWC if things go wrong.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.
A CMIS repository may have zero or more renditions for a document or folder in addition to the document's content stream.
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.
The following code fragment checks if this repository supports renditions. It then scans the object tree starting from the root folder for a document object that has renditions associated with it. It then gets the document again using an OperationContext
to retrieve all renditions of a particular type.
if (session.getRepositoryInfo().getCapabilities().getRenditionsCapability() .equals(CapabilityRenditions.NONE)) { System.out.println("Repository does not support renditions"); } else { System.out.println("Finding first object in repository with thumbnail renditions - start"); Folder node = root; Stack<Folder> stack = new Stack<Folder>(); while (node != null) { children = node.getChildren(); for (CmisObject o : children) { if ((o.getType().isBaseType() && o.getType().getId().equals("cmis:folder")) || o.getBaseType().getId().equals("cmis:folder")) { stack.push((Folder) o); } else { OperationContext context = session.createOperationContext(); context.setRenditionFilterString("cmis:thumbnail"); CmisObject oo = session.getObject(o.getId(), context); List<Rendition> rl = oo.getRenditions(); if (!rl.isEmpty()) { System.out.println("found " + o.getName() + " of type " + o.getType().getDisplayName() + "that has renditions..."); for (Rendition rendition : rl) { System.out.print("kind: " + rendition.getKind()); System.out.print("\tmimetype: " + rendition.getMimeType()); System.out.print("\twidth: " + rendition.getWidth()); System.out.print("\theight: " + rendition.getHeight()); System.out.println("\tstream id: " + rendition.getStreamId()); } break; // Just show the first object with // renditions. Remove this // Break to show them all } } } if (stack.isEmpty()) { node = null; } else { node = (Folder) stack.pop(); } } System.out.println("Finding first object in repository with thumbnail renditions - end"); }
Not all objects in a repository can be described by the basic CMIS types. A repository can define additional object-types that extend the CMIS base object-types. You can see the descendants of the cmis:document type in the output of code snippet in the Object Types section of this guide. Additional object types inherit the properties of the base type, and can define new custom properties. OpenCMIS does not allow you create objects with a custom type, but you can access existing objects of a custom type, and access the custom property values. The following code snippet searches for all objects in a repository of a particular custom type, and prints a some of each resulting object's custom properties.
In this snippet the code looks for objects of type ia:calendarEvent
, which is an Alfresco custom type, but you can alter the sample to access objects of a custom type within your repository.
if (session.getRepositoryInfo().getCapabilities().getQueryCapability() .equals(CapabilityQuery.METADATAONLY)) { System.out.println("Full search not supported"); } else { String query = "SELECT * FROM ia:calendarEvent"; ItemIterable<QueryResult> queryResult = session.query(query, false); for (QueryResult item : queryResult) { System.out.println("Found " + item.getPropertyByQueryName("cmis:name").getFirstValue() + " of type " + item.getPropertyByQueryName("cmis:objectTypeId").getFirstValue()); System.out.println("property ia:descriptionEvent is " + item.getPropertyByQueryName("ia:descriptionEvent").getFirstValue()); System.out.println("property ia:toDate is " + item.getPropertyByQueryName("ia:toDate").getFirstValue()); System.out.println("property ia:fromDate is " + item.getPropertyByQueryName("ia:fromDate").getFirstValue()); } }
The OpenCMIS Workbench is a useful tool for examining the custom types defined in your repository. Just click on Types in the menu bar and expand the base-type tree. You can view all the properties of the type by clicking on its name.
A Relationship Object 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.
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.
Explicit relationships can be used to create an arbitrary relationship graph among independent objects. Such a relationship graph is only structural in nature. No inheritance or transitive properties are attached to a relationship graph.
The following code snippet creates a relationship between 2 objects. Note that the Apache Chemistry in-memory repository does not support relationship objects, so this code expects to run against an Alfresco repository. This repository supports relationships, but the base type cmis:relationship
is not defined as creatable, so the snippet uses an existing type R:cmiscustom:assoc
which is a creatable sub-type of cmis:relationship
. You can change this code to create a relationship of a type supported by your repository, between two objects of types supported by the relationship type.
// Check if the repo supports relationships ObjectType relationshipType = null; try { relationshipType = session.getTypeDefinition("cmis:relationship"); } catch (CmisObjectNotFoundException e) { relationshipType = null; } if (relationshipType == null) { System.out.println("Repository does not support cmis:relationship objects"); } else { ObjectType cmiscustomRelationshipType = null; try { cmiscustomRelationshipType = session.getTypeDefinition("R:cmiscustom:assoc"); } catch (CmisObjectNotFoundException e) { cmiscustomRelationshipType = null; } if (cmiscustomRelationshipType == null) { System.out.println("Repository does not support R:cmiscustom:assoc objects"); } else { System.out.println("Creating folders for relationships example"); newFolderProps = new HashMap<String, String>(); newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); newFolderProps.put(PropertyIds.NAME, "ADGFolderAssociations"); Folder folderAssociations = root.createFolder(newFolderProps); newFileProps = new HashMap<String, String>(); newFileProps.put(PropertyIds.OBJECT_TYPE_ID, "D:cmiscustom:document"); newFileProps.put(PropertyIds.NAME, "ADGFileSource"); Document sourceDoc = folderAssociations.createDocument(newFileProps, null, VersioningState.MAJOR); newFileProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); newFileProps.put(PropertyIds.NAME, "ADGFileTarget"); Document targetDoc = folderAssociations.createDocument(newFileProps, null, VersioningState.MAJOR); Map<String, String> relProps = new HashMap<String, String>(); relProps.put("cmis:sourceId", sourceDoc.getId()); relProps.put("cmis:targetId", targetDoc.getId()); relProps.put("cmis:objectTypeId", "R:cmiscustom:assoc"); ObjectId relId = session.createRelationship(relProps, null, null, null); System.out.println("created relationship"); } }
Each relationship type defines allowed source and target object types. The following code finds the type of an existing relationship, and displays the lists of allowed source types, and allowed object types.
RelationshipType relType = (RelationshipType) relationship.getType(); System.out.println(relType.getDisplayName() + "has the following allowed source types:"); for (ObjectType objectType : relType.getAllowedSourceTypes()) { System.out.println("\t" + objectType.getDisplayName()); } System.out.println(relType.getDisplayName() + "has the following allowed target types:"); for (ObjectType objectType : relType.getAllowedTargetTypes()) { System.out.println("\t" + objectType.getDisplayName()); }
Document or folder objects can have an access control list (ACL), which controls access to the object. Policy objects can also control access to a document or folder.
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.
An ACE holds :-
direct
, which is true if the ACE is directly assigned to the object, or false if the ACE is somehow derivedThere are three basic permissions predefined by CMIS:
cmis:read
: permission for reading properties and reading contentcmis:write
: permission to write properties and the content of an object. A respository can defin the property to include cmis:read
cmis:all
: all the permissions of a repository. It includes all other basic CMIS permissions.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 getRepositoryInfo
. 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, and the mappings of allowable actions to repository permissions:-
System.out.println("getting ACL capabilities"); AclCapabilities aclCapabilities = session.getRepositoryInfo().getAclCapabilities(); System.out.println("Propogation for this repository is " + aclCapabilities.getAclPropagation().toString()); System.out.println("permissions for this repository are: "); for (PermissionDefinition definition : aclCapabilities.getPermissions()) { System.out.println(definition.toString()); } System.out.println("\npermission mappings for this repository are: "); Map<String, PermissionMapping> repoMapping = aclCapabilities.getPermissionMapping(); for (String key: repoMapping.keySet()) { System.out.println(key + " maps to " + repoMapping.get(key).getPermissions()); }
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:-
OBJECTONLY
: apply ACEs to a document or folder, without changing the ACLs of other objectsPROPAGATE
: apply ACEs to the given object and all inheriting objectsREPOSITORYDETERMINED
: allow the repositoryto use its own method of computing how changing an ACL for an object influences the non-direct ACEs of other objects.The following code snippet creates a document object, and prints out the ACEs in the created document‘s ACL. It then creates a new ACL with one ACE, adds it to the document, retrieves the document again, and prints out the ACEs now present in the document’s ACL :-
// Check if the repo supports ACLs if (!session.getRepositoryInfo().getCapabilities().getAclCapability() .equals(CapabilityAcl.MANAGE)) { System.out.println("Repository does not allow ACL management"); } else { System.out.println("Repository allows ACL management"); System.out.println("Creating folders for permissions example"); HashMap<String, String> newFolderProps = new HashMap<String, String>(); newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); newFolderProps.put(PropertyIds.NAME, "ADGFolderPermissions"); Folder folderAssociations = session.getRootFolder().createFolder(newFolderProps); HashMap<String, String> newFileProps = new HashMap<String, String>(); ContentStream contentStream = new ContentStreamImpl("permissions.txt", null, "plain/text", new ByteArrayInputStream("some content".getBytes())); newFileProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); newFileProps.put(PropertyIds.NAME, "ADGFilePermissions"); Document testDoc = folderAssociations.createDocument(newFileProps, contentStream, VersioningState.MAJOR); OperationContext operationContext = new OperationContextImpl(); operationContext.setIncludeAcls(true); testDoc = (Document) session.getObject(testDoc, operationContext); System.out.println("ACL before adding an ace..."); for (Ace ace : testDoc.getAcl().getAces()) { System.out.println("Found ace: " + ace.getPrincipalId() + " toString "+ ace.toString()); } List<String> permissions = new ArrayList<String>(); permissions.add("cmis:write"); String principal = "admin"; Ace aceIn = session.getObjectFactory().createAce(principal, permissions); List<Ace> aceListIn = new ArrayList<Ace>(); aceListIn.add(aceIn); testDoc.addAcl(aceListIn, AclPropagation.REPOSITORYDETERMINED); testDoc = (Document) session.getObject(testDoc, operationContext); System.out.println("ACL after adding an ace..."); for (Ace ace : testDoc.getAcl().getAces()) { System.out.println("Found ace: " + ace.getPrincipalId() + " toString "+ ace.toString()); } }
Note the following points :-
AclPropagation.REPOSITORYDETERMINED
which lets the repository determine how the permissions are set for inheriting objects. Since this object is a document, there are no inheriting objects.OpenCMIS provides a number of features which you can use to avoid potential performance problems.
You can use paging to manage potentially large result sets.
You can use an OperationContext to tailor the metdata you wish to retrieve for a CMIS object. ##Troubleshooting
When handling CMIS exceptions in a CMIS client which connects with the AtomPub binding, remember that CmisBaseException
provides a getErrorContent()
method which returns the content of the error page returned from the server, if there is one. This can be extremely useful debugging, as the server side is normally able to provide far more information that the client.
When handling CMIS exceptions in a CMIS client which connects with the Web Services binding, CmisBaseException
provides a getCode()
method which returns the error code returned from the server, if there is one.
If a java.lang.NullPointerException
is thrown when you are trying to access properties of a CMIS object such as the list of ACLs, Renditions, relationships, or allowable actions, it is likely you have either omitted an OperationContext
when getting the object, or you have not set the OperationContext
properly.