Return-Path: Delivered-To: apmail-incubator-chemistry-commits-archive@minotaur.apache.org Received: (qmail 84540 invoked from network); 20 Feb 2011 22:32:31 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 20 Feb 2011 22:32:31 -0000 Received: (qmail 25697 invoked by uid 500); 20 Feb 2011 22:32:31 -0000 Delivered-To: apmail-incubator-chemistry-commits-archive@incubator.apache.org Received: (qmail 25646 invoked by uid 500); 20 Feb 2011 22:32:31 -0000 Mailing-List: contact chemistry-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: chemistry-dev@incubator.apache.org Delivered-To: mailing list chemistry-commits@incubator.apache.org Received: (qmail 25638 invoked by uid 99); 20 Feb 2011 22:32:31 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 20 Feb 2011 22:32:31 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 20 Feb 2011 22:32:23 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id DE35123889D7; Sun, 20 Feb 2011 22:32:01 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r785815 - /websites/staging/chemistry/trunk/content/how-to-build-a-server.html Date: Sun, 20 Feb 2011 22:32:01 -0000 To: chemistry-commits@incubator.apache.org From: buildbot@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20110220223201.DE35123889D7@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: buildbot Date: Sun Feb 20 22:32:01 2011 New Revision: 785815 Log: Staging update by buildbot Modified: websites/staging/chemistry/trunk/content/how-to-build-a-server.html Modified: websites/staging/chemistry/trunk/content/how-to-build-a-server.html ============================================================================== --- websites/staging/chemistry/trunk/content/how-to-build-a-server.html (original) +++ websites/staging/chemistry/trunk/content/how-to-build-a-server.html Sun Feb 20 22:32:01 2011 @@ -172,7 +172,594 @@ Apache Chemistry - How To Build A Server -
+

+

How to create a CMIS server using OpenCMIS

+

This document contains a step-by-step introduction how you can use opencmis +to build an CMIS server. The document is divided iton the following +sections:

+

Getting started. (download, setup Eclipse, etc.)

+
    +
  1. Implementing the services
  2. +
  3. The ServiceWrapper
  4. +
  5. Differences between SOAP and AtomPub (ObjectHolder)
  6. +
  7. Running the server
  8. +
  9. Handling Authentication
  10. +
  11. Testing the server
  12. +
+

+

Getting started:

+

The following step-by-step guide is a sample how to create a web +application acting as CMIS server. It will support both bindings web +services and AtomPub.

+

The following section describes how to initially setup a project to compile +a CMIS server. Please note that CMIS comes with two built-in servers. The +fileshare and the in-memory server. It is a good hint for all upcoming +questions to look at these implementations as example code containing +working implementations.

+

+

Using maven

+

Using maven is the easiest way to get started. OpenCMIS itself is using +maven and you can get easily your setup and dependencies using maven. This +requires that you have a working maven environment. In case you don't yet +have one you can find instructions here +.

+

The first step is to create your initial pom.xml file to build your project +and to setup the dependencies. The following steps require that you have +build opencmis and installed it to your local maven repository {{(mvn +install)}}. See here + for detailed instructions how to do this.

+

You can create your initial setup using maven itself (adjust package name, +version number and so on according to your needs):

+
mvn archetype:generate -DgroupId=org.mycmis -DartifactId= mycmissrv \
+-DpackageName=local.mycmis -Dversion=0.1
+
+ + +

Then select option 18 maven-archetype-webapp and confirm settings.

+

You will find a project setup then consisting of a directory mycmissvr +and subdirectories for source code and test code and the Java packages. We +need to adapt the .pom file so that maven creates a web application file +that can be deployed in a servlet container (like Apache Tomcat or Jetty). +We also need to setup the dependencies to opencmis and we need to instruct +maven generating an overlay with the existing server code in opencmis. +Please remove therefore the entire generated src/webapp directory and all +included files (.jsp, web.xml). +The final pom.xml* will look like this:

+

{code:xml|title=pom.xml|borderStyle=solid} + + 4.0.0 + org.mycmis + mycmissrv + war + 0.1 + mycmissrv Maven Webapp + http://maven.apache.org

+

+ 0.1.0-incubating-SNAPSHOT + org.apache.chemistry.opencmis +

+

+ mycmissrv + + + org.apache.maven.plugins + maven-war-plugin + + + + + + ${opencmisGroupId} + chemistry-opencmis-server-bindings + + + + + + maven-compiler-plugin + + 1.5 + 1.5 + UTF-8 + + + +

+

