OpenCMIS Server Development Guide - 2nd Edition
OpenCMIS Server Development Guide - 2nd Edition
OpenCMIS Server Development Guide - 2nd Edition
This work is licensed under a Creative Commons Attribution 3.0 Unported License.
Version History:
Version 1.0 November 6, 2013
Notes: First edition of Guide.
ISBN : 9781617294419
Authors:
Jay Brown, ECM Architect, IBM
Florian Müller, ECM Architect, SAP
OpenCMIS Server Development Guide – 2nd Edition
Table of Contents
Introduction................................................................................................................................................ 4
Overview for Parts 1 and 2....................................................................................................................4
A note about extensions versus plugins:...........................................................................................4
Prerequisites......................................................................................................................................4
Goals of the tutorial.......................................................................................................................... 5
Tutorial task description.............................................................................................................................5
Initial setup of your developer environment.............................................................................................. 5
Getting and building the latest OpenCMIS libraries............................................................................. 6
Initial build of OpenCMIS......................................................................................................................... 6
Building OpenCMIS..............................................................................................................................6
Getting the project source from GitHub................................................................................................7
Building the solution from the command line.......................................................................................9
Building and running from Eclipse.......................................................................................................... 11
Importing the OpenCMIS code formatter settings into Eclipse.......................................................... 11
Importing the project into Eclipse....................................................................................................... 11
Setting up a Tomcat server target in Eclipse....................................................................................... 15
A note about running from Windows..............................................................................................16
Running and debugging from Eclipse................................................................................................. 17
A note about startup timeouts......................................................................................................... 17
Connecting CMIS Workbench to our local server...............................................................................19
Starting CMIS Workbench..............................................................................................................20
Creating a new server project from scratch (Optional)............................................................................22
A cooks tour of the FileBridge project.....................................................................................................23
OpenCMIS Server Framework Interfaces........................................................................................... 23
CmisService....................................................................................................................................24
CmisServiceFactory........................................................................................................................25
OpenCMIS Server Framework Operation...........................................................................................25
FileBridgeCmisServiceFactory.......................................................................................................26
FileBridgeCmisService...................................................................................................................27
FileBridgeRepositoryManager....................................................................................................... 27
FileBridgeRepository......................................................................................................................27
ContentRangeInputStream..............................................................................................................27
FileBridgeUserManager................................................................................................................. 28
Page 2
OpenCMIS Server Development Guide – 2nd Edition
FileBridgeUtils............................................................................................................................... 28
FileBridgeTypeManager................................................................................................................. 28
Tutorial exercises..................................................................................................................................... 29
Exercise 1: Filling out the RepositoryInfo structures..........................................................................29
Exercise 1.1 Setting the CMIS supported version.......................................................................... 30
Exercise 1.2 Setting product, version and vendor.......................................................................... 30
Exercise 1.3 Setting the root folder ID........................................................................................... 30
Exercise 2: Computing CMIS IDs for your objects............................................................................ 31
Spot the design problem................................................................................................................. 31
Exercise 2.1 Handle null and root when computing IDs................................................................ 32
Exercise 3: Returning an Object..........................................................................................................32
Exercise 3.1 Getting the File or Folder...........................................................................................32
Exercise 3.2 Identify all of the Properties - Research.................................................................... 33
Exercise 3.3 Return the Properties..................................................................................................33
Exercise 3.4 Honoring the Property Filter - Research....................................................................34
Exercise 4: getContentStream............................................................................................................. 34
Exercise 4.1 Offset and Range........................................................................................................34
Exercise 5: Adding logging and tracing to your server....................................................................... 35
Exercise 5.1 Adding slf4j to our project for logging...................................................................... 36
Exercise 5.2 Adding some logging code.........................................................................................38
Exercise 5.3 Observe the logging output........................................................................................38
Exercise 5.4 Overwriting the web.xml file to enable HTTP tracing..............................................39
Examine the HTTP trace output..................................................................................................... 41
Exercise 6: Testing your CMIS server.................................................................................................42
Exercise 7: Supporting multiple repositories for your service............................................................ 45
Miscellany for Developers....................................................................................................................... 47
IBM Content Navigator's CMIS client - minimum requirements....................................................... 47
Subversion clients for Windows..........................................................................................................49
Auto start CMIS Workbench connected to your server.......................................................................49
Conclusion for Part I................................................................................................................................ 49
Part 2 – The OpenCMIS Server Plugins Framework...............................................................................50
What are Server Plugins?.................................................................................................................... 50
How plugins came about..................................................................................................................... 50
Supported versions of OpenCMIS..................................................................................................50
Engineering requirements...............................................................................................................51
Design and Discussion....................................................................................................................52
ServerSide Changes to enable plugins................................................................................................ 54
Changes to CmisService implementation (FileBridgeCmisService)..............................................54
Changes to your ServiceFactory (FileBridgeCmisServiceFactory)...............................................55
The WrapperManager..................................................................................................................... 55
Setting a custom CallContext (optional).........................................................................................56
Building a Server Plugin – Example #1 - Logging............................................................................. 56
The AbstractCmisServiceWrapper................................................................................................. 58
Sidebar: Building a Server Plugin from scratch..................................................................................58
Page 3
OpenCMIS Server Development Guide – 2nd Edition
Introduction
Overview for Parts 1 and 2
In part one of this tutorial you will be introduced to the Apache Chemistry project, its architecture,
tools and APIs but from a perspective of a developer that needs to build a custom server. We will be
focusing specifically on building our server using Java and even more specifically using the Apache
Chemistry OpenCMIS Server Framework (version 0.11.0 or higher).
In part two, you will learn about the new (as of 2014) runtime OpenCMIS server plugins. Both how to
support them from your server and how to build and deploy them as well.
Prerequisites
• Experience with Java development using Eclipse and Maven.
• It is assumed the reader has a good familiarity with the CMIS specification and its purpose.
◦ For a quick tour, the Apache Chemistry site has a “What is CMIS?” page with lots of useful
links to get you acquainted here: http://chemistry.apache.org/project/cmis.html.
◦ Also the first chapter of “CMIS and Apache Chemistry in Action” is a very good
introduction and is available as a free PDF download at Manning's site here:
http://www.manning.com/mueller/
Page 4
OpenCMIS Server Development Guide – 2nd Edition
◦ Finally it is always a good idea to keep a copy of the CMIS specification handy. (See
resources section at end of document for links.)
• A familiarity with Apache Chemistry CMIS Workbench is helpful but not mandatory. Refer to
the 10 minute video introducing this tool if you are not already familiar with it here:
http://www.youtube.com/watch?v=akvCDVh03qs . Note that this video refers to a much older
version of CMIS Workbench than is available today but the general concepts have not changed.
Page 5
OpenCMIS Server Development Guide – 2nd Edition
◦ The servlet container for hosting our server. Tomcat is not required but deploying this server to
other JEE containers like WebSphere, JBoss and WebLogic is outside the scope of this tutorial.
All of the these tools we have chosen for this tutorial can be freely obtained and can be installed on
Windows, OSX or Linux.
then press enter to start the checkout operation. This may take a few minutes depending on your
connection speed.
Building OpenCMIS
Note: Refer to the “Initial setup of your developer environment” section before starting this section.
To build the entire tree we need to change directories to the top of the source tree where the top level
Maven pom.xml file is located. CD into the ./trunk directory if you are not already there.
Since the build is pretty large, let's increase the max Java heap size for Maven here:
Windows Note: use 'set' in place of 'export' for (and omit the quotes) throughout this document.
Then we will run the build while skipping the tests like this:
mvn clean install -Dmaven.test.skip=true
Page 6
OpenCMIS Server Development Guide – 2nd Edition
If you see out of memory errors, etc., try running a second time without the clean like this:
mvn install -Dmaven.test.skip=true
-Dorg.apache.chemistry.opencmis.tck.test=false
This is also a much faster way to build if you are just refreshing after a few small changes. That last -D
parameter will turn off the TCK tests during the build and reduce total build time and memory needed
even further.
Create a directory to contain the source code tree for the project. For consistency throughout the
tutorial let's hypothetically use:
Page 7
OpenCMIS Server Development Guide – 2nd Edition
/project.code
In a terminal window cd to your new project directory and execute the following command to checkout
the project source tree.
Note: You may also use a git client of your choice including the one that is available for Eclipse which
is what we will be using in the Hands on Lab.
After this completes, take a moment to examine the structure of the project before we move on to
building it for the first time.
The illustration below shows the directory once it has been extracted or checked out.
Page 8
OpenCMIS Server Development Guide – 2nd Edition
• trunk: Contains the actual working version of the FileBridge sample CMIS server application
from the guide as well as the code from part 2.
• doc: (not shown here) Contains the various renderings of this document (PDF, etc)
• pom.xml (the Maven POM file for the project used in this guide)
/project.code/cmisFileBridge-master
After a few seconds, you should see something like this at the tail end of a lot of output.
If you want to deploy this WAR file manually to your own Tomcat (or other container) have a look at
the target directory under trunk (shown in illustration below) and you will see the xxx-
SNAPSHOT.war file (shown highlighted).
Page 9
OpenCMIS Server Development Guide – 2nd Edition
Going forward we will be letting eclipse handle all of our deployments but again its nice to know how
to do this from the command line in case you need to automate your build later.
Page 10
OpenCMIS Server Development Guide – 2nd Edition
Page 11
OpenCMIS Server Development Guide – 2nd Edition
Page 12
OpenCMIS Server Development Guide – 2nd Edition
Open up the 'Maven' submenu and select 'Existing Maven Projects' then click 'Next'.
On the next dialog select the 'browse' button to the right of the 'root directory' field then navigate to
your trunk directory where the project pom.xml file it located. For the our tutorial environment the
location is
/project.code/ServerDevelopmentGuideV2.git/trunk
After selecting the directory, Eclipse will do a bit of processing (reading the pom.xml file) and then
display an entry for your project (pre checked) as shown in the illustration below:
Select 'Finish' to finish the import and go back to your workspace to see the newly imported CMIS
server project. The following illustration shows the new project explorer view of the project showing
the files in the org.example.cmis.server package.
Page 13
OpenCMIS Server Development Guide – 2nd Edition
Now that we have the project successfully imported we will setup an embedded Tomcat instance so that
Eclipse will have a container target for deployments.
Page 14
OpenCMIS Server Development Guide – 2nd Edition
Download Apache Tomcat 7 from any of the standard mirrors. (e.g. http://tomcat.apache.org/download-
70.cgi) and extract the archive to a working location where you want to keep tools for your project. On
our hypothetical tutorial image we place the .tar.gz file in the
/root/Desktop/dev.tools/to.install
directory.
/root/Desktop/dev.tools/to.install/apache-tomcat-7.0.42
directory. Make a note of wherever you place this directory since you will need it in the next step.
Next start your Eclipse again and look at the bottom of your Eclipse workspace at the lower most row
of tabs. There you will find a 'Server' tab. Select it and then click on the blue link to create a new
server.
Then you will be prompted with the 'Define a new server' dialog. Open up the Apache dropdown and
select 'Tomcat v7.0 server' then select the 'Next' button at the bottom.
In the 'Tomcat Installation Directory' field enter in the path where your Apache Tomcat is located (for
the tutorial we are using /root/Desktop/dev.tools/to.install/apache-tomcat-
7.0.42) then click 'Next'.
On this last dialog (Illustration below) you will select your server app on the left and press the 'Add'
button which will move it over into the 'Configured' side like shown in the illustration below:
Page 15
OpenCMIS Server Development Guide – 2nd Edition
repository.test = /
repository.test = c:\\myrootdirectory
and make sure whatever directory you indicate actually exists and has some files and directories
already populated.
The relative location of this properties file is shown in the illustration below:
Page 16
OpenCMIS Server Development Guide – 2nd Edition
You will know when the server has started when you see an info message in your 'Console' tab showing
successful startup like this:
Page 17
OpenCMIS Server Development Guide – 2nd Edition
At this point lets bring up a web browser to see our new server's landing page and make sure it really
did startup. Go to http://localhost:8080/server/ and you should see a page like the following illustration.
Page 18
OpenCMIS Server Development Guide – 2nd Edition
Now we are ready to connect CMIS Workbench and poke around to see what this little bit of code can
really do.
Page 19
OpenCMIS Server Development Guide – 2nd Edition
/trunk/chemistry-opencmis-workbench/chemistry-opencmis-
workbench/target
Here you will grab the file that ends with -SNAPSHOT-full.zip as shown in the illustration below:
Copy the zip file up to a shared dev.tools directory (in our tutorial we will use ../desktop/dev.tools).
There create a new sub-directory named Workbench. Once there uncompress the file.
Page 20
OpenCMIS Server Development Guide – 2nd Edition
Since our server is still running all we need to do is enter in the connection info and we will be off and
running.
org.apache.chemistry.opencmis.binding.spi.type=browser
org.apache.chemistry.opencmis.binding.browser.url=http://localhost:8080/server/browser
org.apache.chemistry.opencmis.user=test
org.apache.chemistry.opencmis.password=test
org.apache.chemistry.opencmis.binding.compression=true
org.apache.chemistry.opencmis.binding.cookies=true
Note: You don't have to use the expert tab. You can also separately enter in the URL, user and
password values then click on the 'Browser' radio button. Same result. Its just less steps to use the
expert page (e.g. a single paste).
Select the 'Load Repositories' button then click on 'Login' and you should almost immediately see the
root directory displayed like the illustration below:
Page 21
OpenCMIS Server Development Guide – 2nd Edition
llustration 4: First screen you see in Workbench after login and selecting a
repository
mvn archetype:generate \
-DgroupId=org.example.cmis \
-DartifactId=server \
-Dversion=1.0-SNAPSHOT \
-Dpackage=org.example.cmis.server \
-DprojectPrefix=FileBridge \
-DarchetypeGroupId=org.apache.chemistry.opencmis \
-DarchetypeArtifactId=chemistry-opencmis-server-archetype \
-DarchetypeVersion=1.0.0-SNAPSHOT \
-DinteractiveMode=false
Note: This archetype generation step can also be done GUI-style, with the latest version of Eclipse
(with Maven integration). We are leaving that out of the tutorial to save space but feel free to use that
instead (as an extra exercise) if you prefer.
Page 22
OpenCMIS Server Development Guide – 2nd Edition
• groupId, artifactId, and version: These are the Maven 'coordinates' for the code you are
generating. (see: http://maven.apache.org/pom.html#Maven_Coordinates for more info on
these)
• package: The Java package for the code.
• ProjectPrefix: Prefix for all the classes that will be generated. For example, the prefix
FileBridge generates the classes FileBridgeCmisService and
FileBridgeCmisServiceFactory.
• archetypeGroupId, archetypeArtifactId, and archetypeVersion: OpenCMIS archetype and
OpenCMIS version that should be used. The archetype OpenCMIS 1.0 may be available by the
time you read this. This version also defines the runtime OpenCMIS Server Framework version.
• InteractiveMode: If false, Maven won’t prompt you for confirmations during the generation
process.
Page 23
OpenCMIS Server Development Guide – 2nd Edition
To connect the server framework to the content repository, you have to implement two Java interfaces:
CmisService and CmisServiceFactory.
CmisService
The CmisService interface aggregates all CMIS 1.0 and CMIS 1.1 operations plus a few additional
methods. There are over 50 methods in total that can be implemented. The methods and method
parameters are named after the operations that are described in the “Services” section of the CMIS
specification. The implementation of the CmisService interface is supposed to behave as defined in
the specification. That includes throwing the exceptions documented there.
Implementing all these methods sounds tedious doesn't it? Luckily, OpenCMIS provides the abstract
class AbstractCmisService, which implements the CmisService interface and provides
convenience implementations for most methods. It reduces the number of required methods to just six.
Providing implementations for these six methods doesn’t make the CMIS connector specification
compliant, but it is sufficient for many CMIS clients (like the CMIS Workbench) to navigate through
the folder structure. It’s recommended to extend this class instead of implementing the interface
directly. You’ll see an example later when we discuss the FileBridgeCmisService class.
Page 24
OpenCMIS Server Development Guide – 2nd Edition
CmisServiceFactory
The main task of an implementation of the CmisServiceFactory interface is to provide
CmisService objects. Whenever the server framework receives a request, it asks the
CmisServiceFactory for a CmisService object, which is then used to process the request.
There is only one CmisServiceFactory object per web application. This object also manages the
initialization and shut down of the CMIS connector and provides some configuration values for the
server framework. The CmisServiceFactory object must be thread-safe but the CmisService
objects it produces don’t need to be because they are only used for one request.
OpenCMIS provides the abstract class AbstractServiceFactory, which should be used to build
a service factory because it sets some sensible default values among other details. The
FileBridgeCmisServiceFactory class, which we will walk through later, also extends the
AbstractServiceFactory class.
Next, let’s see how the server framework uses these objects.
Page 25
OpenCMIS Server Development Guide – 2nd Edition
Incoming CMIS requests are processed by the servlets. The requests are parsed, checked for syntactical
correctness, and the data is converted into Java objects. The framework then requests a
CmisService object from CmisServiceFactory and calls the suitable method with the received
data. The data returned by the method is converted into XML or JSON and sent back to the client.
Now you should have a rough understanding how the server framework works.
Next lets look into the FileBridge implementation – class by class.
FileBridgeCmisServiceFactory
As you might figure out from the name, the FileBridgeCmisServiceFactory is the service
factory class for the FileBridgeService. Its init() method gets the repository configuration
and sets up all necessary objects. The method receives a map of configuration parameters. This map
represents the content of the repository.properties file, which resides in the classpath. The
repository.properties file is used by the server framework to identify the service factory class
and must at least contain an entry with the key “class” and the fully qualified classname of the service
factory as the value. The repository.properties file can also contain any other configuration.
In case of the FileBridge, we use it to configure the repositories, their root paths on disc, and the logins.
The readConfiguration() method iterates through the map, collects all the repository and login
details and stores them in the repository manager (FileBridgeRepositoryManager) and the
user manager (FileBridgeUserManager). As you will see in Part II, this file is also used to hook
in your server plugin classes.
The factory's main task, though, is to serve FileBridgeCmisService objects. The framework
calls the getService() method whenever it needs one. The framework provides a CallContext
object, which contains all kinds of details about the incoming call. That includes the user name and the
password that the client sent. Before the FileBridgeCmisServiceFactory returns a
FileBridgeCmisService object, it hands the CallContext object over to the user manager
(FileBridgeUserManager) to authenticate the user. If the authentication fails, it throws a
CmisPermissionDeniedException.
Now it’s time to serve a FileBridgeCmisService object. There are multiple ways to manage
those objects. The easiest, but most inefficient way would be to create a new object every time. Here,
we decided to use a ThreadLocal. Over time each thread will have it’s own object that is reused when a
subsequent request hits that thread.
FileBridgeCmisService objects are very lightweight and are only proxies for repository objects
(FileBridgeRepository), which we’ll discuss in a moment. For heavyweight CmisService objects or
CmisService objects that are expensive to create, a pool of objects might be a better option.
Page 26
OpenCMIS Server Development Guide – 2nd Edition
Once the FileBridgeCmisService object has been created or retrieved, the CallContext
object has to be handed over to the object. Here we use a custom CallContext object that wraps the
one originally provided by OpenCMIS. There is more information on CallContext objects in part 2.
Eventually, the service factory returns the object to the server framework.
FileBridgeCmisService
The FileBridgeCmisService class implements the CmisService interface and therefore has
to provide more than 50 method implementations. By extending the AbstractCmisService class,
we can focus here on just the methods that we want to (and can) implement.
The FileBridgeCmisService only contains a tiny amount of logic since its task is merely to
forward the call to the appropriate repository instance.
FileBridgeRepositoryManager
The repository manager maps repository IDs to FileBridgeRepository object, which in turn
contains the repository logic. The repository manager is set up when the
FileBridgeCmisServiceFactory starts up and is used by the FileBridgeCmisService
to find the right FileBridgeRepository object.
FileBridgeRepository
The FileBridgeRepository class contains the main logic of our CMIS server. There is one
instance per repository at runtime. It has to be thread-safe because multiple threads could access
the same repository at the same time.
The FileBridgeRepository class maps CMIS operations to file system operations. Most of the
code is straightforward. Creating a document or folder maps to creating a file or directory on the file
system. The CMIS operations getObject and getContentStream provide metadata and content
respectively of a file (or folder). The getChildren and getDescendants operations return a list
or tree of children of a directory. And so on.
We skip the details here because the implementation of this class is the main topic of this tutorial's
exercises.
ContentRangeInputStream
The getContentStream operation allows a client to request an excerpt from a document content by
Page 27
OpenCMIS Server Development Guide – 2nd Edition
proving an offset and a length. This class is a simple wrapper around a Java InputStream that takes
the offset and length into account. The FileBridgeRepository uses it when
getContentStream is called and the client has requested a content excerpt.
FileBridgeUserManager
The FileBridgeUserManager manages user logins and passwords. It is kept very simple and acts
a placeholder for a real user management system that you would be connecting to. In FileBridge, logins
are necessary to demonstrate the Allowable Actions and ACLs features. The
FileBridgeRepository distinguishes between read-only and read-write users, which affects the
Allowable Actions and ACLs.
FileBridgeUtils
This class provides a set of static helper methods. Most of them deal with extracting values from a set
of properties and are mainly used in FileBridgeRepository.
FileBridgeTypeManager
This class manages the CMIS type system. All repositories share the same type system in this
implementation and therefore there is just one instance of this class at runtime.
The type manager is as simple as it can be. It only manages the two base types for documents and
folders. It provides access methods to these type definitions that are similar to the CMIS operations.
This class makes use of the TypeDefinitionFactory, which is an OpenCMIS helper class that
provides methods to create and transform type and property definitions in a CMIS compliant way. It
should work for all servers with a straight forward type hierarchy and provides spec conforming base
type definitions. This is helpful to developers starting with CMIS setting up a spec compliant type
system quickly which can otherwise be somewhat tedious.
Note: In your own custom repository you may need to have a separate set of type definitions per
repository.
Page 28
OpenCMIS Server Development Guide – 2nd Edition
Tutorial exercises
For all of the exercises that follow, we will be using the same project as the one that you downloaded at
the start of this tutorial here: https://github.com/cmisdocs/ServerDevelopmentGuideV2.git
For this guide we will have only one final working version of the source code that includes the server
from part 1 and the plugins from part 2. Our exercises will guide you through specific areas of the
code. First explaining what is going on (often using code comments) then encouraging you to
optionally make any changes you wish and observe the modified output / behavior of the server. This
allows you to stop the tutorial at any time and debug the working server to try changes as they occur to
you.
method.
Lets take a moment to look at the context of how this is first called. If you set a breakpoint set at the
top of this method you will see the following when we startup the server. (below) Note this is getting
called before any client has tried to connect. This is part of the first time initialization and is only
called at startup.
Page 29
OpenCMIS Server Development Guide – 2nd Edition
The red arrow in the illustration above shows the call to the createServiceFactory. There the
init() method is called, which after setting up its 3 manager classes, calls
readConfiguration() passing it the parameters that were read from the
repository.properties file.
At the tail end of readConfiguration(), the constructor for FileBridgeRepository is
called which calls createRepositoryInfo two times. Once with Version.CMIS_1_0 passed in
and once with CmisVersion.CMIS_1_1. You may wonder why do we need two of these?
Recall during the cooks tour in the 'OpenCMIS Server Framework Operation' section, we talked about
how there are 5 servlets setup by the framework. These are divided into two groups, 2 of the endpoints
for CMIS 1.0 and 3 for 1.1 We are pre-populating a unique repository info versioned for each of these.
So if a CMIS 1.0 client comes in on either of the two 1.0 bindings they will get a CMIS 1.0 compliant
repository info structure. Likewise for a CMIS 1.1 client on any of the three CMIS 1.1 bindings.
The structures are the same except for the code that is contained in the block starting with this test:
if (cmisVersion != CmisVersion.CMIS_1_0) {
Please have a look at that code now. You will see these are mostly settings having to do with the new
CMIS 1.1 type mutability features.
Armed with this we now should have a better idea what to do for the
repositoryInfo.setCmisVersionSupported(...)
method. All we need to pass in here is the .value() of the cmisVersion object that was passed to us for
this method.
Page 30
OpenCMIS Server Development Guide – 2nd Edition
Note that you are free to use whatever makes sense in your repository here. It may be a GUID that the
underlying repository uses to identify the root folder, or it can be a special static value. It all depends on
your implementation.
Choosing an ID for your root object is a nice stepping off point to the bigger subject of choosing IDs
for all your objects which we will cover in the next exercise.
With these in mind how would you go about mapping all filesystem objects to a set of unique IDs? You
could use a file's iNode number as its ID. That would certainly be unique and URL friendly but it
would limit us to only Unix / Linux filesystems, so let's skip that one.
We could use the fully qualified path (from the root) of the object. That would be unique for sure, but
then we have the problem of all of those non URL friendly characters like spaces and '/'s not to mention
all of the non-Latin characters that could be in filenames. So what we have done in this sample is
base64 encode the path to get the ID and unencode to get the path. This produces IDs that are URL
friendly and guaranteed to be 1:1 and bi directional. E.g. /home/test → 'aG9tZS90ZXN0' and then back
again.
A note about Base 64 encoding / decoding: OpenCMIS comes with a handy Base64 class (package
org.apache.chemistry.opencmis.commons.impl) Take note of it! This class not only implements base64
in the most efficient manner possible but it also supports base64 encoded streams which you will have
to deal with here on the server side.
Page 31
OpenCMIS Server Development Guide – 2nd Edition
in FileBridgeRepository. This is the one place that gets called when the repository need to
obtain a CMIS ID for a given file.
Please go there now and have a look at the code. Have a quick look over this important method. Place
a break point here if you want to see what the raw paths come out looking like after they have been
encoded. Note how in the case of the root folder we simply return our constant that we discussed in
exercise 1.3.
A note about exceptions: Check the CMIS specification and the OpenCMIS JavaDoc (URL's for both in
Resources section) for exception definitions. Another good starting place to look is here
(http://docs.oasis-open.org/cmis/CMIS/v1.1/os/CMIS-v1.1-os.html#x1-1630001), section 2.2.1.4.1
General Exceptions from the specification itself (the source is always definitive).
Also to note:
If the object exists, then we need to identify if it is a file or folder (nothing to change for this one just be
Page 32
OpenCMIS Server Development Guide – 2nd Edition
aware what is happening). We need to know this because both types have a slightly different set of
properties.
Hints:
The class PropertyIds has constants for the property IDs and indicates when which property has
been introduced. Make sure that you don’t return CMIS 1.1 properties to a CMIS 1.0 client.
Documents and folders share a set of base properties but also have type specific properties. Make sure
that you take this into account.
Make sure you treat the root folder correctly. The root folder is unique in that it has no parent folder.
OpenCMIS provides the helper class MimeTypes that guesses the MIME type of a file.
You can decide how you want to handle empty files. You can return them as documents without content
(and no length and no MIME type) or as an empty document (with length 0 and a MIME type).
Once the properties collection object is created the first thing we need to do is compute the ID for the
object and add it as a property using our addProperty<type>() method mentioned in the
previous exercise. Note that the addProperty<type>() methods are part of FileBridge not
OpenCMIS. More on that soon. Have a look at some of the other completed property types for
examples on how to handle the ID.
Page 33
OpenCMIS Server Development Guide – 2nd Edition
Our code that added all of the properties used the constant values to identify each property we were
adding. (e.g. PropertyIds.BASE_TYPE_ID) So we must be able to convert between those
property IDs and the query names used in filters. To understand how this is done, have a look at one of
the addProperty<type>() methods to see how it calls checkAddProperty() to see if the
property should be added to the response. Next look at the checkAddProperty() method to see
how this is implemented. Pay special attention here to how the TypeManager is used to convert from
the typeId to the Query name so that it can be compared to the filter.
Exercise 4: getContentStream
Now that we have gotten this far things are going to start looking pretty simple. For this exercise
examine the implementation of getContentStream; and in doing so, see some details on how to
handle Offset + Range even if your underlying repository does not support them. The illustration below
shows what the top of the stack will look like after a call to getContentStream.
method in the FileBridgeRepository class and set a breakpoint here. Then retrieve a document
in Workbench to catch it.
Page 34
OpenCMIS Server Development Guide – 2nd Edition
Those of you that are really paying attention have noticed something odd here. Where is that call to
CmisCustomPdfWatermarkServiceWrapper coming from? We are going to cover that in
part 2 of this guide. For now just know it's supposed to be here.
Go ahead and step through the method paying attention to how we would handle offset and range if
they were present. They won't be unless you have built your own client to test this. Workbench just
defaults to retrieving the entire file.
Extra Credit: Construct a groovy script to run inside of Workbench that will retrieve a test text
document using the range and offset parameters to see if our implementation really works.
After inspection you will see that a lot of the work is done in this line
where we initialize a BufferedInputStream with the stream from our local file.
Page 35
OpenCMIS Server Development Guide – 2nd Edition
Unlike the solution project, the lab version of your project represents a generically generated project
from Maven. In order to add logging we are going to have to modify that project in the following
ways:
Modify the pom.xml to include whatever dependencies we need for our logging framework of choice.
Add a log4j.properties file.
Please go to your pom.xml now and verify you have this change.
/src/main/webapp/WEB-INF/classes
At this point we will are ready to add some logging code in the next exercise.
For more information about how slf4j can dynamically plug in logging frameworks at deployment time
please consult the documentation at http://www.slf4j.org/.
Remember that anytime you change the pom.xml you must remember to do a (right click on the
server project) / maven / update project
to have Maven make all of the necessary changes to your Eclipse project. After selecting this option
you will see the dialog in the illustration below:
Page 36
OpenCMIS Server Development Guide – 2nd Edition
Select 'OK' to continue. After Maven has finished processing you can check the libraries view of your
project (properties / Java Build Path / libraries / maven dependencies) and you will now see the project
has been updated with any new dependencies (like log4j and slf4j jars associated).
(verify this now in your own project)
Page 37
OpenCMIS Server Development Guide – 2nd Edition
Now that all of the dependencies are setup we are going to add a log entry for every time the
framework calls the getRepositoryInfos() method on our service.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
then on the first line of getRepositoryInfos we will add the logger (info) line:
At this point if you wish to see how this all works in a normal Tomcat container you may want to build
from the command line to produce a war file in the /target directory.
Recall how we did this earlier in the lab by running
Page 38
OpenCMIS Server Development Guide – 2nd Edition
After running again you should see your logging output in the catalina.out file.
Armed with the knowledge of how to add logging, we should be ready to start running some unit tests
in the next exercise.
Note: A generic OpenCMIS server gets its web.xml file from the framework so it will not normally
appear in your Eclipse project. But since we need to make changes to it we have already added one
and in the process overrode the default framework version. As you must do in your own projects.
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>org.apache.chemistry.opencmis.server.support.filter.LoggingFilter</filter-class>
<init-param>
<param-name>LogDir</param-name>
<param-value>/home</param-value>
</init-param>
<init-param>
<param-name>PrettyPrint</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>LogHeader</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>Indent</param-name>
<param-value>4</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<servlet-name>cmisatom10</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<servlet-name>cmisatom11</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<servlet-name>cmisws10</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<servlet-name>cmisws11</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<servlet-name>cmisbrowser</servlet-name>
Page 39
OpenCMIS Server Development Guide – 2nd Edition
</filter-mapping>
Note the value of LogDir must be set to a valid directory. For the tutorial image we are using /home.
So don't forget to set this value after you uncomment the xml. If you don't the output will go to your
temp directory. e.g. /tmp.
The screen shot in the illustration below is of the home directory after a CMIS Workbench login has
taken place. As you can see there are a total of 8 round trips made in the process of getting the service
document, retrieving the type definitions, getting the root folder children, etc.
Next we will open up a couple of these and see what a typical browser trace looks like.
Page 40
OpenCMIS Server Development Guide – 2nd Edition
GET /server/browser/test/root?objectId=%40root
%40&cmisselector=object&includeAllowableActions=true&includeRelationships=none&rend
itionFilter=cmis%3Anone&includePolicyIds=false&includeACL=false&succinct=true
HTTP/1.1
User-agent: Apache Chemistry OpenCMIS/1.0.0-SNAPSHOT
Authorization: Basic dGVzdDp0ZXN0
Accept-encoding: gzip,deflate
Cache-control: no-cache
Pragma: no-cache
Host: localhost:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
The response contains all of the properties for the folder formatted as JSON since we are using the
browser binding. Here is the full text of the response:
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Server: Apache-Chemistry-OpenCMIS/1.0.0-SNAPSHOT
Cache-Control: private, max-age=0
{
"succinctProperties":{
"cmis:objectId":"@root@",
"cmis:name":"",
"cmis:createdBy":"<unknown>",
"cmis:lastModifiedBy":"<unknown>",
"cmis:creationDate":1379287941000,
"cmis:lastModificationDate":1379287941000,
"cmis:changeToken":null,
"cmis:description":null,
"cmis:secondaryObjectTypeIds":null,
"cmis:baseTypeId":"cmis:folder",
"cmis:objectTypeId":"cmis:folder",
"cmis:path":"\/",
"cmis:parentId":null,
"cmis:allowedChildObjectTypeIds":null
},
"allowableActions":{
"canDeleteObject":false,
"canUpdateProperties":true,
"canGetFolderTree":true,
"canGetProperties":true,
"canGetObjectRelationships":false,
"canGetObjectParents":false,
"canGetFolderParent":false,
"canGetDescendants":true,
Page 41
OpenCMIS Server Development Guide – 2nd Edition
"canMoveObject":false,
"canDeleteContentStream":false,
"canCheckOut":false,
"canCancelCheckOut":false,
"canCheckIn":false,
"canSetContentStream":false,
"canGetAllVersions":false,
"canAddObjectToFolder":false,
"canRemoveObjectFromFolder":false,
"canGetContentStream":false,
"canApplyPolicy":false,
"canGetAppliedPolicies":false,
"canRemovePolicy":false,
"canGetChildren":true,
"canCreateDocument":true,
"canCreateFolder":true,
"canCreateRelationship":false,
"canCreateItem":false,
"canDeleteTree":true,
"canGetRenditions":false,
"canGetACL":true,
"canApplyACL":false
}
}
As you can imagine. Having this level of tracing detail can prove invaluable when you are doing
interoperability tests with 3 rd party CMIS clients.
The objective of a CMIS server is to be able to serve any CMIS client. Compliance with the CMIS
specification is key. To make this compliance easier, OpenCMIS provides a test utility called the Test
Compatibility Kit (TCK), which makes a few hundred calls to the repository and checks if the
repository reacts as defined in the CMIS specification. It covers most areas of the specification and is
an essential testing tool. It cannot replace repository specific tests, though.
In this exercise we run the OpenCMIS TCK against the final version of the FileBridge Repository. The
TCK is a library and can be triggered in many different ways. There is, for example, an Ant task to run
the TCK and all TCK test are also JUnit tests. You can build your own TCK runner or just use the
CMIS Workbench. In this exercise we do the latter.
Open the CMIS Workbench and connect to the FileBridge server. When you press the TCK button in
the Workbench toolbar, a dialog should open that lets you pick the tests you want to run (shown in the
illustration below)
Page 42
OpenCMIS Server Development Guide – 2nd Edition
When the TCK run is done, a new window opens with the results. If you need more details, open the
HTML report (at the bottom of the window). It contains more information and links to the test code.
The illustration below shows the test report output dialog:
Page 43
OpenCMIS Server Development Guide – 2nd Edition
Go through the report and check all warnings and failures. Why do you think the FileBridge server has
failures?
Page 44
OpenCMIS Server Development Guide – 2nd Edition
method. Here take a moment to see how we are currently parsing the .properties file to get a list
of the repositories that we will expose from our getRepositories implementation. Of course, this
is entirely arbitrary how we are doing this, the important thing to note here is that we are maintaining a
list of the repositories and their IDs. Even though your native service may only have one repository that
does not mean that you cannot synthesize additional ones. For example you would have one repository
that serves up the documents, and one that serves up the same documents with with watermarks.
After you have absorbed what is going on here (in this case the code is self explanatory), open up the
repository.properties file for editing.
Recall the location of this file is shown in the illustration for the “A Note about Running From
Windows” section.
(also note that this example will be for Linux. If you are using Windows please modify the file paths to
something with a local drive letter and path as we did earlier.
There is already a section marked as exercise 7. If you uncomment that section you will have
something like this:
Note you can ignore the stuff at the tail end of the file concerning server plugins for now. We will
cover that in part 2.
class=org.example.cmis.server.FileBridgeCmisServiceFactory
login.1 = test:test
login.2 = reader:reader
repository.test = /
repository.test.readwrite = test
repository.test.readonly = reader
repository.test2 = /home
repository.test2.readwrite = test
repository.test2.readonly = reader
Then start your server and reconnect with Workbench. Once you have done a load repositories, you
should see your new 'test2' repository as shown in the illustration below.
Page 45
OpenCMIS Server Development Guide – 2nd Edition
Another example would be to have the code dynamically discover all of the mounted filesystems (e.g.
network drives) (for Windows use each drive letter) and expose each as its own repository (left as an
exercise for the student).
Page 46
OpenCMIS Server Development Guide – 2nd Edition
Other than the extra query capability there are no other 'optional' features needed. In the book “CMIS
and Apache Chemistry in Action”, (see references section) chapter 14 covers the subject of building a
query parser which we will not be able to cover in this short tutorial.
Instead for this tutorial we have added a tiny bit of code to fileBridge so that is can handle two specific
queries namely:
Page 47
OpenCMIS Server Development Guide – 2nd Edition
Page 48
OpenCMIS Server Development Guide – 2nd Edition
Create a shell file to kick off Workbench along these lines (linux version):
#! /bin/sh
export CUSTOM_JAVA_OPTS="\
-Dcmis.workbench.binding=browser \
-Dcmis.workbench.url=http://localhost:8080/server/browser \
-Dcmis.workbench.user=reader \
-Dcmis.workbench.password=reader \
-Dcmis.workbench.compression=true \
-Dcmis.workbench.cookies=true"
Page 49
OpenCMIS Server Development Guide – 2nd Edition
Page 50
OpenCMIS Server Development Guide – 2nd Edition
Engineering requirements
To ease the tasks of development and deployment we defined the following additional requirements:
The image below shows the typical lifecycle of these plugins at a customer site.
Page 51
OpenCMIS Server Development Guide – 2nd Edition
Note that in many cases the same custom plugin can be deployed to any number of vendor's CMIS
implementations assuming it is not doing something vendor specific. For example, a watermarking, or
detailed logging plugin would be vendor neutral.
Page 52
OpenCMIS Server Development Guide – 2nd Edition
As you can see from the diagram above. Plugins (shown in green – xxxWrapper_...) end up
implementing (via their extension of the AbstraceServiceWrapper) the same CmisService
interface as all OpenCMIS servers. Any function they do not override will be handled by another
extension further down the chain and eventually by the main CMIS service itself (shown in blue). The
Page 53
OpenCMIS Server Development Guide – 2nd Edition
CmisServiceWrapper handles reading in the registered plugins from the repository.properties file
and sets up the service chain between them. If the main CmisService adds any objects to the
CallContext either in its ServiceFactory or while processing requests, all of the plugins on
the request chain will be able to have access to that object. A common use case for this is as follows:
The main CMIS service has already setup a connection with the underlying repository. If it shares that
connection with the CallContext then plugins can share that (already authenticated for the call)
connection to improve performance of the plugins. Any object can be shared in the CallContext
including objects that are shared between plugins. Just remember that the order that the plugins are
registered will prevent one plugin from seeing objects added from another added later in the chain.
And the order is reversed between the requests and responses.
You will now add an implements for the CallContextAwareCmisService interface like this:
which opens up the ability to get and set the internal CallContext object. Those accessor functions
are already present, so nothing else to do here.
Page 54
OpenCMIS Server Development Guide – 2nd Edition
The WrapperManager
As we have discussed in the intro section, the CmisServiceWrapperManager is the class that
manages the chain of plugins so it makes sense that we would have to initialize it in the service factory.
First off we will declare it as a private like this:
Then we will allocate it, and initialize it in our init() method like this:
The last line (in bold) sets up the ConformanceCmisServiceWrapper, which is a replacement
of the old way we did this in getService where we would allocate and initialize a new
CmisServiceWrapper and hand it our newly created service object.
In our getService method the main difference is that now that we have the new
wrapperManager, we give it our newly created service implementation so that it can be added into
the bottom of the chain of plugins (if there are any found at runtime). In the case of the
fileShareService the code looks like this:
service =
(CallContextAwareCmisService)wrapperManager.wrap(fileShareService);
threadLocalService.set(service);
The last line passes the fully setup wrapper manager to our
ThreadLocal<CallContextAwareCmisService>
declaration.
Page 55
OpenCMIS Server Development Guide – 2nd Edition
That's all there is. All the rest of your server code will continue to work the same and it will be
unaware if there are any plugins running on top.
Under normal conditions you would be building a server plugin separately then adding its jars at
runtime to the target CMIS server. However in this tutorial we are including the two plugins as part of
our project so that you don't have to manually deploy them every time you want to test a new code
change.
Below you will see a screenshot of the project explorer for our server with the org.foo namespace
highlighted. These two files are the two server plugins that we will be building in the remaining
exercises.
Page 56
OpenCMIS Server Development Guide – 2nd Edition
Page 57
OpenCMIS Server Development Guide – 2nd Edition
The AbstractCmisServiceWrapper
As you can see by looking at the CmisCustomLoggingServiceWrapper.java file. The
signatures of the methods that we override/modify are the same CmisService methods that we
talked about in part I of this guide. If you know how to build CMIS servers with OpenCMIS then you
already know what you need to build server plugins. It's the same interface.
Have a look at the getChildren() method now. Any code you place before the passthrough to the
underlying service getWrappedService().getChildren(...) will be to modify the
request. You can also choose not to call the underlying service at all and completely override with
your own implementation.
Any code placed after the call to the base service will allow you to inspect and or modify the response
from the vendor's CMIS implementation before you pass it on to the client.
mvn archetype:generate \
-DgroupId=org.example.cmis \
-DartifactId=server-extension \
-Dversion=1.0-SNAPSHOT \
-Dpackage=org.example.cmis.server.extension \
-DprojectPrefix=MyExtension \
-DarchetypeGroupId=org.apache.chemistry.opencmis \
-DarchetypeArtifactId=chemistry-opencmis-server-extension-archetype \
-DarchetypeVersion=1.0.0-SNAPSHOT \
-DinteractiveMode=false
Note: This archetype generation step can also be done GUI-style, with the latest version of Eclipse
(with Maven integration). We are leaving that out of the tutorial to save space but feel free to use that
instead (as an extra exercise) if you prefer.
Page 58
OpenCMIS Server Development Guide – 2nd Edition
Note: Adding a plugin to a CMIS implementation that does not support 0.11.0 extensions will have no
effect, so make sure your vendor has added support. If they have not, point them to this guide!
Now that your extension files are on the CMIS server's classpath, all that is left to do is hook them in
by adding some instructions for the WrapperManager into the repository.properties file. This is shown
in the next section.
Registering Plugins
Again this part is already done for you in the sample but if you were doing this to a vendor's CMIS
server this is how you would do it. Your plugins should be registered in the repository.properties file
(located in \WEB-INF\classes) like this:
servicewrapper.1=com.example.my.SimpleWrapper
servicewrapper.2=com.example.my.AdvancedWrapper,1,cmis:document
servicewrapper.3=com.example.my.DebuggingWrapper,testRepositoryId
The number at the key is the position in the wrapper stack. Lower numbers are outer wrappers, higher
numbers are inner wrappers closer to the vendor's service implementation. So the wrapper with key 1
will be the first one executed when a request comes in and the last to see or change the response on the
way out.
Wrappers can have parameters that can be attached as a comma separated list to the class name. They
are provided to the wrapper implementation when the manager calls
AbstractCmisServiceWrapper.initialize(). The last two examples show these optional
parameters.
In the case of our sample wrapper you will want to add the following line:
servicewrapper.1=org.foo.CmisCustomLoggingServiceWrapper
In our FileBridge example this line is already been added for you.
Page 59
OpenCMIS Server Development Guide – 2nd Edition
Now stop your server and set a breakpoint at the first line of the plugin's getChildren method.
Re-start in debug mode and trace through the code as it is executed before and after the underling
server getChildren method.
Page 60
OpenCMIS Server Development Guide – 2nd Edition
Page 61
OpenCMIS Server Development Guide – 2nd Edition
Often while doing client or server development with OpenCMIS you will encounter the need for helper
classes. One such class is the ThresholdOutputStream. Navigate to the
getContentStream again and find the first occurrence of this class. Rather than explain what this
class does we will show you how to find out for yourself by inspecting the code in a really browser
friendly way. You may not be aware that the folks from Apache Chemistry keep their code mirrored in
GitHub here:
https://github.com/apache/chemistry-opencmis
Go to this repository and do a search for ThresholdOutputStream. You will see a few references
as well as the factory. If you look down the list eventually you will find the actual class. Note you
could also just navigate directly to is via the package names. Go to the source listing for the class now
here:
https://github.com/apache/chemistry-
opencmis/blob/9b821292e708c24827ed526d4226b4308eff91bf/chemistry-opencmis-server/chemistry-
opencmis-server-
bindings/src/main/java/org/apache/chemistry/opencmis/server/shared/ThresholdOutputStream.java
Have a look at the comments for the class. We will include part of them here in case you are reading
this tutorial without an internet connection.
/**
* An OutputStream that stores the data in main memory until it reaches a
* threshold. If the threshold is passed the data is written to a temporary
* file.
Take a bit of time to look through the file to see how it works. Now next time you have a question
about why something works the way it does in OpenCMIS you know how to find out!
CmisCustomPdfWatermarkServiceWrapper walkthrough
Since we have already covered the initialize method let's have a quick look over the rest of our
wrapper version of getContentStream. We will deliberately not get into how to user pdfBox
since that is well documented at the PDFBox web site.
(here: https://pdfbox.apache.org/)
The call to getCallContext is an important one to note. Generally most of the sorts of data about
the current request will be found here since you can add whatever custom data you wish to this object.
Note: See the getService call in the FileBridgeCmisServiceFactor class for an example of
setting a custom name value pair in the call context.
Page 62
OpenCMIS Server Development Guide – 2nd Edition
In this case all our work takes place after the call to
getWrappedService().getContentStream() since we need the underlying service to
retrieve the raw content stream for us from the repository. Spend a couple minutes looking all this
over before we run it. If necessary place a break point here and step through a retrieve of a pdf
document.
The last change we will make (in addition to the watermark) is the tweak the PDF metadata with some
static text. This will serve as an example for later if you want to stamp the document with the user's
login name or other identifing information like their ip address. Navigate to the watermarkPDF
towards the bottom of the method. See the line:
pdf.getDocumentInformation().setTitle("Modified by fileBridge");
Have a look at the documentation for PDFBox and see what other metadata you can change this way.
The screenshot in the exercise below shows the result of this static metadata change.
Optional extra: Pick a piece of dynamic information about this transaction (e.g. the time of day, the ip
address of the user, the user's id, etc.) and change some other PDF metadata fields to reflect these
values.
Page 63
OpenCMIS Server Development Guide – 2nd Edition
The arrow shows the CMIS graphic that has been added to the first page in the lower left. Also note the
header of the application showing the new PDF's title “Modified by fileBridge”.
Now check the original PDF by loading it directly from your filesystem to verify only your CMIS copy
was modified.
Page 64
OpenCMIS Server Development Guide – 2nd Edition
First build the FileBridge project from the command line to get a new copy of the WAR file from
the target directory. Then take a copy of the CmisCustomLoggingServiceWrapper and
create a brand new project. Rename the new logging wrapper to something different. The only other
thing you will need is the pom.xml from our sample project and that one java wrapper file. Once
you have it compiling, change the output of the logger so that you will be able to identify it uniquely
from the other output. Then add the jar file for your new logging wrapper to the FileBridge WAR
file's lib directory.
See the 'Deploying the Plugin' section for additional details on this. Next edit the
repository.properties file to include your new wrapper then deploy the new WAR to a test
Tomcat container. Spin it up and see if you missed anything. If it does not work you should have
enough information at this point to debug and find out why.
Page 65
OpenCMIS Server Development Guide – 2nd Edition
Conclusion of Part 2
In part two you were introduced to the concept of CMIS runtime server plugins including their design.
You have seen how to modify the FileBridge server from Part 1 to support these plugins. You have built
your own simple custom plugin that will output logging information for all
folder.getChildren() calls that arrive at your server. Finally, you created a plugin that
watermarks all retrieved PDF documents requested by a flagged user.
We hope this document has been helpful for you in your understanding of building CMIS servers. You
are now armed with enough information to go out and customize CMIS servers that you have
purchased and tailor them specifically for your needs. If you have questions about this material or
suggestions for a future version please contact the authors directly.
Resources
OASIS CMIS 1.1 Specification :
http://docs.oasis-open.org/cmis/CMIS/v1.1/os/CMIS-v1.1-os.html
Eclipse : http://eclipse.org/
SLF4J : http://www.slf4j.org
Page 66