Return-Path: X-Original-To: apmail-chemistry-commits-archive@www.apache.org Delivered-To: apmail-chemistry-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D30A46CF7 for ; Wed, 22 Jun 2011 09:39:52 +0000 (UTC) Received: (qmail 28697 invoked by uid 500); 22 Jun 2011 09:39:52 -0000 Delivered-To: apmail-chemistry-commits-archive@chemistry.apache.org Received: (qmail 28660 invoked by uid 500); 22 Jun 2011 09:39:52 -0000 Mailing-List: contact commits-help@chemistry.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@chemistry.apache.org Delivered-To: mailing list commits@chemistry.apache.org Received: (qmail 28652 invoked by uid 99); 22 Jun 2011 09:39:52 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 22 Jun 2011 09:39:52 +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; Wed, 22 Jun 2011 09:39:44 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 7482C23889DA; Wed, 22 Jun 2011 09:39:21 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1138360 - in /chemistry/site/trunk/content/java/developing: guide.mdtext images/ images/FileTree.png images/todo.gif Date: Wed, 22 Jun 2011 09:39:21 -0000 To: commits@chemistry.apache.org From: fmui@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20110622093921.7482C23889DA@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: fmui Date: Wed Jun 22 09:39:21 2011 New Revision: 1138360 URL: http://svn.apache.org/viewvc?rev=1138360&view=rev Log: Added developers guide Added: chemistry/site/trunk/content/java/developing/guide.mdtext (with props) chemistry/site/trunk/content/java/developing/images/ chemistry/site/trunk/content/java/developing/images/FileTree.png (with props) chemistry/site/trunk/content/java/developing/images/todo.gif (with props) Added: chemistry/site/trunk/content/java/developing/guide.mdtext URL: http://svn.apache.org/viewvc/chemistry/site/trunk/content/java/developing/guide.mdtext?rev=1138360&view=auto ============================================================================== --- chemistry/site/trunk/content/java/developing/guide.mdtext (added) +++ chemistry/site/trunk/content/java/developing/guide.mdtext Wed Jun 22 09:39:21 2011 @@ -0,0 +1,1111 @@ +Title: OpenCMIS Client API Developer's Guide +Notice: Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +#OpenCMIS Client API Developer's Guide +##Audience +This guide is for software engineers who want to use the Apache Chemistry OpenCMIS client to access CMIS-complaint +content repositories from java code. The examples included in the guide assume the code is running in +a standalone Java application, but the OpenCMIS client can be used by on any Java platform, +such as Web applications and phone apps. +###How to use this guide +The guide is divided into 5 parts :- + + 1. *The Basics* - A brief introduction to CMIS and a practical "hello world" sample. Read this section to find out how to build, install and run a sample OpenCMIS application using Apache Chemistry. + 1. *Getting Started with OpenCMIS* - quick, practical examples of the the most commonly needed CMIS function. + 1. *Advanced CMIS features* - the rest of the CMIS API + 1. *OpenCMIS bindings* - the 2 CMIS 1.0 bindings, and when and how to use them. + 1. *Troubleshooting* - common pitfalls you may encounter when using OpenCMIS. + +###Assumptions +The guide assumes you are using the Eclipse IDE for code development, and have Maven and SVN installed. + +* You can download and install Eclipse [here](http://eclipse.org/downloads.html). +* You can download and install Maven [here](http://maven.apache.org/download.html). +* You can download and install SVN for your platform [here](http://subversion.apache.org/packages.html). + +##The basics +This section gives a high-level overview of the OpenCMIS library, and introduces a supplied "Hello World" sample to +get an OpenCMIS client up and running against a repository and accessing CMIS objects. +###What is OpenCMIS? +CMIS (Content Management Interoperability Services) is vendor-neutral [OASIS Web services interface specification](http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=cmis) +that enables interoperability between Enterprise Content Management (ECM) systems. +CMIS allows rich information to be shared across Internet protocols in vendor-neutral formats, +among document systems, publishers and repositories, in a single enterprise and between companies. + +OpenCMIS is a collection of Java libraries frameworks and tools based on the CMIS specification. +It is available under the open source [Apache License](http://www.apache.org/licenses/) from the +[Apache Chemistry project](http://chemistry.apache.org/). +The OpenCMIS Client API is a Java client-side library that implements the CMIS specification. You can see the Javadoc for the +OpenCMIS Client API [here](http://incubator.apache.org/chemistry/javadoc/org/apache/chemistry/opencmis/client/api/package-summary.html). +CMIS defines a domain model that describes a vendor-neutral ECM repository and its contents, a set of services to work with the +repository and its content, and a set of bindings which a client uses to access the services. +####The CMIS Domain Model +CMIS defines a domain model. A client will access a CMIS service endpoint described by a URL. A service endpoint must have at least one repository. +A repository is a data store and contains content. Each item of content is an object such as a folder, or a document. A repository is identified by its ID, and has a set of capabilities which describe what optional +CMIS functionality the repository supports. + +An object has an ID, has a type, and has set of properties for that type. +There are four base types for a CMIS object :- + +* *Document* - is an item of content. The document may have a content stream, which is the actual file associated with the document. +A content stream exists only as part of its containing document object. A content stream has a mimetype associated with it. +A document object may contain one or more renditions, which are alternate views of the content. +Documents objects are the only objects that are *versionable*. +Each version of a document has its own object ID. All the versions of a document make up a version series and share a version series ID. +You can create, read, update and delete documents using OpenCMIS methods. +* *Folder* - is a container used to organize the document objects. A repository had one root folder. All other folder objects +have one parent folder. A folder has a folder path representing its place in the repository's folder hierarchy. +A folder object can have renditions. For example, a folder can have a thumbnail as a rendition representing the contents of the folder. +* *Relationship* - define relationships between a source object and a target object. +Creating, changing and deleting relationships do not change the source or target objects themselves. +Relationship objects are optional. A CMIS repository may not support relationship objects. +* *Policy* - are optional repository-specific objects that can be applied to controllable objects. The behavior of policies are not modeled by the CMIS specification. +A policy object may be applied to multiple controllable objects and a controllable object may have multiple policies applied to it. +A policy object can not be deleted if it is currently applied to one or more controllable objects. + +There can be other object types in the repository, defined in a repository as subtypes of these base types. +CMIS services are provided for the discovery of object-types that are defined in a repository, but for the moment +folders and documents are the main objects you will be concerned with for the first 2 parts of this document. +####CMIS Services +The CMIS specification describes a set of services that act on repositories. The OpenCMIS client API uses these services, but by using a client binding, +presents the user of the API simple set of classes rather than the services described by CMIS. If you want to access a CMIS repository at the service level, you can do that +by using the [OpenCMIS client bindings layer](http://chemistry.apache.org/java/developing/client/dev-client-bindings.html) directly. +The CMIS services are:- + +* *Repository services* - let you discover available repositories, get the capabilities of these repositories, and provide basic Data Dictionary information of what types are available in the repository. +* *Navigation services* - let you navigate the repository by accessing the folder tree and traversing the folder/child hierarchy. You can use these services to get both children and parents of an object. +* *Object services* - provide the basic CRUD (Create, Read, Update, Delete) and Control services on any object, including document, folder, policy, and relationship objects. For document objects, this includes setting and getting of properties, policies, and content streams. Object services retrieve objects by path or object ID. Applications may also discover what actions users are allowed to perform. +* *Multi-filing services* - let you establish the hierarchy by adding or removing an object to or from a folder. +* *Discovery services* - provide Query and Change services, and a means of paging the results of the query. +* *Change services* - let you discover what content has changed since the last time checked, as specified by a special token. You can use Change services for external search indexing and replication services. +* *Versioning services* - control concurrent operation of the Object services by providing Check In and Check Out services. Version services also provide version histories for objects that are versioned. +* *Relationship services* - let you create, manage, and access relationships or associations between objects as well as allow an application to traverse those associations. +* *Policy services* - apply policies on document objects. Policies are free-form objects and can be used by implementations for security, record, or control policies. +* *ACL services* - let you create, manage, and access Access Control Lists to control who can perform certain operations on an object. + +####CMIS Bindings +How does an OpenCMIS client communicate over the wire with a CMIS service endpoint? A CMIS repository can communicate over two protocols, +SOAP and [AtomPub](http://tools.ietf.org/html/rfc5023), and provide tow corresponding bindings, a web services binding, and an AtomPub binding. A CMIS service endpoint will +provide a URL for both types of binding, and in an OpenCMIS you specify the binding type when calling the `getRepositories()` method on +the `SessionFactory` object. +####The OpenCMIS Workbench +The workbench is a GUI repository browser that lets you quickly view the contents of a repository, create documents and folders, look at metadata, and +perform queries. It's really useful for debugging your applications, and for quickly testing out queries before you commit them to code. You can +download the latest version [here](http://incubator.apache.org/chemistry/cmis-workbench.html). The workbench is a Java Swing application, and comes +with both Windows and UNIX starter scripts. +###Hello World +You can download, install, and test the Apache Chemistry client code using the `Hello World` sample. On a command line, type the following command :- +