+ + junit + junit + 3.8.1 + test + + + ${opencmisGroupId} + chemistry-opencmis-commons-api + ${opencmisVersion} + + + ${opencmisGroupId} + chemistry-opencmis-commons-impl + ${opencmisVersion} + + + ${opencmisGroupId} + chemistry-opencmis-test-util + ${opencmisVersion} + + + ${opencmisGroupId} + chemistry-opencmis-server-bindings + ${opencmisVersion} + war + + + ${opencmisGroupId} + chemistry-opencmis-server-support + ${opencmisVersion} + + + org.antlr + antlr-runtime + 3.1.3 +
+
+

+
You will get your {{web.xml}} and other required files for your web
+
+ + +

application from opencmis which you may adjust according to your needs. You +may run {{mvn eclipse:eclipse}} to generate the required {{.projects}} and +{{.classpath}} files for Eclipse that you just need to import in your +Eclipse workspace.

+
h2. Without maven
+
+If you do not want to use maven to build your project you currently have to
+
+ + +

use maven to produce the binaries from the source code. Download the source +code and run

+
{noformat}
+mvn package
+{noformat}
+to produce a binary version of all packaged files. To reuse the relevant
+
+ + +

files from the server implementation in opencmis type:

+
{noformat}
+cd chemistry-opencmis-server
+cd chemistry-opencmis-server-bindings
+{noformat}
+
+Unzip the file
+
+ + +

{{chemistry-opencmis-server-bindings-0.1-incubating-SNAPSHOT.jar}} and copy +the contents to your project. It includes all required jars, web.xml and +other supporting files. In case you prefer an empty classes directory and +use a jar instead you can use:

+
{noformat}
+mvn jar:jar
+{noformat}
+Then copy the file named {{opencmis-server-impl-0.1-SNAPSHOT.jar}} from the
+
+ + +

target directory to your project directory. Add all jar files from the +{{.war}} and the one created in the previous step to your project setup. +Add the {{repository.properties}} file to your classpath.

+
Now you have an initial {{web.xml}} and the required support files for the
+
+ + +

web services binding. Unless you have specific requirements you do not need +to modify them.

+

Implementing the services

+
The first step is to tell opencmis where it can find your classes that
+
+ + +

implement the CMIS logic. For this purpose a file named +repository.properties must be in the classpath. It must contain a property +named class that refers to your service factory:

+
{noformat}
+class=local.mycmis.ServiceFactory
+{noformat}
+
+The ServiceFactory class should extend the {{AbstractServiceFactory}} class
+
+ + +

from org.apache.chemistry.opencmis.commons.impl.server. You have to +implement the interface CmisServiceFactory:

+
{code:java|borderStyle=solid}
+  public void init(Map<String, String> parameters) {
+    }
+
+  public void destroy() {
+    }
+
+  public abstract CmisService getService(CallContext context);
+
+ + +

The init() method is used to perform initialization and passes a set of +configuration parameters from repository.properties:

+

{code:java|borderStyle=solid} +@Override +public void init(Map parameters) { +}

+
Typically you will create an instance of your service implementation.
+
+There is also wrapper classes is explained in the next section.
+
+The next step is that you need to implement the services according to the
+
+ + +

CMIS spec. We will do this here for one example call. The important piece +is that your class implements {{CmisService}} which is central access point +to all incoming calls. Here is an implementation of an example call +getRepositoryInfo():

