Since OpenCms version 8.5 it is possible to access the content inside the OpenCms Virtual File System using CMIS, the Content Management Interoperability Services. Here, we give a small practical example of accessing a running OpenCms instance via CMIS, followed by more detailed information about the CMIS implementation of OpenCms. We do not describe the CMIS standard in depth. Therefore, we refer to the CMIS specification.

Practical example: Accessing OpenCms via CMIS

OpenCms version 8.5 and later comes with CMIS connectivity out of the box. So no configuration is required on the OpenCms side. We assume that an OpenCms instance is running locally under http://localhost:8080/opencms, where opencms is the web application name.

We use the Apache Chemistry Workbench for this example. This is a Java GUI client application for accessing arbitrary CMIS repositories. You can download it from here.

Start the Apache Chemistry Workbench and click the "Connection" button. The following dialog will open:

Fig. [Apache Chemistry Workbench connection dialog]: The connection dialog of the Apache Chemistry Workbench

The CMIS standard defines both an AtomPub binding and a SOAP web service binding. OpenCms supports both of them, but we are using the AtomPub binding for this example, so enter the connection URL http://localhost:8080/opencms/cmisatom and select the "AtomPub" radio button.

Enter the user name and password of the OpenCms user as which you want to log in (all operations performed via CMIS are executed in the context of an OpenCms user). Then click on the "Load repositories" button.

Two repositories will be displayed: cmis-online, with which you can access contents from OpenCms' Online project, but not make any modifications, and cmis-offline, which allows you to access and make changes to the offline contents. Select "cmis-offline" and click the Login button.

The GUI will now display the resources from the root folder of the VFS. You can navigate to other folders by double clicking on them. Double clicking on other resources will load the resources from the VFS and display them. Please note that CMIS only allows you to access the raw content of the resources. More specifically, you cannot get the rendered output of XML content via CMIS, only its source code.

On the workbench's right side, you can select different tabs for access various information about or perform actions on the currently selected resource on the left. For example, the "Actions" tab allows you to perform actions like deleting the current resource, while in the "Properties" tab, you can view or modify the properties for the currently selected resource.

CMIS integration

CMIS is a standard for accessing content repositories over web services. OpenCms provides a CMIS interface through which CMIS client software can access the OpenCms VFS. For that purpose, OpenCms comes with two additional servlets configured in the WEB-INF/web.xml file for the two possible binding types defined by the CMIS standard, AtomPub and SOAP. The servlets are mapped to the paths /cmisatom and /services, respectively.

Additionally, some servlet context listeners are defined in the web.xml file. These servlets are provided by the Apache Chemistry server implementation framework. If you do not intend to use CMIS at all, you can just comment out the CMIS section in the web.xml file marked by "Start of/End of CMIS configuration", which will reduce the startup time of the OpenCms web application. These servlets provide access to any CMIS repositories which have been configured in opencms-importexport.xml. CMIS repositories, like WebDav repositories, are configured using the <repositories>-element. By default, this configuration has two configured repositories:

<repositories>
  ...
  <repository name="cmis-offline" class="org.opencms.cmis.CmsCmisRepository">
    <params>
      <param name="project">Offline</param>
      <param name="description">Offline project CMIS repository</param>
      <param name="index">Solr Offline</param>
    </params>
  </repository>
  <repository name="cmis-online" class="org.opencms.cmis.CmsCmisRepository">
    <params>
      <param name="project">Online</param>
      <param name="description">Online project CMIS repository</param>
      <param name="index">Solr Online</param>
    </params>
  </repository>
...
</repositories>

The <repositories>-section of the configuration is also used to configure WebDAV repositories for OpenCms. OpenCms distinguishes configured CMIS repositories from WebDAV repositories by the fact that the value of the class-attribute refers to the name of a Java class which implements the interface org.opencms.cmis.I_CmsCmisRepository.

Currently the only class implementing this interface that comes with OpenCms is org.opencms.cmis.CmsCmisRepository. This class supports various configuration parameters which are defined under the <params>-element:

Parameters supported by CmsCmisRepository
project (required)