Need a full URL here

+ + svn co https://svn.apache.org/repos/asf/chemistry/opencmis/trunk/chemistry-opencmis-samples/chemistry-opencmis-hello + +You can then build the sample and run the `org.apache.chemistry.opencmis.doc.Hello` sample using maven, by typing the following command + + mvn clean install + +You will see some output towards the end of the build that looks similar to the following lines:- + + org.apache.chemistry.opencmis.doc.Hello started + Found repository: InMemory Repository + Got a connection to repository: InMemory Repository, with id: A1 + Found the following objects in the root folder:- + MasterClass + My_Document-0-0 + My_Document-0-1 + My_Document-0-2 + My_Folder-0-0 + My_Folder-0-1 + org.apache.chemistry.opencmis.doc.Hello ended + +The Hello World sample has connected to a public CMIS repository and retrieved all the objects in the root folder of that repository. +The simplest way to view the sample code is to use eclipse + +1. Add the maven repository to your eclipse workspace classpath with the following command. You only need to perform this step once. + + `mvn eclipse:configure-workspace -Declipse.workspace=` + +1. Create a an eclipse project from the code, and import it into your eclipse. +Type the following command :- + + `mvn eclipse:eclipse` + +1. Import the project into eclipse:- + + 1. click `File->Import` and choose `General->Existing projects into workspace` in the + Import wizard. + 1. Click `Next` and browse to the folder you extracted the project to. + 1. Click the checkbox next to `chemistry-opencmis-hello` project. The project will be imported into your workspace. + +You can run the `main` method in the `Hello` class as a Java application. + +Once you have successfully run the `Hello World` sample, you can start to get familiar with the core parts of the +OpenCMIS API in the next section. +##Getting Started with OpenCMIS +This section introduces the most commonly used parts of the OpenCMIS client API. The code snippets are taken from the GettingStarted sample class supplied with Apache chemistry. +###Installing the getting started sample +On a command line, type the following command :- +

Need a full URL here

