CMIS mit Apache Chemistry – ein Praxisbeispiel

Seite 3: Serverimplementierung

Inhaltsverzeichnis

Auch wenn mehr Applikationsentwickler die Notwendigkeit in einer Client-Bibliothek sehen dürften, skizziert der Artikel grob auch eine Serverimplementierung. Mit dem Server-Framework aus opencmis ist das gar nicht so schwierig. Im Prinzip bringt die Java-Bibliothek bereits den ganzen Server mit. Sie enthält eine Servlet-Klasse und die Konfigurationsdatei web.xml. Entweder kompiliert man diese selbst mit Maven, oder man lädt sich von der Chemistry-Distribution das Paket chemistry-opencmis-dist-0.2.0-incubating-server.zip herunter. Dieses enthält eine fertige Webapplikation (.war), die allerdings erst mal nichts unternimmt. Ein Programmierer eines Servers muss erst für sinnvolle Funktionen sorgen. Im Wesentlichen besteht das aus der Implementierung der Java-Schnittstelle CMISService. Neben der .war-Datei liegt in dem Paket noch die Bibliothek chemistry-opencmis-server-support-0.2.0-incubating.jar. Diese benötigt man beim Implementieren eines Query-Parsers und kopiert sie dann gegebenenfalls ins lib-Verzeichnis der Webapplikation.

Bekanntermaßen ist eine .war-Datei nichts anderes als ein Zip-Archiv. Als Ausgangspunkt entpackt man dieses und nimmt die enthaltene Ordnerstruktur als Vorbild für die eigene Serverimplementierung. Alle benötigten .jar-Bibliotheken für einen Server befinden sich daher im Unterverzeichnis WEB-INF/lib. Die eigene Implementierung der Schnittstelle CMISService (auf die noch eingegangen sei) muss mit dem Server-Framework in Verbindung treten. Dazu gibt es eine Datei repository.properties, die man im WEB-INF/classes-verzeichnis findet und für das man als Beispiel folgenden Eintrag bekommt:

class=de.heise.cmisserver.HeiseServiceFactoryImpl

Diese Factory-Klasse liefert unter anderem die Implementierung von CMISService und sieht wie folgt aus:

public class HeiseServiceFactoryImpl extends AbstractServiceFactory {

@Override
public void init(Map<String, String> parameters) {
}

@Override
public CmisService getService(CallContext context) {
return new HeiseServiceImpl();
}

@Override
public void destroy() {
}
}

Die init()- und destroy()-Methoden lassen sich für Initialisierungscode beim Starten des Servers und bei Aufräumarbeiten am Ende benutzen. Die entscheidende Methode ist getService(), die eine Instanz der eigentlichen Serverimplementierung zurückgibt. Für das Beispiel ist als einzige Methode getRepositoryInfo() zu implementieren:

public class HeiseServiceImpl extends AbstractCmisService {

@Override
public List<RepositoryInfo> getRepositoryInfos(ExtensionsData arg0) {

RepositoryInfoImpl repoInfo = new RepositoryInfoImpl();
String rootFolderId = „1000“;
repoInfo = new RepositoryInfoImpl();

// set capabilities
RepositoryCapabilitiesImpl caps
= new RepositoryCapabilitiesImpl();
caps.setAllVersionsSearchable(false);
caps.setCapabilityAcl(CapabilityAcl.NONE);
caps.setCapabilityChanges(CapabilityChanges.NONE);
caps.setCapabilityContentStreamUpdates(
CapabilityContentStreamUpdates.NONE);
caps.setCapabilityJoin(CapabilityJoin.NONE);
caps.setCapabilityQuery(CapabilityQuery.NONE);
caps.setCapabilityRendition(CapabilityRenditions.NONE);
caps.setIsPwcSearchable(false);
caps.setIsPwcUpdatable(false);
caps.setSupportsGetDescendants(true);
caps.setSupportsGetFolderTree(true);
caps.setSupportsMultifiling(false);
caps.setSupportsUnfiling(false);
caps.setSupportsVersionSpecificFiling(false);

repoInfo.setId("heise");
repoInfo.setName("Heise-Repository");
repoInfo.setDescription("Heise Developer CMIS Demo");
repoInfo.setCmisVersionSupported("1.0");
repoInfo.setCapabilities(caps);
repoInfo.setRootFolder(rootFolderId);
repoInfo.setPrincipalAnonymous("anonymous");
repoInfo.setPrincipalAnyone("anyone");
repoInfo.setThinClientUri(null);
repoInfo.setChangesIncomplete(Boolean.TRUE);
repoInfo.setChangesOnType(null);
repoInfo.setLatestChangeLogToken(null);
repoInfo.setVendorName("Heise Demo");
repoInfo.setProductName("Heise-Developer Demo-Server");
repoInfo.setProductVersion("1.0");
repoInfo.setAclCapabilities(null);

List<RepositoryInfo> repoInfoList = new
ArrayList<RepositoryInfo>();
repoInfoList.add(repoInfo);
return repoInfoList;
}
...
}

