You are here

Surfing with CMIS

Ben Dougherty's picture
Ben Dougherty

Ixxus were recently commissioned by a well established firm to develop a small publicly accessible web site. The goal of the site was to increase customer numbers for their subscription service and improve public awareness of who they are and what services they provide. The site had to be lightweight, fast, SEO friendly, and produced in a relatively short period (No change there!). To develop this site the framework would need to support rapid development but also be stable enough to produce a robust web facing site.

Learning to Surf

In the end we opted for a fledgling MVC framework called Surf, although new it builds on well established technologies like Spring and Alfresco's Webscript technology. It seemed a natural fit for us as the team who had prior knowledge of Spring MVC but also experience of Webscript development. In addition to a good knowledge base fit, the Webscript technology allowed us to utilise our team’s non java developers to help with the backend support code, which was important due to the time constraints.

A key area of the site was teaser content, snippets of articles which are available in their subscription service. An intelligence page would contain several lists of these articles and those lists would periodically update. For improved SEO ratings the article snippet content was to be included in the page as opposed to an asynchronous request. This presented an issue, we would need to query for article snippets depending on the page context, retrieve the content and display it without a significant impact to the page load time. Due to the number of these articles a content management system was required, and to aid performance we pre-generated the snippet renditions. Our final decision lay with the method we would use to communicate with the repository, for this we decided on 'CMIS'.

Content Interoperability

It was around the time of Alfresco's 3.3 CMS release that marked a significant milestone not just for Alfresco but for the CMS industry as a whole. It was significant for Alfresco because the release included the first fully compliant CMIS 1.0 implementation in the market and significant for the industry because shortly after the CMIS 1.0 specification was approved as an OASYS standard. CMIS stands for Content Management Interoperability Services, phew I hate acronyms but I think this one is justified! The open standard has managed to bring the major players of content management together to agree on a common set of services for working with content repositories. This is great when you want to separate front end from backend, deal with multiple repositories and provide the added bonus of no vender tie in.