+
{code:java|borderStyle=solid}
+public class MyCmisServiceImpl extends AbstractCmisService {
+
+    public RepositoryInfo getRepositoryInfo(CallContext context,
+      String repositoryId, ExtensionsData extension) {
+
+        RepositoryInfoImpl repoInfo = new RepositoryInfoImpl();
+        repoInfo.setRepositoryId(repositoryId);
+        repoInfo.setRepositoryName("My CMIS Repository");
+        repoInfo.setRepositoryDescription("Sample Repository");
+        repoInfo.setCmisVersionSupported("1.0");
+        repoInfo.setRepositoryCapabilities(null);
+        repoInfo.setRootFolder("MyRootFolderId");
+        repoInfo.setPrincipalAnonymous("anonymous");
+        repoInfo.setPrincipalAnyone("anyone");
+        repoInfo.setThinClientUri(null);
+        repoInfo.setChangesIncomplete(Boolean.TRUE);
+        repoInfo.setChangesOnType(null);
+        repoInfo.setLatestChangeLogToken(null);
+        repoInfo.setVendorName("ACME");
+        repoInfo.setProductName("ACME CMIS Connector");
+        repoInfo.setProductVersion("0.1");
+
+        // set capabilities
+        RepositoryCapabilitiesImpl caps = new
+            RepositoryCapabilitiesImpl();
+        caps.setAllVersionsSearchable(false);
+        caps.setCapabilityAcl(CapabilityAcl.NONE);
+        caps.setCapabilityChanges(CapabilityChanges.PROPERTIES);
+
+        caps.setCapabilityContentStreamUpdates(
+            CapabilityContentStreamUpdates.PWCONLY);
+        caps.setCapabilityJoin(CapabilityJoin.NONE);
+        caps.setCapabilityQuery(CapabilityQuery.METADATAONLY);
+        caps.setCapabilityRendition(CapabilityRenditions.NONE);
+        caps.setIsPwcSearchable(false);
+        caps.setIsPwcUpdatable(true);
+        caps.setSupportsGetDescendants(true);
+        caps.setSupportsGetFolderTree(true);
+        caps.setSupportsMultifiling(true);
+        caps.setSupportsUnfiling(true);
+        caps.setSupportsVersionSpecificFiling(false);
+            repoInfo.setRepositoryCapabilities(caps);
+
+        AclCapabilitiesDataImpl aclCaps = new AclCapabilitiesDataImpl();
+        aclCaps.setAclPropagation(AclPropagation.REPOSITORYDETERMINED);
+        aclCaps.setPermissionDefinitionData(null);
+        aclCaps.setPermissionMappingData(null);
+        repoInfo.setAclCapabilities(aclCaps);
+        return repoInfo;
+      }
+     // ...
+}
+
+ + +

Now you can start implementing all the required methods with their methods.

+

+

The ServiceWrapper

+

For the CmisService interface exists a corresponding abstract class. This +can be used as a starting point for your implementation. This is optional +but gives you the benefit of providing many common null pointer checks and +default values for optional parameters. The abstract class also provides a +default implementation for generating the ObjectInfo needed for the AtomPub +binding. This wrapper can reduce the code required in your service +implementation. You should always start using the wrapper class and +directly implement the CmisService interface only when necessary. It is +strongly recommended however to provide a better implementation for create +the ObjectInfo however (see next section for details).

+

+

Differences between the CMIS bindings

+

OpenCMIS supports both the AtomPub and the web services binding interface. +It hides all the details how to handle the protocol but there are some +subtle differences that need to be reflected in the Java interfaces. This +chapter explains where the differences are and why they are needed.

+

+

The ObjectInfo interface

+

The methods in the interfaces CmisService are following the data model +in the CMIS specification. The specification defines the following +services:

+
    +
  • AclService
  • +
  • DiscoveryService
  • +
  • ObjectService
  • +
  • MultifFlingService
  • +
  • NavigationService
  • +
  • PolicyService
  • +
  • RelationshipService
  • +
  • RepositoryService
  • +
  • Versioning Service
  • +
+

For each of these services exists a corresponding interface in opencmis. +CmisService is an interface that unifies all interfaces in one interface. +The methods in this interface correspond to the methods in the +specification. For the web service binding this is a one-to-one mapping. +For the AtomPub binding this information is not sufficient in all cases. +Take the creation of a document as an example. The web service returns in +this case a single string value containing the id of the created document. +A response in the AtomPub binding however consists of an AtomPub entry +element which is an XML fragment (for an example look at +DocumentEntry.xml in the examples directory of the CMIS specification). +You will notice that generating this XML requires much more information +than the create...() methods in the specification provide as return value. +Your implementation needs to provide all the information so that opencmis +can generate the complete response. For this purpose the ObjectInfo was +introduced. getObjectInfo is the method of the CmisService interface +that provides this information. The AbstractCmisService contains a +member objectInfoMap that you can fill with this information. If you +ignore this the class AbstractCmisService contains a default +implementation which works but is very ineffecient. You should use this +only as a starting point. Beware that a service can be called from multiple +threads, so have to take care to handle threading issues properly.

+

If the method you are implementing returns a list of objects and not only a +single value (for example methods like getChildren(), getDescendants() +in the navigation service) you need to provide an ObjectInfo for each +element in the collection. getObjectInfo therefore returns a map with +the object id for each object as key and the corresponding ObjectInfo +as value. You can use the method addObjectInfo to add an element to the +map.

+

+

The create() methods

+

The web service binding has separate calls for each object type to be +created: policies, relationshiops, documents and folders:

+
    +
  • createDocument
  • +
  • createFolder
  • +
  • createRelationship
  • +
  • createPolicy
  • +
+

In the AtomPub binding there is one general create() method that is +used for all object types. The CmisObjectService interface therefore +contains 5 create() methods, the four specific ones are used for the +web service bindings and the general one for the AtomPub binding.

+

+

Applying ACLs

+