In der gleichen Weise verfährt man analog mit den anderen abstrakten Methoden aus AbstractCmisService. Es empfiehlt sich in der Regel, von der Klasse AbstractCmisService abzuleiten, statt direkt das Interface CmisService zu implementieren, denn das spart Arbeit: Für das AtomPub-Protokoll ist etwas mehr Information notwendig, als die Rückgabewerte der einzelnen Methoden aus CmisService zu liefern, um einen vollständigen Atom-Feed oder ein Atom-Entry erzeugen zu können. Die Klasse AbstractCmisService bietet dafür bereits eine Standardimplementierung an, sodass der Entwickler sich darum nicht kümmern muss. (Für produktive Server sei jedoch empfohlen, die Methode getObjectInfo() selber zu implementieren, da das unter Umständen deutlich effizienter als die Standardimplementierung.)

Die restlichen Methoden des CMISService sind reine Fleißarbeit und erfolgen nach dem gleichen Muster. Heraus kommt ein lauffähiger CMIS-Server, der eine einzige Methode sinnvoll beantwortet. Man kann jetzt aus seiner IDE heraus oder dem Build-Werkzeug seiner Wahl eine Webapplikation (cmis-server.war) bauen und diese in das webapp-Verzeichnis des Tomcat kopieren. Für Eclipse steht im Download-Bereich ein vollständiges Projekt (huebel_cmis_eclipse-workspace.zip) bereit. Bei Benutzung der "Eclipse IDE for Java EE Developers" kann man sein Projekt einfach auf einen im Eclipse konfigurierten Webserver (zum Beispiel Tomcat) ziehen. Dadurch nutzt der Entwickler einfach den Debugger, wenn er genauer verstehen möchte, wie der eigene Code aufgerufen wird oder was intern in opencmis läuft.

Hat der Programmierer das .war-Archiv installiert, kann er den Server aufrufen. Der erste Test erfolgt am besten aus dem Webbrowser, in dem man folgende Adresse eingibt:

http://localhost:8080/cmis-server/atom

Der Server antwortet mit einer XML-Datei, die man sich in einem Texteditor ansehen kann (siehe oben). Der Service versteht jedoch auch das SOAP-Binding: Mit

http://localhost:8080/cmis-server/services/RepositoryService?wsdl

bekommt der Entwickler eine WSDL-Datei, die er weiterverarbeiten kann (ersetzt er "RepositoryService" durch die anderen Servicenamen, funktioniert das entsprechend). Ohne die Endung ?wsdl sieht der Programmierer eine HTML-Seite mit allen Webservices. Probiert er die CMIS-Workbench aus, kann er sich immerhin verbinden.

Die CMIS-Workbench kann sich mit dem Beispielserver verbinden (Abb. 2).

Versucht man nun den Login, lädt die Workbench im nächsten Schritt das Typsystem – und mangels Implementierung schlägt das fehl. Möchte der Entwickler mehr Funktionen haben, orientiert er
sich am besten an den zwei Beispielservern in Chemistry: in-memory und fileshare. Vorzugsweise beginnt er mit einem minimalen Typsystem (zunächst reichen cmis:document und cmis:folder), dann implementiert er getChildren im NavigationService, was zumindest bei Übergabe der ID des Root-Folders eine sinnvolle Antwort geben sollte. Von dort kann man sich Stück für Stück weiterhangeln.

Erwähnt sei noch, dass ein Parser für die CMIS-Query-Sprache ziemlich aufwendig zu implementieren ist. Das Server-Framework bietet dazu Hilfe in Form eines integrierten Parsers an, sodass man sich für viele Fälle auf eine relativ einfache Umsetzung auf die spezielle Query-Sprache eines Repository oder direkt auf SQL beschränken kann. Näheres dazu ist auf den Chemistry-Webseiten zu finden.

Chemistry ermöglicht es, mit wenig Aufwand zu einem funktionsfähigen System mit Client und Server zu kommen. Es bietet jedoch weit mehr, als der Artikel schildern kann. Nichts steht darüber hinaus Experimenten mit Python, Groovy oder .NET im Weg. In jedem Fall lohnt es sich, die Projektwebseiten zu besuchen, die in den nächsten Wochen weiter ausgebaut und vervollständigt werden. Auch die Mailing-Liste bietet sich für weitere Fragen an.

Jens Hübel
ist bei OASIS im CMIS Technical Committee Vertreter für die Open Text Software, für die er als Softwarearchitekt in München arbeitet. Außerdem ist er Committer bei der Apache Software Foundation. Er beschäftigt sich seit vielen Jahren beruflich mit Enterprise Content Management.

  1. Jens Hübel; An einem Strang; CMIS: Eine Sprache für das Content Management; in iX 3/2011, S. 128
  2. Christian Thiede; Glücksgriff; CMIS – neuer Standard für die ECM-Industrie; Artikel auf heise Developer

(ane)