/* * Copyright (C) 2010 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xcmis.spi; import org.xcmis.spi.model.AccessControlEntry; import org.xcmis.spi.model.AccessControlPropagation; import org.xcmis.spi.model.AllowableActions; import org.xcmis.spi.model.BaseType; import org.xcmis.spi.model.CapabilityACL; import org.xcmis.spi.model.CapabilityRendition; import org.xcmis.spi.model.ChangeEvent; import org.xcmis.spi.model.ChangeInfo; import org.xcmis.spi.model.ChangeType; import org.xcmis.spi.model.CmisObject; import org.xcmis.spi.model.ContentStreamAllowed; import org.xcmis.spi.model.IncludeRelationships; import org.xcmis.spi.model.ObjectInfo; import org.xcmis.spi.model.ObjectParent; import org.xcmis.spi.model.Permission; import org.xcmis.spi.model.Property; import org.xcmis.spi.model.PropertyDefinition; import org.xcmis.spi.model.RelationshipDirection; import org.xcmis.spi.model.Rendition; import org.xcmis.spi.model.RepositoryCapabilities; import org.xcmis.spi.model.TypeDefinition; import org.xcmis.spi.model.UnfileObject; import org.xcmis.spi.model.Updatability; import org.xcmis.spi.model.VersioningState; import org.xcmis.spi.model.Permission.BasicPermissions; import org.xcmis.spi.model.impl.DecimalProperty; import org.xcmis.spi.model.impl.IdProperty; import org.xcmis.spi.query.Query; import org.xcmis.spi.query.Result; import org.xcmis.spi.query.Score; import org.xcmis.spi.utils.CmisUtils; import org.xcmis.spi.utils.Logger; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * Connection to CMIS storage. It should be used for all operation with storage. * The <code>Connection</code> object is associated with <code>Storage</code> * object. When <code>Connection</code> is no longer needed then method * {@link #close()} should be used to release all associated resources. After * this connection should not be in use any more. * * @author <a href="mailto:andrey00x@gmail.com">Andrey Parfonov</a> * @version $Id: Connection.java 332 2010-03-11 17:24:56Z andrew00x $ */ public abstract class Connection { protected static final int CREATE = 1; protected static final int UPDATE = 2; protected static final int VERSION = 4; private static final Logger LOG = Logger.getLogger(Connection.class); protected Storage storage; public Connection(Storage storage) { this.storage = storage; } /** * Adds an existing fileable non-folder object to a folder. * * 2.2.5.1 addObjectToFolder * * * @param objectId the id of the object * @param folderId the target folder id into which the object is to be filed * @param allVersions to add all versions of the object to the folder or only * current document if the storage supports version-specific filing * @throws ObjectNotFoundException if <code>objectId</code> or * <code>folderId</code> were not found * @throws ConstraintException MUST throw this exception if the * cmis:objectTypeId property value of the given object is NOT in the * list of AllowedChildObjectTypeIds of the parent-folder specified * by folderId or if <code>allVersions</code> is <code>false</code> * but version-specific filling capability is not supported by * storage * @throws InvalidArgumentException if <code>objectId</code> is id of object * that is not fileable or if <code>folderId</code> is id of object * that base type is not Folder * @throws NotSupportedException if multifiling feature is not supported by * backend storage * @see RepositoryCapabilities#isCapabilityVersionSpecificFiling() */ public void addObjectToFolder(String objectId, String folderId, boolean allVersions) throws ObjectNotFoundException, ConstraintException { checkConnection(); if (!storage.getRepositoryInfo().getCapabilities().isCapabilityMultifiling()) { throw new NotSupportedException("Multi-filing is not supported."); } if (!allVersions && !storage.getRepositoryInfo().getCapabilities().isCapabilityVersionSpecificFiling()) { throw new ConstraintException("Version-specific filling capability is not supported."); } ObjectData object = storage.getObjectById(objectId); ObjectData folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + folderId + " is not a folder object."); } if (!object.getTypeDefinition().isFileable()) { throw new InvalidArgumentException("Object " + objectId + " is not fileable."); } if (!((FolderData)folder).isAllowedChildType(object.getTypeId())) { throw new ConstraintException("Object type " + object.getTypeId() + " is not allowed as child for destination folder"); } ((FolderData)folder).addObject(object); } /** * Adds the new Object-type. * * It is not a standard CMIS feature (xCMIS specific) * * 2.1.3 Object-Type A repository MAY define additional object-types beyond * the CMIS Base Object-Types * * * @param type type definition * @return ID of newly added type * @throws ConstraintException if any of the following conditions are met: * <ul> * <li>Storage already has type with the same id, see * {@link TypeDefinition#getId()}</li> * <li>Base type is not specified or is one of optional type that is * not supported by storage, see {@link TypeDefinition#getBaseId()}</li> * <li>Parent type is not specified or does not exist, see * {@link TypeDefinition#getParentId()}</li> * <li>New type has at least one property definitions that has * unsupported type, invalid id, so on</li> * </ul> * @throws StorageException if type can't be added (save changes) cause to * storage internal problem */ public String addType(TypeDefinition type) throws ConstraintException, StorageException { checkConnection(); String id = storage.addType(type); return id; } /** * Adds or(and) removes the given Access Control Entries to(from) the Access * Control List of object. * * 2.2.10.2 applyACL * * @param objectId the identifier of object for which should be applied * specified ACEs * @param addACL the ACEs that will be added from object's ACL. May be * <code>null</code> or empty list * @param removeACL the ACEs that will be removed from object's ACL. May be * <code>null</code> or empty list * @param propagation specifies how ACEs should be handled: * <ul> * <li>objectonly: ACEs must be applied without changing the ACLs of * other objects</li> * <li>propagate: ACEs must be applied by propagate the changes to all * inheriting objects</li> * <li>repositorydetermined: Indicates that the client leaves the * behavior to the storage</li> * </ul> * @throws ObjectNotFoundException if object with <code>objectId</code> does * not exist * @throws ConstraintException if any of the following conditions are met: * <ul> * <li>The specified object's Object-Type definition's attribute for * controllableACL is <code>false</code></li> * <li>The value for ACLPropagation does not match the values as * returned via getACLCapabilities</li> * <li>At least one of the specified values for permission in ANY of * the ACEs does not match ANY of the permissionNames as returned by * getACLCapability and is not a CMIS Basic permission</li> * </ul> * @throws NotSupportedException if managing of ACL is not supported by * backend storage */ public void applyACL(String objectId, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, AccessControlPropagation propagation) throws ObjectNotFoundException, ConstraintException { if ((addACL == null || addACL.size() == 0) && (removeACL == null || removeACL.size() == 0)) { return; } checkConnection(); if (propagation == null) { propagation = AccessControlPropagation.REPOSITORYDETERMINED; } AccessControlPropagation storagePropagation = storage.getRepositoryInfo().getAclCapability().getPropagation(); if (!propagation.equals(storagePropagation)) { throw new ConstraintException("Specified ACL propagation '" + propagation + "' does not to supported by repository '" + storagePropagation + "' "); } ObjectData object = storage.getObjectById(objectId); TypeDefinition typeDefinition = object.getTypeDefinition(); checkACL(typeDefinition, addACL, removeACL); // Merge ACL include existed one. It may be inherited from parent even for newly created object . List<AccessControlEntry> mergedACL = CmisUtils.mergeACLs(object.getACL(false), addACL, removeACL); object.setACL(mergedACL); } /** * Applies a specified policy to an object. * * 2.2.9.1 applyPolicy * * @param policyId the policy Id to be applied to object * @param objectId the target object Id for policy * @throws ObjectNotFoundException if object with <code>objectId</code> or * <code>policyId</code> does not exist * @throws ConstraintException if object with id <code>objectId</code> is not * controllable by policy * @throws InvalidArgumentException if object with id <code>policyId</code> * is not object which base type is Policy */ public void applyPolicy(String policyId, String objectId) throws ConstraintException, ObjectNotFoundException { checkConnection(); ObjectData object = storage.getObjectById(objectId); if (!object.getTypeDefinition().isControllablePolicy()) { throw new ConstraintException("Object type " + object.getTypeId() + " is not controllable by policy."); } ObjectData policy = storage.getObjectById(policyId); if (policy.getBaseType() != BaseType.POLICY) { throw new InvalidArgumentException("Object " + policy.getObjectId() + " is not a Policy object."); } object.applyPolicy((PolicyData)policy); } /** * Discard the check-out operation. As result Private Working Copy (PWC) must * be removed and storage ready to next check-out operation. * * 2.2.7.2 cancelCheckOut * * @param documentId document id. May be PWC id or id of any other Document * in Version Series * @throws ObjectNotFoundException if object with <code>documentId</code> * does not exist * @throws ConstraintException if the object is not versionable * @throws UpdateConflictException if update an object that is no longer * current * @throws VersioningException if object is a non-current document version * @throws StorageException if PWC can't be removed from storage cause to * storage internal problem * @throws InvalidArgumentException if object with <code>documentId</code> is * not Document or if PWC in version series does not exist */ public void cancelCheckout(String documentId) throws ObjectNotFoundException, ConstraintException, UpdateConflictException, VersioningException, StorageException { checkConnection(); ObjectData document = storage.getObjectById(documentId); if (!document.getTypeDefinition().isVersionable()) { throw new ConstraintException("Type " + document.getTypeId() + " is not versionable."); } if (document.getBaseType() != BaseType.DOCUMENT) { // be sure it is realy document type throw new InvalidArgumentException("Object " + documentId + " is not a Document object."); } if (!((DocumentData)document).isVersionSeriesCheckedOut()) { throw new InvalidArgumentException("There is no Private Working Copy in version series."); } // cancelCheckedOut may be invoked on any object in version series. // In other way 'cmis:versionSeriesCheckedOutId' may not reflect // current PWC id. ((DocumentData)document).cancelCheckout(); } /** * Check-in Private Working Copy. * * 2.2.7.3 checkIn * * * @param documentId document id * @param major <code>true</code> is new version should be marked as major * <code>false</code> otherwise * @param properties properties to be applied to new version * @param content content of document * @param checkinComment check-in comment * @param addACL set Access Control Entry to be applied for newly created * version of document. May be <code>null</code> or empty list * @param removeACL set Access Control Entry that MUST be removed from the * newly created version of document. May be <code>null</code> or * empty list * @param policies list of policy id that MUST be applied to the newly * created document. May be <code>null</code> or empty collection * @return ID of checked-in document * @throws ObjectNotFoundException if object with <code>documentId</code> * does not exist * @throws ConstraintException if the object is not versionable * @throws VersioningException if object is a not PWC * @throws NameConstraintViolationException if <i>cmis:name</i> specified in * properties throws conflict * @throws UpdateConflictException if update an object that is no longer * current * @throws StreamNotSupportedException if document does not supports content * stream * @throws StorageException if changes can't be saved in storage cause to * storage internal problem * @throws InvalidArgumentException if object with <code>documentId</code> is * not Document */ public String checkin(String documentId, boolean major, Map<String, Property<?>> properties, ContentStream content, String checkinComment, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies) throws ObjectNotFoundException, ConstraintException, VersioningException, NameConstraintViolationException, UpdateConflictException, StreamNotSupportedException, StorageException { checkConnection(); ObjectData pwc = storage.getObjectById(documentId); if (!pwc.getTypeDefinition().isVersionable()) { throw new ConstraintException("Type " + pwc.getTypeId() + " is not versionable."); } if (pwc.getBaseType() != BaseType.DOCUMENT) { // be sure it is realy document type throw new InvalidArgumentException("Object " + documentId + " is not a Document object."); } if (!((DocumentData)pwc).isPWC()) { throw new VersioningException("Object " + documentId + " is not Private Working Copy."); } TypeDefinition typeDefinition = pwc.getTypeDefinition(); checkProperties(typeDefinition, properties, VERSION); // Do not use method 'checkContent' because stream may be null if content does not changed. if (typeDefinition.getContentStreamAllowed() == ContentStreamAllowed.NOT_ALLOWED && content != null) { throw new StreamNotSupportedException("Content stream not allowed for object of type " + typeDefinition.getId()); } checkACL(typeDefinition, addACL, removeACL); checkPolicies(typeDefinition, policies); DocumentData version = ((DocumentData)pwc).checkin(major, checkinComment, properties, content, CmisUtils.mergeACLs(pwc.getACL(false), addACL, removeACL), createPolicyList(policies)); return version.getObjectId(); } /** * Check-out document. * * 2.2.7.1 checkOut * * @param documentId document id. Storage MAY allow checked-out ONLY latest * version of Document * @return ID of checked-out document (PWC) * @throws ObjectNotFoundException if object with <code>documentId</code> * does not exist * @throws ConstraintException if the object is not versionable * @throws UpdateConflictException if update an object that is no longer * current * @throws VersioningException if one of the following conditions are met: * <ul> * <li>object is not latest version of document version and it is not * supported to checked-out other then latest version</li> * <li>version series already have one checked-out document. It is * not possible to have more then one PWC at time</li> * </ul> * @throws StorageException if newly created PWC can't be saved in storage * cause to storage internal problem * @throws InvalidArgumentException if object with <code>documentId</code> is * not Document */ public String checkout(String documentId) throws ObjectNotFoundException, ConstraintException, UpdateConflictException, VersioningException, StorageException { checkConnection(); ObjectData document = storage.getObjectById(documentId); if (!document.getTypeDefinition().isVersionable()) { throw new ConstraintException("Type " + document.getTypeId() + " is not versionable."); } if (document.getBaseType() != BaseType.DOCUMENT) { // be sure it is realy document type throw new InvalidArgumentException("Object " + documentId + " is not a Document object."); } DocumentData pwc = ((DocumentData)document).checkout(); return pwc.getObjectId(); } /** * Close the connection and release underlying resources. Not able to use * this connection any more. */ public abstract void close(); /** * Create a document object. * * @param parentId parent folder id for object. May be null if storage * supports unfiling * @param properties properties that will be applied to newly created * document. If <code>properties</code> contains some property which * updatability is other then {@link Updatability#ONCREATE} or * {@link Updatability#READWRITE} this properties will be ignored * @param content the document content. May be <code>null</code>. MUST be * required if the type requires it. * @param addACL Access Control Entries that MUST added for newly created * document, either using the ACL from <code>parentId</code> if * specified, or being applied if no <code>parentId</code> is * specified. May be <code>null</code> or empty list * @param removeACL set Access Control Entries that MUST be removed from the * newly created document, either using the ACL from * <code>parentId</code> if specified, or being ignored if no * <code>parentId</code> is specified. May be <code>null</code> or * empty list * @param policies list of policy id that MUST be applied to the newly * created document. May be <code>null</code> or empty collection * @param versioningState enumeration specifying what the versioning state of * the newly created object shall be * @return ID of newly created document * @throws ObjectNotFoundException if target folder with specified id * <code>parentId</code> does not exist * @throws TypeNotFoundException if type specified by property * <code>cmis:objectTypeId</code> does not exist * @throws ConstraintException if any of following condition are met: * <ul> * <li><code>cmis:objectTypeId</code> property value is not an object * type whose baseType is Document</li> * <li><code>cmis:objectTypeId</code> property value is not in the * list of AllowedChildObjectTypeIds of the parent-folder specified * by <code>parentId</code></li> * <li>value of any of the properties violates the * min/max/required/length constraints specified in the property * definition in the object type * <li> * <li>contentStreamAllowed attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <i>required</i> and no content input parameter is provided</li> * <li>versionable attribute of the object type definition specified * by the <code>cmis:objectTypeId</code> property value is set to * <code>false</code> and a value for the versioningState input * parameter is provided that is something other than <i>none</i></li> * <li>versionable attribute of the object type definition specified * by the <code>cmis:objectTypeId</code> property value is set to * <code>true</code> and the value for the versioningState input * parameter is provided that is <i>none</i></li> * <li>controllablePolicy attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one policy is provided</li> * <li>controllableACL attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one ACE is provided</li> * <li>at least one of the permissions is used in an ACE provided * which is not supported by the storage</li> * </ul> * @throws StreamNotSupportedException if the contentStreamAllowed attribute * of the object type definition specified by the * <code>cmis:objectTypeId</code> property value is set to 'not * allowed' and a contentStream input parameter is provided * @throws NameConstraintViolationException violation is detected with the * given <code>cmis:name</code> property value. Storage MAY chose * other name which does not conflict * @throws StorageException if new Document can't be saved in storage cause * to storage internal problem */ public String createDocument(String parentId, Map<String, Property<?>> properties, ContentStream content, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies, VersioningState versioningState) throws ObjectNotFoundException, TypeNotFoundException, ConstraintException, StreamNotSupportedException, NameConstraintViolationException, StorageException { checkConnection(); if (properties == null) { throw new InvalidArgumentException("Properties may not by null."); } String typeId = getTypeId(properties); TypeDefinition typeDefinition = getTypeDefinition(typeId, true); if (typeDefinition.getBaseId() != BaseType.DOCUMENT) { throw new ConstraintException("Type " + typeId + " is not type whose base type is cmis:document."); } ObjectData parent = null; if (parentId != null) { parent = storage.getObjectById(parentId); if (parent.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + parentId + " is not a Folder object."); } if (!((FolderData)parent).isAllowedChildType(typeId)) { throw new ConstraintException("Object type " + typeId + " is not allowed as child for destination folder"); } } else if (!storage.getRepositoryInfo().getCapabilities().isCapabilityUnfiling()) { throw new ConstraintException("Unfiling capability is not supported, parent folder must be provided."); } if (versioningState == null) { versioningState = VersioningState.MAJOR; } if (versioningState == VersioningState.NONE && typeDefinition.isVersionable()) { throw new ConstraintException("Type " + typeDefinition.getId() + " is versionable, versioning state 'none' not allowed."); } // XXX : Do not throw ConstraintException if versioning is not supported // and versioning state is other than NONE. Some client may not specify // this attribute and may not be able create documents. Lets backend storage // to resolve this issue. // check inputs checkProperties(typeDefinition, properties, CREATE); checkContent(typeDefinition, content); checkACL(typeDefinition, addACL, removeACL); checkPolicies(typeDefinition, policies); try { DocumentData newDocument = storage.createDocument((FolderData)parent, typeDefinition, properties, content, CmisUtils.mergeACLs( parent != null ? parent.getACL(false) : null, addACL, removeACL), createPolicyList(policies), versioningState); return newDocument.getObjectId(); } catch (IOException ioe) { throw new CmisRuntimeException(ioe.getMessage(), ioe); } } /** * Create a document object as a copy of the given source document in the * specified parent folder <code>parentId</code>. * * * @param sourceId id for the source document * @param parentId parent folder id for object. May be null if storage * supports unfiling * @param properties properties that will be applied to newly created * document * @param addACL Access Control Entries that MUST added for newly created * document, either using the ACL from <code>parentId</code> if * specified, or being applied if no <code>parentId</code> is * specified. May be <code>null</code> or empty list * @param removeACL set Access Control Entries that MUST be removed from the * newly created document, either using the ACL from * <code>parentId</code> if specified, or being ignored if no * <code>parentId</code> is specified. May be <code>null</code> or * empty list * @param policies list of policy id that MUST be applied to the newly * created document. May be <code>null</code> or empty collection * @param versioningState enumeration specifying what the versioning state of * the newly created object shall be * @return ID of newly created document * @throws ObjectNotFoundException if target folder with specified id * <code>parentId</code> or source document with id * <code>sourceId</code> does not exist * @throws ConstraintException if any of following condition are met: * <ul> * <li>sourceId is not an Object whose baseType is Document</li> * <li>source document's <code>cmis:objectTypeId</code> property * value is NOT in the list of AllowedChildObjectTypeIds of the * parent-folder specified by <code>parentId</code></li> * <li>versionable attribute of the object type definition specified * by the <code>cmis:objectTypeId</code> property value is set to * <code>false</code> and a value for the versioningState input * parameter is provided that is something other than <i>none</i></li> * <li>versionable attribute of the object type definition specified * by the <code>cmis:objectTypeId</code> property value is set to * <code>true</code> and the value for the versioningState input * parameter is provided that is <i>none</i></li> * <li>controllablePolicy attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one policy is provided</li> * <li>controllableACL attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one ACE is provided</li> * <li>At least one of the permissions is used in an ACE provided * which is not supported by the storage</li> * </ul> * @throws NameConstraintViolationException violation is detected with the * given <code>cmis:name</code> property value. Storage MAY chose * other name which does not conflict * @throws StorageException if new Document can't be saved in storage cause * to storage internal problem */ public String createDocumentFromSource(String sourceId, String parentId, Map<String, Property<?>> properties, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies, VersioningState versioningState) throws ObjectNotFoundException, ConstraintException, NameConstraintViolationException, StorageException { checkConnection(); ObjectData source = storage.getObjectById(sourceId); TypeDefinition typeDefinition = source.getTypeDefinition(); if (typeDefinition.getBaseId() != BaseType.DOCUMENT) { throw new ConstraintException("Source object is not Document."); } ObjectData parent = null; if (parentId != null) { parent = storage.getObjectById(parentId); if (parent.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + parentId + " is not a Folder object."); } if (!((FolderData)parent).isAllowedChildType(typeDefinition.getId())) { throw new ConstraintException("Object type " + typeDefinition.getId() + " is not allowed as child for destination folder"); } } else if (!storage.getRepositoryInfo().getCapabilities().isCapabilityUnfiling()) { throw new ConstraintException("Unfiling capability is not supported, parent folder must be provided."); } if (versioningState == null) { versioningState = VersioningState.MAJOR; } // XXX : Do not throw ConstraintException if versioning is not supported // and versioning state is othe than NONE. Some client may not specify // this attribute and may not be able create documents. Lets backend storage // to resolve this issue. // check inputs checkProperties(typeDefinition, properties, CREATE); checkACL(typeDefinition, addACL, removeACL); checkPolicies(typeDefinition, policies); DocumentData newDocument = storage.copyDocument((DocumentData)source, (FolderData)parent, properties, CmisUtils.mergeACLs(parent != null ? parent.getACL(false) : null, addACL, removeACL), createPolicyList(policies), versioningState); return newDocument.getObjectId(); } /** * Create a folder object. * * * @param parentId parent folder id for new folder * @param properties properties that will be applied to newly created folder * @param addACL Access Control Entries that MUST added for newly created * Folder, either using the ACL from <code>parentId</code> if * specified, or being applied if no <code>parentId</code> is * specified. May be <code>null</code> or empty list * @param removeACL set Access Control Entry that MUST be removed from the * newly created folder, either using the ACL from * <code>parentId</code> if specified, or being ignored if no * <code>parentId</code> is specified. May be <code>null</code> or * empty list * @param policies list of policy id that MUST be applied to the newly * created folder. May be <code>null</code> or empty collection * @return ID of newly created folder * @throws ObjectNotFoundException if target folder with specified id * <code>parentId</code> does not exist * @throws TypeNotFoundException if type specified by property * <code>cmis:objectTypeId</code> does not exist * @throws ConstraintException if any of following condition are met: * <ul> * <li><code>cmis:objectTypeId</code> property value is not an object * type whose baseType is Folder</li> * <li>value of any of the properties violates the * min/max/required/length constraints specified in the property * definition in the object type</li> * <li><code>cmis:objectTypeId</code> property value is not in the * list of AllowedChildObjectTypeIds of the parent-folder specified * by <code>parentId</code></li> * <li>controllablePolicy attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one policy is provided</li> * <li>controllableACL attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one ACE is provided</li> * <li>at least one of the permissions is used in an ACE provided * which is not supported by the storage</li> * </ul> * @throws NameConstraintViolationException violation is detected with the * given <code>cmis:name</code> property value. Storage MAY chose * other name which does not conflict. * @throws StorageException if new Folder can't be saved in storage cause to * storage internal problem */ public String createFolder(String parentId, Map<String, Property<?>> properties, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies) throws ObjectNotFoundException, TypeNotFoundException, ConstraintException, NameConstraintViolationException, StorageException { checkConnection(); if (properties == null) { throw new InvalidArgumentException("Properties may not by null."); } if (parentId == null) { throw new ConstraintException("Parent folder id is not specified."); } String typeId = getTypeId(properties); TypeDefinition typeDefinition = getTypeDefinition(typeId, true); if (typeDefinition.getBaseId() != BaseType.FOLDER) { throw new ConstraintException("Type " + typeId + " is not type whose base type is cmis:folder."); } ObjectData parent = storage.getObjectById(parentId); if (parent.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + parentId + " is not a Folder object."); } if (!((FolderData)parent).isAllowedChildType(typeId)) { throw new ConstraintException("Object type " + typeId + " is not allowed as child for destination folder"); } checkProperties(typeDefinition, properties, CREATE); checkACL(typeDefinition, addACL, removeACL); checkPolicies(typeDefinition, policies); ObjectData newFolder = storage.createFolder((FolderData)parent, typeDefinition, properties, CmisUtils.mergeACLs(parent.getACL(false), addACL, removeACL), createPolicyList(policies)); return newFolder.getObjectId(); } /** * Create a policy object. * * 2.2.4.5 createPolicy * * * @param parentId parent folder id should be <code>null</code> if policy * object type is not fileable * @param properties properties to be applied to newly created Policy * @param addACL Access Control Entries that MUST added for newly created * Policy, either using the ACL from <code>parentId</code> if * specified, or being applied if no <code>parentId</code> is * specified. May be <code>null</code> or empty list * @param removeACL set Access Control Entry that MUST be removed from the * newly created Policy, either using the ACL from * <code>parentId</code> if specified, or being ignored if no * <code>parentId</code> is specified. May be <code>null</code> or * empty list * @param policies list of policy id that MUST be applied to the newly * created policy. May be <code>null</code> or empty collection * @return ID of newly created policy * @throws ObjectNotFoundException if target folder with specified id * <code>parentId</code> does not exist * @throws TypeNotFoundException if type specified by property * <code>cmis:objectTypeId</code> does not exist * @throws ConstraintException if any of following condition are met: * <ul> * <li><code>cmis:objectTypeId</code> property value is not an object * type whose baseType is Policy</li> * <li>value of any of the properties violates the * min/max/required/length constraints specified in the property * definition in the object type</li> * <li><code>cmis:objectTypeId</code> property value is NOT in the * list of AllowedChildObjectTypeIds of the parent-folder specified * by parentId</li> * <li>controllablePolicy attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one policy is provided</li> * <li>controllableACL attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one ACE is provided</li> * <li>at least one of the permissions is used in an ACE provided * which is not supported by the storage</li> * </ul> * @throws NameConstraintViolationException violation is detected with the * given <code>cmis:name</code> property value. Storage MAY chose * other name which does not conflict * @throws StorageException if new Policy can't be saved in storage cause to * storage internal problem */ public String createPolicy(String parentId, Map<String, Property<?>> properties, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies) throws ObjectNotFoundException, TypeNotFoundException, ConstraintException, NameConstraintViolationException, StorageException { checkConnection(); if (properties == null) { throw new InvalidArgumentException("Properties may not by null."); } String typeId = getTypeId(properties); TypeDefinition typeDefinition = getTypeDefinition(typeId, true); if (typeDefinition.getBaseId() != BaseType.POLICY) { throw new ConstraintException("Type " + typeId + " is not type whose base type is cmis:policy."); } ObjectData parent = null; if (parentId != null) { parent = storage.getObjectById(parentId); if (parent.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + parentId + " is not a Folder object."); } if (!((FolderData)parent).isAllowedChildType(typeId)) { throw new ConstraintException("Object type " + typeId + " is not allowed as child for destination folder"); } } else if (typeDefinition.isFileable()) { throw new ConstraintException("Policy type is fileable. Parent folder must be provided."); } checkProperties(typeDefinition, properties, CREATE); checkACL(typeDefinition, addACL, removeACL); checkPolicies(typeDefinition, policies); ObjectData newPolicy = storage.createPolicy((FolderData)parent, typeDefinition, properties, CmisUtils.mergeACLs(parent != null ? parent.getACL(false) : null, addACL, removeACL), createPolicyList(policies)); return newPolicy.getObjectId(); } /** * Create a relationship object. * * @param properties properties to be applied to newly created relationship * @param addACL set Access Control Entry to be applied for newly created * relationship. May be <code>null</code> or empty list * @param removeACL set Access Control Entry that MUST be removed from the * newly created relationship. May be <code>null</code> or empty list * @param policies list of policy id that MUST be applied to the newly * created relationship. May be <code>null</code> or empty collection * @return ID of newly created relationship * @throws ObjectNotFoundException if <code>cmis:sourceId</code> or * <code>cmis:targetId</code> property value is id of object that * can't be found in storage * @throws TypeNotFoundException if type specified by property * <code>cmis:objectTypeId</code> does not exist * @throws ConstraintException if any of following condition are met: * <ul> * <li><code>cmis:objectTypeId</code> property value is not an object * type whose baseType is Relationship</li> * <li>value of any of the properties violates the * min/max/required/length constraints specified in the property * definition in the object type</li> * <li>sourceObjectId ObjectType is not in the list of * AllowedSourceTypes specified by the object type definition * specified by <code>cmis:objectTypeId</code> property value</li> * <li>targetObjectId ObjectType is not in the list of * AllowedTargetTypes specified by the object type definition * specified by <code>cmis:objectTypeId</code> property value</li> * <li>controllablePolicy attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one policy is provided</li> * <li>controllableACL attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value is * set to <code>false</code> and at least one ACE is provided</li> * <li>at least one of the permissions is used in an ACE provided * which is not supported by the storage</li> * </ul> * @throws NameConstraintViolationException violation is detected with the * given <code>cmis:name</code> property value. Storage MAY chose * other name which does not conflict * @throws StorageException if new Relationship can't be saved in storage * cause to storage internal problem */ public String createRelationship(Map<String, Property<?>> properties, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies) throws ObjectNotFoundException, TypeNotFoundException, ConstraintException, NameConstraintViolationException, StorageException { checkConnection(); if (properties == null) { throw new InvalidArgumentException("Properties may not by null."); } String typeId = getTypeId(properties); TypeDefinition typeDefinition = getTypeDefinition(typeId, true); if (typeDefinition.getBaseId() != BaseType.RELATIONSHIP) { throw new ConstraintException("Type " + typeId + " is not type whose base type is cmis:relationship."); } String sourceId = getSourceId(properties); String targetId = getTargetId(properties); // check is source and target object types are supported ObjectData source = storage.getObjectById(sourceId); if (typeDefinition.getAllowedSourceTypes() != null && !(Arrays.asList(typeDefinition.getAllowedSourceTypes()).contains(source.getTypeId()))) { throw new ConstraintException("Source object type " + source.getTypeId() + " is not supported."); } ObjectData target = storage.getObjectById(targetId); if (typeDefinition.getAllowedTargetTypes() != null && !(Arrays.asList(typeDefinition.getAllowedTargetTypes()).contains(target.getTypeId()))) { throw new ConstraintException("Target object type " + target.getTypeId() + " is not supported."); } checkProperties(typeDefinition, properties, CREATE); checkACL(typeDefinition, addACL, removeACL); checkPolicies(typeDefinition, policies); ObjectData newRelationship = storage.createRelationship(source, target, typeDefinition, properties, CmisUtils.mergeACLs(null, addACL, removeACL), createPolicyList(policies)); return newRelationship.getObjectId(); } private String getTypeId(Map<String, Property<?>> properties) { String typeId = getSingleValue(properties, CmisConstants.OBJECT_TYPE_ID); if (typeId == null) { throw new InvalidArgumentException("Type Id ('cmis:objectTypeId') is not specified."); } return typeId; } private String getSourceId(Map<String, Property<?>> properties) { String typeId = getSingleValue(properties, CmisConstants.SOURCE_ID); if (typeId == null) { throw new InvalidArgumentException("Source Id ('cmis:sourceId') is not specified."); } return typeId; } private String getTargetId(Map<String, Property<?>> properties) { String typeId = getSingleValue(properties, CmisConstants.TARGET_ID); if (typeId == null) { throw new InvalidArgumentException("Target Id ('cmis:targetId') is not specified."); } return typeId; } private String getSingleValue(Map<String, Property<?>> properties, String name) { String value = null; Property<?> typeProperty = properties.get(name); if (typeProperty != null && typeProperty.getValues().size() > 0) { value = (String)typeProperty.getValues().get(0); } return value; } /** * Delete the content stream for the specified Document object. * * * @param documentId document id * @param changeTokenHolder is used for optimistic locking and/or concurrency * checking to ensure that user updates do not conflict. This * parameter must never be <code>null</code> but * {@link ChangeTokenHolder#getValue()} may return <code>null</code> * if caller does not provide change token. After successful deleting * content stream <code>changeTokenHolder</code> may contains updated * change token if backend support this feature * @return ID of updated object * @throws NullPointerException if <code>changeTokenHolder</code> is * <code>null</code> * @throws ObjectNotFoundException if document with specified id * <code>documentId</code> does not exist * @throws ConstraintException if object's type definition * <i>contentStreamAllowed</i> attribute is set to <i>required</i> * @throws UpdateConflictException if update an object that is no longer * current. Storage determine this by using change token * @throws VersioningException if object is a non-current (latest) document * version and updatiing other then latest version is not supported * @throws StorageException if content of document can not be removed cause * to storage internal problem */ public String deleteContentStream(String documentId, ChangeTokenHolder changeTokenHolder) throws ObjectNotFoundException, ConstraintException, UpdateConflictException, VersioningException, StorageException { checkConnection(); if (changeTokenHolder == null) { throw new NullPointerException("changeTokenHolder may not by null."); } ObjectData document = storage.getObjectById(documentId); if (document.getBaseType() != BaseType.DOCUMENT) { // be sure object is document throw new InvalidArgumentException("Object " + documentId + " is not Document."); } if (document.getTypeDefinition().getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) { throw new ConstraintException("Content stream is required for object and may not be removed."); } // Validate change token, object may be already updated. validateChangeToken(document, changeTokenHolder.getValue()); try { ((DocumentData)document).setContentStream(null); } catch (IOException never) { // should never happen because to null content stream throw new CmisRuntimeException("Unable delete document content stream. " + never.getMessage(), never); } // Update change token String changeToken = document.getChangeToken(); changeTokenHolder.setValue(changeToken); return document.getObjectId(); } /** * Delete the specified object. * * @param objectId the object id * @param deleteAllVersions if <code>true</code> (Default if not specified) * then delete all versions of the document. If <code>false</code>, * delete only the document object specified. This parameter will be * ignored if parameter when <code>objectId</code> non-document object * or non-versionable document * @throws ObjectNotFoundException if object with specified id * <code>objectId</code> does not exist * @throws ConstraintException if objectId is folder that contains one or * more children or is root folder * @throws UpdateConflictException if object that is no longer current (as * determined by the storage) * @throws VersioningException if object can not be removed cause to * versioning conflict * @throws StorageException if object can not be removed cause to storage * internal problem */ public void deleteObject(String objectId, Boolean deleteAllVersions) throws ObjectNotFoundException, ConstraintException, UpdateConflictException, VersioningException, StorageException { checkConnection(); ObjectData object = storage.getObjectById(objectId); if (object.getBaseType() == BaseType.FOLDER) { if (((FolderData)object).hasChildren()) { throw new ConstraintException("Failed delete object. Object " + objectId + " is Folder and contains one or more objects."); } if (((FolderData)object).isRoot()) { throw new ConstraintException("Root folder can't be deleted."); } } if (deleteAllVersions == null) { deleteAllVersions = true; // Default. } storage.deleteObject(object, deleteAllVersions); } /** * Delete the specified folder object and all of its child- and * descendant-objects. * * @param folderId folder id * @param deleteAllVersions if <code>true</code> (Default if not specified) * then delete all versions of the document. If <code>false</code>, * delete only the document object specified. This parameter will be * ignored if parameter when <code>objectId</code> non-document object * or non-versionable document * @param unfileObject an enumeration specifying how the storage MUST process * file-able child objects: * <ul> * <li>unfile: Unfile all fileable objects</li> * <li>deletesinglefiled: Delete all fileable non-folder objects whose * only parent-folders are in the current folder tree. Unfile all * other fileable non-folder objects from the current folder tree</li> * <li>delete: Delete all fileable objects</li> * </ul> * @param continueOnFailure if <code>true</code>, then the stprage SHOULD * continue attempting to perform this operation even if deletion of a * child object in the specified folder cannot be deleted. Default is * <code>false</code>. * @return list of id that were not deleted * @throws ObjectNotFoundException if folder with specified id * <code>folderId</code> does not exist * @throws ConstraintException if folder with specified id * <code>folderId</code> is root folder * @throws UpdateConflictException if some object(s) that is no longer * current (as determined by the storage) */ public Collection<String> deleteTree(String folderId, Boolean deleteAllVersions, UnfileObject unfileObject, Boolean continueOnFailure) throws ObjectNotFoundException, ConstraintException, UpdateConflictException { checkConnection(); ObjectData folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new ConstraintException("Failed delete tree. Object " + folderId + " is not a Folder."); } if (((FolderData)folder).isRoot()) { throw new ConstraintException("Root folder can't be removed."); } // Default values. if (unfileObject == null) { unfileObject = UnfileObject.DELETE; } if (deleteAllVersions == null) { deleteAllVersions = true; } if (continueOnFailure == null) { continueOnFailure = false; } // Check unfiling capability if 'unfileObject' is other then 'DELETE' if (unfileObject != UnfileObject.DELETE && !storage.getRepositoryInfo().getCapabilities().isCapabilityUnfiling()) { throw new InvalidArgumentException( "Unfiling capability is not supported. Parameter 'unfileObject' may not be other then 'DELETE'."); } Collection<String> failedDelete = storage.deleteTree((FolderData)folder, deleteAllVersions, unfileObject, continueOnFailure); return failedDelete; } /** * Get the ACL currently applied to the specified object. * * 2.2.10.1 getACL * * * @param objectId identifier of object * @param onlyBasicPermissions if <code>true</code> then return only the CMIS * Basic permissions * @return actual ACL or empty list if no ACL applied to object * @throws ObjectNotFoundException if <code>objectId</code> or does not * exists */ public List<AccessControlEntry> getACL(String objectId, boolean onlyBasicPermissions) throws ObjectNotFoundException { checkConnection(); if (storage.getRepositoryInfo().getCapabilities().getCapabilityACL() == CapabilityACL.NONE) { throw new NotSupportedException("ACL capability is not supported."); } ObjectData object = storage.getObjectById(objectId); List<AccessControlEntry> acl = object.getACL(onlyBasicPermissions); return acl; } /** * Get the list of allowable actions for an Object. * * * @param objectId object id * @return allowable actions for object * @throws ObjectNotFoundException if object with specified id * <code>objectId</code> does not exist */ public AllowableActions getAllowableActions(String objectId) throws ObjectNotFoundException { checkConnection(); ObjectData object = storage.getObjectById(objectId); return storage.calculateAllowableActions(object); } /** * Get all documents in version series. * * 2.2.7.6 getAllVersions * * * @param versionSeriesId version series id * @param includeAllowableActions <code>true</code> if allowable actions * should be included in response <code>false</code> otherwise * @param includeObjectInfo if <code>true</code> then in result must be * included external information about each object. See * {@link ObjectInfo}. Particular this info may be used by REST Atom * binding for building correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @return documents in the specified <code>versionSeriesId</code>sorted by * 'cmis:creationDate' descending. Even not versionable documents * must have exactly one document in version series * @throws ObjectNotFoundException if object with specified id * <code>versionSeriesId</code> does not exist * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public List<CmisObject> getAllVersions(String versionSeriesId, boolean includeAllowableActions, boolean includeObjectInfo, String propertyFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); Collection<DocumentData> versions = storage.getAllVersions(versionSeriesId); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); List<CmisObject> cmisVersions = new ArrayList<CmisObject>(versions.size()); for (ObjectData objectData : versions) { cmisVersions.add(getCmisObject(objectData, includeAllowableActions, IncludeRelationships.NONE, false, false, includeObjectInfo, parsedPropertyFilter, RenditionFilter.NONE_FILTER)); } return cmisVersions; } /** * Gets the list of policies currently applied to the specified object. * * 2.2.9.3 getAppliedPolicies * * * @param objectId the object id * @param includeObjectInfo if <code>true</code> then in result must be * included external information about each object. See * {@link ObjectInfo}. Particular this info may be used by REST Atom * binding for building correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @return list of policy objects. If object has not applied policies that * empty list will be returned * @throws ObjectNotFoundException if object with <code>objectId</code> does * not exist * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public List<CmisObject> getAppliedPolicies(String objectId, boolean includeObjectInfo, String propertyFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); ObjectData object = storage.getObjectById(objectId); Collection<PolicyData> policies = object.getPolicies(); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); List<CmisObject> policyIDs = new ArrayList<CmisObject>(policies.size()); for (ObjectData policy : policies) { CmisObject cmisPolicy = getCmisObject(policy, false, IncludeRelationships.NONE, false, false, includeObjectInfo, parsedPropertyFilter, RenditionFilter.NONE_FILTER); policyIDs.add(cmisPolicy); } return policyIDs; } /** * Documents that are checked out that the user has access to. * * * @param folderId folder from which get checked-out documents if null get * all checked-out documents in storage * @param includeAllowableActions if <code>true</code> then allowable actions * should be included in response * @param includeRelationships indicates what relationships of object must be * returned * @param includeObjectInfo if <code>true</code> then result must include * external information about each object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @param orderBy comma-separated list of query names and the ascending * modifier 'ASC' or the descending modifier 'DESC' for each query * name. A storage's handling of the orderBy input is storage-specific * and storage may ignore this parameter if it not able sort items * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @param skipCount the skip items. Must be equals or greater the 0 * @return checked-out documents * @throws ObjectNotFoundException if <code>folderId</code> is not * <code>null</code> and object with <code>folderId</code> was not * found * @throws InvalidArgumentException if <code>folderId</code> is id of object * that base type is not a Folder * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public ItemsList<CmisObject> getCheckedOutDocs(String folderId, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includeObjectInfo, String propertyFilter, String renditionFilter, String orderBy, int maxItems, int skipCount) throws ObjectNotFoundException, InvalidArgumentException, FilterNotValidException { checkConnection(); if (skipCount < 0) { throw new InvalidArgumentException("skipCount parameter is negative."); } ObjectData folder = null; if (folderId != null) { folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Can't get checkedout documents. Object " + folderId + " is not a Folder."); } } ItemsIterator<DocumentData> iterator = storage.getCheckedOutDocuments((FolderData)folder, orderBy); try { if (skipCount > 0) { iterator.skip(skipCount); } } catch (NoSuchElementException nse) { throw new InvalidArgumentException("skipCount parameter is greater then total number of items"); } PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; // Default. } ItemsList<CmisObject> checkedout = new ItemsList<CmisObject>(); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { ObjectData pwcData = iterator.next(); CmisObject pwc = getCmisObject(pwcData, includeAllowableActions, includeRelationships, false, false, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); checkedout.getItems().add(pwc); } checkedout.setHasMoreItems(iterator.hasNext()); checkedout.setNumItems(iterator.size()); // ItemsIterator gives -1 if total number is unknown return checkedout; } /** * Get the list of child objects contained in the specified folder. * * @param folderId folder id * @param includeAllowableActions if <code>true</code> then allowable actions * for each child object should be included in response * @param includeRelationships indicates what relationships of object must be * returned * @param includePathSegments if <code>true</code> then returns a PathSegment * for each child object * @param includeObjectInfo if <code>true</code> then result must include * external information about each object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @param orderBy comma-separated list of query names and the ascending * modifier 'ASC' or the descending modifier 'DESC' for each query * name. A storage's handling of the orderBy input is storage-specific * and storage may ignore this parameter if it not able sort items. * May be <code>null</code> if sorting is not required * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @param skipCount the skip items. Must be equals or greater the 0 * @return folder's children * @throws ObjectNotFoundException if object with <code>folderId</code> was * not found * @throws InvalidArgumentException if object with id <code>folderId</code> * is not a Folder * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public ItemsList<CmisObject> getChildren(String folderId, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePathSegments, boolean includeObjectInfo, String propertyFilter, String renditionFilter, String orderBy, int maxItems, int skipCount) throws ObjectNotFoundException, InvalidArgumentException, FilterNotValidException { checkConnection(); if (skipCount < 0) { throw new InvalidArgumentException("skipCount parameter is negative."); } ObjectData folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Can't get children. Object " + folderId + " is not a Folder."); } /* TODO : orderBy in some more usable form */ ItemsIterator<ObjectData> iterator = ((FolderData)folder).getChildren(orderBy); try { if (skipCount > 0) { iterator.skip(skipCount); } } catch (NoSuchElementException nse) { throw new InvalidArgumentException("'skipCount' parameter is greater then total number of items"); } PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; // Default } ItemsList<CmisObject> cmisChildren = new ItemsList<CmisObject>(); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { ObjectData childData = iterator.next(); CmisObject child = getCmisObject(childData, includeAllowableActions, includeRelationships, false, false, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); if (includePathSegments) { child.setPathSegment(childData.getName()); } cmisChildren.getItems().add(child); } // Indicate that we have some more results. cmisChildren.setHasMoreItems(iterator.hasNext()); cmisChildren.setNumItems(iterator.size()); // ItemsIterator gives -1 if total number is unknown. return cmisChildren; } /** * Gets content changes. This service is intended to be used by search * crawlers or other applications that need to efficiently understand what * has changed in the storage. * * @param changeLogTokenHolder if {@link ChangeLogTokenHolder#getToken()} return * value other than <code>null</code>, then change event corresponded * to the value of the specified change log token will be returned as * the first result in the output. If not specified, then will be * returned the first change event recorded in the change log. When * set of changes is returned then <code>changeLogToken</code> must * contains log token corresponded to the last change event. Then it * may be used by client for getting next set on change events. * @param includeProperties if <code>true</code>, then the result includes * the updated property values for 'updated' change events. If * <code>false</code>, then the result will not include the updated * property values for 'updated' change events. The single exception * to this is that the objectId MUST always be included * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties. This parameter will be * ignored <code>includeProperties</code> is <code>false</code> * @param includePolicyIDs if <code>true</code>, then the include the IDs of * Policies applied to the object referenced in each change event, if * the change event modified the set of policies applied to the object * @param includeAcl if <code>true</code>, then include ACL applied to the * object referenced in each change event * @param includeObjectInfo if <code>true</code> then result must include * external information about each object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @return content changes * @throws NullPointerException if <code>changeLogTokenHolder</code> is * <code>null</code> * @throws ConstraintException if the event corresponding to the change log * token provided as an input parameter is no longer available in the * change log. (E.g. because the change log was truncated) * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public ItemsList<CmisObject> getContentChanges(ChangeLogTokenHolder changeLogTokenHolder, boolean includeProperties, String propertyFilter, boolean includePolicyIDs, boolean includeAcl, boolean includeObjectInfo, int maxItems) throws ConstraintException, FilterNotValidException { if (changeLogTokenHolder == null) { throw new NullPointerException("ChangeLogTokenHolder may not by null."); } String token = changeLogTokenHolder.getValue(); ItemsIterator<ChangeEvent> iterator = storage.getChangeLog(token); // Not need filter if all properties rejected. PropertyFilter parsedPropertyFilter = includeProperties ? new PropertyFilter(propertyFilter) : null; ItemsList<CmisObject> cmisChanges = new ItemsList<CmisObject>(); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { ChangeEvent event = iterator.next(); String objectId = event.getObjectId(); CmisObject cmis = new CmisObject(); // policies if (includePolicyIDs && event.getType() == ChangeType.SECURITY && event.getPolicyIds() != null && event.getPolicyIds().size() > 0) { cmis.getPolicyIds().addAll(event.getPolicyIds()); } // ACL if (includeAcl && event.getType() == ChangeType.SECURITY && event.getAcl() != null && event.getAcl().size() > 0) { cmis.getACL().addAll(event.getAcl()); } // properties if (includeProperties && event.getType() == ChangeType.UPDATED && event.getProperties() != null) { for (Property<?> property : event.getProperties()) { if (parsedPropertyFilter.accept(property.getQueryName())) { String id = property.getId(); cmis.getProperties().put(id, property); } } } // cmis:objectId must be always returned if (cmis.getProperties().get(CmisConstants.OBJECT_ID) == null) { // NOTE Do not provide query, local and display names for property // since we can be not able to determine it. cmis.getProperties().put(CmisConstants.OBJECT_ID, new IdProperty(CmisConstants.OBJECT_ID, null, null, null, objectId)); } // able to provide object id only if (includeObjectInfo) { ObjectInfo objectInfo = new ObjectInfo(); objectInfo.setId(objectId); cmis.setObjectInfo(objectInfo); } cmis.setChangeInfo(new ChangeInfo(event.getDate(), event.getType())); // update token, it will keep latestChangeLogToken when all events // will be retrieved all maxItems limit reached. token = event.getLogToken(); cmisChanges.getItems().add(cmis); } // Indicate that we have some more results. cmisChanges.setHasMoreItems(iterator.hasNext()); cmisChanges.setNumItems(iterator.size()); // ItemsIterator gives -1 if total number is unknown. changeLogTokenHolder.setValue(token); return cmisChanges; } /** * Get document's content stream. * * @param objectId object id * @param streamId identifier for the rendition stream, when used to get a * rendition stream. For Documents, if not provided then this method * returns the content stream. For Folders (if Folders supports * renditions) this parameter must be provided * @return object's content stream or throws {@link ConstraintException} if * object has not content stream. Never return null * @throws ObjectNotFoundException if object with specified id * <code>objectId</code> does not exist * @throws ConstraintException if the object specified by objectId does NOT * have a content stream or rendition stream */ public ContentStream getContentStream(String objectId, String streamId) throws ObjectNotFoundException, ConstraintException { checkConnection(); ObjectData object = storage.getObjectById(objectId); ContentStream contentStream = null; try { if (streamId != null) { contentStream = object.getContentStream(streamId); } else if (object.getBaseType() == BaseType.DOCUMENT) { contentStream = ((DocumentData)object).getContentStream(); } } catch (IOException ioe) { throw new CmisRuntimeException("Unable get content stream. " + ioe.getMessage(), ioe); } if (contentStream == null) { throw new ConstraintException("Object does not have content stream."); } return contentStream; } /** * Get the collection of descendant objects contained in the specified folder * and any (according to <code>depth</code>) of its child-folders. * * 2.2.3.2 getDescendants * * * @param folderId folder id * @param depth depth for discover descendants if -1 then discovery * descendants at all levels * @param includeAllowableActions if <code>true</code> then allowable actions * for each object should be included in response * @param includeRelationships indicates what relationships of object must be * returned * @param includePathSegments if <code>true</code> then returns a PathSegment * for each child object * @param includeObjectInfo if <code>true</code> then result must include * external information about each object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @return folder's tree * @throws ObjectNotFoundException if object with <code>folderId</code> was * not found * @throws InvalidArgumentException if object with id <code>folderId</code> * is not a Folder or if <code>depth != -1 && !(depth >= 1)</code> * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public List<ItemsTree<CmisObject>> getDescendants(String folderId, int depth, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePathSegments, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); if (depth != -1 && !(depth >= 1)) { throw new InvalidArgumentException("Invalid depth parameter. Must be 1 or greater then 1 or -1 but " + depth + " specified."); } return getObjectTree(folderId, depth, null, includeAllowableActions, includeRelationships, includePathSegments, includeObjectInfo, propertyFilter, renditionFilter); } /** * Get parent for specified folder. This method MUST NOT be used for getting * parents of other fileable objects. * * @param folderId folder id * @param includeObjectInfo if <code>true</code> then result must include * external information about object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @return folder's parent * @throws ObjectNotFoundException if object with <code>folderId</code> was * not found * @throws InvalidArgumentException if the <code>folderId</code> is id of the * root folder * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public CmisObject getFolderParent(String folderId, boolean includeObjectInfo, String propertyFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); ObjectData folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + folderId + " is not a Folder."); } if (((FolderData)folder).isRoot()) { throw new InvalidArgumentException("Can't get parent of root folder."); } FolderData parent = null; try { parent = folder.getParent(); } catch (ConstraintException never) { // Should never happen because we already determined object is folder // and it is not root folder so MUST have exactly one parent throw new CmisRuntimeException(never.getMessage()); } PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); CmisObject cmisParent = getCmisObject(parent, false, IncludeRelationships.NONE, false, false, includeObjectInfo, parsedPropertyFilter, RenditionFilter.NONE_FILTER); return cmisParent; } /** * Get the collection of descendant folder objects contained in the specified * folder and any (according to <code>depth</code>) of its child-folders. * * 2.2.3.3 getFolderTree * * @param folderId folder id * @param depth depth for discover descendants if -1 then discovery * descendants at all levels * @param includeAllowableActions if <code>true</code> then allowable actions * for each object should be included in response * @param includeRelationships indicates what relationships of object must be * returned * @param includePathSegments if <code>true</code> then returns a PathSegment * for each child object * @param includeObjectInfo if <code>true</code> then result must include * external information about each object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @return folder's tree * @throws ObjectNotFoundException if object with <code>folderId</code> was * not found * @throws InvalidArgumentException if object with id <code>folderId</code> * is not a Folder or if <code>depth != -1 && !(depth >= 1)</code> * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public List<ItemsTree<CmisObject>> getFolderTree(String folderId, int depth, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePathSegments, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); if (depth != -1 && !(depth >= 1)) { throw new InvalidArgumentException("Invalid depth parameter. Must be 1 or greater then 1 or -1 but " + depth + " specified."); } return getObjectTree(folderId, depth, BaseType.FOLDER, includeAllowableActions, includeRelationships, includePathSegments, includeObjectInfo, propertyFilter, renditionFilter); } /** * Get object. * * @param objectId object id * @param includeAllowableActions if <code>true</code> then include object * allowable actions for object * @param includeRelationships include object relationships * @param includePolicyIDs include policies applied to object * @param includeAcl include object's ACL * @param includeObjectInfo if <code>true</code> then in result must be * included external information about object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document. * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @return retrieval object * @throws ObjectNotFoundException if object with specified id * <code>objectId</code> does not exist * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public CmisObject getObject(String objectId, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePolicyIDs, boolean includeAcl, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; } ObjectData objectData = storage.getObjectById(objectId); CmisObject cmisObject = getCmisObject(objectData, includeAllowableActions, includeRelationships, includePolicyIDs, includeAcl, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); return cmisObject; } /** * Get object by specified path. * * @param path object's path * @param includeAllowableActions <code>true</code> if allowable actions * should be included in response <code>false</code> otherwise * @param includeRelationships include object's relationship * @param includePolicyIDs include policies IDs applied to object * @param includeAcl include ACL * @param includeObjectInfo if <code>true</code> then in result must be * included external information about object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @return retrieval object * @throws ObjectNotFoundException if object with specified <code>path</code> * does not exist * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public CmisObject getObjectByPath(String path, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePolicyIDs, boolean includeAcl, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; // Default. } ObjectData object = storage.getObjectByPath(path); CmisObject cmis = getCmisObject(object, includeAllowableActions, includeRelationships, includePolicyIDs, includeAcl, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); return cmis; } /** * Get the latest Document object in the version series. * * 2.2.7.4 getObjectOfLatestVersion * * @param versionSeriesId version series id * @param major if <code>true</code> then return the properties for the * latest major version object in the Version Series, otherwise return * the properties for the latest (major or non-major) version. If the * input parameter major is <code>true</code> and the Version Series * contains no major versions, then the ObjectNotFoundException will * be thrown. * @param includeAllowableActions <code>true</code> if allowable actions * should be included in response <code>false</code> otherwise * @param includeRelationships include object's relationship * @param includePolicyIDs include policies IDs applied to object * @param includeAcl include ACL * @param includeObjectInfo if <code>true</code> then in result must be * included external information about object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @return object of latest version in version series * @throws ObjectNotFoundException if Version Series with id * <code>versionSeriesId</code> does not exist or the input parameter * <code>major</code> is <code>true</code> and the Version Series * contains no major versions. * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public CmisObject getObjectOfLatestVersion(String versionSeriesId, boolean major, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePolicyIDs, boolean includeAcl, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); Collection<DocumentData> versions = storage.getAllVersions(versionSeriesId); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; // Default } // Even for not-versionable documents version series contains exactly one version of document. if (versions.size() == 1) { return getCmisObject(versions.iterator().next(), includeAllowableActions, includeRelationships, includePolicyIDs, includeAcl, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); } // Storage#getAllVersions(versionSeriesId) return sorted by // 'cmis:creationDate' descending. Latest version is version with latest // 'cmis:lastModificationDate'. List<DocumentData> v = new ArrayList<DocumentData>(versions); Collections.sort(v, CmisUtils.versionComparator); if (!major) { return getCmisObject(v.get(0), includeAllowableActions, includeRelationships, includePolicyIDs, includeAcl, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); } for (DocumentData document : v) { if (document.isMajorVersion()) { return getCmisObject(document, includeAllowableActions, includeRelationships, includePolicyIDs, includeAcl, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); } } // May happen only if major version requested but there is no any major version. throw new ObjectNotFoundException("Not found any major versions in version series."); } /** * Gets the parent folder(s) for the specified object. * * * @param objectId object id * @param includeAllowableActions if <code>true</code> then allowable actions * should be included in response * @param includeRelationships indicates what relationships of object must be * returned * @param includeRelativePathSegment if <code>true</code>, returns a * PathSegment for each child object * @param includeObjectInfo if <code>true</code> then result must include * external information about object. See {@link ObjectInfo} and * {@link ObjectParent#getObject()}. Particular this info may be used * by REST Atom binding for building correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @return object's parents. Empty list for unfiled objects or for the root * folder * @throws ObjectNotFoundException if object with <code>objectId</code> was * not found * @throws ConstraintException if this method is invoked on an not fileable * object * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition or <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public List<ObjectParent> getObjectParents(String objectId, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includeRelativePathSegment, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, ConstraintException, FilterNotValidException { checkConnection(); ObjectData object = storage.getObjectById(objectId); TypeDefinition typeDefinition = object.getTypeDefinition(); if (!typeDefinition.isFileable()) { throw new ConstraintException("Can't get parents. Object " + objectId + " has type " + object.getTypeId() + " that is not fileable"); } Collection<FolderData> parents = object.getParents(); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; // Default } List<ObjectParent> cmisParents = new ArrayList<ObjectParent>(parents.size()); for (ObjectData parent : parents) { CmisObject cmisParent = getCmisObject(parent, includeAllowableActions, includeRelationships, false, false, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); ObjectParent parentType = new ObjectParent(cmisParent, includeRelativePathSegment ? object.getName() : null); cmisParents.add(parentType); } return cmisParents; } /** * Get all or a subset of relationships associated with an independent * object. * * * @param objectId object id * @param direction relationship direction * @param typeId relationship type id. If <code>null</code> then return * relationships of all types * @param includeSubRelationshipTypes if <code>true</code>, then the return * all relationships whose object types are descendant types of * <code>typeId</code>. * @param includeAllowableActions if <code>true</code> then allowable actions * should be included in response * @param includeObjectInfo if <code>true</code> then in result must be * included external information about each object. See * {@link ObjectInfo}. Particular this info may be used by REST Atom * binding for building correct Atom document * @param propertyFilter property filter as string * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @param skipCount the skip items. Must be equals or greater the 0 * @return object's relationships * @throws ObjectNotFoundException if object with <code>objectId</code> does * not exist * @throws TypeNotFoundException if <code>typeId != null</code> and type * <code>typeId</code> does not exist * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public ItemsList<CmisObject> getObjectRelationships(String objectId, RelationshipDirection direction, String typeId, boolean includeSubRelationshipTypes, boolean includeAllowableActions, boolean includeObjectInfo, String propertyFilter, int maxItems, int skipCount) throws FilterNotValidException, ObjectNotFoundException, TypeNotFoundException { checkConnection(); if (skipCount < 0) { throw new InvalidArgumentException("skipCount parameter is negative."); } if (direction == null) { direction = RelationshipDirection.SOURCE; // Default } TypeDefinition type = getTypeDefinition(typeId == null ? BaseType.RELATIONSHIP.value() : typeId); if (type.getBaseId() != BaseType.RELATIONSHIP) { throw new InvalidArgumentException("Type " + typeId + " is not Relationship type."); } ObjectData object = storage.getObjectById(objectId); ItemsIterator<RelationshipData> iterator = object.getRelationships(direction, type, includeSubRelationshipTypes); try { if (skipCount > 0) { iterator.skip(skipCount); } } catch (NoSuchElementException nse) { throw new InvalidArgumentException("skipCount parameter is greater then total number of items"); } PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); ItemsList<CmisObject> relationships = new ItemsList<CmisObject>(); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { ObjectData rel = iterator.next(); CmisObject cmis = getCmisObject(rel, includeAllowableActions, null, false, false, includeObjectInfo, parsedPropertyFilter, RenditionFilter.NONE_FILTER); relationships.getItems().add(cmis); } // Indicate we have some more results or not relationships.setHasMoreItems(iterator.hasNext()); relationships.setNumItems(iterator.size()); // ItemsIterator gives -1 if total number is unknown return relationships; } /** * Get object's properties. * * * @param objectId object id * @param includeObjectInfo if <code>true</code> then in result must be * included external information about object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @return CMIS object that contains properties * @throws ObjectNotFoundException if object with specified id * <code>objectId</code> does not exist * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public CmisObject getProperties(String objectId, boolean includeObjectInfo, String propertyFilter) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); ObjectData object = storage.getObjectById(objectId); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); CmisObject cmis = getCmisObject(object, false, IncludeRelationships.NONE, false, false, includeObjectInfo, parsedPropertyFilter, RenditionFilter.NONE_FILTER); return cmis; } /** * Get properties of latest version in version series. * * 2.2.7.5 getPropertiesOfLatestVersion * * @param versionSeriesId version series id * @param major if <code>true</code> then return the properties for the * latest major version object in the Version Series, otherwise return * the properties for the latest (major or non-major) version. If the * input parameter major is <code>true</code> and the Version Series * contains no major versions, then the ObjectNotFoundException will * be thrown. * @param includeObjectInfo if <code>true</code> then in result must be * included external information about object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param propertyFilter comma-delimited list of property definition Query * Names. A wildcard '*' is supported and minds return all properties. * If empty string or <code>null</code> provided than storage MAY * return storage specific set of properties * @return CMIS object that contains properties of latest version of object * in version series * @throws ObjectNotFoundException if Version Series with id * <code>versionSeriesId</code> does not exist or the input parameter * <code>major</code> is <code>true</code> and the Version Series * contains no major versions. * @throws FilterNotValidException if <code>propertyFilter</code> has invalid * syntax or contains at least one property name that is not in * object's property definition */ public CmisObject getPropertiesOfLatestVersion(String versionSeriesId, boolean major, boolean includeObjectInfo, String propertyFilter) throws FilterNotValidException, ObjectNotFoundException { return getObjectOfLatestVersion(versionSeriesId, major, false, null, false, false, includeObjectInfo, propertyFilter, RenditionFilter.NONE); } /** * Get the list of associated Renditions for the specified object. Only * rendition attributes are returned, not rendition stream. * * * @param objectId object id * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @param skipCount the skip items. Must be equals or greater then 0 * @return object's renditions * @throws ObjectNotFoundException if object with specified * <code>objectId</code> does not exist * @throws FilterNotValidException if <code>renditionFilter</code> has * invalid syntax or contains at least one unknown rendition */ public List<Rendition> getRenditions(String objectId, String renditionFilter, int maxItems, int skipCount) throws ObjectNotFoundException, FilterNotValidException { checkConnection(); if (skipCount < 0) { throw new InvalidArgumentException("skipCount parameter is negative."); } if (storage.getRepositoryInfo().getCapabilities().getCapabilityRenditions() == CapabilityRendition.NONE) { throw new NotSupportedException("Renditions is not supported."); } ObjectData objectData = storage.getObjectById(objectId); ItemsIterator<Rendition> iterator = storage.getRenditions(objectData); try { if (skipCount > 0) { iterator.skip(skipCount); } } catch (NoSuchElementException nse) { throw new InvalidArgumentException("skipCount parameter is greater then total number of items"); } List<Rendition> renditions = new ArrayList<Rendition>(); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { Rendition r = iterator.next(); if (parsedRenditionFilter.accept(r)) { renditions.add(r); } } return renditions; } /** * Gets the storage associated to this connection. * * @return storage */ public Storage getStorage() { return storage; } /** * Set of object types. * * @param typeId the type id, if not <code>null</code> then return only * specified Object Type and its direct descendant. If * <code>null</code> then return base types. * @param includePropertyDefinition <code>true</code> if property definition * should be included <code>false</code> otherwise * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @param skipCount the skip items. Must be equals or greater then 0 * @return list of all base types or specified object type and its direct * children * @throws TypeNotFoundException if type <code>typeId</code> does not exist */ public ItemsList<TypeDefinition> getTypeChildren(String typeId, boolean includePropertyDefinition, int maxItems, int skipCount) throws TypeNotFoundException { checkConnection(); if (skipCount < 0) { throw new InvalidArgumentException("skipCount parameter is negative."); } ItemsIterator<TypeDefinition> iterator = storage.getTypeChildren(typeId, includePropertyDefinition); try { if (skipCount > 0) { iterator.skip(skipCount); } } catch (NoSuchElementException nse) { throw new InvalidArgumentException("skipCount parameter is greater then total number of items"); } ItemsList<TypeDefinition> typeChildren = new ItemsList<TypeDefinition>(); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { TypeDefinition type = iterator.next(); typeChildren.getItems().add(type); } // Indicate that we have some more results. typeChildren.setHasMoreItems(iterator.hasNext()); typeChildren.setNumItems(iterator.size()); // ItemsIterator gives -1 if total number is unknown return typeChildren; } /** * Get type definition for type <code>typeId</code> include property * definition, see {@link #getTypeDefinition(String, boolean)}. * * @param typeId type Id * @return type definition * @throws TypeNotFoundException if type <code>typeId</code> does not exist */ public TypeDefinition getTypeDefinition(String typeId) throws TypeNotFoundException { return getTypeDefinition(typeId, true); } /** * Get type definition for type <code>typeId</code>. * * @param typeId type Id * @param includePropertyDefinition if <code>true</code> property definition * should be included * @return type definition * @throws TypeNotFoundException if type <code>typeId</code> does not exist */ public TypeDefinition getTypeDefinition(String typeId, boolean includePropertyDefinition) throws TypeNotFoundException { checkConnection(); return storage.getTypeDefinition(typeId, includePropertyDefinition); } /** * Get all descendants of specified <code>typeId</code> in hierarchy. If * <code>typeId</code> is <code>null</code> then return all types and ignore * the value of the <code>depth</code> parameter. * * @param typeId the type id * @param depth the depth of level in hierarchy * @param includePropertyDefinition true if property definition should be * included false otherwise * @return list of descendant types * @throws TypeNotFoundException if type <code>typeId</code> does not exist * @throws InvalidArgumentException if * <code>depth != -1 && !(depth >= 1)</code> */ public List<ItemsTree<TypeDefinition>> getTypeDescendants(String typeId, int depth, boolean includePropertyDefinition) throws TypeNotFoundException { checkConnection(); if (depth != -1 && !(depth >= 1)) { throw new InvalidArgumentException("Invalid depth parameter. Must be 1 or greater then 1 or -1 but " + depth + " specified."); } return getTypeTree(typeId, depth, includePropertyDefinition); } /** * Moves the specified file-able object from one folder to another. * * @param objectId object id * @param targetFolderId target folder for moving object * @param sourceFolderId move object from which object to be moved * @return moved object ID * @throws ObjectNotFoundException if object with <code>objectId</code> or * <code>sourceFolderId</code> or <code>targetFolderId</code> were * not found * @throws ConstraintException if type of the given object is NOT in the list * of AllowedChildObjectTypeIds of the parent-folder specified by * <code>targetFolderId</code> * @throws NameConstraintViolationException violation is detected with the * given <code>cmis:name</code> property value in destination folder * @throws InvalidArgumentException if the service is invoked with a missing * <code>sourceFolderId</code> or the <code>sourceFolderId</code> * doesn't match the specified object's parent folder (or one of the * parent folders if the storage supports multifiling.). * @throws UpdateConflictException if object that is no longer current (as * determined by the storage) * @throws VersioningException if object is a non-current (latest) document * version * @throws StorageException if object can not be moved (save changes) cause * to storage internal problem */ public String moveObject(String objectId, String targetFolderId, String sourceFolderId) throws ObjectNotFoundException, NameConstraintViolationException, ConstraintException, UpdateConflictException, VersioningException, StorageException { checkConnection(); ObjectData object = storage.getObjectById(objectId); ObjectData target = storage.getObjectById(targetFolderId); if (target.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + targetFolderId + " is not a Folder object."); } if (!((FolderData)target).isAllowedChildType(object.getTypeId())) { throw new ConstraintException("Object with type " + object.getTypeId() + " is not allowed as child object fro target folder."); } if (sourceFolderId == null) { throw new InvalidArgumentException("sourceFolderId parameter may not be null"); } boolean found = false; for (ObjectData one : object.getParents()) { if (one.getObjectId().equals(sourceFolderId)) { found = true; break; } } if (!found) { throw new InvalidArgumentException("Specified source folder " + sourceFolderId + " is not a parent of " + objectId); } ObjectData source = storage.getObjectById(sourceFolderId); if (source.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + sourceFolderId + " is not a Folder object."); } ObjectData movedObject = storage.moveObject(object, (FolderData)target, (FolderData)source); return movedObject.getObjectId(); } /** * Executes a CMIS-SQL query statement against the contents of the CMIS * Storage. * * @param statement SQL statement * @param searchAllVersions if <code>false</code>, then include latest * versions of documents in the query search scope otherwise all * versions. If the Storage does not support the optional * capabilityAllVersionsSearchable capability, then this parameter * value MUST be set to <code>false</code> * @param includeAllowableActions if <code>true</code> return allowable * actions in request * @param includeRelationships indicates what relationships of object must be * returned * @param includeObjectInfo if <code>true</code> then result must include * external information about each object. See {@link ObjectInfo}. * Particular this info may be used by REST Atom binding for building * correct Atom document * @param renditionFilter renditions kinds or mimetypes that must be included * in result. If <code>null</code> or empty string provided then no * renditions will be returned. The Rendition Filter grammar is * defined as follows: * * <pre> * <renditionInclusion> ::= <none> | <wildcard> | <termlist> * <termlist> ::= <term> | <term> ',' <termlist> * <term> ::= <kind> | <mimetype> * <kind> ::= <text> * <mimetype> ::= <type> '/' <subtype> * <type> ::= <text> * <subtype> ::= <text> | <wildcard> * <text> ::= any char except whitespace * <wildcard> ::= '* * <none> ::= 'cmis:none' * </pre> * * An inclusion pattern allows: * <ul> * <li>Wildcard : include all associated Renditions</li> * <li>Comma-separated list of Rendition kinds or mimetypes : include * only those Renditions that match one of the specified kinds or * mimetypes</li> * <li>cmis:none: exclude all associated Renditions</li> * </ul> * @param maxItems max number of items in response. If -1 then no limit of * max items in result set * @param skipCount the skip items. Must be equals or greater then 0 * @return query results * @throws FilterNotValidException if <code>renditionFilter</code> has * invalid syntax or contains unknown rendition kinds or mimetypes */ public ItemsList<CmisObject> query(String statement, boolean searchAllVersions, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includeObjectInfo, String renditionFilter, int maxItems, int skipCount) throws FilterNotValidException { checkConnection(); if (skipCount < 0) { throw new InvalidArgumentException("skipCount parameter is negative."); } ItemsIterator<Result> iterator = storage.query(new Query(statement, searchAllVersions)); try { if (skipCount > 0) { iterator.skip(skipCount); } } catch (NoSuchElementException nse) { throw new InvalidArgumentException("skipCount parameter is greater then total number of items"); } if (includeRelationships == null) { includeRelationships = IncludeRelationships.NONE; // Default. } RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); ItemsList<CmisObject> list = new ItemsList<CmisObject>(); for (int count = 0; iterator.hasNext() && (maxItems < 0 || count < maxItems); count++) { Result result = iterator.next(); StringBuilder propertyFilter = new StringBuilder(); if (result.getPropertyNames() != null) { for (String s : result.getPropertyNames()) { if (propertyFilter.length() > 0) { propertyFilter.append(','); } propertyFilter.append(s); } } ObjectData data = null; try { data = storage.getObjectById(result.getObjectId()); } catch (ObjectNotFoundException e) { // If object was removed but found in index LOG.warn("Object " + result.getObjectId() + " was removed."); continue; } CmisObject object = getCmisObject(data, includeAllowableActions, includeRelationships, false, false, includeObjectInfo, new PropertyFilter(propertyFilter.toString()), parsedRenditionFilter); Score score = result.getScore(); if (score != null) { String scoreColumnName = score.getScoreColumnName(); DecimalProperty scoreProperty = new DecimalProperty(scoreColumnName, scoreColumnName, scoreColumnName, scoreColumnName, score .getScoreValue()); object.getProperties().put(scoreColumnName, scoreProperty); } list.getItems().add(object); } // Indicate that we have some more results. list.setHasMoreItems(iterator.hasNext()); list.setNumItems(iterator.size()); // ItemsIterator gives -1 if total number is unknown return list; } /** * Remove an existing fileable non-folder object from a folder. * * 2.2.5.2 removeObjectFromFolder * * * @param objectId the id of object to be removed * @param folderId the folder from which the object is to be removed. If * null, then remove the object from all folders in which it is * currently filed. In this case unfiling capability must be supported * otherwise {@link NotSupportedException} will be thrown * @throws ObjectNotFoundException if <code>objectId</code> or * <code>folderId</code> were not found * @throws InvalidArgumentException if object <code>objectId</code> is not * fileable or is folder or if object <code>folderId</code> is not a * folder * @throws NotSupportedException if unfiling capability is not supported by * backend storage */ public void removeObjectFromFolder(String objectId, String folderId) throws ObjectNotFoundException { checkConnection(); ObjectData object = storage.getObjectById(objectId); Collection<FolderData> parents = object.getParents(); if ((folderId == null || parents.size() == 1) && !storage.getRepositoryInfo().getCapabilities().isCapabilityUnfiling()) { // May not remove object from all folders. throw new NotSupportedException("Unfiling is not supported."); } if (!object.getTypeDefinition().isFileable() || object.getBaseType() == BaseType.FOLDER) { throw new InvalidArgumentException("Object " + objectId + " is not fileable or folder. Can't be unfiled."); } if (folderId != null) { ObjectData folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Object " + folderId + " is not a Folder object."); } ((FolderData)folder).removeObject(object); } else { storage.unfileObject(object); } } /** * Removes a specified policy from an object. * * * @param policyId id of policy to be removed from object * @param objectId id of object * @throws ObjectNotFoundException if object with <code>objectId</code> does * not exist * @throws ConstraintException if object with id <code>objectId</code> is not * controllable by policy * @throws InvalidArgumentException if object with <code>policyId</code> is * not object whose base type is Policy */ public void removePolicy(String policyId, String objectId) throws ConstraintException, ObjectNotFoundException { checkConnection(); ObjectData object = storage.getObjectById(objectId); if (!object.getTypeDefinition().isControllablePolicy()) { throw new ConstraintException("Object is not controllable by policy."); } ObjectData policyData = storage.getObjectById(policyId); if (policyData.getBaseType() != BaseType.POLICY) { throw new InvalidArgumentException("Object " + policyId + " is not a Policy object."); } object.removePolicy((PolicyData)policyData); } /** * Remove type definition for type <code>typeId</code> . * * @param typeId type Id * @throws TypeNotFoundException if type <code>typeId</code> does not exist * @throws ConstraintException if removing type violates a storage * constraint. For example, if storage already contains object of * this type * @throws StorageException if type can't be removed (save changes) cause to * storage internal problem */ public void removeType(String typeId) throws TypeNotFoundException, ConstraintException, StorageException { checkConnection(); storage.removeType(typeId); } /** * Sets the content stream for the specified Document object. * * * @param documentId document id * @param content content stream to be applied to object * @param changeTokenHolder is used for optimistic locking and/or concurrency * checking to ensure that user updates do not conflict. This * parameter must never be <code>null</code> but * {@link ChangeTokenHolder#getValue()} may return <code>null</code> * if caller does not provide change token. After successful updating * content stream <code>changeTokenHolder</code> may contains updated * change token if backend support this feature * @param overwriteFlag if <code>true</code> on object's content stream * exists it will be overridden. If <code>false</code> and context * stream already exists then ContentAlreadyExistsException will be * thrown * @return ID of updated object * @throws ObjectNotFoundException if document with specified id * <code>documentId</code> does not exist * @throws NullPointerException if <code>changeTokenHolder</code> is * <code>null</code> * @throws ContentAlreadyExistsException if the input parameter * <code>overwriteFlag</code> is <code>false</code> and the Object * already has a content-stream * @throws ConstraintException if document type definition attribute * {@link TypeDefinition#getContentStreamAllowed()} is 'notallowed' * and specified <code>contentStream</code> is other then * <code>null</code> or if * {@link TypeDefinition#getContentStreamAllowed()} attribute is * 'required' and <code>contentStream</code> is <code>null</code> * @throws StreamNotSupportedException will be thrown if the * contentStreamAllowed attribute of the object type definition * specified by the <code>cmis:objectTypeId</code> property value of * the given document is set to not allowed * @throws UpdateConflictException if update an object that is no longer * current. Storage determine this by using change token * @throws VersioningException if object is a non-current (latest) document * version and updatiing other then latest version is not supported * @throws StorageException if object's content stream can not be updated * (save changes) cause to storage internal problem */ public String setContentStream(String documentId, ContentStream content, ChangeTokenHolder changeTokenHolder, Boolean overwriteFlag) throws ObjectNotFoundException, ContentAlreadyExistsException, ConstraintException, StreamNotSupportedException, UpdateConflictException, VersioningException, StorageException { checkConnection(); if (changeTokenHolder == null) { throw new NullPointerException("changeTokenHolder may not by null."); } ObjectData document = storage.getObjectById(documentId); if (document.getBaseType() != BaseType.DOCUMENT) { throw new InvalidArgumentException("Object " + documentId + " is not Document."); } checkContent(document.getTypeDefinition(), content); if (overwriteFlag == null) { overwriteFlag = true; // Default } if (!overwriteFlag && ((DocumentData)document).hasContent()) { throw new ContentAlreadyExistsException("Document already has content stream and 'overwriteFlag' is false."); } // Validate change token, object may be already updated. validateChangeToken(document, changeTokenHolder.getValue()); try { ((DocumentData)document).setContentStream(content); } catch (IOException ioe) { throw new CmisRuntimeException("Unable set document content stream. " + ioe.getMessage(), ioe); } String changeToken = document.getChangeToken(); changeTokenHolder.setValue(changeToken); return document.getObjectId(); } /** * Update object properties. * * @param objectId object id * @param changeTokenHolder is used for optimistic locking and/or concurrency * checking to ensure that user updates do not conflict. This * parameter must never be <code>null</code> but * {@link ChangeTokenHolder#getValue()} may return <code>null</code> * if caller does not provide change token. After successful updating * properties <code>changeTokenHolder</code> may contains updated * change token if backend support this feature * @param properties the properties to be applied for object * @return ID of updated object * @throws ObjectNotFoundException if document with specified id * <code>objectId</code> does not exist * @throws ConstraintException if value of any of the properties violates the * min/max/required/length constraints specified in the property * definition in the object type * @throws NameConstraintViolationException if <i>cmis:name</i> specified in * properties throws conflict * @throws UpdateConflictException if update an object that is no longer * current. Storage determine this by using change token * @throws VersioningException if any of following conditions are met: * <ul> * <li>The object is not checked out and any of the properties being * updated are defined in their object type definition have an * attribute value of Updatability 'when checked-out'</li> * <li>if object is a non-current (latest) document version and * updatiing other then latest version is not supported</li> * </ul> * @throws StorageException if object's properties can not be updated (save * changes) cause to storage internal problem */ public String updateProperties(String objectId, ChangeTokenHolder changeTokenHolder, Map<String, Property<?>> properties) throws ObjectNotFoundException, ConstraintException, NameConstraintViolationException, UpdateConflictException, VersioningException, StorageException { checkConnection(); if (properties == null) { throw new InvalidArgumentException("Properties may not by null."); } if (changeTokenHolder == null) { throw new NullPointerException("changeTokenHolder may not by null."); } ObjectData object = storage.getObjectById(objectId); // Validate change token, object may be already updated. validateChangeToken(object, changeTokenHolder.getValue()); checkProperties(object.getTypeDefinition(), properties, UPDATE); object.setProperties(properties); String changeToken = object.getChangeToken(); changeTokenHolder.setValue(changeToken); return object.getObjectId(); } private List<ItemsTree<CmisObject>> getObjectTree(String folderId, int depth, BaseType typeFilter, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePathSegments, boolean includeObjectInfo, String propertyFilter, String renditionFilter) throws ObjectNotFoundException, InvalidArgumentException, FilterNotValidException { ObjectData folder = storage.getObjectById(folderId); if (folder.getBaseType() != BaseType.FOLDER) { throw new InvalidArgumentException("Can't get children. Object " + folderId + " is not a Folder."); } List<ItemsTree<CmisObject>> tree = new ArrayList<ItemsTree<CmisObject>>(); PropertyFilter parsedPropertyFilter = new PropertyFilter(propertyFilter); RenditionFilter parsedRenditionFilter = new RenditionFilter(renditionFilter); for (ItemsIterator<ObjectData> children = ((FolderData)folder).getChildren(null); children.hasNext();) { ObjectData childData = children.next(); if (typeFilter != null && childData.getBaseType() != typeFilter) { continue; } CmisObject container = getCmisObject(childData, includeAllowableActions, includeRelationships, false, false, includeObjectInfo, parsedPropertyFilter, parsedRenditionFilter); if (includePathSegments) { container.setPathSegment(childData.getName()); } List<ItemsTree<CmisObject>> subTree = (childData.getBaseType() == BaseType.FOLDER && (depth > 1 || depth == -1)) // ? getObjectTree(childData.getObjectId(), depth != -1 ? depth - 1 : depth, typeFilter, includeAllowableActions, includeRelationships, includePathSegments, includeObjectInfo, propertyFilter, renditionFilter) // : null; tree.add(new ItemsTree<CmisObject>(container, subTree)); } return tree; } private List<ItemsTree<TypeDefinition>> getTypeTree(String typeId, int depth, boolean includePropertyDefinition) throws TypeNotFoundException { List<ItemsTree<TypeDefinition>> tree = new ArrayList<ItemsTree<TypeDefinition>>(); for (ItemsIterator<TypeDefinition> children = storage.getTypeChildren(typeId, includePropertyDefinition); children .hasNext();) { TypeDefinition childType = children.next(); List<ItemsTree<TypeDefinition>> subTree = (depth > 1 || depth == -1) // ? getTypeDescendants(childType.getId(), depth != -1 ? depth - 1 : depth, includePropertyDefinition) // : null; tree.add(new ItemsTree<TypeDefinition>(childType, subTree)); } return tree; } /** * Check is connection may be used at the moment, e.g. it may be already * closed. * * @throws IllegalStateException if connection may not be used any more */ protected abstract void checkConnection() throws IllegalStateException; protected CmisObject getCmisObject(ObjectData object, boolean includeAllowableActions, IncludeRelationships includeRelationships, boolean includePolicyIds, boolean includeACL, boolean includeObjectInfo, PropertyFilter parsedPropertyFilter, RenditionFilter parsedRenditionFilter) { CmisObject cmis = new CmisObject(); Map<String, Property<?>> properties = object.getProperties(parsedPropertyFilter); if (properties.size() != 0) { cmis.getProperties().putAll(properties); } if (includeAllowableActions) { cmis.setAllowableActions(storage.calculateAllowableActions(object)); } RelationshipDirection direction = null; if (includeRelationships != null) { switch (includeRelationships) { case BOTH : direction = RelationshipDirection.EITHER; break; case SOURCE : direction = RelationshipDirection.SOURCE; break; case TARGET : direction = RelationshipDirection.TARGET; break; case NONE : break; } if (direction != null) { TypeDefinition relBaseType = null; try { relBaseType = getTypeDefinition(BaseType.RELATIONSHIP.value()); } catch (TypeNotFoundException e) { // If relationship is not supported } if (relBaseType != null) { for (ItemsIterator<RelationshipData> iter = object.getRelationships(direction, relBaseType, true); iter .hasNext();) { RelationshipData next = iter.next(); cmis.getRelationship().add( getCmisObject(next, false, includeRelationships, false, false, includeObjectInfo, PropertyFilter.ALL_FILTER, RenditionFilter.NONE_FILTER)); } } } } if (includePolicyIds) { for (Iterator<PolicyData> iter = object.getPolicies().iterator(); iter.hasNext();) { cmis.getPolicyIds().add(iter.next().getObjectId()); } } if (includeACL) { for (Iterator<AccessControlEntry> iter = object.getACL(true).iterator(); iter.hasNext();) { cmis.getACL().add(iter.next()); } } if (!parsedRenditionFilter.isNone()) { for (ItemsIterator<Rendition> renditions = storage.getRenditions(object); renditions.hasNext();) { Rendition r = renditions.next(); if (parsedRenditionFilter.accept(r)) { cmis.getRenditions().add(r); } } } if (includeObjectInfo) { BaseType baseType = object.getBaseType(); ObjectInfo objectInfo = new ObjectInfo(); objectInfo.setBaseType(baseType); objectInfo.setTypeId(object.getTypeId()); objectInfo.setId(object.getObjectId()); objectInfo.setName(object.getName()); objectInfo.setCreatedBy(object.getCreatedBy()); objectInfo.setCreationDate(object.getCreationDate()); objectInfo.setLastModifiedBy(object.getLastModifiedBy()); objectInfo.setLastModificationDate(object.getLastModificationDate()); objectInfo.setChangeToken(object.getChangeToken()); if (baseType == BaseType.FOLDER) { try { objectInfo.setParentId(((FolderData)object).isRoot() ? null : object.getParent().getObjectId()); } catch (ConstraintException never) { // object.getParent() expression should never throw // ConstraintException becuase we already checked object is folder, // not root folder so it has exactly one parent throw new CmisRuntimeException(never.getMessage()); } } else if (baseType == BaseType.DOCUMENT) { DocumentData doc = (DocumentData)object; objectInfo.setLatestVersion(doc.isLatestVersion()); objectInfo.setMajorVersion(doc.isMajorVersion()); objectInfo.setLatestMajorVersion(doc.isLatestMajorVersion()); objectInfo.setVersionSeriesId(doc.getVersionSeriesId()); objectInfo.setVersionSeriesCheckedOutId(doc.getVersionSeriesCheckedOutId()); objectInfo.setVersionSeriesCheckedOutBy(doc.getVersionSeriesCheckedOutBy()); objectInfo.setVersionLabel(doc.getVersionLabel()); objectInfo.setContentStreamMimeType(doc.getContentStreamMimeType()); } else if (baseType == BaseType.RELATIONSHIP) { RelationshipData rel = (RelationshipData)object; objectInfo.setSourceId(rel.getSourceId()); objectInfo.setTargetId(rel.getTargetId()); } cmis.setObjectInfo(objectInfo); } return cmis; } // private void checkInputs(TypeDefinition typeDefinition, Map<String, Property<?>> properties, ContentStream content, // List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL, Collection<String> policies) throws // ConstraintException, StreamNotSupportedException // { // checkContent(typeDefinition, content); // checkProperties(typeDefinition, properties); // checkACL(typeDefinition, addACL, removeACL); // checkPolicies(typeDefinition, policies); // } private void checkContent(TypeDefinition typeDefinition, ContentStream content) throws ConstraintException, StreamNotSupportedException { if (typeDefinition.getBaseId() == BaseType.DOCUMENT) { if (typeDefinition.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED && content == null) { throw new ConstraintException("Content stream required for object of type " + typeDefinition.getId() + ", it can't be null."); } if (typeDefinition.getContentStreamAllowed() == ContentStreamAllowed.NOT_ALLOWED && content != null) { throw new StreamNotSupportedException("Content stream not allowed for object of type " + typeDefinition.getId()); } } else if (content != null) { throw new ConstraintException("Object type " + typeDefinition.getId() + " may not have content stream."); } } private void checkProperties(TypeDefinition typeDefinition, Map<String, Property<?>> properties, int operation) throws ConstraintException { // NOTE do not check is property updatable. Lets storage to do it. // Some client may sent whole set of properties but storage should // modify only read-write properties and ignore others. for (PropertyDefinition<?> definition : typeDefinition.getPropertyDefinitions()) { Property<?> property = null; if (properties != null && properties.size() != 0) { property = properties.get(definition.getId()); } if (definition.isRequired() && operation == CREATE && (property == null || property.getValues().size() == 0)) { throw new ConstraintException("Required property " + definition.getId() + " can't be not set."); } else if (property != null) { if (property.getType() != definition.getPropertyType()) { throw new ConstraintException("Property type is not match. Property id " + property.getId()); } if (!definition.isMultivalued() && property.getValues().size() > 1) { throw new ConstraintException("Property " + property.getId() + " is not multi-valued."); } } // TODO : validate min/max/length etc. } } private void checkACL(TypeDefinition typeDefinition, List<AccessControlEntry> addACL, List<AccessControlEntry> removeACL) throws ConstraintException { if ((addACL != null && addACL.size() != 0) || (removeACL != null && removeACL.size() != 0)) { CapabilityACL capabilityACL = storage.getRepositoryInfo().getCapabilities().getCapabilityACL(); if (capabilityACL != CapabilityACL.MANAGE) { throw new NotSupportedException("Managing of ACL is not supported."); } if (!typeDefinition.isControllableACL()) { throw new ConstraintException("Type " + typeDefinition.getId() + " is not controllable by ACL."); } List<Permission> permissions = storage.getRepositoryInfo().getAclCapability().getPermissions(); // create set of supported permissions Set<String> supportedPermissions = new HashSet<String>(); if (permissions == null || permissions.size() == 0) { for (Permission perm : permissions) { supportedPermissions.add(perm.getPermission()); } } // check is all permissions is valid validatePermissions(supportedPermissions, addACL); validatePermissions(supportedPermissions, removeACL); } } private void validatePermissions(Set<String> supportedPermissions, List<AccessControlEntry> acl) throws ConstraintException { if (acl != null) { for (AccessControlEntry ace : acl) { for (String perm : ace.getPermissions()) { // From spec it looks like 'bacis permissions' not need be in the // list of permissions. So be tolerant if set does not contains // basic permissions. if (!supportedPermissions.contains(perm)) { try { BasicPermissions.fromValue(perm); } catch (IllegalArgumentException iae) { throw new ConstraintException("Unsupported permissions " + perm); } } } } } } private void checkPolicies(TypeDefinition typeDefinition, Collection<String> policies) throws ConstraintException { if (policies != null && policies.size() != 0 && !typeDefinition.isControllablePolicy()) { throw new ConstraintException("Type " + typeDefinition.getId() + " is not controllable by Policy."); } } private Collection<PolicyData> createPolicyList(Collection<String> policyIds) throws ObjectNotFoundException { if (policyIds == null) { return null; } Collection<PolicyData> policies = new HashSet<PolicyData>(); for (String policyID : policyIds) { ObjectData policy = storage.getObjectById(policyID); if (policy.getBaseType() != BaseType.POLICY) { throw new InvalidArgumentException("Object " + policyID + " is not a Policy object."); } policies.add((PolicyData)policy); } return policies; } /** * Validate change token provided by caller with current change token of * object. * * @param object object * @param changeToken change token from 'client' * @throws UpdateConflictException if specified change token does not match * to object change token */ protected abstract void validateChangeToken(ObjectData object, String changeToken) throws UpdateConflictException; }