The "working project" for the CMIS repository. If this is the Online project, the repository will only provide read access to the Online project of the VFS. For any other project, the CMIS repository will access the Offline state of the VFS, and any changes are performed inside the configured project. Note that, from the point of view of the CMIS standard, different repositories are independent objects. This means that if you are writing a client application that needs to access both offline and online contents from OpenCms through CMIS, you will need to connect to both: an Offline and an Online repository.

description

A description text that may be displayed by CMIS clients for the repository.

index (optional)

The name of the OpenCms Solr index used for querying the repository. OpenCms' standard Solr search facilities are used (see here). If this parameter is omitted, the CMIS implementation will report the repository as "not queryable". Note that Lucene indexes will not work here.

rendition

The class name of a "rendition provider class". CMIS has the concept of 'renditions', which are alternative views of the the same content, e.g., thumbnails of images. The class configured here can provide such renditions. The class needs to implement the interface org.opencms.cmis.I_CmsCmisRenditionProvider. Note that the standard OpenCms distribution does not included an implementation for this interface.

property

The class name of a "property provider" class. With this parameter, it is possible to add special properties to the CMIS view of the OpenCms VFS which do not directly correspond to any resource properties or attributes, but are instead read or written by calling methods on an instance of the provided class. You can use this to make custom code in OpenCms available through CMIS calls. The configured class needs to implement the interface org.opencms.cmis.I_CmsPropertyProvider. No classes implementing this interface are included in the standard OpenCms distribution.

OpenCms makes resources and relations available as CMIS objects. All files have the CMIS type cmis:document. All folders have the CMIS type cmis:folder. Relations have the CMIS type opencms:<relationtype>, where <relationtype> is the relation type name defined in OpenCms.

Files and folders can be created, deleted and modified through CMIS, assuming the repository project is not the Online project and the current user has the permissions to perform the operation. Relations can only be created or deleted if the relation type is not marked as “defined in content” (See the method org.opencms.relation.CmsRelationType.isDefinedInContent()).

For each OpenCms property X, there are two CMIS properties available on both cmis:document and cmis:folder - opencms:X and opencms-inherited:X. The opencms:X property for a CMIS document or folder will contain the property value for the resource which is directly set on the resource, while the opencms-inherited:X property will contain the value which was either directly set on the resource or inherited from a parent folder. The opencms:X property can be both read and written, while the opencms-inherited:X property is read-only. Writing to the opencms:X property with CMIS will set both the structure and the resource value of the property in OpenCms.

Some standard CMIS properties are filled with resource attributes or other useful resource information from OpenCms:

Standard CMIS properties filled with resource attributes
cmis:objectId

The internal OpenCms structure id of the resource.

cmis:path

The root path of the resource.

cmis:lastModifiedBy

The name of the user who last modified the resource.

cmis:lastModificationDate

The date at which the resource was last modified.

cmis:createdBy

The name of the user who created the resource.

cmis:creationDate

The date when the resource was created.

opencms-special:resource-type

Contains the OpenCms resource type name.

opencms-dynamic:

A dynamic property which is handled by a custom property provider class configured in opencms-importexport.xml, as described above.

It is not possible to create completely new OpenCms properties through CMIS. You can only define new properties from OpenCms itself. These new properties may not be immediately visible through the CMIS interface, as the list of valid OpenCms properties in the CMIS implementation is only refreshed every 5 minutes.

When uploading new files into OpenCms via CMIS, the OpenCms resource type will be automatically determined from the file extension of the uploaded file. You can override this by specifying the opencms-special:resource-type property with the OpenCms resource type as a value when creating a new document through CMIS.

2.1 Limitations of the CMIS implementation

The CMIS implementation of OpenCms does not support all optional CMIS features, and not all OpenCms functionality. The currently unsupported features are:

  • Versioning and PWCs
  • Unfiled and multifiled resources
  • Changing permissions/access control entries
  • Changelogs
  • Policies
  • CMIS-SQL support