We are certainly not the first to use CMIS in Surf it has been be developed in Web Quick Start (http://wiki.alfresco.com/wiki/Web_Quick_Start) which is an example application built on Alfresco's new implementation of WCM in Share. The Share product is Alfresco's front end to the repository and is itself build on the Surf platform. In addition to the Web Quick Start CMIS/Surf integration was demonstrated by David Caruana in his CMIS Browser prototype back in June 2010: http://blogs.alfresco.com/wp/cmis/2010/03/17/spring-surf-and-opencmis-integration. This prototype can be found in the Spring Surf source code and is easy to get up and running. In both cases OpenCMIS is used to provide a client api and to help with issuing CMIS calls and manage response objects.

Putting the bits together

Nothing could be easier than to create a Surf application using Spring Surf's Roo add-on. Roo is a rapid application development tool which is used in the Surf getting started tutorial found on the Spring web site http://www.springsurf.org/sites/1.0.0-RC1/spring-surf-devtools/spring-surf-roo-addon/reference/html/index.html. The tutorial walks you through installing Roo and how to use it to construct and develop a Surf site. After satisfying a few system dependencies you quickly are on to issuing your first Roo command to create a Spring Surf project 'project --topLevelPackage org.app.surf'. The Roo command creates a new Spring Surf project which contains an example application that can be started there and then entering the command 'mvn clean package jetty:run'. Once you have your Surf project you can quickly add your web site pages, either manually or by issuing more Roo commands:

  • surf addon install
  • surf addon list
  • surf component create
  • surf component list
  • surf component property create
  • surf component resource create
  • surf content association create
  • surf page association create
  • surf page association list
  • surf page create
  • surf page list
  • surf report page
  • surf site create
  • surf template create
  • surf template instance create
  • surf template instance list
  • surf template list
  • surf template region list
  • surf webscript list

From this point we created our home and landing page from static content in a couple of hours, so in one afternoon we had a working integrated and navigable site for demonstration to our customer. One of our first development sprints was to integrate CMIS into our Surf application, to do that we started off with adding in the prototype application browser which is a great starting point to learn how CMIS integrates with Surf. In addition to a good example it's also a useful tool to quickly hook up to a CMIS repository and browse its contents. If you wish to use the CMIS  Application browser provided in spring framework extensions then add the following dependency in your maven POM file:

<dependency>
  <groupId>org.springframework.extensions.surf</groupId>
  <artifactId>spring-cmis-components</artifactId>
  <version>1.0.0-RC1</version>
</dependency>

You will also require a dispatcher service to catch the CMIS Webscript requests and direct them at a CMIS specific application context.

<servlet>
  <servlet-name>Spring MVC CMIS Dispatcher Servlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>
          /WEB-INF/config/cmis-web-application-config.xml
     </param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>Spring MVC CMIS Dispatcher Servlet</servlet-name>
  <url-pattern>/service/cmis/*</url-pattern>
</servlet-mapping>

If you do not wish to use the CMIS application browser or supporting functions in your Surf application you can just add the Open CMIS dependency:

<dependency>
  <groupId>org.apache.chemistry.opencmis</groupId>
  <artifactId>chemistry-opencmis-client-impl</artifactId>
  <version>0.1.0-incubating</version>
</dependency>

A few beans

As all the component requirements are provided in the dependency all that's left is to add a few configuration snippets. To manage our connection to the CMIS repository we decided to reuse some of the application browser code, namely the org.springframework.extensions.cmis.service.InMemoryConnectionManager located in the CMIS framework library. We then exposed the connection bean with a script object:

<bean id="cmis.alleycat.connection" class="org.ixxus.extensions.cmis.connection.AlleycatConnection">
    <property name="cmisService" ref="cmis.service" />
    <property name="connectionManager" ref="cmis.connection.manager" />
    <property name="username" value="${alleycat.connection.username}" />
    <property name="passwd" value="${alleycat.connection.password}" />
</bean>

<bean id="cmis.connection.manager" class="org.springframework.extensions.cmis.service.InMemoryConnectionManager">
    <constructor-arg index="0"><ref bean="cmis.service"/></constructor-arg>
</bean>
<bean id="cmis.service" class="org.springframework.extensions.cmis.service.CMISServiceImpl">
    <property name="configService" ref="web.config" />
</bean>

The CMISServiceImpl class looks for any CMIS conditioned config descriptions to populate its server endpoints which are then passed to OpenCMIS:

<config evaluator="string-compare" condition="CMIS">
  <cmis>
     <server>
          <name>alleycat</name>
          <description>Alfresco Repository</description>
          <parameters>
             <binding>
               <spi>
                   <type>atompub</type>
               </spi>
               <atompub>
                   <url>${cmis.server}</url>
               </atompub>
             </binding>
             <session>
                   <type>persistent</type>
             </session>
          </parameters>
     </server>
  </cmis>
</config>

Develop with script

When using Surf or developing with Webscripts we take the approach to code in script and support that in java only when required, so our CMIS queries where no different:

var iterable = conn.session.query(statement, false).getPage();
var rows = iterable.iterator();
while (rows.hasNext()) {
  var item = rows.next();
  var someProp=item.getPropertyValueById(somePropId);
  ...
}

With the cmisObject you can stream the object content from the repository to display in context on the page. The queries and content streaming must perform well or it would delay the page rendering, I have described some of the performance modifications we made later in the blog.

var objectId=item.getPropertyValueById("cmis:objectId");
var cmisObject = conn.session.getObject(conn.session.createObjectId(objectId));
var contentStream = cmisObject.getContentStream();

In addition to the content of a cmisObject we were required to retrieve renditions from the repository. To receive renditions you need to add a rendition filter to your query statement:

var operationContext = session.createOperationContext();
operationContext.renditionFilterString = someFilter;
var iterable = conn.session.query(statement, false, operationContext).getPage();
Then iterate the rows as shown above and process the renditions for each item:
var iterable = item.getRenditions();
var renditions = iterable.iterator();
while(renditions.hasNext()) {
  var rendition = renditions.next();
  var contentStream = rendition.getContentStream();
  ...
}

We managed to improve performance

When performing a CMIS query we found a significant gain in performance by retrieving only the required items from the query. So if we were interested in object renditions we created an OperationContext which only included renditions. If we required a subset of properties from an object, we explicitly named them instead of using wildcards in the select statement. OpenCMIS supports an object cache which can be enabled in OperationContext via the setCacheEnabled method.

Other sources

http://ecmarchitect.com/images/articles/cmis/cmis-article.pdf

http://www.cmisdev.org/cmis

http://blogs.alfresco.com/wp/cmis/2010/03/17/spring-surf-and-opencmis-integration

http://blogs.alfresco.com/wp/cmis/2010/06/14/spring-surf-and-opencmis-integration-part-2/