This project has retired. For details please refer to its Attic page.
Apache Chemistry - OpenCMIS Client API Developer's Guide
     
   
 

OpenCMIS Client API Developer's Guide

Audience

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.

How to use this guide

The guide is divided into 5 parts :-

  1. The Basics - A brief introduction to CMIS and a practical "hello world" sample. Read this section to find out how to build, install and run a sample OpenCMIS application using Apache Chemistry.
  2. Getting Started with OpenCMIS - quick, practical examples of the the most commonly needed CMIS function.
  3. Advanced CMIS features - the rest of the CMIS API
  4. OpenCMIS bindings - the 2 CMIS 1.0 bindings, and when and how to use them.
  5. Troubleshooting - common pitfalls you may encounter when using OpenCMIS.

Assumptions

The guide assumes you are using the Eclipse IDE for code development, and have Maven and SVN installed.

  • You can download and install Eclipse here.
  • You can download and install Maven here.
  • You can download and install SVN for your platform here.

The basics

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.

What is OpenCMIS?

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.

The CMIS Domain Model

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 :-

  • Document - is an item of content. The document may have a content stream, which is the actual file associated with the document. A content stream exists only as part of its containing document object. A content stream has a mimetype associated with it. A document object may contain one or more renditions, which are alternate views of the content. Documents objects are the only objects that are versionable. Each version of a document has its own object ID. All the versions of a document make up a version series and share a version series ID. You can create, read, update and delete documents using OpenCMIS methods.
  • Folder - is a container used to organize the document objects. A repository had one root folder. All other folder objects have one parent folder. A folder has a folder path representing its place in the repository's folder hierarchy. A folder object can have renditions. For example, a folder can have a thumbnail as a rendition representing the contents of the folder.
  • Relationship - define relationships between a source object and a target object. Creating, changing and deleting relationships do not change the source or target objects themselves. Relationship objects are optional. A CMIS repository may not support relationship objects.
  • Policy - are optional repository-specific objects that can be applied to controllable objects. The behavior of policies are not modeled by the CMIS specification. A policy object may be applied to multiple controllable objects and a controllable object may have multiple policies applied to it. A policy object can not be deleted if it is currently applied to one or more controllable objects.

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.

CMIS Services

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:-

  • Repository services - let you discover available repositories, get the capabilities of these repositories, and provide basic Data Dictionary information of what types are available in the repository.
  • Navigation services - let you navigate the repository by accessing the folder tree and traversing the folder/child hierarchy. You can use these services to get both children and parents of an object.
  • Object services - provide the basic CRUD (Create, Read, Update, Delete) and Control services on any object, including document, folder, policy, and relationship objects. For document objects, this includes setting and getting of properties, policies, and content streams. Object services retrieve objects by path or object ID. Applications may also discover what actions users are allowed to perform.
  • Multi-filing services - let you establish the hierarchy by adding or removing an object to or from a folder.
  • Discovery services - provide Query and Change services, and a means of paging the results of the query.
  • Change services - let you discover what content has changed since the last time checked, as specified by a special token. You can use Change services for external search indexing and replication services.
  • Versioning services - control concurrent operation of the Object services by providing Check In and Check Out services. Version services also provide version histories for objects that are versioned.
  • Relationship services - let you create, manage, and access relationships or associations between objects as well as allow an application to traverse those associations.
  • Policy services - apply policies on document objects. Policies are free-form objects and can be used by implementations for security, record, or control policies.
  • ACL services - let you create, manage, and access Access Control Lists to control who can perform certain operations on an object.

CMIS Bindings

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 OpenCMIS Workbench

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.

Hello World

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

  1. 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>
    
  2. Create an Eclipse project from the code, and import it into your eclipse. Type the following command :-

     mvn eclipse:eclipse
    
  3. Import the project into eclipse:-

    1. click File->Import and choose General->Existing projects into workspace in the Import wizard.
    2. Click Next and browse to the folder you checked out the project to.
    3. Click the checkbox next 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.

Getting Started with OpenCMIS

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.

Installing the getting started 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-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:-

  1. click File->Import and choose General->Existing projects into workspace in the Import wizard.
  2. Click Next and browse to the folder you checked out the project to.
  3. Click the checkbox next 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.

Connecting to a CMIS repository

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:-

  1. This repository does not require credentials, but the code snippet sets a user id and password to show how this is done

  2. 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());
    

Connecting to a repository by id

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.

Connecting to one of a list of repositories

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.

Working with Folders and Documents

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.

Finding the contents of the root folder

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());
    }

Creating a folder object

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.

Creating a simple document object

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 :-

  • The createDocument() method returns the newly created document object.
  • Each version of a document object has its own object ID.
  • You can create a document without an associated content stream, by passing null as the contentStream Parameter.

Reading the contents of a 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 :-

  • All paths start with the root folder /.
  • The 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.

Updating a document.

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.

Deleting a document

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.

Deleting a folder tree

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:-

  1. The children of a Tree are themselves of type Tree.
  2. The single argument of 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.

Object Types

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.

CMIS Properties

Displaying the properties of an Object

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]

Getting a property explicitly

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()));

Getting a property value by Id

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())); 

Getting a property value by query name

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());
    } 

Working with CMIS Queries

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.

A Simple Query

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:-

  • each property defined in the object type definition.
  • all properties defined on any ancestor-type of the object type but not defined in the object type definition.

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.

OpenCMIS programming patterns

Exceptions

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)

Operation Context

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

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.

Paging

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;
        }

Query - prepared statments

Advanced CMIS features

Capabilities

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 and Unfiling

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.

        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");
            }
        }

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.

    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:-

  • If the first parameter value on the checkin() call is true, you are specifying a major version.
  • getAllversions() returns the all the versions with the latest version first.
  • The code snippet uses the cancelCheckout() method to discard the PWC if things go wrong.
  • Different repositories support different capabilities for a newly created PWC. Repositories that support server-side PWCs can allow visibility to other users who have access to other document objects in the version series. For example other users may be able to update the PWC, or have it included in the results of a search query. Repositories supporting client-side only PWCs will not have these capabilities.

Renditions

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");
        }

Advanced use of types

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.

Relationships

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());
        }

Access Control

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 :-

  • One String with the principalid
  • One or more Strings with the names of the permissions.
  • A boolean flag direct, which is true if the ACE is directly assigned to the object, or false if the ACE is somehow derived

There are three basic permissions predefined by CMIS:

  • cmis:read: permission for reading properties and reading content
  • cmis: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 objects
  • PROPAGATE: apply ACEs to the given object and all inheriting objects
  • REPOSITORYDETERMINED: 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 :-

  • OpenCMIS does not return ACLs by default when retrieving a CMIS object. You must provide an OperationContext on the method and set it to include ACLs.
  • We have set the propogation to be 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.

Change logs

OpenCMIS bindings

Performance

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.