Although queries don't support CMIS-SQL, querying is still possible using Solr queries. If a Solr index is configured for a repository as described above, OpenCms will report that repository as queryable, but standard CMIS clients that rely on CMIS-SQL being available for queryable repositories may not work.

Publishing files is not supported, since the CMIS standard has no concept of “Offline” and “Online” mode like OpenCms. The Offline and Online repositories in the standard configuration are completely independent from the CMIS point of view. Although the CMIS implementation provides basic access control lists for resources, those are very incomplete compared to the OpenCms permission system, and are not sufficient to determine whether a user can perform an action through CMIS. So if you writing a client application to access OpenCms through CMIS, you need to use the "allowable actions" API call to determine whether an operation can be performed before actually trying to perform it.

2.2 Example CMIS client program

Thanks to CMIS, you can use any client library which correctly implements CMIS to write custom client applications that access the VFS. One such example application that uses the Apache Chemistry client libraries is appended below. This example application uploads documents from a folder in the local file system to the RFS. You can find more information about developing client applications with Apache Chemistry in Java at http://chemistry.apache.org/java/developing/index.html.

/**
* Demo for uploading files to CMIS.
*/
public class CmisUploader {

    /** The repository id. */
    public static final String REPOSITORY_ID = "cmis-offline";

    /** The session factory. */
    private static SessionFactory sessionFactory = SessfromionFactoryImpl.newInstance();

    private String m_repositoryUrl;

    /** The session. */
    private Session m_session = null;

    public CmisUploader(String repositoryUrl) {

        m_repositoryUrl = repositoryUrl;
    }

    /**
     * Main method.
     */
    public static void main(String[] args) throws Exception {

        String repositoryUrl = args[0];
        String source = args[1];
        String target = args[2];
        CmisUploader uploader = new CmisUploader(repositoryUrl);
        uploader.copyFiles(source, target);
    }

    /**
     * Copies files from a local folder to a CMIS folder.
     */
    public void copyFiles(String sourceFolder, String targetFolder) throws Exception {

        File sourcefolder = new File(sourceFolder);
        for (File child : sourcefolder.listFiles()) {
            if (child.isFile()) {
                uploadFileToCmis(child.getAbsolutePath(), targetFolder);
            }
        }
    }

    /**
     * Uploads a single file from the local file system to a CMIS folder.
     */
    public void uploadFileToCmis(String fileSystemPath, String targetFolder) throws Exception {

        Session session = getSession();
        File file = new File(fileSystemPath);
        String name = file.getName();
        byte[] fileContent = readFile(file);
        Folder parent = (Folder)(session.getObjectByPath(targetFolder));
        String targetPath = (targetFolder + "/" + name).replaceAll("/+", "/");
        try {
            session.getObjectByPath(targetPath);
            System.out.println("Not creating " + targetPath + " since it already exists. ");
        } catch (CmisObjectNotFoundException e) {
            System.out.println("Creating " + targetPath);
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document");
            properties.put(PropertyIds.NAME, name);
            ContentStream contentStream = new ContentStreamImpl(
                name,
                BigInteger.valueOf(fileContent.length),
                "binary/octet-stream",
                new ByteArrayInputStream(fileContent));
            parent.createDocument(properties, contentStream, VersioningState.MAJOR);
        }
    }

    /**
     * Gets the session, creating it if possible.
     */
    private Session getSession() {

        if (m_session == null) {
            Map<String, String> parameters = new HashMap<String, String>();
            // OpenCms user name and password
            parameters.put(SessionParameter.USER, "Admin");
            parameters.put(SessionParameter.PASSWORD, "admin");
            parameters.put(SessionParameter.ATOMPUB_URL, m_repositoryUrl);
            parameters.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
            parameters.put(SessionParameter.REPOSITORY_ID, "cmis-offline");
            m_session = sessionFactory.createSession(parameters);
        }
        return m_session;
    }

    /**
     * Utility method for reading a file's content.
     */
    private byte[] readFile(File file) throws IOException {

        byte[] fileContent = new byte[(int)file.length()];
        FileInputStream istream = new FileInputStream(file);
        istream.read(fileContent);
        istream.close();
        return fileContent;
    }
}