+ + svn co https://svn.apache.org/repos/asf/chemistry/opencmis/trunk/chemistry-opencmis-samples/chemistry-opencmis-getting-started + +You can then build the sample and run the `org.apache.chemistry.opencmis.doc.GettingStarted` sample using maven, by typing the following command + + mvn clean install + +This will pull in any dependencies for the sample, build it, and run it against a public repository. + +The simplest way to work with the getting started sample is to use Maven to create an eclipse project from the code, and import it into your eclipse. +To create the eclipse project type the following command :- + + mvn eclipse:eclipse + +To import the project into eclipse:- + +1. click `File->Import` and choose `General->Existing projects into workspace` in the +Import wizard. +1. Click `Next` and browse to the folder you extracted the project to. +1. Click the checkbox next to `chemistry-opencmis-gettingstarted` project. The project will be imported into your workspace. + +You can run the `main` method in the `GettingStarted` class as a Java application. +###Connecting to a CMIS repository +To do any work on a repository, you must first find your repository, and create a session with it. +To do that, you create a SessionFactory, and set up a parameter map that describes the credentials +and connection settings for the target URL. This code snippet uses the publicly accessible OpenCMIS InMemory Repository +which you will be sharing with everyone. There are other publicly accessible CMIS repositories which you can try your code out on, or +you can change the code to use a repository local to you. +Note that:- + +1. This repository does not require credentials, but the code snippet sets a user id and password to show how this is done +1. The code uses the `ATOMPUB` binding for connection. + + SessionFactory sessionFactory = SessionFactoryImpl.newInstance(); + Map parameter = new HashMap(); + parameter.put(SessionParameter.USER, "admin"); + parameter.put(SessionParameter.PASSWORD, "admin"); + parameter.put(SessionParameter.ATOMPUB_URL, + "http://opencmis.cloudapp.net/inmemory/atom/"); + parameter.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value()); + +####Connecting to a repository by id +In a production environment, the client code will probably know the ID of the repository that it wants to connnect to. +The following code snippet shows how to connect to a repository using its ID. + + parameter.put(SessionParameter.REPOSITORY_ID, "A1"); + Session session = sessionFactory.createSession(parameter); + +That's it. Any work you want to do against the repository uses this session. + +####Connecting to one of a list of repositories +The CMIS specification allows a CMIS service endpoint to advertise one or more repositories, so this code snippet retrieves a list of repositories. In this +case there the code chooses the first repository in list:- + + List repositories = new ArrayList(); + repositories = sessionFactory.getRepositories(parameter); + for (Repository r : repositories) { + System.out.println("Found repository: " + r.getName()); + } + +Now you can create your session with the first and only repository:- + + Repository repository = repositories.get(0); + parameter.put(SessionParameter.REPOSITORY_ID, repository.getId()); + Session session = sessionFactory.createSession(parameter); + + System.out.println("Got a connection to repository: " + + repository.getName() + ", with id: " + + repository.getId()); + +That's it. Any work you want to do against the repository uses this session. + +###Working with Folders and Documents +Of the for base objects described by the CMIS domain model, the most commonly used are the folder and the document. + +A folder is a container for other objects. Folders exist in a hierarchical structure as you would expect, but +a CMIS repository can optionally store objects in multiple folders, and an object can exist but not be contained in a folder. +CMIS uses the terminology multifiling and unfiling for these +capabilities. + +The Document is the only object that can have content, described by a content stream, and has properties such as the author and modification date. +####Finding the contents of the root folder +Now you have a session you can start examining the repository. All CMIS repositories have a root folder, which is the single top level folder in the heirarchy. +To list the objects in the root folder of your repository you use the `getRootFolder()` method on the session. + + Folder root = session.getRootFolder(); + ItemIterable children = root.getChildren(); + System.out.println("Found the following objects in the root folder:-"); + for (CmisObject o : children) { + System.out.println(o.getName() + " which is of type " + o.getType().getDisplayName()); + } + +####Creating a folder object +To create a folder, you use the `createFolder()` method on the parent folder. +As with most CMIS methods, it takes a map of properties which define the Object to be created. +In this code snippet a folder with the name `ADGNewFolder` is created in the root folder:- + + System.out.println("Creating 'ADGNewFolder' in the root folder"); + Map newFolderProps = new HashMap(); + newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); + newFolderProps.put(PropertyIds.NAME, "ADGNewFolder"); + Folder newFolder = root.createFolder(newFolderProps); + + // Did it work? + children = root.getChildren(); + System.out.println("Now finding the following objects in the root folder:-"); + for (CmisObject o : children) { + System.out.println(o.getName()); + } + +Note that the properties set for in the above code snippet are the minimum set of name and object type id. +There are many other properties you can set on creation, depending on the object type. + +####Creating a a simple document object +Document objects describe entities in the CMIS repository. A document object with content +contains a content stream that is the actual file contents, and a mimetype for the content stream. So, to create Document Object, first you need a content stream + + final String textFileName = "test.txt"; + System.out.println("creating a simple text file, " + textFileName); + String mimetype = "text/plain; charset=UTF-8"; + String content = "This is some test content."; + String filename = textFileName; + + byte[] buf = null; + try { + buf = content.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + ByteArrayInputStream input = new ByteArrayInputStream(buf); + ContentStream contentStream = session.getObjectFactory().createContentStream(filename, + buf.length, mimetype, input); + +To add a document with some content to the repository, you use the `createDocument()` method on the parent folder, and provide the content stream +and a map of properties which define the object to be created. + + Map properties = new HashMap(); + properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); + properties.put(PropertyIds.NAME, filename); + ObjectId id = newFolder.createDocument(properties, contentStream, VersioningState.MAJOR); + +Note the following points :- + +* The `createDocument()` method returns the Object ID of the newly created document object. +* Each version of a document object has its own object ID. +* You can create a document without an associated content stream. + +####Reading the contents of a document object +You can retrieve a CMIS object by path, or by using the object ID. + +The following code snippet retrieves the newly created object using the object ID:- + + // Get the contents of the file + doc = (Document) session.getObject(id); + try { + content = getContentAsString(doc.getContentStream()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + System.out.println("Contents of " + filename + " are: " + content); + + ... + + /** + * Helper method to get the + * @param stream + * @return + * @throws IOException + */ + private static String getContentAsString(ContentStream stream) throws IOException { + InputStream in2 = stream.getStream(); + StringBuffer sbuf = null; + sbuf = new StringBuffer(in2.available()); + int count; + byte[] buf2 = new byte[100]; + while ((count = in2.read(buf2)) != -1) { + for (int i = 0; i < count; i++) { + sbuf.append((char) buf2[i]); + } + } + in2.close(); + return sbuf.toString(); + } + +The following code snippet retrieves the newly created object using a path:- + + String path = newFolder.getPath() + "/" + textFileName; + System.out.println("Getting object by path " + path); + doc = (Document) session.getObjectByPath(path); + try { + content = getContentAsString(doc.getContentStream()); + } catch (IOException e) { + e.printStackTrace(); + } + +Note the following points :- + +* All paths start with the root folder `/`. +* The `Document` class does not have a `getPath()` method, since multifiling means a document can have more than one parent folder. It does implement the +`getPaths()` method from the `FileableCmisObject` interface. This returns a list of the current paths to the document object. +####Updating a document. +You can update the properties of a Document object, and you update the contents of the document by overwriting the document's content stream with a new content stream. +Note that the object ID returned when updating a document is not guaranteed to remain the same, some repository implementations return the same object ID, and some may not. + +The following code snippet updates the name property of the `test2.txt` document:- + + Document doc2 = (Document) session.getObject(id2); + System.out.println("renaming " + doc2.getName() + " to test3.txt"); + properties = new HashMap(); + properties.put(PropertyIds.NAME, "test3.txt"); + id2 = doc2.updateProperties(properties); + System.out.println("renamed to " + doc2.getName()); + +You can update the contents of a document by setting a new content stream using the `setContentStream()` method. +You must set the overwrite flag if the document has existing content, otherwise an exception will be thrown. The following code snippet updated the content of the `test3.txt` document:- + + content = "This is some updated test content for our renamed second document."; + buf = null; + try { + buf = content.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + input = new ByteArrayInputStream(buf); + contentStream = session.getObjectFactory().createContentStream("test3.txt", buf.length, + mimetype, input); + properties = new HashMap(); + properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); + properties.put(PropertyIds.NAME, "test3.txt"); + doc2.setContentStream(contentStream, true); + + // did it work? + try { + content = getContentAsString(doc2.getContentStream()); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println("Contents of " + doc2.getName() + " are: " + content); + +####Deleting a document +You can delete any CMIS object using the `delete` method. The following code snippet lists the +contents of the test folder before and after a delete of one of the two documents +in the folder. + + children = newFolder.getChildren(); + System.out.println("Now finding the following objects in our folder:-"); + for (CmisObject o : children) { + System.out.println(o.getName()); + } + System.out.println("Deleting document " + doc2.getName()); + doc2.delete(true); + System.out.println("Now finding the following objects in our folder:-"); + for (CmisObject o : children) { + System.out.println(o.getName()); + } + +Note that the `allVersions` flag is set o `true`. If you set it to false then the delete will +be of this version only. + +####Deleting a folder tree +You can delete a folder and all its contents using just one method. The following code snippet +creates a new folder tree and then deletes it, ignoring any failures to delete individual objects, and deleting +all versions of any documents in the tree. + + System.out.println("Creating 'ADGFolder1' in the root folder"); + newFolderProps = new HashMap(); + newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); + newFolderProps.put(PropertyIds.NAME, "ADGFolder1"); + Folder folder1 = root.createFolder(newFolderProps); + newFolderProps.put(PropertyIds.NAME, "ADGFolder11"); + Folder folder11 = folder1.createFolder(newFolderProps); + newFolderProps.put(PropertyIds.NAME, "ADGFolder12"); + Folder folder12 = folder1.createFolder(newFolderProps); + System.out.println("delete the 'ADGFolder1' tree"); + folder1.deleteTree(true, UnfileObject.DELETE, true); + +Note that with the continueOnFailure parameter set to true, folders and documents are deleted individually. +If a document or folder cannot be deleted, the method moves to the next document or folder in the list. +When the method completes, it returns a list of the document IDs and folder IDs that were not deleted. + +With the continueOnFailure parameter set to false, all of the folders and documents can be deleted in a single batch, +which, depending on the repository design, may improve performance. If a document or folder cannot be deleted, +an exception is raised. Some repository implementations will attempt the delete transactionally, so if it fails, +no objects are deleted. In other repositories a failed delete may have deleted some, but not all, objects in the tree. + +####Navigating through a folder tree +CMIS provides several concepts for navigating through the tree of objects in the repository. For any folder, +you can get its children, get its descendants, and get the folder tree. The getting started sample application sets +up a tree in the repository that looks like this:- + + + +The following snippet from the getting started sample will print out the children of a folder :- + + children = folder1.getChildren(); + System.out.println("Children of " + folder1.getName() + ":-"); + for (CmisObject o : children) { + System.out.println(o.getName()); + } + +Note that the children of a Folder object can be of any type supported by the repository. +Running the sample produces the following output :- + + Children of ADGFolder1:- + ADGFolder11 + ADGFolder12 + ADGFile1f1 + +If your repository supports `getDescendants`, you can navigate the whole subtree of descendants of any folder. +This code snippet uses a recursive method to print out the entire folder subtree:- + + if (!session.getRepositoryInfo().getCapabilities().isGetDescendantsSupported()) { + System.out.println("getDescendants not supported in this repository"); + } else { + System.out.println("Descendants of " + folder1.getName() + ":-"); + for (Tree t : folder1.getDescendants(-1)) { + printTree(t); + } + } + + ... + + private static void printTree(Tree tree) { + System.out.println("Descendant " + tree.getItem().getName()); + for (Tree t : tree.getChildren()) { + printTree(t); + } + } + +Note the following points:- + +1. The children of a `Tree` are themselves of type `Tree`. +1. The single argument of `getDescendants` is the depth or level of the tree that you want to go to. A depth of 1 will give you just the root of the tree. +A depth of -1 will return the full tree. + +Running the sample produces the following output :- + + Descendants of ADGFolder1:- + Descendant ADGFolder11 + Descendant ADGFolder111 + Descendant ADGFolder112 + Descendant ADGFile11f1 + Descendant ADGFile11f2 + Descendant ADGFolder12 + Descendant ADGFolder121 + Descendant ADGFile121f1 + Descendant ADGFolder122 + Descendant ADGFile1f1 + +If your repository supports `getFolderTree`, you can navigate the whole subtree of folders from any folder in the repository. +This code snippet uses a recursive method to print out the entire folder subtree:- + + if (!session.getRepositoryInfo().getCapabilities().isGetFolderTreeSupported()) { + System.out.println("getFolderTree not supported in this repository"); + } else { + System.out.println("Foldertree for " + folder1.getName() + ":-"); + for (Tree t : folder1.getFolderTree(-1)) { + printFolderTree(t); + } + } + + ... + + private static void printFolderTree(Tree tree) { + System.out.println("Folder " + tree.getItem().getName()); + for (Tree t : tree.getChildren()) { + printFolderTree(t); + } + } + +Running the sample produces the following output :- + + Foldertree for ADGFolder1:- + Folder ADGFolder11 + Folder ADGFolder111 + Folder ADGFolder112 + Folder ADGFolder12 + Folder ADGFolder121 + Folder ADGFolder122 + +Note that `getDescendants` and `getFolderTree` are similar, but `getDescendants` finds objects of any fileable type, `getFolderTree` finds only folder +objects. + +###Object Types +CMIS Objects have an object type. There are four base types, two of which *must* be supported by a repository + +* `cmis:document` +* `cmis:folder` + +The other two base types *may* be supported by a repository :- + +* `cmis:relationship` +* `cmis:policy` + +Any other types in a repository must be defined as sub-types of a base type. +An object type definition includes a set of attributes (for example Fileable, Queryable) +and a property schema that will apply to Objects of that type. + +Every object type has a parent type, except a base type. + +CMIS 1.0 does not allow you to create types, but it does have allow you to navigate the object type tree, and look at type definitions. + +So, we can retrieve information in the type definition for our `doc` object :- + + System.out.println("Getting type definition for doc type"); + ObjectType objectType = session.getTypeDefinition(doc.getType().getId()); + System.out.println("isBaseType() returns " + (objectType.isBaseType() ? "true" : "false")); + ObjectType baseType = objectType.getBaseType(); + if (baseType == null) { + System.out.println("getBaseType() returns null"); + } else { + System.out.println("getBaseType() returns " + baseType.getDisplayName()); + } + ObjectType parentType = objectType.getParentType(); + if (parentType == null) { + System.out.println("getParentType() returns null"); + } else { + System.out.println("getParentType() returns " + parentType.getDisplayName()); + } + System.out.println("Listing child types of " + objectType.getDisplayName()); + for (ObjectType o : objectType.getChildren()) { + System.out.println("\t" + o.getDisplayName()); + } + System.out.println("Getting immediate descendant types of " + objectType.getDisplayName()); + for (Tree o : objectType.getDescendants(1)) { + System.out.println("\t" + o.getItem().getDisplayName()); + } + +You will see that, not surprisingly, doc has a type of `Document`, which is a base type, so has no parent. Depending on the repository +you are running this code against, there may be a number of children of the `Document` type, which are types derived from this base type. + +###CMIS Properties +####Displaying the properties of an Object +All objects of the CMIS base types implement the interface `CmisObjectProperties` which provides +accessors to CMIS object properties. The following code snippet uses the `getProperties()` method +to print out all the available properties on a newly created Document object, doc. + + List> props = doc.getProperties(); + for (Property p : props) { + System.out.println(p.getDefinition().getDisplayName() + "=" + + p.getValuesAsString() + ); + } + +The output should look something like this :- + + Is Latest Major Version=[false] + Content Stream Length=[26] + Content Stream Id=[store://2011/5/20/12/54/0d69ee48-ef03-4715-ae23-b84b4342315c.bin] + Version Series Checked Out By=[] + Object Type Id=[cmis:document] + Version Series Checked Out Id=[] + Name=[test.txt] + Content Stream MIME Type=[text/plain; charset=UTF-8] + Version series id=[workspace://SpacesStore/3cd2cbbf-1a00-4653-8ea8-1e91515c6092] + Creation Date=[Fri May 20 12:54:47 BST 2011] + Change token=[] + Version Label=[0.0] + Is Latest Version=[true] + Is Version Series Checked Out=[false] + Last Modified By=[admin] + Created by=[admin] + Checkin Comment=[] + Object Id=[workspace://SpacesStore/3cd2cbbf-1a00-4653-8ea8-1e91515c6092] + Is Immutable=[false] + Is Major Version=[false] + Base Type Id=[cmis:document] + Content Stream Filename=[test.txt] + Last Modified Date=[Fri May 20 12:54:47 BST 2011] + +####Getting a property explicitly +Each object type has a known set of properties, and you can retrieve these explicitly. +For example, the document type has a set of properties described by the `DocumentProperties` interface, and you can use the methods on this interface to retrieve +the value a property:- + + System.out.println("VersionLabel property on " + doc.getName() + " is " + + doc.getVersionLabel()); + System.out.println("Is this the latest version of " + doc.getName() + " ?: " + + (doc.isLatestVersion() ? "yes" : "no")); + +Note that properties can be of simple types, `Boolean`, String, long, or more complex types, +such as `GregorianCalendar`. The following code snippet prints out the `CreationDate` property:- + + GregorianCalendar calendar = doc.getCreationDate(); + String DATE_FORMAT = "yyyyMMdd"; + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + System.out.println("Creation date of " + doc.getName() + " is " + + sdf.format(calendar.getTime()) + ); + +####Getting a property value +You can get the value of any property using the `getPropertyValue()` method. +This is particularly useful for retrieving properties that are not part of the CMIS specification. You should +always use the `queryName` to get a property value. Given a property, you can always get its `queryName` from the +property definition. +The following code snippet takes a property, finds the `queryName` from the property definition, and gets the +property's value using the `queryName`:- + + Property someProperty = props.get(0); + System.out + .println(someProperty.getDisplayName() + " property on " + doc.getName() + + " (by getPropertValue()) is " + + doc.getPropertyValue(someProperty.getQueryName())); + +###Working with CMIS Queries +CMIS provides a type-based query service for discovering objects that match specified criteria. To allow this, CMIS provides +a relational view of the CMIS data model. Through this relational view, queries may be performed using a simplified SQL SELECT +statement. The query language is based on a subset of the SQL-92 grammar, with some extensions to enhance filtering +capability for the CMIS data model. You can see an explanation of relational view and the full query language grammar in the +[CMIS Specification](http://docs.oasis-open.org/cmis/CMIS/v1.0/os/cmis-spec-v1.0.html#_Toc243905420). +#####A Simple Query +Let's look at a query that will find all document objects that have a name that starts with the string `test`:- + + SELECT * FROM cmis:document WHERE cmis:name LIKE 'test%' + +This query specifies the virtual table `cmis:document` in the FROM clause. In the CMIS relational view, a virtual table +is implicitly defined for each queryable object type defined in the repository. +In each virtual table, virtual columns are implicitly defined for:- + +* each property defined in the object type definition. +* all properties defined on any ancestor-type of the object type but not defined in the object type definition. + +In this case we have selected all columns from the cmis:document virtual table, by specifying the asterisk. + +The statement contains a WHERE clause that specifies constraints that must be satisfied. +In this case the value in the virtual column `cmis:name` in the table `cmis:document` must start with the string `test`. +This is specified using a LIKE predicate. + +You can run the query against any repository using the OpenCMIS workbench. + +Let's look at the code required for this query when using the OpenCMIS client library:- + + String query = "SELECT * FROM cmis:document WHERE cmis:name LIKE 'test%'"; + ItemIterable q = session.query(query, false); + + // Did it work? + System.out.println("***results from query " + query); + + int i = 1; + for (QueryResult qr : q) { + System.out.println("--------------------------------------------\n" + i + " , " + + qr.getPropertyByQueryName("cmis:objectTypeId").getFirstValue() + " , " + + qr.getPropertyByQueryName("cmis:name").getFirstValue() + " , " + + qr.getPropertyByQueryName("cmis:createdBy").getFirstValue() + " , " + + qr.getPropertyByQueryName("cmis:objectId").getFirstValue() + " , " + + qr.getPropertyByQueryName("cmis:contentStreamFileName").getFirstValue() + " , " + + qr.getPropertyByQueryName("cmis:contentStreamMimeType").getFirstValue() + " , " + + qr.getPropertyByQueryName("cmis:contentStreamLength").getFirstValue()); + i++; + } + +So, you make query on the session object, and the results set is returned. You can iterate through each row of the results +set to see the search results. Note that each row of the results set includes values for every column in the table +in this particular query. This code snippet just displays a subset of them. +##OpenCMIS programming patterns +###Exceptions +If something goes wrong in an OpenCMIS method, an exception will be thrown. +All OpenCMIS exceptions extend +[CmisBaseException](http://incubator.apache.org/chemistry/javadoc/org/apache/chemistry/opencmis/commons/exceptions/package-tree.html) +which is a Java runtime exception. Because all exceptions are runtime, you do not have to catch or specify the exceptions in your own code. +It does make sense to explicitly catch them in robust code. This code snippet forces an `CmisInvalidArgumentException` by supplying a null content stream. + + try { + doc2.setContentStream(null, false); + } catch (CmisInvalidArgumentException e1) { + System.err.println("caught an " + e1.getClass().getName() + " exception with message " + + e1.getMessage()); + } + +###Operation Context +The amount of metadata and associated information retrieved during an OpenCMIS operation could be large, +so certain OpenCMIS methods return a sensible subset of the information by default, +and provide additional methods that take an [OperationContext](http://incubator.apache.org/chemistry/javadoc/org/apache/chemistry/opencmis/client/api/OperationContext.html). +An OperationContext allows you to tune the amount of information returned by setting property filters, renditions filters, or by setting what. It is also used to control +paging and caching in an operation. +####Setting + +####Caching +Caching of objects is turned on by default in OpenCMIS, but if you want to guarantee that `getObject()` +or `getObjectByPath()` will not return stale objects, you can turn it off using an `OperationContext`:- + + OperationContext oc = session.createOperationContext(); + oc.setCacheEnabled(false); + CmisObject object = session.getObject(id, oc); + +####Rendition Filter +For example the following code snippet looks like it retrieves a list of renditions for a document object. + + CmisObject oo = session.getObject(o.getId()); + List rl = oo.getRenditions(); + +The client will now have access to the default set of information, but this code will +return an empty list, even when there are renditions associated with the document object. +This is because renditions are not included in the default set of information. + +You can use an `OperationContext` with a rendition filter to extend the information retrieved:- + + OperationContext context = session.createOperationContext(); + context.setRenditionFilterString("cmis:thumbnail"); + CmisObject oo = session.getObject(o.getId(), context); + List rl = oo.getRenditions(); + +This code will retrieve any renditions of type `cmis:thumbnail` for this document object. + +###Paging +When you retrieve the children of a CMIS object, the result set returned is of an arbitrary size. +Retrieving a large result set synchronously could increase response times. +To improve performance, you can use OpenCMIS's paging support to control the size of the result set retrieved from the repository. +To use paging, you must specify an `OperationContext` in the `getChildren` method call on the parent object. +The `OperationContext` specifies the maximum number of items to retrieve in a page. + +The following code snippet retrieves a page of three objects from the result set, starting at the fifth item. + + OperationContext operationContext = new OperationContextImpl(); + operationContext.setMaxItemsPerPage(3); + ItemIterable children1 = folderPaging.getChildren(operationContext); + int count = 0; + for (CmisObject child : children1.skipTo(5).getPage()) { + System.out.println("object " + count + " in page of " + children1.getPageNumItems() + + " is " + child.getName()); + count++; + } + +Note that `skipTo()` method returns a an iterable over the complete collection. The getPage() method is then be used to create an iterable over the page. + +The following code snippet retrieves all objects in the result set, using the `getHasMoreItems()` method to end the `getPage()` loop. + + operationContext = new OperationContextImpl(); + operationContext.setMaxItemsPerPage(3); + children1 = folderPaging.getChildren(operationContext); + int pageNumber = 0; + boolean finished = false; + while (!finished) { + count = 0; + ItemIterable currentPage = children1.skipTo( + pageNumber * operationContext.getMaxItemsPerPage()).getPage(); + for (CmisObject item : currentPage) { + System.out.println("object " + count + " in page" + pageNumber + " is " + + item.getName()); + count++; + } + pageNumber++; + if (!currentPage.getHasMoreItems()) + finished = true; + } + +###Query - prepared statments +