The class CmisAclService has two methods applyAcl(). One uses two +AccessControlLists with one Acl to add  and one to remove. The other +method contains only one Acl to be set on the target object. The web +service binding uses the method with two parameters, the AtomPub binding +uses the method with one parameter.

+

+

Running The Server

+

When your services are implemented (or parts of them) you can try to run +the server in a servlet container like Tomcat. If you have used maven to +setup your project you can run

+
mvn package
+
+ + +

to generate a war file that you will find in the target directory. If you +have not used maven create the war with the mechanisms of your build +environment. Check the war contains a  web.xml, the wsdl files in +WEB-INF and all the required jars from opencmis and dependent libraries +in WEB-INF/lib. For jetty maven contains a build-in integration that +you may use if you like using mvn jetty:run. Adjust your pom.xml in +this case accordingly.

+

Deploy your application and start testing.

+

+

Handling Authentication

+

Opencmis does not provide or expect any specific mechanism how +authentication is handled. It provides some basic mechanisms hot to extract +user name and password depending on the protocol. For AtomPub binding http +basic authentication is supported, for web services WS-Security 1.1 for +Username Token Profile 1.1 is supported.

+

If you want to use servlet filters dealing with authentication,  just +add them to your web.xml file. You also can handle authentication inside of +your code. In this case derive a class from BasicAuthCallContextHandler +and implement method getCallContextMap(). There you have access to user +name and password as provided by the user. By default opencmis does not +enforce authentication, so initially the map will be null. If you raise a +CmisPermissionDeniedException the exception is caught by the server +implementation and a 401 http return code is sent as response to the +browser. This usually opens a dialog for user name and password then.

+

{code:java|borderStyle=solid} +public class MyContextHandler + extends BasicAuthCallContextHandler +{ + public Map getCallContextMap(HttpServletRequest request){

+
// call superclass to get user and password via basic authentication
+Map<String, String>  ctxMap = super.getCallContextMap(request);
+
+if (null == ctxMap)
+  // no user name, password given yet say: we need authentication:
+  throw new CmisPermissionDeniedException("Authentication required");
+
+// call your authentication
+
+MyAuthentication.login(
+    ctxMap.get(CallContext.USERNAME),
+    ctxMap.get(CallContext.PASSWORD));
+
+return ctxMap;
+
+ + +

} +}

+
Beyond this it is up to you how to implement authentication. Creating
+
+ + +

tokens for example, using cookies, etc. is not covered by opencmis, but you +can add it in your code.

+

Configuring your server

+
Opencmis reads a file repository.properties on startup. By default you only
+
+ + +

have to configure the class of your service factory (see above). You can +add additional properties in this file. These configuration parameters are +then passed to the {{init()}} method of your ServiceFactory method as key +value pairs in a hashmap.

+
{code:title=repository.properties|borderStyle=solid}
+  class=local.mycmis.ServiceFactory
+  myparam=my-configuration-value
+
+ + +

{code:java|title=local.mycmis.ServiceFactory.java|borderStyle=solid} +// ... +@Override +public void init(Map parameters) { + String myParamValue = parameters.get("myparam"); + // use myParamValue +} +// ...

+

Testing the server

+
There are various ways how you can test your implementation. You may add
+
+ + +

unit tests that directly call your service implementations as a first step. +Opencmis also contains some basic tests that perform client-server +communication. You can choose the protocol binding and whether read-only or +read-write tests are performed. The amount of functionality tested depends +on the capabilities returned in your getRepositoryInfo return value. You +can run them using junit:

+
Examples:
+
+Test class:
+{noformat}
+org.apache.chemistry.opencmis.client.bindings.atompub.SimpleReadOnlyTests
+{noformat}
+
+Test Parameters (passed as JVM args):
+{noformat}
+-Dopencmis.test=true
+-Dopencmis.test.username=myuser
+-Dopencmis.test.password=mypasswd
+-Dopencmis.test.repository=my_repository_id&nbsp;
+-Dopencmis.test.atompub.url=[http://localhost:8080/opencmis/atom]
+{noformat}
+
+The following test classes exist:
+{noformat}
+org.apache.opencmis.client.bindings.atompub.SimpleReadOnlyTests
+org.apache.opencmis.client.bindings.atompub.SimpleReadWriteTests
+org.apache.opencmis.client.bindings.webservices.SimpleReadOnlyTests
+org.apache.opencmis.client.bindings.webservices.SimpleReadWriteTests
+{noformat}
+
+For web services you need an additional parameter for the service endpoint:
+{noformat}
+-Dopencmis.test.webservices.url=[http://localhost:8080/opencmis/services/]
+{noformat}
+
+ + +

Test using curl

+
Simple tests (which might be useful at the beginning) can also be done
+
+ + +

using tools like curl or wget (AtomPub binding only). A simple example for +a .BAT file (Windows) would look like this:

+
{noformat}
+rem set PATH=...\libcurl-7.19.7;...\OpenSSL\bin;%PATH%
+set CURL=curl.exe
+set VIEWER="C:\Program Files\Mozilla Firefox\firefox.exe"
+set USER=XXX
+set PWD=YYY
+set URLPREFIX=[http://localhost:8080/opencmis/atom]
+SET OUTFILE=atom.xml
+
+%CURL% \--user %USER%:%PWD% \--dump-header header.txt \--output %OUTFILE%
+
+ + +

%URLPREFIX% + IF ERRORLEVEL 0 %VIEWER% %OUTFILE% + {noformat}

+
Another example how to get a document with id MyDocument:
+
+{noformat}
+%CURL% \--user %USER%:%PWD% \--dump-header header.txt \--output %OUTFILE%
+
+ + +

%URLPREFIX%/A1/entry?id=%%2FMyDocument + {noformat}

+
You also can use this to create documents:
+
+{noformat}
+%CURL% \--user %USER%:%PWD% \--header "Content-Type:
+
+ + +

application/atom+xml;type=entry" -d @post-item.xml --output +%OUTFILE%  --url %URLPREFIX%/A1/children?id=/ + {noformat}

+
The file {{post-item.xml}} must contain a valid Atom entry then:
+
+{code:xml title=post-item.xml|borderStyle=solid}
+<?xml version="1.0" encoding="utf-8"?>
+<entry xmlns="http://www.w3.org/2005/Atom"
+    xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200908/"
+    xmlns:cmisra="http://docs.oasis-open.org/ns/cmis/restatom/200908/"
+    xmlns:app="http://www.w3.org/2007/app">
+  <id>http://vmelcmis1:8080/cmis/atom/cd-library/main/Default-I10915</id>
+  <author>
+    <name>Admin</name>
+  </author>
+  <updated>2009-03-01T00:00:00.000Z</updated>
+  <title type="text">Content from Curl</title>
+  <cmisra:object>
+    <cmis:properties>
+      <cmis:propertyId propertyDefinitionId="cmis:name">
+    <cmis:value>Content from Curl</cmis:value>
+      </cmis:propertyId>
+      <cmis:propertyId propertyDefinitionId="cmis:objectTypeId">
+    <cmis:value>cmis:document</cmis:value>
+      </cmis:propertyId>
+    </cmis:properties>
+  </cmisra:object>
+
+  <cmisra:content type="text/plain">
+  <cmisra:base64>
+RmF1c3Q6DQoNCk1laW4gc2No9m5lcyBGcuR1bGVpbiwgZGFyZiBpY2ggd2FnZW4sDQpNZWlu
+ZW4gQXJtIHVuZCBHZWxlaXQgSWhyIGFuenV0cmFnZW4/DQoNCk1hcmdhcmV0ZToNCg0KQmlu
+IHdlZGVyIEZy5HVsZWluLCB3ZWRlciBzY2j2biwNCkthbm4gdW5nZWxlaXRldCBuYWNoIEhh
+dXNlIGdlaG4uDQoNCihTaWUgbWFjaHQgc2ljaCBsb3MgdW5kIGFiLikNCg0KRmF1c3Q6DQoN
+CkJlaW0gSGltbWVsLCBkaWVzZXMgS2luZCBpc3Qgc2No9m4hDQpTbyBldHdhcyBoYWIgaWNo
+IG5pZSBnZXNlaG4uDQpTaWUgaXN0IHNvIHNpdHQtIHVuZCB0dWdlbmRyZWljaCwNClVuZCBl
+dHdhcyBzY2huaXBwaXNjaCBkb2NoIHp1Z2xlaWNoLg0KRGVyIExpcHBlIFJvdCwgZGVyIFdh
+bmdlIExpY2h0LA0KRGllIFRhZ2UgZGVyIFdlbHQgdmVyZ2XfIGljaCdzIG5pY2h0IQ0KV2ll
+IHNpZSBkaWUgQXVnZW4gbmllZGVyc2NobORndCwNCkhhdCB0aWVmIHNpY2ggaW4gbWVpbiBI
+ZXJ6IGdlcHLkZ3Q7DQpXaWUgc2llIGt1cnogYW5nZWJ1bmRlbiB3YXIsDQpEYXMgaXN0IG51
+biB6dW0gRW50evxja2VuIGdhciE=
+  </cmisra:base64>
+  </cmisra:content>
+</entry>
+