todo

+ +##Advanced CMIS features +###Capabilities +Existing ECM repositories vary in their capabilities. Some are designed for a specific application domain and do not provide capabilities +that are not needed for that domain. This means a repository implementation may not be able to support all the capabilities that the CMIS specification provides. +To allow this, some capabilities can be optionally supported by a CMIS repository. +You can discover which optional capabilities your repository supports using the getRepositoryInfo service:- + + System.out.println("Printing repository capabilities..."); + final RepositoryInfo repInfo = session.getRepositoryInfo(); + RepositoryCapabilities cap = repInfo.getCapabilities(); + System.out.println("\nNavigation Capabilities"); + System.out.println("-----------------------"); + System.out.println("Get descendants supported: " + (cap.isGetDescendantsSupported()?"true":"false")); + System.out.println("Get folder tree supported: " + (cap.isGetFolderTreeSupported()?"true":"false")); + System.out.println("\nObject Capabilities"); + System.out.println("-----------------------"); + System.out.println("Content Stream: " + cap.getContentStreamUpdatesCapability().value()); + System.out.println("Changes: " + cap.getChangesCapability().value()); + System.out.println("Renditions: " + cap.getRenditionsCapability().value()); + System.out.println("\nFiling Capabilities"); + System.out.println("-----------------------"); + System.out.println("Multifiling supported: " + (cap.isMultifilingSupported()?"true":"false")); + System.out.println("Unfiling supported: " + (cap.isUnfilingSupported()?"true":"false")); + System.out.println("Version specific filing supported: " + (cap.isVersionSpecificFilingSupported()?"true":"false")); + System.out.println("\nVersioning Capabilities"); + System.out.println("-----------------------"); + System.out.println("PWC searchable: " + (cap.isPwcSearchableSupported()?"true":"false")); + System.out.println("PWC searchable: " + (cap.isPwcUpdatableSupported()?"true":"false")); + System.out.println("All versions searchable: " + (cap.isAllVersionsSearchableSupported()?"true":"false")); + System.out.println("\nQuery Capabilities"); + System.out.println("-----------------------"); + System.out.println("Query: " + cap.getQueryCapability().value()); + System.out.println("Join: " + cap.getJoinCapability().value()); + System.out.println("\nACL Capabilities"); + System.out.println("-----------------------"); + System.out.println("ACL: " + cap.getAclCapability().value()); + System.out.println("End of repository capabilities"); + +The sample code above prints the capabilities of the repository in the same groups as +shown in the CMIS specification, which has a full explanation of the meaning of each +capability in the section on [optional capabilities](http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Toc243712491). +###Allowable Actions +The CMIS specification allows an application to discover the set of operations that can currently be performed on a particular object, +without having to actually having to perform the action itself. The set of allowable actions for a particular object are not fixed, but are +influenced by ACLs, constraints on the object's base type, and policies or other mechanisms not specified by CMIS. The following code snippet +gets the current allowable actions for the test.txt document object:- + + System.out.println("Getting the current allowable actions for the " + doc.getName() + " document object..."); + for (Action a: doc.getAllowableActions().getAllowableActions()) { + System.out.println("\t" + a.value()); + } + +In practice, you would check a single allowable action before executing that action. So, the following code snippet shows how to check if +your application is currently allowed to check out the document `test.txt` + + if (doc.getAllowableActions().getAllowableActions().contains(Action.CAN_CHECK_OUT)) { + System.out.println("can check out " + doc.getName()); + } else { + System.out.println("can not check out " + doc.getName()); + } + + +###Multi-filing and Unfiling +Multi-filing allows you to file a document object in more than one folder. +Unfiling allows you to leave a document without parent folder. Both this capabilities are +optional, and your repository may not support them. + +The following code snippet checks if multi-filing is supported by this repository. If it is, + creates a second folder in the root folder, and then adds our document to the new +folder. Since multi-filing is supported, the document should be seen in both folders, +and the code checks for this. + + if (!(cap.isMultifilingSupported())) { + System.out.println("Multifiling not supported by this repository"); + } else { + // Add a new folder to the root folder + System.out.println("Creating 'ADGNewFolder 2' in the root folder"); + newFolderProps = new HashMap(); + newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); + newFolderProps.put(PropertyIds.NAME, "ADGNewFolder 2"); + newFolder2 = root.createFolder(newFolderProps, null, null, null, + session.getDefaultContext()); + System.out.println("Adding " + textFileName + "to 'ADGNewFolder 2' in the root folder"); + doc.addToFolder(newFolder2, true); + + // Did it work? + children = newFolder.getChildren(); + System.out.println("Now finding the following objects in the 'ADGNewFolder' folder:-"); + for (CmisObject o : children) { + System.out.println(o.getName()); + } + children = newFolder2.getChildren(); + System.out + .println("Now finding the following objects in the 'ADGNewFolder 2' folder:-"); + for (CmisObject o : children) { + System.out.println(o.getName()); + } + } + +The following code snippet checks if unfiling is supported by this repository. +If it is, it removes our document from both folders that contain it. +Since unfiling is supported, the document should still be accessible by its ID, +and the code checks for this. + + if (!(cap.isUnfilingSupported())) { + System.out.println("Unfiling not supported by this repository"); + } else { + // remove our document from both folders + System.out.println("removing :" + doc.getName() + "from 'ADGNewFolder':-"); + doc.removeFromFolder(newFolder); + System.out.println("removing :" + doc.getName() + "from 'ADGNewFolder 2':-"); + doc.removeFromFolder(newFolder2); + // Did it work? + Document docTest = (Document) session.getObject(id); + if (docTest != null) { + System.out.println(docTest.getName() + " still exists"); + } + + } + +###Versioning +Only document objects can be versioned. +Whether or not a Document object is versionable is specified by the `versionable` attribute on its Object-type. +A version is copy of the object, preserving its state at a certain point in time. +Each version is itself a Document object, with its own ObjectId. + +The CMIS specification uses the following terminology when discussing versioning: + +* *Version Series* - all the document objects that have been created from an original document in a repository. Each version series has a unique version series ID. +* *Latest Version* - the latest version of any document object in the series. If the latest version in a version series is deleted, the previous version (if there is one) becomes the latest version. +* *Major Versions* - you can specify that a version is a major or minor version on check-in. The specification does not define any differences between Major and non-Major +versions in a Version Series. Repositories can apply additional constraints or semantics for Major versions, if the effect on CMIS services does not violate the CMIS model. +If a Version Series contains one or more Major versions, the one that has the most recent LastModificationDate is the Latest Major Version of the version series. A +Version Series need not have a Latest Major Version. When the latest major version is deleted, the previous major version (if there is one) becomes the latest major version. + +The following code snippet checks if the test.txt document object is versionable. + + if (((DocumentType)(doc.getType())).isVersionable()) { + System.out.println(doc.getName() + " is versionable"); + } else { + System.out.println(doc.getName() + " is NOT versionable"); + } + +You create new version of a document by checking-in a Private Working copy (PWC) of the object. +A PWC is created by checking-out a document. +Repositories may allow any version of a document to be checked out, or may only allow the latest version to be checked out. +The following code snippet checks out the latest version of the test.txt document, updates the content stream, and checks in the new version of the +document as a minor version. + + if (versionable) { + Document pwc = (Document) session.getObject(doc.checkOut()); + try { + content = getContentAsString(pwc.getContentStream()); + } catch (IOException e) { + e.printStackTrace(); + } + String updatedContents = content + "\nLine added in new version"; + + try { + buf = updatedContents.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + contentStream = session.getObjectFactory().createContentStream( + doc.getContentStream().getFileName(), buf.length, + doc.getContentStream().getMimeType(), new ByteArrayInputStream(buf)); + + // Check in the pwc + try { + pwc.checkIn(false, null, contentStream, "minor version"); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("checkin failed, trying to cancel the checkout"); + pwc.cancelCheckOut(); + } + + System.out.println("Document version history"); + { + List versions = doc.getAllVersions(); + for (Document version : versions) { + System.out.println("\tname: " + version.getName()); + System.out.println("\tversion label: " + version.getVersionLabel()); + System.out.println("\tversion series id: " + version.getVersionSeriesId()); + System.out.println("\tchecked out by: " + + version.getVersionSeriesCheckedOutBy()); + System.out.println("\tchecked out id: " + + version.getVersionSeriesCheckedOutId()); + System.out.println("\tmajor version: " + version.isMajorVersion()); + System.out.println("\tlatest version: " + version.isLatestVersion()); + System.out.println("\tlatest major version: " + version.isLatestMajorVersion()); + System.out.println("\tcheckin comment: " + version.getCheckinComment()); + System.out.println("\tcontent length: " + version.getContentStreamLength() + + "\n"); + } + } + } + +The code snippet uses the `getAllVersions()` method to retrieve all the versions in the version series. +The output from this code snippet will look something like this:- + + Document version history + name: test.txt + version label: 1.0 + version series id: workspace://SpacesStore/72522204-016b-4bab-817c-bf6deb155d0a + checked out by: null + checked out id: null + major version: false + latest version: true + latest major version: false + checkin comment: minor version + content length: 52 + +Note the following points:- + +* If the first parameter value on the `checkin()` call is `true`, you are specifying a major version. +* `getAllversions()` returns the all the versions with the latest version first. +* The code snippet uses the `cancelCheckout()` method to discard the PWC if things go wrong. +* Different repositories support different capabilities for a newly created PWC. +Repositories that support server-side PWCs can allow visibility to other users who have access to other document objects in the version series. +For example other users may be able to update the PWC, or have it included in the results of a search query. Repositories supporting client-side only PWCs will not have these capabilities. + + +###Renditions +Some repositories provide a facility to retrieve alternative representations, or renditions of a document. +An example is a preview thumbnail image of the content of a document, which could be presented to the user without needing to download the full document content. +Another example is a PDF version of a word document. + +A CMIS repository may have zero or more renditions for a document or folder in addition to the document's content stream. +At present the CMIS specification only allows renditions to be read. There are no facilities to create, update or delete renditions. +Renditions are of a specific version of the document and may differ between document versions. +Each rendition consists of a set of rendition attributes and a rendition stream. +Rendition attributes are not object properties, and are not queryable. +Renditions can be retrieved using the getRenditions service. + +The following code fragment checks if this repository supports renditions. +It then scans the object tree starting from the root folder for a +document object that has renditions associated with it. It then gets the document again using an `OperationContext` +to retrieve all renditions of a particular type. + + if (session.getRepositoryInfo().getCapabilities().getRenditionsCapability() + .equals(CapabilityRenditions.NONE)) { + System.out.println("Repository does not support renditions"); + } else { + System.out + .println("Finding first object in repository with thumbnail renditions - start"); + Folder node = root; + Stack stack = new Stack(); + while (node != null) { + children = node.getChildren(); + for (CmisObject o : children) { + if ((o.getType().isBaseType() && o.getType().getId().equals("cmis:folder")) + || o.getBaseType().getId().equals("cmis:folder")) { + stack.push((Folder) o); + } else { + OperationContext context = session.createOperationContext(); + context.setRenditionFilterString("cmis:thumbnail"); + CmisObject oo = session.getObject(o.getId(), context); + List rl = oo.getRenditions(); + if (!rl.isEmpty()) { + System.out.println("found " + o.getName() + " of type " + + o.getType().getDisplayName() + "that has renditions..."); + for (Rendition rendition : rl) { + System.out.print("kind: " + rendition.getKind()); + System.out.print("\tmimetype: " + rendition.getMimeType()); + System.out.print("\twidth: " + rendition.getWidth()); + System.out.print("\theight: " + rendition.getHeight()); + System.out.println("\tstream id: " + rendition.getStreamId()); + } + break; // Just show the first object with + // renditions. Remove this + // Break to show them all + + } + } + } + if (stack.isEmpty()) { + node = null; + } else { + node = (Folder) stack.pop(); + } + } + System.out + .println("Finding first object in repository with thumbnail renditions - end"); + } + +###Relationships +

todo

+###Permissions +

todo

+###Advance uses of types +

todo

+###Change logs +

todo

+##OpenCMIS bindings +##Performance +

todo

+##Troubleshooting +

Common pitfalls

Propchange: chemistry/site/trunk/content/java/developing/guide.mdtext ------------------------------------------------------------------------------ svn:eol-style = native Added: chemistry/site/trunk/content/java/developing/images/FileTree.png URL: http://svn.apache.org/viewvc/chemistry/site/trunk/content/java/developing/images/FileTree.png?rev=1138360&view=auto ============================================================================== Binary file - no diff available. Propchange: chemistry/site/trunk/content/java/developing/images/FileTree.png ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Added: chemistry/site/trunk/content/java/developing/images/todo.gif URL: http://svn.apache.org/viewvc/chemistry/site/trunk/content/java/developing/images/todo.gif?rev=1138360&view=auto ============================================================================== Binary file - no diff available. Propchange: chemistry/site/trunk/content/java/developing/images/todo.gif ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream