diff --git a/config/alfresco/opencmis-context.xml b/config/alfresco/opencmis-context.xml index b4c85792cf..dc13a17ead 100644 --- a/config/alfresco/opencmis-context.xml +++ b/config/alfresco/opencmis-context.xml @@ -33,10 +33,32 @@ + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + PROPAGATION_SUPPORTS, readOnly + PROPAGATION_SUPPORTS, readOnly + ${server.transaction.mode.default} + + + + + + + + diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisExceptionInterceptor.java b/source/java/org/alfresco/opencmis/AlfrescoCmisExceptionInterceptor.java new file mode 100644 index 0000000000..a2ebb3b22b --- /dev/null +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisExceptionInterceptor.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco 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 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import org.alfresco.repo.node.integrity.IntegrityException; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException; +import org.alfresco.service.cmr.model.FileExistsException; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; + +/** + * Interceptor to catch various exceptions and translate them into CMIS-related exceptions + *

+ * TODO: Externalize messages + * TODO: Use ExceptionStackUtil to dig out exceptions of interest regardless of depth + * + * @author Derek Hulley + * @since 4.0 + */ +public class AlfrescoCmisExceptionInterceptor implements MethodInterceptor +{ + public Object invoke(MethodInvocation mi) throws Throwable + { + try + { + return mi.proceed(); + } + catch (AuthenticationException e) + { + throw new CmisPermissionDeniedException(e.getMessage(), e); + } + catch (CheckOutCheckInServiceException e) + { + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + } + catch (FileExistsException fee) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } + catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } + catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + catch (Exception e) + { + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } + else + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java index de55dd1e47..29d17046dc 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java @@ -18,3083 +18,31 @@ */ package org.alfresco.opencmis; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.transaction.Status; -import javax.transaction.UserTransaction; - -import org.alfresco.cmis.CMISInvalidArgumentException; -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.dictionary.CMISNodeInfo; -import org.alfresco.opencmis.dictionary.CMISObjectVariant; -import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; -import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; -import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.content.encoding.ContentCharsetFinder; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; -import org.alfresco.repo.node.integrity.IntegrityException; -import org.alfresco.repo.search.QueryParameterDefImpl; -import org.alfresco.repo.security.authentication.AuthenticationException; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.Authorization; -import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.repo.version.VersionModel; -import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.search.QueryParameterDefinition; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionHistory; -import org.alfresco.service.cmr.version.VersionType; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.Pair; -import org.alfresco.util.TempFileProvider; -import org.apache.chemistry.opencmis.commons.PropertyIds; -import org.apache.chemistry.opencmis.commons.data.Acl; -import org.apache.chemistry.opencmis.commons.data.AllowableActions; -import org.apache.chemistry.opencmis.commons.data.ContentStream; -import org.apache.chemistry.opencmis.commons.data.ExtensionsData; -import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; -import org.apache.chemistry.opencmis.commons.data.ObjectData; -import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer; -import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData; -import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; -import org.apache.chemistry.opencmis.commons.data.ObjectList; -import org.apache.chemistry.opencmis.commons.data.ObjectParentData; -import org.apache.chemistry.opencmis.commons.data.Properties; -import org.apache.chemistry.opencmis.commons.data.RenditionData; -import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; -import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; -import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; -import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; -import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList; -import org.apache.chemistry.opencmis.commons.enums.AclPropagation; -import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; -import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; -import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; -import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; -import org.apache.chemistry.opencmis.commons.enums.UnfileObject; -import org.apache.chemistry.opencmis.commons.enums.VersioningState; -import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; -import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl; -import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl; -import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService; -import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl; -import org.apache.chemistry.opencmis.commons.impl.server.RenditionInfoImpl; import org.apache.chemistry.opencmis.commons.server.CallContext; -import org.apache.chemistry.opencmis.commons.server.ObjectInfo; -import org.apache.chemistry.opencmis.commons.server.RenditionInfo; -import org.apache.chemistry.opencmis.commons.spi.Holder; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.chemistry.opencmis.commons.server.CmisService; /** - * OpenCMIS service object. + * Extended interface for lifecycle management * - * @author florian.mueller - * @since + * @author Derek Hulley + * @since 4.0 */ -public class AlfrescoCmisService extends AbstractCmisService +public interface AlfrescoCmisService extends CmisService { - private static Log logger = LogFactory.getLog(AlfrescoCmisService.class); - - private static final QName PARAM_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent"); - private static final QName PARAM_USERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "username"); - private static final String LUCENE_QUERY_CHECKEDOUT = "+@cm\\:workingCopyOwner:${cm:username}"; - private static final String LUCENE_QUERY_CHECKEDOUT_IN_FOLDER = "+@cm\\:workingCopyOwner:${cm:username} +PARENT:\"${cm:parent}\""; - - private static final String MIN_FILTER = "cmis:name,cmis:baseTypeId,cmis:objectTypeId," - + "cmis:createdBy,cmis:creationDate,cmis:lastModifiedBy,cmis:lastModificationDate," - + "cmis:contentStreamLength,cmis:contentStreamMimeType,cmis:contentStreamFileName," - + "cmis:contentStreamId"; - - private CMISConnector connector; - private CallContext context; - private UserTransaction txn; - private Map nodeInfoMap; - private Map objectInfoMap; - - public AlfrescoCmisService(CMISConnector connector) - { - this.connector = connector; - nodeInfoMap = new HashMap(); - objectInfoMap = new HashMap(); - } - - protected void beginCall(CallContext context) - { - this.context = context; - - if (connector.openHttpSession()) - { - // create a session -> set a cookie - // if the CMIS client supports cookies that might help in clustered - // environments - ((HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); - } - - AuthenticationUtil.pushAuthentication(); - - try - { - String currentUser = connector.getAuthenticationService().getCurrentUserName(); - String user = context.getUsername(); - String password = context.getPassword(); - - if (currentUser == null) - { - Authorization auth = new Authorization(user, password); - - if (auth.isTicket()) - { - connector.getAuthenticationService().validate(auth.getTicket()); - } - else - { - connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); - } - - } - else if (currentUser.equals(connector.getProxyUser())) - { - if (user != null && user.length() > 0) - { - AuthenticationUtil.setFullyAuthenticatedUser(user); - } - } - } - catch (AuthenticationException ae) - { - throw new CmisPermissionDeniedException(ae.getMessage(), ae); - } - - // start read-only transaction - try - { - beginReadOnlyTransaction(); - } - catch (Exception e) - { - AuthenticationUtil.popAuthentication(); - - if (e instanceof CmisBaseException) - { - throw (CmisBaseException) e; - } - else - { - throw new CmisRuntimeException(e.getMessage(), e); - } - } - } - - @Override - public void close() - { - try - { - endReadOnlyTransaction(); - } - catch (Exception e) - { - if (e instanceof CmisBaseException) - { - throw (CmisBaseException) e; - } - else - { - throw new CmisRuntimeException(e.getMessage(), e); - } - } - finally - { - AuthenticationUtil.popAuthentication(); - context = null; - nodeInfoMap.clear(); - objectInfoMap.clear(); - } - } - /** - * Begins the embracing read-only transaction. + * Called directly before any CMIS method is used */ - protected void beginReadOnlyTransaction() - { - txn = null; - try - { - txn = connector.getTransactionService().getNonPropagatingUserTransaction(true); - txn.begin(); - } - catch (Exception e) - { - throw new CmisRuntimeException(e.getMessage(), e); - } - } - + void beforeCall(); + /** - * Ends embracing read-only transaction. + * Called directly after any CMIS method is used */ - protected void endReadOnlyTransaction() - { - try - { - if (txn != null) - { - // there isn't anything to commit, really - // we just have to end the transaction - if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK) - { - txn.rollback(); - } - else - { - txn.commit(); - } - txn = null; - } - } - catch (Exception e) - { - throw new CmisRuntimeException(e.getMessage(), e); - } - } - - protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) - { - if (logger.isDebugEnabled()) - { - logger.debug( - "createNodeInfo: \n" + - " current user: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" + - " running as: " + AuthenticationUtil.getRunAsUser() + "\n" + - " nodeRef: " + nodeRef); - } - - CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef); - nodeInfoMap.put(result.getObjectId(), result); - - return result; - } - - protected CMISNodeInfo createNodeInfo(AssociationRef assocRef) - { - if (logger.isDebugEnabled()) - { - logger.debug( - "createNodeInfo: \n" + - " current user: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" + - " running as: " + AuthenticationUtil.getRunAsUser() + "\n" + - " assocRef: " + assocRef); - } - - CMISNodeInfoImpl result = connector.createNodeInfo(assocRef); - nodeInfoMap.put(result.getObjectId(), result); - - return result; - } - - protected CMISNodeInfo getOrCreateNodeInfo(String objectId) - { - CMISNodeInfo result = nodeInfoMap.get(objectId); - if (result == null) - { - result = connector.createNodeInfo(objectId); - nodeInfoMap.put(objectId, result); - } - - return result; - } - - protected CMISNodeInfo getOrCreateNodeInfo(String objectId, String what) - { - CMISNodeInfo result = getOrCreateNodeInfo(objectId); - if (result instanceof CMISNodeInfoImpl) - { - ((CMISNodeInfoImpl) result).checkIfUseful(what); - } - - return result; - } - - protected CMISNodeInfo getOrCreateFolderInfo(String folderId, String what) - { - CMISNodeInfo result = getOrCreateNodeInfo(folderId); - if (result instanceof CMISNodeInfoImpl) - { - ((CMISNodeInfoImpl) result).checkIfFolder(what); - } - - return result; - } - - protected CMISNodeInfo addNodeInfo(CMISNodeInfo info) - { - nodeInfoMap.put(info.getObjectId(), info); - - return info; - } - - // --- repository service --- - - @Override - public List getRepositoryInfos(ExtensionsData extension) - { - return Collections.singletonList(connector.getRepositoryInfo()); - } - - @Override - public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - return connector.getRepositoryInfo(); - } - - @Override - public TypeDefinitionList getTypeChildren( - String repositoryId, String typeId, Boolean includePropertyDefinitions, - BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // convert BigIntegers to int - int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); - int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); - - // set up the result - TypeDefinitionListImpl result = new TypeDefinitionListImpl(); - List list = new ArrayList(); - result.setList(list); - - // get the types from the dictionary - List childrenList; - if (typeId == null) - { - childrenList = connector.getOpenCMISDictionaryService().getBaseTypes(); - } - else - { - TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); - if (tdw == null) - { - throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); - } - childrenList = tdw.getChildren(); - } - - // create result - if (max > 0) - { - int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; - for (int i = skip; i <= lastIndex; i++) - { - list.add(childrenList.get(i).getTypeDefinition(includePropertyDefinitions)); - } - } - - result.setHasMoreItems(childrenList.size() - skip > result.getList().size()); - result.setNumItems(BigInteger.valueOf(childrenList.size())); - - return result; - } - - @Override - public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // find the type - TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); - if (tdw == null) - { - throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); - } - - // return type definition - return tdw.getTypeDefinition(true); - } - - @Override - public List getTypeDescendants( - String repositoryId, String typeId, BigInteger depth, - Boolean includePropertyDefinitions, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - List result = new ArrayList(); - - // check depth - int d = (depth == null ? -1 : depth.intValue()); - if (d == 0) - { - throw new CmisInvalidArgumentException("Depth must not be 0!"); - } - - if (typeId == null) - { - for (TypeDefinitionWrapper tdw : connector.getOpenCMISDictionaryService().getBaseTypes()) - { - result.add(getTypesDescendants(d, tdw, includePropertyDefinitions)); - } - } - else - { - TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); - if (tdw == null) - { - throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); - } - - if (tdw.getChildren() != null) - { - for (TypeDefinitionWrapper child : tdw.getChildren()) - { - result.add(getTypesDescendants(d, child, includePropertyDefinitions)); - } - } - } - - return result; - } - + void afterCall(); + /** - * Gathers the type descendants tree. - */ - private TypeDefinitionContainer getTypesDescendants( - int depth, TypeDefinitionWrapper tdw, boolean includePropertyDefinitions) - { - TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl(); - - result.setTypeDefinition(tdw.getTypeDefinition(includePropertyDefinitions)); - - if (depth != 0) - { - if (tdw.getChildren() != null) - { - result.setChildren(new ArrayList()); - for (TypeDefinitionWrapper tdc : tdw.getChildren()) - { - result.getChildren().add( - getTypesDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions)); - } - } - } - - return result; - } - - // --- navigation service --- - - @Override - public ObjectInFolderList getChildren( - String repositoryId, String folderId, String filter, String orderBy, - Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, - Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) - { - long start = System.currentTimeMillis(); - - checkRepositoryId(repositoryId); - - // convert BigIntegers to int - int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); - int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); - - ObjectInFolderListImpl result = new ObjectInFolderListImpl(); - List list = new ArrayList(); - result.setObjects(list); - - // get the children references - NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); - - // convert orderBy to sortProps - List> sortProps = null; - if (orderBy != null) - { - sortProps = new ArrayList>(1); - - String[] parts = orderBy.split(","); - int len = parts.length; - final int origLen = len; - - if (origLen > 0) - { - int maxSortProps = GetChildrenCannedQuery.MAX_FILTER_SORT_PROPS; - if (len > maxSortProps) - { - if (logger.isDebugEnabled()) - { - logger.debug( - "Too many sort properties in 'orderBy' - ignore those above max (max=" - + maxSortProps + ",actual=" + len + ")"); - } - len = maxSortProps; - } - for (int i = 0; i < len; i++) - { - String[] sort = parts[i].split(" +"); - - if (sort.length > 0) - { - PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService() - .findPropertyByQueryName(sort[0]); - if (propDef != null) - { - QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); - if (sortProp != null) - { - boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc"); - sortProps.add(new Pair(sortProp, sortAsc)); - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignore sort property '" + sort[0] + " - mapping not found"); - } - } - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignore sort property '" + sort[0] + " - query name not found"); - } - } - } - } - } - - if (sortProps.size() < origLen) - { - logger.warn("Sort properties trimmed - either too many and/or not found: \n" + " orig: " + orderBy - + "\n" + " final: " + sortProps); - } - } - - PagingRequest pageRequest = new PagingRequest(skip, max, null); - pageRequest.setRequestTotalCountMax(skip + 10000); // TODO make this optional/configurable - // - affects whether numItems may be returned - - PagingResults pageOfNodeInfos = connector.getFileFolderService().list( - folderNodeRef, true, true, - null, sortProps, pageRequest); - - if (max > 0) - { - for (FileInfo child : pageOfNodeInfos.getPage()) - { - try - { - // create a child CMIS object - CMISNodeInfo ni = createNodeInfo(child.getNodeRef()); - ObjectData object = connector.createCMISObject(ni, child, filter, includeAllowableActions, - includeRelationships, renditionFilter, false, false); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); - } - - ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); - childData.setObject(object); - - // include path segment - if (includePathSegment) - { - childData.setPathSegment(child.getName()); - } - - // add it - list.add(childData); - } - catch (InvalidNodeRefException e) - { - // ignore invalid children - } - } - } - - // has more ? - result.setHasMoreItems(pageOfNodeInfos.hasMoreItems()); - - // total count ? - Pair totalCounts = pageOfNodeInfos.getTotalResultCount(); - if (totalCounts != null) - { - Integer totalCountLower = totalCounts.getFirst(); - Integer totalCountUpper = totalCounts.getSecond(); - if ((totalCountLower != null) && (totalCountLower.equals(totalCountUpper))) - { - result.setNumItems(BigInteger.valueOf(totalCountLower)); - } - } - - if (logger.isDebugEnabled()) - { - logger.debug("getChildren: " + list.size() + " in " + (System.currentTimeMillis() - start) + " msecs"); - } - - return result; - } - - @Override - public List getDescendants( - String repositoryId, String folderId, BigInteger depth, - String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, - String renditionFilter, Boolean includePathSegment, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - List result = new ArrayList(); - - getDescendantsTree( - repositoryId, - getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), - depth.intValue(), - filter, - includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, false, - result); - - return result; - } - - @Override - public List getFolderTree( - String repositoryId, String folderId, BigInteger depth, - String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, - String renditionFilter, Boolean includePathSegment, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - List result = new ArrayList(); - - getDescendantsTree( - repositoryId, - getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), - depth.intValue(), - filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, true, - result); - - return result; - } - - private void getDescendantsTree( - String repositoryId, NodeRef folderNodeRef, int depth, String filter, - Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, - Boolean includePathSegment, boolean foldersOnly, List list) - { - // get the children references - List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef); - for (ChildAssociationRef child : childrenList) - { - try - { - TypeDefinitionWrapper type = connector.getType(child.getChildRef()); - if (type == null) - { - continue; - } - - boolean isFolder = (type instanceof FolderTypeDefintionWrapper); - - if (foldersOnly && !isFolder) - { - continue; - } - - // create a child CMIS object - ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); - CMISNodeInfo ni = createNodeInfo(child.getChildRef()); - object.setObject(connector.createCMISObject( - ni, filter, includeAllowableActions, includeRelationships, - renditionFilter, false, false)); - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); - } - - if (includePathSegment) - { - object.setPathSegment(connector.getName(child.getChildRef())); - } - - // create the container - ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl(); - container.setObject(object); - - if ((depth != 1) && isFolder) - { - container.setChildren(new ArrayList()); - getDescendantsTree( - repositoryId, - child.getChildRef(), - depth - 1, filter, includeAllowableActions, - includeRelationships, renditionFilter, includePathSegment, foldersOnly, - container.getChildren()); - } - - // add it - list.add(container); - } - catch (InvalidNodeRefException e) - { - // ignore invalid children - } - } - } - - @Override - public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get the node ref - CMISNodeInfo info = getOrCreateFolderInfo(folderId, "Folder"); - - // the root folder has no parent - if (info.isRootFolder()) - { - throw new CmisInvalidArgumentException("Root folder has no parent!"); - } - - // get the parent - List parentInfos = info.getParents(); - if (parentInfos.isEmpty()) - { - throw new CmisRuntimeException("Folder has no parent and is not the root folder?!"); - } - - CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0)); - - ObjectData result = connector.createCMISObject( - parentInfo, filter, false, IncludeRelationships.NONE, - CMISConnector.RENDITION_NONE, false, false); - if (context.isObjectInfoRequired()) - { - getObjectInfo( - repositoryId, - parentInfo.getObjectId(), - IncludeRelationships.NONE); - } - - return result; - } - - @Override - public List getObjectParents( - String repositoryId, String objectId, String filter, - Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, - Boolean includeRelativePathSegment, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - List result = new ArrayList(); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // relationships are not filed - if (info.isRelationship()) - { - throw new CmisConstraintException("Relationships are not fileable!"); - } - - if (info.isFolder() && !info.isRootFolder()) - { - List parentInfos = info.getParents(); - if (!parentInfos.isEmpty()) - { - CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0)); - - ObjectData object = connector.createCMISObject( - parentInfo, filter, includeAllowableActions, - includeRelationships, renditionFilter, false, false); - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, object.getId(), includeRelationships); - } - - ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); - objectParent.setObject(object); - - // include relative path segment - if (includeRelativePathSegment) - { - objectParent.setRelativePathSegment(info.getName()); - } - - result.add(objectParent); - } - } - else if (info.isCurrentVersion() || info.isPWC()) - { - List parentInfos = info.getParents(); - for (CMISNodeInfo parentInfo : parentInfos) - { - addNodeInfo(parentInfo); - - ObjectData object = connector.createCMISObject( - parentInfo, filter, includeAllowableActions, - includeRelationships, renditionFilter, false, false); - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, object.getId(), includeRelationships); - } - - ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); - objectParent.setObject(object); - - // include relative path segment - if (includeRelativePathSegment) - { - objectParent.setRelativePathSegment(info.getName()); - } - - result.add(objectParent); - } - } - - return result; - } - - @Override - public ObjectList getCheckedOutDocs( - String repositoryId, String folderId, String filter, String orderBy, - Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, - BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // convert BigIntegers to int - int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); - int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); - - // prepare query - SearchParameters params = new SearchParameters(); - params.setLanguage(SearchService.LANGUAGE_LUCENE); - QueryParameterDefinition usernameDef = new QueryParameterDefImpl( - PARAM_USERNAME, - connector.getDictionaryService().getDataType(DataTypeDefinition.TEXT), - true, - AuthenticationUtil.getFullyAuthenticatedUser()); - params.addQueryParameterDefinition(usernameDef); - - if (folderId == null) - { - params.setQuery(LUCENE_QUERY_CHECKEDOUT); - params.addStore(connector.getRootStoreRef()); - } - else - { - CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); - - params.setQuery(LUCENE_QUERY_CHECKEDOUT_IN_FOLDER); - params.addStore(folderInfo.getNodeRef().getStoreRef()); - QueryParameterDefinition parentDef = new QueryParameterDefImpl( - PARAM_PARENT, - connector.getDictionaryService().getDataType(DataTypeDefinition.NODE_REF), - true, - folderInfo.getNodeRef().toString()); - params.addQueryParameterDefinition(parentDef); - } - - // set up order - if (orderBy != null) - { - String[] parts = orderBy.split(","); - for (int i = 0; i < parts.length; i++) - { - String[] sort = parts[i].split(" +"); - - if (sort.length < 1) - { - continue; - } - - PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService().findPropertyByQueryName(sort[0]); - if (propDef != null) - { - if (propDef.getPropertyDefinition().isOrderable()) - { - QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); - if (sortProp != null) - { - boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc"); - params.addSort(propDef.getPropertyLuceneBuilder().getLuceneFieldName(), sortAsc); - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignore sort property '" + sort[0] + " - mapping not found"); - } - } - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignore sort property '" + sort[0] + " - not orderable"); - } - } - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignore sort property '" + sort[0] + " - query name not found"); - } - } - } - } - - // execute query - ResultSet resultSet = null; - List nodeRefs; - try - { - resultSet = connector.getSearchService().query(params); - nodeRefs = resultSet.getNodeRefs(); - } - finally - { - if (resultSet != null) - { - resultSet.close(); - } - } - - // collect results - ObjectListImpl result = new ObjectListImpl(); - List list = new ArrayList(); - result.setObjects(list); - - int skipCounter = skip; - if (max > 0) - { - for (NodeRef nodeRef : nodeRefs) - { - if (skipCounter > 0) - { - skipCounter--; - continue; - } - - if (list.size() == max) - { - break; - } - - try - { - // create a CMIS object - CMISNodeInfo ni = createNodeInfo(nodeRef); - ObjectData object = connector.createCMISObject( - ni, filter, includeAllowableActions, - includeRelationships, renditionFilter, false, false); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); - } - - // add it - list.add(object); - } - catch (InvalidNodeRefException e) - { - // ignore invalid objects - } - } - } - - // has more ? - result.setHasMoreItems(nodeRefs.size() - skip > list.size()); - - return result; - } - - // --- object service --- - - @Override - public String create( - String repositoryId, Properties properties, String folderId, ContentStream contentStream, - VersioningState versioningState, List policies, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // check properties - if (properties == null || properties.getProperties() == null) - { - throw new CmisInvalidArgumentException("Properties must be set!"); - } - - // get the type - String objectTypeId = connector.getObjectTypeIdProperty(properties); - - // find the type - TypeDefinitionWrapper type = connector.getOpenCMISDictionaryService().findType(objectTypeId); - if (type == null) - { - throw new CmisInvalidArgumentException("Type '" + objectTypeId + "' is unknown!"); - } - - // create object - String newId = null; - switch (type.getBaseTypeId()) - { - case CMIS_DOCUMENT: - newId = createDocument(repositoryId, properties, folderId, contentStream, versioningState, policies, null, - null, extension); - break; - case CMIS_FOLDER: - newId = createFolder(repositoryId, properties, folderId, policies, null, null, extension); - break; - case CMIS_POLICY: - newId = createPolicy(repositoryId, properties, folderId, policies, null, null, extension); - break; - } - - // check new object id - if (newId == null) - { - throw new CmisRuntimeException("Creation failed!"); - } - - if (context.isObjectInfoRequired()) - { - try - { - getObjectInfo(repositoryId, newId, "*", IncludeRelationships.NONE); - } - catch (InvalidNodeRefException e) - { - throw new CmisRuntimeException("Creation failed! New object not found!"); - } - } - - // return the new object id - return newId; - } - - @Override - public String createFolder(String repositoryId, final Properties properties, String folderId, - final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get the parent folder node ref - final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Folder"); - - // get name and type - final String name = connector.getNameProperty(properties, null); - final String objectTypeId = connector.getObjectTypeIdProperty(properties); - final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_FOLDER); - - connector.checkChildObjectType(parentInfo, type.getTypeId()); - - // run transaction - endReadOnlyTransaction(); - NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Exception - { - try - { - NodeRef nodeRef = connector.getFileFolderService() - .create(parentInfo.getNodeRef(), name, type.getAlfrescoClass()).getNodeRef(); - - connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, - PropertyIds.OBJECT_TYPE_ID }); - connector.applyPolicies(nodeRef, type, policies); - connector.applyACL(nodeRef, type, addAces, removeAces); - - return nodeRef; - } - catch (FileExistsException fee) - { - throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - return newNodeRef.toString(); - } - - @Override - public String createDocument( - String repositoryId, final Properties properties, String folderId, - final ContentStream contentStream, final VersioningState versioningState, final List policies, - final Acl addAces, final Acl removeAces, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get the parent folder node ref - final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder"); - - // get name and type - final String name = connector.getNameProperty(properties, null); - final String objectTypeId = connector.getObjectTypeIdProperty(properties); - final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_DOCUMENT); - - connector.checkChildObjectType(parentInfo, type.getTypeId()); - - DocumentTypeDefinition docType = (DocumentTypeDefinition) type.getTypeDefinition(false); - - if ((docType.getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) && (contentStream != null)) - { - throw new CmisConstraintException("This document type does not support content!"); - } - - if ((docType.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) && (contentStream == null)) - { - throw new CmisConstraintException("This document type does requires content!"); - } - - if (docType.isVersionable() && (versioningState == VersioningState.NONE)) - { - throw new CmisConstraintException("This document type is versionable!"); - } - - if (!docType.isVersionable() && (versioningState != VersioningState.NONE)) - { - throw new CmisConstraintException("This document type is not versionable!"); - } - - // copy stream to temp file - final File tempFile = copyToTempFile(contentStream); - final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); - - // run transaction - endReadOnlyTransaction(); - NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Exception - { - try - { - NodeRef nodeRef = connector.getFileFolderService() - .create(parentInfo.getNodeRef(), name, type.getAlfrescoClass()).getNodeRef(); - - connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, - PropertyIds.OBJECT_TYPE_ID }); - connector.applyPolicies(nodeRef, type, policies); - connector.applyACL(nodeRef, type, addAces, removeAces); - - // handle content - if (contentStream != null) - { - // write content - ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); - writer.setEncoding(encoding.name()); - writer.putContent(tempFile); - } - - connector.applyVersioningState(nodeRef, versioningState); - - return nodeRef; - } - catch (FileExistsException fee) - { - removeTempFile(tempFile); - throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); - } - catch (IntegrityException ie) - { - removeTempFile(tempFile); - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - removeTempFile(tempFile); - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - removeTempFile(tempFile); - - return connector.createObjectId(newNodeRef); - } - - @Override - public String createDocumentFromSource( - String repositoryId, String sourceId, final Properties properties, - String folderId, final VersioningState versioningState, final List policies, final Acl addAces, - final Acl removeAces, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get the parent folder node ref - final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder"); - - // get source - CMISNodeInfo info = getOrCreateNodeInfo(sourceId, "Source"); - - // check source - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - throw new CmisConstraintException("Source object is not a document!"); - } - - final NodeRef sourceNodeRef = info.getNodeRef(); - if (!info.isDocument()) - { - throw new CmisConstraintException("Source object is not a document!"); - } - - // get name and type - final String name = connector.getNameProperty(properties, info.getName()); - - final TypeDefinitionWrapper type = info.getType(); - connector.checkChildObjectType(parentInfo, type.getTypeId()); - - // run transaction - endReadOnlyTransaction(); - NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public NodeRef execute() throws Exception - { - try - { - NodeRef newDocumentNodeRef = connector.getFileFolderService() - .copy(sourceNodeRef, parentInfo.getNodeRef(), name).getNodeRef(); - - connector.setProperties(newDocumentNodeRef, type, properties, new String[] { - PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); - connector.applyPolicies(newDocumentNodeRef, type, policies); - connector.applyACL(newDocumentNodeRef, type, addAces, removeAces); - connector.applyVersioningState(newDocumentNodeRef, versioningState); - - return newDocumentNodeRef; - } - catch (FileExistsException fee) - { - throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - return connector.createObjectId(newNodeRef); - } - - @Override - public String createPolicy( - String repositoryId, Properties properties, String folderId, List policies, - Acl addAces, Acl removeAces, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get the parent folder - getOrCreateFolderInfo(folderId, "Parent Folder"); - - String objectTypeId = connector.getObjectTypeIdProperty(properties); - connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_POLICY); - - // we should never get here - policies are not creatable! - throw new CmisRuntimeException("Polcies cannot be created!"); - } - - @Override - public String createRelationship( - String repositoryId, Properties properties, List policies, Acl addAces, - Acl removeAces, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get type - String objectTypeId = connector.getObjectTypeIdProperty(properties); - final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_RELATIONSHIP); - - // get source object - String sourceId = connector.getSourceIdProperty(properties); - CMISNodeInfo sourceInfo = getOrCreateNodeInfo(sourceId, "Source"); - - if (!sourceInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !sourceInfo.isVariant(CMISObjectVariant.FOLDER)) - { - throw new CmisInvalidArgumentException("Source is not the latest version of a document or a folder object!"); - } - - final NodeRef sourceNodeRef = sourceInfo.getNodeRef(); - - // get target object - String targetId = connector.getTargetIdProperty(properties); - CMISNodeInfo targetInfo = getOrCreateNodeInfo(targetId, "Target"); - - if (!targetInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !targetInfo.isVariant(CMISObjectVariant.FOLDER)) - { - throw new CmisInvalidArgumentException( - "Target is not the latest version of a document or a folder object!!"); - } - - final NodeRef targetNodeRef = targetInfo.getNodeRef(); - - // check policies and ACLs - if ((policies != null) && (!policies.isEmpty())) - { - throw new CmisConstraintException("Relationships are not policy controllable!"); - } - - if ((addAces != null) && (addAces.getAces() != null) && (!addAces.getAces().isEmpty())) - { - throw new CmisConstraintException("Relationships are not ACL controllable!"); - } - - if ((removeAces != null) && (removeAces.getAces() != null) && (!removeAces.getAces().isEmpty())) - { - throw new CmisConstraintException("Relationships are not ACL controllable!"); - } - - // create relationship - endReadOnlyTransaction(); - AssociationRef assocRef = connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public AssociationRef execute() throws Exception - { - try - { - return connector.getNodeService().createAssociation(sourceNodeRef, targetNodeRef, - type.getAlfrescoClass()); - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId(); - } - - @Override - public void setContentStream( - String repositoryId, Holder objectId, Boolean overwriteFlag, - Holder changeToken, final ContentStream contentStream, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); - - if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC)) - { - throw new CmisStreamNotSupportedException("Content can only be set ondocuments!"); - } - - final NodeRef nodeRef = info.getNodeRef(); - - if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) - { - throw new CmisStreamNotSupportedException("Document type doesn't allow content!"); - } - - boolean existed = connector.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT) != null; - if (existed && !overwriteFlag) - { - throw new CmisContentAlreadyExistsException("Content already exists!"); - } - - if ((contentStream == null) || (contentStream.getStream() == null)) - { - throw new CmisInvalidArgumentException("No content!"); - } - - // copy stream to temp file - final File tempFile = copyToTempFile(contentStream); - final Charset encoding = getEncoding(tempFile, contentStream.getMimeType()); - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); - writer.setEncoding(encoding.name()); - writer.putContent(tempFile); - - return null; - } - catch (IntegrityException ie) - { - removeTempFile(tempFile); - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - removeTempFile(tempFile); - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - removeTempFile(tempFile); - - objectId.setValue(connector.createObjectId(nodeRef)); - } - - @Override - public void deleteContentStream( - String repositoryId, Holder objectId, Holder changeToken, - ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); - - if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC)) - { - throw new CmisStreamNotSupportedException("Content can only be deleted from ondocuments!"); - } - - final NodeRef nodeRef = info.getNodeRef(); - - if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) - { - throw new CmisInvalidArgumentException("Document type requires content!"); - } - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_CONTENT, null); - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - objectId.setValue(connector.createObjectId(nodeRef)); - } - - @Override - public void moveObject( - String repositoryId, Holder objectId, String targetFolderId, String sourceFolderId, - ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get object and source and target parent - CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); - - final NodeRef nodeRef = info.getCurrentNodeNodeRef(); - final CMISNodeInfo sourceInfo = getOrCreateFolderInfo(sourceFolderId, "Source Folder"); - final CMISNodeInfo targetInfo = getOrCreateFolderInfo(targetFolderId, "Target Folder"); - - connector.checkChildObjectType(targetInfo, info.getType().getTypeId()); - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - ChildAssociationRef primaryParentRef = connector.getNodeService().getPrimaryParent(nodeRef); - // if this is a primary child node, move it - if (primaryParentRef.getParentRef().equals(sourceInfo.getNodeRef())) - { - connector.getNodeService().moveNode(nodeRef, targetInfo.getNodeRef(), - primaryParentRef.getTypeQName(), primaryParentRef.getQName()); - } - else - { - // otherwise, reparent it - for (ChildAssociationRef parent : connector.getNodeService().getParentAssocs(nodeRef, - ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL)) - { - if (parent.getParentRef().equals(sourceInfo.getNodeRef())) - { - connector.getNodeService().removeChildAssociation(parent); - connector.getNodeService().addChild(targetInfo.getNodeRef(), nodeRef, - ContentModel.ASSOC_CONTAINS, parent.getQName()); - return null; - } - } - throw new CMISInvalidArgumentException( - "Document is not a child of the source folder that was specified!"); - } - - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - } - - @Override - public void updateProperties( - String repositoryId, Holder objectId, Holder changeToken, - final Properties properties, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - final CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); - - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - throw new CmisInvalidArgumentException("Relationship properties cannot be updated!"); - } - else - { - if (info.isVariant(CMISObjectVariant.VERSION)) - { - throw new CmisInvalidArgumentException("Document is not the latest version!"); - } - - final NodeRef nodeRef = info.getNodeRef(); - - // run transaction - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - connector.setProperties(nodeRef, info.getType(), properties, new String[0]); - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - objectId.setValue(connector.createObjectId(nodeRef)); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, objectId.getValue(), "*", IncludeRelationships.NONE); - } - } - } - - @Override - public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension) - { - deleteObjectOrCancelCheckOut(repositoryId, objectId, allVersions, extension); - } - - @Override - public void deleteObjectOrCancelCheckOut( - String repositoryId, final String objectId, final Boolean allVersions, - ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - final CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // run transaction - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Boolean execute() throws Exception - { - try - { - // handle relationships - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - AssociationRef assocRef = info.getAssociationRef(); - connector.getNodeService().removeAssociation(assocRef.getSourceRef(), - assocRef.getTargetRef(), assocRef.getTypeQName()); - return true; - } - - NodeRef nodeRef = info.getNodeRef(); - - // handle PWC - if (info.isVariant(CMISObjectVariant.PWC)) - { - connector.getCheckOutCheckInService().cancelCheckout(nodeRef); - return true; - } - - // handle folders - if (info.isFolder()) - { - if (connector.getNodeService().getChildAssocs(nodeRef).size() > 0) - { - throw new CmisConstraintException( - "Could not delete folder with at least one child!"); - } - - connector.getNodeService().deleteNode(nodeRef); - return true; - } - - // handle versions - if (allVersions) - { - NodeRef workingCopy = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); - if (workingCopy != null) - { - connector.getCheckOutCheckInService().cancelCheckout(workingCopy); - } - } - else if (info.isVariant(CMISObjectVariant.VERSION)) - { - Version version = ((CMISNodeInfoImpl) info).getVersion(); - connector.getVersionService().deleteVersion(nodeRef, version); - return true; - } - - if (info.isVariant(CMISObjectVariant.VERSION)) - { - nodeRef = info.getCurrentNodeNodeRef(); - } - - // remove not primary parent associations - List childAssociations = connector.getNodeService().getParentAssocs(nodeRef); - if (childAssociations != null) - { - for (ChildAssociationRef childAssoc : childAssociations) - { - if (!childAssoc.isPrimary()) - { - connector.getNodeService().removeChildAssociation(childAssoc); - } - } - } - - // attempt to delete the node - if (allVersions) - { - connector.getNodeService().deleteNode(nodeRef); - } - else - { - CMISNodeInfoImpl infoImpl = ((CMISNodeInfoImpl) info); - Version version = infoImpl.getVersion(); - - if (infoImpl.getVersionHistory().getPredecessor(version) == null) - { - connector.getNodeService().deleteNode(nodeRef); - } - else - { - connector.getVersionService().deleteVersion(nodeRef, version); - } - } - return true; - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - endReadOnlyTransaction(); - } - - @Override - public FailedToDeleteData deleteTree( - String repositoryId, String folderId, Boolean allVersions, - UnfileObject unfileObjects, final Boolean continueOnFailure, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (!allVersions) - { - throw new CmisInvalidArgumentException("Only allVersions=true supported!"); - } - - if (unfileObjects == UnfileObject.UNFILE) - { - throw new CmisInvalidArgumentException("Unfiling not supported!"); - } - - final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); - final FailedToDeleteDataImpl result = new FailedToDeleteDataImpl(); - - // run transaction - endReadOnlyTransaction(); - result.setIds(connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback>() - { - public List execute() throws Exception - { - return deleteBranch(folderNodeRef, continueOnFailure); - }; - }, false, true)); - beginReadOnlyTransaction(); - - return result; - } - - private List deleteBranch(NodeRef nodeRef, boolean continueOnFailure) - { - List result = new ArrayList(); - - try - { - // remove children - List childrenList = connector.getNodeService().getChildAssocs(nodeRef); - if (childrenList != null) - { - for (ChildAssociationRef child : childrenList) - { - List ftod = deleteBranch(child.getChildRef(), continueOnFailure); - if (!ftod.isEmpty()) - { - result.addAll(ftod); - if (!continueOnFailure) - { - return result; - } - } - } - } - - // remove not primary parent associations - // TODO: This can be removed once we fix node archival - List childAssociations = connector.getNodeService().getParentAssocs(nodeRef); - if (childAssociations != null) - { - for (ChildAssociationRef childAssoc : childAssociations) - { - if (!childAssoc.isPrimary()) - { - connector.getNodeService().removeChildAssociation(childAssoc); - } - } - } - - // attempt to delete the node - connector.getNodeService().deleteNode(nodeRef); - } - catch (Exception e) - { - result.add(nodeRef.toString()); - } - - return result; - } - - @Override - public ObjectData getObject( - String repositoryId, String objectId, String filter, Boolean includeAllowableActions, - IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, - Boolean includeAcl, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // create a CMIS object - ObjectData object = connector.createCMISObject( - info, filter, includeAllowableActions, includeRelationships, - renditionFilter, includePolicyIds, includeAcl); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); - } - - return object; - } - - @Override - public ObjectData getObjectByPath( - String repositoryId, String path, String filter, Boolean includeAllowableActions, - IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, - Boolean includeAcl, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // start at the root node - NodeRef rootNodeRef = connector.getRootNodeRef(); - - if (path.equals("/")) - { - return connector.createCMISObject(createNodeInfo(rootNodeRef), filter, includeAllowableActions, - includeRelationships, renditionFilter, includePolicyIds, includeAcl); - } - else - { - try - { - // resolve path and get the node ref - FileInfo fileInfo = connector.getFileFolderService().resolveNamePath( - rootNodeRef, - Arrays.asList(path.substring(1).split("/"))); - - CMISNodeInfo info = createNodeInfo(fileInfo.getNodeRef()); - - ObjectData object = connector.createCMISObject( - info, fileInfo, filter, includeAllowableActions, - includeRelationships, renditionFilter, includePolicyIds, includeAcl); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); - } - - return object; - } - catch (FileNotFoundException e) - { - throw new CmisObjectNotFoundException("Object not found: " + path); - } - } - } - - @Override - public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); - } - - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - return connector.getAssocProperties(info, filter); - } - else - { - return connector.getNodeProperties(info, filter); - } - } - - @Override - public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - return connector.getAllowableActions(info); - } - - @Override - public ContentStream getContentStream( - String repositoryId, String objectId, String streamId, BigInteger offset, - BigInteger length, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // relationships cannot have content - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - throw new CmisInvalidArgumentException("Object is a relationship and cannot have content!"); - } - - // now get it - return connector.getContentStream(info, streamId, offset, length); - } - - @Override - public List getRenditions(String repositoryId, String objectId, String renditionFilter, - BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - return Collections.emptyList(); - } - else - { - return connector.getRenditions(info.getNodeRef(), renditionFilter, maxItems, skipCount); - } - } - - // --- versioning service --- - - @Override - public void checkOut( - String repositoryId, final Holder objectId, ExtensionsData extension, - final Holder contentCopied) - { - checkRepositoryId(repositoryId); - - CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); - - // check for current version - if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION)) - { - throw new CmisInvalidArgumentException("Only documents can be checked out!"); - } - - // get object - final NodeRef nodeRef = info.getNodeRef(); - - if (!((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).isVersionable()) - { - throw new CmisConstraintException("Document is not versionable!"); - } - - // check out - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - NodeRef pwcNodeRef = connector.getCheckOutCheckInService().checkout(nodeRef); - CMISNodeInfo pwcNodeInfo = createNodeInfo(pwcNodeRef); - objectId.setValue(pwcNodeInfo.getObjectId()); - - if (contentCopied != null) - { - contentCopied.setValue(connector.getFileFolderService().getReader(pwcNodeRef) != null); - } - return null; - } - catch (CheckOutCheckInServiceException e) - { - throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); - - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - } - - @Override - public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // only accept a PWC - if (!info.isVariant(CMISObjectVariant.PWC)) - { - throw new CmisVersioningException("Object is not a PWC!"); - } - - // get object - final NodeRef nodeRef = info.getNodeRef(); - - // cancel check out - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Exception - { - try - { - connector.getCheckOutCheckInService().cancelCheckout(nodeRef); - return null; - } - catch (CheckOutCheckInServiceException e) - { - throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); - - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - } - - @Override - public void checkIn( - String repositoryId, final Holder objectId, final Boolean major, - final Properties properties, final ContentStream contentStream, final String checkinComment, - final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); - - // only accept a PWC - if (!info.isVariant(CMISObjectVariant.PWC)) - { - throw new CmisVersioningException("Object is not a PWC!"); - } - - // get object - final NodeRef nodeRef = info.getNodeRef(); - final TypeDefinitionWrapper type = info.getType(); - - // copy stream to temp file - final File tempFile = copyToTempFile(contentStream); - final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); - - // check in - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Exception - { - try - { - // update PWC - connector.setProperties(nodeRef, type, properties, - new String[] { PropertyIds.OBJECT_TYPE_ID }); - connector.applyPolicies(nodeRef, type, policies); - connector.applyACL(nodeRef, type, addAces, removeAces); - - // handle content - if (contentStream != null) - { - // write content - ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); - writer.setEncoding(encoding.name()); - writer.putContent(tempFile); - } - - // check aspect - if (connector.getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) - { - Map props = new HashMap(); - props.put(ContentModel.PROP_INITIAL_VERSION, false); - props.put(ContentModel.PROP_AUTO_VERSION, false); - connector.getNodeService().addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); - } - - // create version properties - Map versionProperties = new HashMap(5); - versionProperties.put(VersionModel.PROP_VERSION_TYPE, major ? VersionType.MAJOR - : VersionType.MINOR); - if (checkinComment != null) - { - versionProperties.put(VersionModel.PROP_DESCRIPTION, checkinComment); - } - - // check in - NodeRef newNodeRef = connector.getCheckOutCheckInService().checkin(nodeRef, - versionProperties); - - objectId.setValue(connector.createObjectId(newNodeRef)); - - return null; - } - catch (FileExistsException fee) - { - removeTempFile(tempFile); - throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); - } - catch (IntegrityException ie) - { - removeTempFile(tempFile); - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (CheckOutCheckInServiceException e) - { - removeTempFile(tempFile); - throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); - - } - catch (AccessDeniedException ade) - { - removeTempFile(tempFile); - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - removeTempFile(tempFile); - } - - @Override - public List getAllVersions( - String repositoryId, String objectId, String versionSeriesId, String filter, - Boolean includeAllowableActions, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (versionSeriesId == null && objectId != null) - { - // it's a browser binding call - versionSeriesId = connector.getCurrentVersionId(objectId); - } - - if (versionSeriesId == null) - { - throw new CmisInvalidArgumentException("Object Id or Object Series Id must be set!"); - } - - List result = new ArrayList(); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); - - if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION)) - { - // the version series id is the id of current version, which is a - // document - throw new CmisInvalidArgumentException("Version Series does not exist!"); - } - - // get current version and it's history - NodeRef nodeRef = info.getNodeRef(); - VersionHistory versionHistory = ((CMISNodeInfoImpl) info).getVersionHistory(); - - if (versionHistory == null) - { - // add current version - result.add(connector.createCMISObject(info, filter, includeAllowableActions, IncludeRelationships.NONE, - CMISConnector.RENDITION_NONE, false, false)); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); - } - } - else - { - if (info.hasPWC()) - { - CMISNodeInfo pwcInfo = createNodeInfo(connector.getCheckOutCheckInService().getWorkingCopy(nodeRef)); - - result.add( - connector.createCMISObject( - pwcInfo, filter, includeAllowableActions, - IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, pwcInfo.getObjectId(), IncludeRelationships.NONE); - } - } - - // convert the version history - for (Version version : versionHistory.getAllVersions()) - { - CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef()); - - result.add( - connector.createCMISObject( - versionInfo, filter, includeAllowableActions, - IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, versionInfo.getObjectId(), IncludeRelationships.NONE); - } - } - } - - return result; - } - - @Override - public ObjectData getObjectOfLatestVersion( - String repositoryId, String objectId, String versionSeriesId, - Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, - String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (objectId != null) - { - // it's an AtomPub call - versionSeriesId = connector.getCurrentVersionId(objectId); - } - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); - CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major)); - - ObjectData object = connector.createCMISObject( - versionInfo, filter, includeAllowableActions, - includeRelationships, renditionFilter, includePolicyIds, includeAcl); - - if (context.isObjectInfoRequired()) - { - getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); - } - - return object; - } - - @Override - public Properties getPropertiesOfLatestVersion( - String repositoryId, String objectId, String versionSeriesId, - Boolean major, String filter, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (objectId != null) - { - // it's an AtomPub call - versionSeriesId = connector.getCurrentVersionId(objectId); - } - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); - - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - return connector.getAssocProperties(info, filter); - } - else - { - CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major)); - addNodeInfo(versionInfo); - return connector.getNodeProperties(versionInfo, filter); - } - } - - // --- multifiling service --- - - @Override - public void addObjectToFolder( - String repositoryId, String objectId, String folderId, Boolean allVersions, - ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (!allVersions) - { - throw new CmisInvalidArgumentException("Only allVersions=true supported!"); - } - - // get node ref - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - if (!info.isDocument()) - { - throw new CmisInvalidArgumentException("Object is not a document!"); - } - - final NodeRef nodeRef = info.getNodeRef(); - - // get the folder node ref - final CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); - - connector.checkChildObjectType(folderInfo, info.getType().getTypeId()); - - final QName name = QName.createQName( - NamespaceService.CONTENT_MODEL_1_0_URI, - QName.createValidLocalName((String) connector.getNodeService().getProperty(nodeRef, - ContentModel.PROP_NAME))); - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - connector.getNodeService().addChild(folderInfo.getNodeRef(), nodeRef, - ContentModel.ASSOC_CONTAINS, name); - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - } - - @Override - public void removeObjectFromFolder(String repositoryId, String objectId, String folderId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // get node ref - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - if (!info.isDocument()) - { - throw new CmisInvalidArgumentException("Object is not a document!"); - } - - final NodeRef nodeRef = info.getNodeRef(); - - // get the folder node ref - final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); - - // check primary parent - if (connector.getNodeService().getPrimaryParent(nodeRef).getParentRef().equals(folderNodeRef)) - { - throw new CmisConstraintException( - "Unfiling from primary parent folder is not supported! Use deleteObject() instead."); - } - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - connector.getNodeService().removeChild(folderNodeRef, nodeRef); - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - } - - // --- discovery service --- - - @Override - public ObjectList getContentChanges( - String repositoryId, Holder changeLogToken, Boolean includeProperties, - String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - return connector.getContentChanges(changeLogToken, maxItems); - } - - @Override - public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions, - Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, - BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (searchAllVersions.booleanValue()) - { - throw new CmisInvalidArgumentException("Search all version is not supported!"); - } - - return connector.query( - statement, includeAllowableActions, includeRelationships, renditionFilter, - maxItems, skipCount); - } - - // --- relationship service --- - - @Override - public ObjectList getObjectRelationships( - String repositoryId, String objectId, Boolean includeSubRelationshipTypes, - RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions, - BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - throw new CmisInvalidArgumentException("Object is a relationship!"); - } - - if (info.isVariant(CMISObjectVariant.VERSION)) - { - throw new CmisInvalidArgumentException("Object is a document version!"); - } - - // check if the relationship base type is requested - if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId)) - { - boolean isrt = (includeSubRelationshipTypes == null ? false : includeSubRelationshipTypes.booleanValue()); - if (isrt) - { - // all relationships are a direct subtype of the base type in - // Alfresco -> remove filter - typeId = null; - } - else - { - // there are no relationships of the base type in Alfresco -> - // return empty list - ObjectListImpl result = new ObjectListImpl(); - result.setHasMoreItems(false); - result.setNumItems(BigInteger.ZERO); - result.setObjects(new ArrayList()); - return result; - } - } - - return connector.getObjectRelationships( - info.getNodeRef(), relationshipDirection, typeId, filter, includeAllowableActions, - maxItems, skipCount); - } - - // --- policy service --- - - @Override - public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - TypeDefinitionWrapper type = info.getType(); - if (type == null) - { - throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); - } - - connector.applyPolicies(info.getNodeRef(), type, Collections.singletonList(policyId)); - } - - @Override - public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - TypeDefinitionWrapper type = info.getType(); - if (type == null) - { - throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); - } - - throw new CmisConstraintException("Object is not policy controllable!"); - } - - @Override - public List getAppliedPolicies( - String repositoryId, String objectId, String filter, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - // what kind of object is it? - getOrCreateNodeInfo(objectId, "Object"); - - // policies are not supported -> return empty list - return Collections.emptyList(); - } - - // --- ACL service --- - - @Override - public Acl applyAcl( - String repositoryId, String objectId, final Acl addAces, final Acl removeAces, - AclPropagation aclPropagation, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - if (aclPropagation == AclPropagation.OBJECTONLY) - { - throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); - } - - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // relationships don't have ACLs - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - throw new CmisConstraintException("Relationships are not ACL controllable!"); - } - - final NodeRef nodeRef = info.getCurrentNodeNodeRef(); - final TypeDefinitionWrapper type = info.getType(); - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - connector.applyACL(nodeRef, type, addAces, removeAces); - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - return connector.getACL(nodeRef, false); - } - - @Override - public Acl applyAcl(String repositoryId, String objectId, final Acl aces, AclPropagation aclPropagation) - { - checkRepositoryId(repositoryId); - - if (aclPropagation == AclPropagation.OBJECTONLY) - { - throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); - } - - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // relationships don't have ACLs - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - throw new CmisConstraintException("Relationships are not ACL controllable!"); - } - - final NodeRef nodeRef = info.getCurrentNodeNodeRef(); - final TypeDefinitionWrapper type = info.getType(); - - endReadOnlyTransaction(); - connector.getTransactionService().getRetryingTransactionHelper() - .doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try - { - connector.applyACL(nodeRef, type, aces); - return null; - } - catch (IntegrityException ie) - { - throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); - } - catch (AccessDeniedException ade) - { - throw new CmisPermissionDeniedException("Permission denied!", ade); - } - }; - }, false, true); - beginReadOnlyTransaction(); - - return connector.getACL(nodeRef, false); - } - - @SuppressWarnings("unchecked") - @Override - public Acl getAcl(String repositoryId, String objectId, Boolean onlyBasicPermissions, ExtensionsData extension) - { - checkRepositoryId(repositoryId); - - CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); - - // relationships don't have ACLs - if (info.isVariant(CMISObjectVariant.ASSOC)) - { - return new AccessControlListImpl(Collections.EMPTY_LIST); - } - - // get the ACL - return connector.getACL(info.getCurrentNodeNodeRef(), onlyBasicPermissions); - } - - // -------------------------------------------------------- - - /** - * Collects the {@link ObjectInfo} about an object. + * Call before the work method and forms the opposite of {@link #close()}. * - * (Provided by OpenCMIS, but optimized for Alfresco.) + * @param context the context in which the service must operate */ - @Override - public ObjectInfo getObjectInfo(String repositoryId, String objectId) - { - return getObjectInfo(repositoryId, objectId, null, IncludeRelationships.BOTH); - } - - protected ObjectInfo getObjectInfo(String repositoryId, String objectId, IncludeRelationships includeRelationships) - { - return getObjectInfo(repositoryId, objectId, null, includeRelationships); - } - - protected ObjectInfo getObjectInfo( - String repositoryId, String objectId, String filter, - IncludeRelationships includeRelationships) - { - ObjectInfo info = objectInfoMap.get(objectId); - if (info == null) - { - CMISNodeInfo nodeInfo = getOrCreateNodeInfo(objectId); - - if (nodeInfo.getObjectVariant() == CMISObjectVariant.INVALID_ID - || nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_EXISTING - || nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_A_CMIS_OBJECT - || nodeInfo.getObjectVariant() == CMISObjectVariant.PERMISSION_DENIED) - { - info = null; - } else - { - // object info has not been found -> create one - try - { - if (filter == null) - { - filter = MIN_FILTER; - } - else if (!filter.equals("*")) - { - filter = filter + "," + MIN_FILTER; - } - - // get the object and its info - ObjectData object = connector.createCMISObject( - nodeInfo, filter, false, includeRelationships, null, false, false); - - info = getObjectInfoIntern(repositoryId, object); - - // add object info - objectInfoMap.put(objectId, info); - } - catch (Exception e) - { - e.printStackTrace(); - info = null; - } - } - } - - return info; - } - - /** - * Collects the {@link ObjectInfo} about an object. - * - * (Provided by OpenCMIS, but optimized for Alfresco.) - */ - @Override - protected ObjectInfo getObjectInfoIntern(String repositoryId, ObjectData object) - { - // if the object has no properties, stop here - if (object.getProperties() == null || object.getProperties().getProperties() == null) - { - throw new CmisRuntimeException("No properties!"); - } - - CMISNodeInfo ni = getOrCreateNodeInfo(object.getId()); - - ObjectInfoImpl info = new ObjectInfoImpl(); - - // general properties - info.setObject(object); - info.setId(object.getId()); - info.setName(ni.getName()); - info.setCreatedBy(getStringProperty(object, PropertyIds.CREATED_BY)); - info.setCreationDate(getDateTimeProperty(object, PropertyIds.CREATION_DATE)); - info.setLastModificationDate(getDateTimeProperty(object, PropertyIds.LAST_MODIFICATION_DATE)); - info.setTypeId(getIdProperty(object, PropertyIds.OBJECT_TYPE_ID)); - info.setBaseType(object.getBaseTypeId()); - - if (ni.isRelationship()) - { - // versioning - info.setWorkingCopyId(null); - info.setWorkingCopyOriginalId(null); - - info.setVersionSeriesId(null); - info.setIsCurrentVersion(true); - info.setWorkingCopyId(null); - info.setWorkingCopyOriginalId(null); - - // content - info.setHasContent(false); - info.setContentType(null); - info.setFileName(null); - - // parent - info.setHasParent(false); - - // policies and relationships - info.setSupportsRelationships(false); - info.setSupportsPolicies(false); - - // renditions - info.setRenditionInfos(null); - - // relationships - info.setRelationshipSourceIds(null); - info.setRelationshipTargetIds(null); - - // global settings - info.setHasAcl(false); - info.setSupportsDescendants(false); - info.setSupportsFolderTree(false); - } - else if (ni.isFolder()) - { - // versioning - info.setWorkingCopyId(null); - info.setWorkingCopyOriginalId(null); - - info.setVersionSeriesId(null); - info.setIsCurrentVersion(true); - info.setWorkingCopyId(null); - info.setWorkingCopyOriginalId(null); - - // content - info.setHasContent(false); - info.setContentType(null); - info.setFileName(null); - - // parent - info.setHasParent(!ni.isRootFolder()); - - // policies and relationships - info.setSupportsRelationships(true); - info.setSupportsPolicies(true); - - // renditions - info.setRenditionInfos(null); - - // relationships - setRelaionshipsToObjectInfo(object, info); - - // global settings - info.setHasAcl(true); - info.setSupportsDescendants(true); - info.setSupportsFolderTree(true); - } - else if (ni.isDocument()) - { - // versioning - info.setWorkingCopyId(null); - info.setWorkingCopyOriginalId(null); - - info.setVersionSeriesId(ni.getCurrentNodeId()); - - if (ni.isPWC()) - { - info.setIsCurrentVersion(false); - info.setWorkingCopyId(ni.getObjectId()); - info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); - } - else - { - info.setIsCurrentVersion(ni.isCurrentVersion()); - - if (ni.hasPWC()) - { - info.setWorkingCopyId(ni.getCurrentNodeId() + CMISConnector.ID_SEPERATOR - + CMISConnector.PWC_VERSION_LABEL); - info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); - } else - { - info.setWorkingCopyId(null); - info.setWorkingCopyOriginalId(null); - } - } - - // content - String fileName = getStringProperty(object, PropertyIds.CONTENT_STREAM_FILE_NAME); - String mimeType = getStringProperty(object, PropertyIds.CONTENT_STREAM_MIME_TYPE); - String streamId = getIdProperty(object, PropertyIds.CONTENT_STREAM_ID); - BigInteger length = getIntegerProperty(object, PropertyIds.CONTENT_STREAM_LENGTH); - boolean hasContent = fileName != null || mimeType != null || streamId != null || length != null; - if (hasContent) - { - info.setHasContent(hasContent); - info.setContentType(mimeType); - info.setFileName(fileName); - } - else - { - info.setHasContent(false); - info.setContentType(null); - info.setFileName(null); - } - - // parent - info.setHasParent(ni.isCurrentVersion() || ni.isPWC()); - - // policies and relationships - info.setSupportsRelationships(true); - info.setSupportsPolicies(true); - - // renditions - info.setRenditionInfos(null); - List renditions = object.getRenditions(); - if (renditions != null && renditions.size() > 0) - { - List renditionInfos = new ArrayList(); - for (RenditionData rendition : renditions) - { - RenditionInfoImpl renditionInfo = new RenditionInfoImpl(); - renditionInfo.setId(rendition.getStreamId()); - renditionInfo.setKind(rendition.getKind()); - renditionInfo.setContentType(rendition.getMimeType()); - renditionInfo.setTitle(rendition.getTitle()); - renditionInfo.setLength(rendition.getBigLength()); - renditionInfos.add(renditionInfo); - } - info.setRenditionInfos(renditionInfos); - } - - // relationships - setRelaionshipsToObjectInfo(object, info); - - // global settings - info.setHasAcl(true); - info.setSupportsDescendants(true); - info.setSupportsFolderTree(true); - } - - return info; - } - - private void setRelaionshipsToObjectInfo(ObjectData object, ObjectInfoImpl info) - { - info.setRelationshipSourceIds(null); - info.setRelationshipTargetIds(null); - - List relationships = object.getRelationships(); - if (relationships != null && relationships.size() > 0) - { - List sourceIds = new ArrayList(); - List targetIds = new ArrayList(); - for (ObjectData relationship : relationships) - { - String sourceId = getIdProperty(relationship, PropertyIds.SOURCE_ID); - String targetId = getIdProperty(relationship, PropertyIds.TARGET_ID); - if (object.getId().equals(sourceId)) - { - sourceIds.add(relationship.getId()); - } - if (object.getId().equals(targetId)) - { - targetIds.add(relationship.getId()); - } - } - if (sourceIds.size() > 0) - { - info.setRelationshipSourceIds(sourceIds); - } - if (targetIds.size() > 0) - { - info.setRelationshipTargetIds(targetIds); - } - } - } - - // -------------------------------------------------------- - - private void checkRepositoryId(String repositoryId) - { - if (!connector.getRepositoryId().equals(repositoryId)) - { - throw new CmisObjectNotFoundException("Unknown repository '" + repositoryId + "'!"); - } - } - - private Charset getEncoding(File tempFile, String mimeType) - { - Charset encoding = null; - - try - { - InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile)); - ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); - encoding = charsetFinder.getCharset(tfis, mimeType); - tfis.close(); - } catch (Exception e) - { - throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); - } - - return encoding; - } - - private File copyToTempFile(ContentStream contentStream) - { - File tempFile = null; - - if (contentStream == null) - { - return tempFile; - } - - int bufferSize = 40 * 1014; - long count = 0; - - try - { - tempFile = TempFileProvider.createTempFile("cmis", "content"); - if (contentStream.getStream() != null) - { - OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); - InputStream in = new BufferedInputStream(contentStream.getStream(), bufferSize); - - byte[] buffer = new byte[bufferSize]; - int i; - while ((i = in.read(buffer)) > -1) - { - out.write(buffer, 0, i); - count += i; - } - - in.close(); - out.close(); - } - } - catch (Exception e) - { - removeTempFile(tempFile); - throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); - } - - if (contentStream.getLength() > -1 && contentStream.getLength() != count) - { - removeTempFile(tempFile); - throw new CmisStorageException( - "Expected " + contentStream.getLength() + " bytes but retrieved " + count + "bytes!"); - } - - return tempFile; - } - - private void removeTempFile(File tempFile) - { - if (tempFile == null) - { - return; - } - - try - { - tempFile.delete(); - } - catch (Exception e) - { - // ignore - file will be removed by TempFileProvider - } - } + void open(CallContext context); } diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java index 43a3f8a576..9babd7828a 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java @@ -20,26 +20,30 @@ package org.alfresco.opencmis; import java.util.Map; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionInterceptor; import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.chemistry.opencmis.commons.server.CmisService; import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.ProxyFactory; /** * Factory for OpenCMIS service objects. * * @author florian.mueller + * @author Derek Hulley */ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory { - private ThreadLocal> threadLocalService = new ThreadLocal>(); - + private static final Log logger = LogFactory.getLog(AlfrescoCmisServiceFactory.class); + private CMISConnector connector; - - @Override - public void init(Map parameters) - { - } + private RetryingTransactionInterceptor cmisTransactions; + private AlfrescoCmisExceptionInterceptor cmisExceptions; + private AlfrescoCmisServiceInterceptor cmisControl; /** * Sets the CMIS connector. @@ -49,26 +53,75 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory this.connector = connector; } + /** + * @param cmisTransactions the interceptor that applies appropriate transactions + */ + public void setCmisTransactions(RetryingTransactionInterceptor cmisTransactions) + { + this.cmisTransactions = cmisTransactions; + } + + /** + * @param cmisExceptions interceptor to translate exceptions + */ + public void setCmisExceptions(AlfrescoCmisExceptionInterceptor cmisExceptions) + { + this.cmisExceptions = cmisExceptions; + } + + /** + * @param cmisControl interceptor that provides logging and authentication checks + */ + public void setCmisControl(AlfrescoCmisServiceInterceptor cmisControl) + { + this.cmisControl = cmisControl; + } + + @Override + public void init(Map parameters) + { + } + @Override public void destroy() { - threadLocalService = null; } - + + /** + * TODO: + * We are producing new instances each time. + */ @Override public CmisService getService(CallContext context) { - CmisServiceWrapper wrapperService = threadLocalService.get(); - if (wrapperService == null) + if (logger.isDebugEnabled()) { - wrapperService = new CmisServiceWrapper(new AlfrescoCmisService(connector), - connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), - connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); - threadLocalService.set(wrapperService); + logger.debug("\n" + + "CMIS getService(): \n" + + " Authenticated as: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" + + " Running as: " + AuthenticationUtil.getRunAsUser() + "\n" + + " User: " + context.getUsername() + "\n" + + " Repo: " + context.getRepositoryId()); } + + AlfrescoCmisService cmisServiceTarget = new AlfrescoCmisServiceImpl(connector); + + // Wrap it + ProxyFactory proxyFactory = new ProxyFactory(cmisServiceTarget); + proxyFactory.addInterface(AlfrescoCmisService.class); + proxyFactory.addAdvice(cmisExceptions); + proxyFactory.addAdvice(cmisControl); + proxyFactory.addAdvice(cmisTransactions); + AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy(); + + CmisServiceWrapper wrapperService = new CmisServiceWrapper( + cmisService, + connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), + connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); - wrapperService.getWrappedService().beginCall(context); - + // We use our specific open method here because only we know about it + cmisService.open(context); + return wrapperService; } } diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java new file mode 100644 index 0000000000..b54cbe1d15 --- /dev/null +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -0,0 +1,2656 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco 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 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import net.sf.acegisecurity.Authentication; + +import org.alfresco.cmis.CMISInvalidArgumentException; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.opencmis.dictionary.CMISObjectVariant; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.Authorization; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.search.QueryParameterDefinition; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.AllowableActions; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.ExtensionsData; +import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; +import org.apache.chemistry.opencmis.commons.data.ObjectData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; +import org.apache.chemistry.opencmis.commons.data.ObjectList; +import org.apache.chemistry.opencmis.commons.data.ObjectParentData; +import org.apache.chemistry.opencmis.commons.data.Properties; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl; +import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService; +import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl; +import org.apache.chemistry.opencmis.commons.impl.server.RenditionInfoImpl; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.ObjectInfo; +import org.apache.chemistry.opencmis.commons.server.RenditionInfo; +import org.apache.chemistry.opencmis.commons.spi.Holder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * OpenCMIS service implementation + * + * @author florian.mueller + * @author Derek Hulley + * @since 4.0 + */ +public class AlfrescoCmisServiceImpl extends AbstractCmisService implements AlfrescoCmisService +{ + private static Log logger = LogFactory.getLog(AlfrescoCmisService.class); + + private static final QName PARAM_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent"); + private static final QName PARAM_USERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "username"); + private static final String LUCENE_QUERY_CHECKEDOUT = "+@cm\\:workingCopyOwner:${cm:username}"; + private static final String LUCENE_QUERY_CHECKEDOUT_IN_FOLDER = "+@cm\\:workingCopyOwner:${cm:username} +PARENT:\"${cm:parent}\""; + + private static final String MIN_FILTER = "cmis:name,cmis:baseTypeId,cmis:objectTypeId," + + "cmis:createdBy,cmis:creationDate,cmis:lastModifiedBy,cmis:lastModificationDate," + + "cmis:contentStreamLength,cmis:contentStreamMimeType,cmis:contentStreamFileName," + + "cmis:contentStreamId"; + + private CMISConnector connector; + private CallContext context; + private Authentication authentication; + private Map nodeInfoMap; + private Map objectInfoMap; + + public AlfrescoCmisServiceImpl(CMISConnector connector) + { + this.connector = connector; + nodeInfoMap = new HashMap(); + objectInfoMap = new HashMap(); + } + + @Override + public void beforeCall() + { + AuthenticationUtil.pushAuthentication(); + if (authentication != null) + { + // Use the previously-obtained authentication + AuthenticationUtil.setFullAuthentication(authentication); + } + else + { + if (context == null) + { + // Service not opened, yet + return; + } + // Sticky sessions? + if (connector.openHttpSession()) + { + // create a session -> set a cookie + // if the CMIS client supports cookies that might help in clustered environments + ((HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); + } + + // Authenticate + if (authentication != null) + { + // We have already authenticated; just reuse the authentication + AuthenticationUtil.setFullAuthentication(authentication); + } + else + { + // We have to go to the repo and authenticate + String user = context.getUsername(); + String password = context.getPassword(); + Authorization auth = new Authorization(user, password); + if (auth.isTicket()) + { + connector.getAuthenticationService().validate(auth.getTicket()); + } + else + { + connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); + } + this.authentication = AuthenticationUtil.getFullAuthentication(); + } + +// // TODO: How is the proxy user working. +// // Until we know what it is meant to do, it's not available +// String currentUser = connector.getAuthenticationService().getCurrentUserName(); +// String user = context.getUsername(); +// String password = context.getPassword(); +// if (currentUser != null && currentUser.equals(connector.getProxyUser())) +// { +// if (user != null && user.length() > 0) +// { +// AuthenticationUtil.setFullyAuthenticatedUser(user); +// } +// } + } + } + + @Override + public void afterCall() + { + AuthenticationUtil.popAuthentication(); + } + + @Override + public void open(CallContext context) + { + this.context = context; + } + + @Override + public void close() + { + // Put these resources on the transactions + context = null; + authentication = null; + nodeInfoMap.clear(); + objectInfoMap.clear(); + } + + protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) + { + CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef); + nodeInfoMap.put(result.getObjectId(), result); + + return result; + } + + protected CMISNodeInfo createNodeInfo(AssociationRef assocRef) + { + CMISNodeInfoImpl result = connector.createNodeInfo(assocRef); + nodeInfoMap.put(result.getObjectId(), result); + + return result; + } + + protected CMISNodeInfo getOrCreateNodeInfo(String objectId) + { + CMISNodeInfo result = nodeInfoMap.get(objectId); + if (result == null) + { + result = connector.createNodeInfo(objectId); + nodeInfoMap.put(objectId, result); + } + + return result; + } + + protected CMISNodeInfo getOrCreateNodeInfo(String objectId, String what) + { + CMISNodeInfo result = getOrCreateNodeInfo(objectId); + if (result instanceof CMISNodeInfoImpl) + { + ((CMISNodeInfoImpl) result).checkIfUseful(what); + } + + return result; + } + + protected CMISNodeInfo getOrCreateFolderInfo(String folderId, String what) + { + CMISNodeInfo result = getOrCreateNodeInfo(folderId); + if (result instanceof CMISNodeInfoImpl) + { + ((CMISNodeInfoImpl) result).checkIfFolder(what); + } + + return result; + } + + protected CMISNodeInfo addNodeInfo(CMISNodeInfo info) + { + nodeInfoMap.put(info.getObjectId(), info); + + return info; + } + + // --- repository service --- + + @Override + public List getRepositoryInfos(ExtensionsData extension) + { + return Collections.singletonList(connector.getRepositoryInfo()); + } + + @Override + public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + return connector.getRepositoryInfo(); + } + + @Override + public TypeDefinitionList getTypeChildren( + String repositoryId, String typeId, Boolean includePropertyDefinitions, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + // set up the result + TypeDefinitionListImpl result = new TypeDefinitionListImpl(); + List list = new ArrayList(); + result.setList(list); + + // get the types from the dictionary + List childrenList; + if (typeId == null) + { + childrenList = connector.getOpenCMISDictionaryService().getBaseTypes(); + } + else + { + TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); + if (tdw == null) + { + throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); + } + childrenList = tdw.getChildren(); + } + + // create result + if (max > 0) + { + int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + list.add(childrenList.get(i).getTypeDefinition(includePropertyDefinitions)); + } + } + + result.setHasMoreItems(childrenList.size() - skip > result.getList().size()); + result.setNumItems(BigInteger.valueOf(childrenList.size())); + + return result; + } + + @Override + public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // find the type + TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); + if (tdw == null) + { + throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); + } + + // return type definition + return tdw.getTypeDefinition(true); + } + + @Override + public List getTypeDescendants( + String repositoryId, String typeId, BigInteger depth, + Boolean includePropertyDefinitions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + // check depth + int d = (depth == null ? -1 : depth.intValue()); + if (d == 0) + { + throw new CmisInvalidArgumentException("Depth must not be 0!"); + } + + if (typeId == null) + { + for (TypeDefinitionWrapper tdw : connector.getOpenCMISDictionaryService().getBaseTypes()) + { + result.add(getTypesDescendants(d, tdw, includePropertyDefinitions)); + } + } + else + { + TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); + if (tdw == null) + { + throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); + } + + if (tdw.getChildren() != null) + { + for (TypeDefinitionWrapper child : tdw.getChildren()) + { + result.add(getTypesDescendants(d, child, includePropertyDefinitions)); + } + } + } + + return result; + } + + /** + * Gathers the type descendants tree. + */ + private TypeDefinitionContainer getTypesDescendants( + int depth, TypeDefinitionWrapper tdw, boolean includePropertyDefinitions) + { + TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl(); + + result.setTypeDefinition(tdw.getTypeDefinition(includePropertyDefinitions)); + + if (depth != 0) + { + if (tdw.getChildren() != null) + { + result.setChildren(new ArrayList()); + for (TypeDefinitionWrapper tdc : tdw.getChildren()) + { + result.getChildren().add( + getTypesDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions)); + } + } + } + + return result; + } + + // --- navigation service --- + + @Override + public ObjectInFolderList getChildren( + String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + long start = System.currentTimeMillis(); + + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + ObjectInFolderListImpl result = new ObjectInFolderListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + // get the children references + NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + + // convert orderBy to sortProps + List> sortProps = null; + if (orderBy != null) + { + sortProps = new ArrayList>(1); + + String[] parts = orderBy.split(","); + int len = parts.length; + final int origLen = len; + + if (origLen > 0) + { + int maxSortProps = GetChildrenCannedQuery.MAX_FILTER_SORT_PROPS; + if (len > maxSortProps) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Too many sort properties in 'orderBy' - ignore those above max (max=" + + maxSortProps + ",actual=" + len + ")"); + } + len = maxSortProps; + } + for (int i = 0; i < len; i++) + { + String[] sort = parts[i].split(" +"); + + if (sort.length > 0) + { + PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService() + .findPropertyByQueryName(sort[0]); + if (propDef != null) + { + QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); + if (sortProp != null) + { + boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc"); + sortProps.add(new Pair(sortProp, sortAsc)); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - mapping not found"); + } + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - query name not found"); + } + } + } + } + } + + if (sortProps.size() < origLen) + { + logger.warn("Sort properties trimmed - either too many and/or not found: \n" + " orig: " + orderBy + + "\n" + " final: " + sortProps); + } + } + + PagingRequest pageRequest = new PagingRequest(skip, max, null); + pageRequest.setRequestTotalCountMax(skip + 10000); // TODO make this optional/configurable + // - affects whether numItems may be returned + + PagingResults pageOfNodeInfos = connector.getFileFolderService().list( + folderNodeRef, true, true, + null, sortProps, pageRequest); + + if (max > 0) + { + for (FileInfo child : pageOfNodeInfos.getPage()) + { + try + { + // create a child CMIS object + CMISNodeInfo ni = createNodeInfo(child.getNodeRef()); + ObjectData object = connector.createCMISObject(ni, child, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); + } + + ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); + childData.setObject(object); + + // include path segment + if (includePathSegment) + { + childData.setPathSegment(child.getName()); + } + + // add it + list.add(childData); + } + catch (InvalidNodeRefException e) + { + // ignore invalid children + } + } + } + + // has more ? + result.setHasMoreItems(pageOfNodeInfos.hasMoreItems()); + + // total count ? + Pair totalCounts = pageOfNodeInfos.getTotalResultCount(); + if (totalCounts != null) + { + Integer totalCountLower = totalCounts.getFirst(); + Integer totalCountUpper = totalCounts.getSecond(); + if ((totalCountLower != null) && (totalCountLower.equals(totalCountUpper))) + { + result.setNumItems(BigInteger.valueOf(totalCountLower)); + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("getChildren: " + list.size() + " in " + (System.currentTimeMillis() - start) + " msecs"); + } + + return result; + } + + @Override + public List getDescendants( + String repositoryId, String folderId, BigInteger depth, + String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + getDescendantsTree( + repositoryId, + getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), + depth.intValue(), + filter, + includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, false, + result); + + return result; + } + + @Override + public List getFolderTree( + String repositoryId, String folderId, BigInteger depth, + String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + getDescendantsTree( + repositoryId, + getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), + depth.intValue(), + filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, true, + result); + + return result; + } + + private void getDescendantsTree( + String repositoryId, NodeRef folderNodeRef, int depth, String filter, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, boolean foldersOnly, List list) + { + // get the children references + List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef); + for (ChildAssociationRef child : childrenList) + { + try + { + TypeDefinitionWrapper type = connector.getType(child.getChildRef()); + if (type == null) + { + continue; + } + + boolean isFolder = (type instanceof FolderTypeDefintionWrapper); + + if (foldersOnly && !isFolder) + { + continue; + } + + // create a child CMIS object + ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); + CMISNodeInfo ni = createNodeInfo(child.getChildRef()); + object.setObject(connector.createCMISObject( + ni, filter, includeAllowableActions, includeRelationships, + renditionFilter, false, false)); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); + } + + if (includePathSegment) + { + object.setPathSegment(connector.getName(child.getChildRef())); + } + + // create the container + ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl(); + container.setObject(object); + + if ((depth != 1) && isFolder) + { + container.setChildren(new ArrayList()); + getDescendantsTree( + repositoryId, + child.getChildRef(), + depth - 1, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePathSegment, foldersOnly, + container.getChildren()); + } + + // add it + list.add(container); + } + catch (InvalidNodeRefException e) + { + // ignore invalid children + } + } + } + + @Override + public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the node ref + CMISNodeInfo info = getOrCreateFolderInfo(folderId, "Folder"); + + // the root folder has no parent + if (info.isRootFolder()) + { + throw new CmisInvalidArgumentException("Root folder has no parent!"); + } + + // get the parent + List parentInfos = info.getParents(); + if (parentInfos.isEmpty()) + { + throw new CmisRuntimeException("Folder has no parent and is not the root folder?!"); + } + + CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0)); + + ObjectData result = connector.createCMISObject( + parentInfo, filter, false, IncludeRelationships.NONE, + CMISConnector.RENDITION_NONE, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo( + repositoryId, + parentInfo.getObjectId(), + IncludeRelationships.NONE); + } + + return result; + } + + @Override + public List getObjectParents( + String repositoryId, String objectId, String filter, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includeRelativePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships are not filed + if (info.isRelationship()) + { + throw new CmisConstraintException("Relationships are not fileable!"); + } + + if (info.isFolder() && !info.isRootFolder()) + { + List parentInfos = info.getParents(); + if (!parentInfos.isEmpty()) + { + CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0)); + + ObjectData object = connector.createCMISObject( + parentInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getId(), includeRelationships); + } + + ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); + objectParent.setObject(object); + + // include relative path segment + if (includeRelativePathSegment) + { + objectParent.setRelativePathSegment(info.getName()); + } + + result.add(objectParent); + } + } + else if (info.isCurrentVersion() || info.isPWC()) + { + List parentInfos = info.getParents(); + for (CMISNodeInfo parentInfo : parentInfos) + { + addNodeInfo(parentInfo); + + ObjectData object = connector.createCMISObject( + parentInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getId(), includeRelationships); + } + + ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); + objectParent.setObject(object); + + // include relative path segment + if (includeRelativePathSegment) + { + objectParent.setRelativePathSegment(info.getName()); + } + + result.add(objectParent); + } + } + + return result; + } + + @Override + public ObjectList getCheckedOutDocs( + String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + // prepare query + SearchParameters params = new SearchParameters(); + params.setLanguage(SearchService.LANGUAGE_LUCENE); + QueryParameterDefinition usernameDef = new QueryParameterDefImpl( + PARAM_USERNAME, + connector.getDictionaryService().getDataType(DataTypeDefinition.TEXT), + true, + AuthenticationUtil.getFullyAuthenticatedUser()); + params.addQueryParameterDefinition(usernameDef); + + if (folderId == null) + { + params.setQuery(LUCENE_QUERY_CHECKEDOUT); + params.addStore(connector.getRootStoreRef()); + } + else + { + CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); + + params.setQuery(LUCENE_QUERY_CHECKEDOUT_IN_FOLDER); + params.addStore(folderInfo.getNodeRef().getStoreRef()); + QueryParameterDefinition parentDef = new QueryParameterDefImpl( + PARAM_PARENT, + connector.getDictionaryService().getDataType(DataTypeDefinition.NODE_REF), + true, + folderInfo.getNodeRef().toString()); + params.addQueryParameterDefinition(parentDef); + } + + // set up order + if (orderBy != null) + { + String[] parts = orderBy.split(","); + for (int i = 0; i < parts.length; i++) + { + String[] sort = parts[i].split(" +"); + + if (sort.length < 1) + { + continue; + } + + PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService().findPropertyByQueryName(sort[0]); + if (propDef != null) + { + if (propDef.getPropertyDefinition().isOrderable()) + { + QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); + if (sortProp != null) + { + boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc"); + params.addSort(propDef.getPropertyLuceneBuilder().getLuceneFieldName(), sortAsc); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - mapping not found"); + } + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - not orderable"); + } + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - query name not found"); + } + } + } + } + + // execute query + ResultSet resultSet = null; + List nodeRefs; + try + { + resultSet = connector.getSearchService().query(params); + nodeRefs = resultSet.getNodeRefs(); + } + finally + { + if (resultSet != null) + { + resultSet.close(); + } + } + + // collect results + ObjectListImpl result = new ObjectListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + int skipCounter = skip; + if (max > 0) + { + for (NodeRef nodeRef : nodeRefs) + { + if (skipCounter > 0) + { + skipCounter--; + continue; + } + + if (list.size() == max) + { + break; + } + + try + { + // create a CMIS object + CMISNodeInfo ni = createNodeInfo(nodeRef); + ObjectData object = connector.createCMISObject( + ni, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); + } + + // add it + list.add(object); + } + catch (InvalidNodeRefException e) + { + // ignore invalid objects + } + } + } + + // has more ? + result.setHasMoreItems(nodeRefs.size() - skip > list.size()); + + return result; + } + + // --- object service --- + + @Override + public String create( + String repositoryId, Properties properties, String folderId, ContentStream contentStream, + VersioningState versioningState, List policies, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // check properties + if (properties == null || properties.getProperties() == null) + { + throw new CmisInvalidArgumentException("Properties must be set!"); + } + + // get the type + String objectTypeId = connector.getObjectTypeIdProperty(properties); + + // find the type + TypeDefinitionWrapper type = connector.getOpenCMISDictionaryService().findType(objectTypeId); + if (type == null) + { + throw new CmisInvalidArgumentException("Type '" + objectTypeId + "' is unknown!"); + } + + // create object + String newId = null; + switch (type.getBaseTypeId()) + { + case CMIS_DOCUMENT: + newId = createDocument(repositoryId, properties, folderId, contentStream, versioningState, policies, null, + null, extension); + break; + case CMIS_FOLDER: + newId = createFolder(repositoryId, properties, folderId, policies, null, null, extension); + break; + case CMIS_POLICY: + newId = createPolicy(repositoryId, properties, folderId, policies, null, null, extension); + break; + } + + // check new object id + if (newId == null) + { + throw new CmisRuntimeException("Creation failed!"); + } + + if (context.isObjectInfoRequired()) + { + try + { + getObjectInfo(repositoryId, newId, "*", IncludeRelationships.NONE); + } + catch (InvalidNodeRefException e) + { + throw new CmisRuntimeException("Creation failed! New object not found!"); + } + } + + // return the new object id + return newId; + } + + @Override + public String createFolder(String repositoryId, final Properties properties, String folderId, + final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Folder"); + + // get name and type + final String name = connector.getNameProperty(properties, null); + final String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_FOLDER); + + connector.checkChildObjectType(parentInfo, type.getTypeId()); + + // run transaction + NodeRef nodeRef = connector.getFileFolderService().create( + parentInfo.getNodeRef(), name, type.getAlfrescoClass()).getNodeRef(); + + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + return nodeRef.toString(); + } + + @Override + public String createDocument( + String repositoryId, final Properties properties, String folderId, + final ContentStream contentStream, final VersioningState versioningState, final List policies, + final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder"); + + // get name and type + final String name = connector.getNameProperty(properties, null); + final String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_DOCUMENT); + + connector.checkChildObjectType(parentInfo, type.getTypeId()); + + DocumentTypeDefinition docType = (DocumentTypeDefinition) type.getTypeDefinition(false); + + if ((docType.getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) && (contentStream != null)) + { + throw new CmisConstraintException("This document type does not support content!"); + } + + if ((docType.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) && (contentStream == null)) + { + throw new CmisConstraintException("This document type does requires content!"); + } + + if (docType.isVersionable() && (versioningState == VersioningState.NONE)) + { + throw new CmisConstraintException("This document type is versionable!"); + } + + if (!docType.isVersionable() && (versioningState != VersioningState.NONE)) + { + throw new CmisConstraintException("This document type is not versionable!"); + } + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + NodeRef nodeRef = connector.getFileFolderService().create( + parentInfo.getNodeRef(), name, type.getAlfrescoClass()).getNodeRef(); + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + // handle content + if (contentStream != null) + { + // write content + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + + connector.applyVersioningState(nodeRef, versioningState); + + removeTempFile(tempFile); + + return connector.createObjectId(nodeRef); + } + + @Override + public String createDocumentFromSource( + String repositoryId, String sourceId, final Properties properties, + String folderId, final VersioningState versioningState, final List policies, final Acl addAces, + final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder"); + + // get source + CMISNodeInfo info = getOrCreateNodeInfo(sourceId, "Source"); + + // check source + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisConstraintException("Source object is not a document!"); + } + + final NodeRef sourceNodeRef = info.getNodeRef(); + if (!info.isDocument()) + { + throw new CmisConstraintException("Source object is not a document!"); + } + + // get name and type + final String name = connector.getNameProperty(properties, info.getName()); + + final TypeDefinitionWrapper type = info.getType(); + connector.checkChildObjectType(parentInfo, type.getTypeId()); + + try + { + NodeRef nodeRef = connector.getFileFolderService().copy( + sourceNodeRef, parentInfo.getNodeRef(), name).getNodeRef(); + connector.setProperties(nodeRef, type, properties, new String[] { + PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + connector.applyVersioningState(nodeRef, versioningState); + + return connector.createObjectId(nodeRef); + } + catch (FileNotFoundException e) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", e); + } + } + + @Override + public String createPolicy( + String repositoryId, Properties properties, String folderId, List policies, + Acl addAces, Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder + getOrCreateFolderInfo(folderId, "Parent Folder"); + + String objectTypeId = connector.getObjectTypeIdProperty(properties); + connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_POLICY); + + // we should never get here - policies are not creatable! + throw new CmisRuntimeException("Polcies cannot be created!"); + } + + @Override + public String createRelationship( + String repositoryId, Properties properties, List policies, Acl addAces, + Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get type + String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_RELATIONSHIP); + + // get source object + String sourceId = connector.getSourceIdProperty(properties); + CMISNodeInfo sourceInfo = getOrCreateNodeInfo(sourceId, "Source"); + + if (!sourceInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !sourceInfo.isVariant(CMISObjectVariant.FOLDER)) + { + throw new CmisInvalidArgumentException("Source is not the latest version of a document or a folder object!"); + } + + final NodeRef sourceNodeRef = sourceInfo.getNodeRef(); + + // get target object + String targetId = connector.getTargetIdProperty(properties); + CMISNodeInfo targetInfo = getOrCreateNodeInfo(targetId, "Target"); + + if (!targetInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !targetInfo.isVariant(CMISObjectVariant.FOLDER)) + { + throw new CmisInvalidArgumentException( + "Target is not the latest version of a document or a folder object!!"); + } + + final NodeRef targetNodeRef = targetInfo.getNodeRef(); + + // check policies and ACLs + if ((policies != null) && (!policies.isEmpty())) + { + throw new CmisConstraintException("Relationships are not policy controllable!"); + } + + if ((addAces != null) && (addAces.getAces() != null) && (!addAces.getAces().isEmpty())) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + if ((removeAces != null) && (removeAces.getAces() != null) && (!removeAces.getAces().isEmpty())) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + // create relationship + AssociationRef assocRef = connector.getNodeService().createAssociation( + sourceNodeRef, targetNodeRef, type.getAlfrescoClass()); + + return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId(); + } + + @Override + public void setContentStream( + String repositoryId, Holder objectId, Boolean overwriteFlag, + Holder changeToken, final ContentStream contentStream, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisStreamNotSupportedException("Content can only be set ondocuments!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) + { + throw new CmisStreamNotSupportedException("Document type doesn't allow content!"); + } + + boolean existed = connector.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT) != null; + if (existed && !overwriteFlag) + { + throw new CmisContentAlreadyExistsException("Content already exists!"); + } + + if ((contentStream == null) || (contentStream.getStream() == null)) + { + throw new CmisInvalidArgumentException("No content!"); + } + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = getEncoding(tempFile, contentStream.getMimeType()); + + try + { + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + finally + { + removeTempFile(tempFile); + } + + objectId.setValue(connector.createObjectId(nodeRef)); + } + + @Override + public void deleteContentStream( + String repositoryId, Holder objectId, Holder changeToken, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisStreamNotSupportedException("Content can only be deleted from ondocuments!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) + { + throw new CmisInvalidArgumentException("Document type requires content!"); + } + + connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_CONTENT, null); + + objectId.setValue(connector.createObjectId(nodeRef)); + } + + @Override + public void moveObject( + String repositoryId, Holder objectId, String targetFolderId, String sourceFolderId, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get object and source and target parent + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + final NodeRef nodeRef = info.getCurrentNodeNodeRef(); + final CMISNodeInfo sourceInfo = getOrCreateFolderInfo(sourceFolderId, "Source Folder"); + final CMISNodeInfo targetInfo = getOrCreateFolderInfo(targetFolderId, "Target Folder"); + + connector.checkChildObjectType(targetInfo, info.getType().getTypeId()); + + ChildAssociationRef primaryParentRef = connector.getNodeService().getPrimaryParent(nodeRef); + // if this is a primary child node, move it + if (primaryParentRef.getParentRef().equals(sourceInfo.getNodeRef())) + { + connector.getNodeService().moveNode( + nodeRef, targetInfo.getNodeRef(), + primaryParentRef.getTypeQName(), primaryParentRef.getQName()); + } + else + { + boolean found = false; + // otherwise, reparent it + for (ChildAssociationRef parent : connector.getNodeService().getParentAssocs( + nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL)) + { + if (parent.getParentRef().equals(sourceInfo.getNodeRef())) + { + connector.getNodeService().removeChildAssociation(parent); + connector.getNodeService().addChild( + targetInfo.getNodeRef(), nodeRef, + ContentModel.ASSOC_CONTAINS, parent.getQName()); + found = true; + } + } + if (!found) + { + throw new IllegalArgumentException( + new CMISInvalidArgumentException( + "Document is not a child of the source folder that was specified!")); + } + } + } + + @Override + public void updateProperties( + String repositoryId, Holder objectId, Holder changeToken, + final Properties properties, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + final CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisInvalidArgumentException("Relationship properties cannot be updated!"); + } + else + { + if (info.isVariant(CMISObjectVariant.VERSION)) + { + throw new CmisInvalidArgumentException("Document is not the latest version!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + connector.setProperties(nodeRef, info.getType(), properties, new String[0]); + + objectId.setValue(connector.createObjectId(nodeRef)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, objectId.getValue(), "*", IncludeRelationships.NONE); + } + } + } + + @Override + public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension) + { + deleteObjectOrCancelCheckOut(repositoryId, objectId, allVersions, extension); + } + + @Override + public void deleteObjectOrCancelCheckOut( + String repositoryId, final String objectId, final Boolean allVersions, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + final CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + do + { + // handle relationships + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + AssociationRef assocRef = info.getAssociationRef(); + connector.getNodeService().removeAssociation( + assocRef.getSourceRef(), + assocRef.getTargetRef(), + assocRef.getTypeQName()); + break; // Reason for do-while + } + + NodeRef nodeRef = info.getNodeRef(); + + // handle PWC + if (info.isVariant(CMISObjectVariant.PWC)) + { + connector.getCheckOutCheckInService().cancelCheckout(nodeRef); + break; // Reason for do-while + } + + // handle folders + if (info.isFolder()) + { + if (connector.getNodeService().getChildAssocs(nodeRef).size() > 0) + { + throw new CmisConstraintException( + "Could not delete folder with at least one child!"); + } + + connector.getNodeService().deleteNode(nodeRef); + break; // Reason for do-while + } + + // handle versions + if (allVersions) + { + NodeRef workingCopy = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); + if (workingCopy != null) + { + connector.getCheckOutCheckInService().cancelCheckout(workingCopy); + } + } + else if (info.isVariant(CMISObjectVariant.VERSION)) + { + Version version = ((CMISNodeInfoImpl) info).getVersion(); + connector.getVersionService().deleteVersion(nodeRef, version); + break; // Reason for do-while + } + + if (info.isVariant(CMISObjectVariant.VERSION)) + { + nodeRef = info.getCurrentNodeNodeRef(); + } + + // remove not primary parent associations + List childAssociations = connector.getNodeService().getParentAssocs(nodeRef); + if (childAssociations != null) + { + for (ChildAssociationRef childAssoc : childAssociations) + { + if (!childAssoc.isPrimary()) + { + connector.getNodeService().removeChildAssociation(childAssoc); + } + } + } + + // attempt to delete the node + if (allVersions) + { + connector.getNodeService().deleteNode(nodeRef); + } + else + { + CMISNodeInfoImpl infoImpl = ((CMISNodeInfoImpl) info); + Version version = infoImpl.getVersion(); + + if (infoImpl.getVersionHistory().getPredecessor(version) == null) + { + connector.getNodeService().deleteNode(nodeRef); + } + else + { + connector.getVersionService().deleteVersion(nodeRef, version); + } + } + } + while (false); // Dodgey, but avoided having to play with too much code during refactor + } + + @Override + public FailedToDeleteData deleteTree( + String repositoryId, String folderId, Boolean allVersions, + UnfileObject unfileObjects, final Boolean continueOnFailure, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (!allVersions) + { + throw new CmisInvalidArgumentException("Only allVersions=true supported!"); + } + + if (unfileObjects == UnfileObject.UNFILE) + { + throw new CmisInvalidArgumentException("Unfiling not supported!"); + } + + final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + final FailedToDeleteDataImpl result = new FailedToDeleteDataImpl(); + + result.setIds(deleteBranch(folderNodeRef, continueOnFailure)); + return result; + } + + private List deleteBranch(NodeRef nodeRef, boolean continueOnFailure) + { + List result = new ArrayList(); + + try + { + // remove children + List childrenList = connector.getNodeService().getChildAssocs(nodeRef); + if (childrenList != null) + { + for (ChildAssociationRef child : childrenList) + { + List ftod = deleteBranch(child.getChildRef(), continueOnFailure); + if (!ftod.isEmpty()) + { + result.addAll(ftod); + if (!continueOnFailure) + { + return result; + } + } + } + } + + // remove not primary parent associations + // TODO: This can be removed once we fix node archival + List childAssociations = connector.getNodeService().getParentAssocs(nodeRef); + if (childAssociations != null) + { + for (ChildAssociationRef childAssoc : childAssociations) + { + if (!childAssoc.isPrimary()) + { + connector.getNodeService().removeChildAssociation(childAssoc); + } + } + } + + // attempt to delete the node + connector.getNodeService().deleteNode(nodeRef); + } + catch (Exception e) + { + result.add(nodeRef.toString()); + } + + return result; + } + + @Override + public ObjectData getObject( + String repositoryId, String objectId, String filter, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, + Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // create a CMIS object + ObjectData object = connector.createCMISObject( + info, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); + } + + return object; + } + + @Override + public ObjectData getObjectByPath( + String repositoryId, String path, String filter, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, + Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // start at the root node + NodeRef rootNodeRef = connector.getRootNodeRef(); + + if (path.equals("/")) + { + return connector.createCMISObject(createNodeInfo(rootNodeRef), filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + } + else + { + try + { + // resolve path and get the node ref + FileInfo fileInfo = connector.getFileFolderService().resolveNamePath( + rootNodeRef, + Arrays.asList(path.substring(1).split("/"))); + + CMISNodeInfo info = createNodeInfo(fileInfo.getNodeRef()); + + ObjectData object = connector.createCMISObject( + info, fileInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); + } + + return object; + } + catch (FileNotFoundException e) + { + throw new CmisObjectNotFoundException("Object not found: " + path); + } + } + } + + @Override + public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); + } + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return connector.getAssocProperties(info, filter); + } + else + { + return connector.getNodeProperties(info, filter); + } + } + + @Override + public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + return connector.getAllowableActions(info); + } + + @Override + public ContentStream getContentStream( + String repositoryId, String objectId, String streamId, BigInteger offset, + BigInteger length, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships cannot have content + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisInvalidArgumentException("Object is a relationship and cannot have content!"); + } + + // now get it + return connector.getContentStream(info, streamId, offset, length); + } + + @Override + public List getRenditions(String repositoryId, String objectId, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return Collections.emptyList(); + } + else + { + return connector.getRenditions(info.getNodeRef(), renditionFilter, maxItems, skipCount); + } + } + + // --- versioning service --- + + @Override + public void checkOut( + String repositoryId, final Holder objectId, ExtensionsData extension, + final Holder contentCopied) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + // check for current version + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION)) + { + throw new CmisInvalidArgumentException("Only documents can be checked out!"); + } + + // get object + final NodeRef nodeRef = info.getNodeRef(); + + if (!((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).isVersionable()) + { + throw new CmisConstraintException("Document is not versionable!"); + } + + // check out + NodeRef pwcNodeRef = connector.getCheckOutCheckInService().checkout(nodeRef); + CMISNodeInfo pwcNodeInfo = createNodeInfo(pwcNodeRef); + objectId.setValue(pwcNodeInfo.getObjectId()); + + if (contentCopied != null) + { + contentCopied.setValue(connector.getFileFolderService().getReader(pwcNodeRef) != null); + } + } + + @Override + public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // only accept a PWC + if (!info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisVersioningException("Object is not a PWC!"); + } + + // get object + final NodeRef nodeRef = info.getNodeRef(); + + // cancel check out + connector.getCheckOutCheckInService().cancelCheckout(nodeRef); + } + + @Override + public void checkIn( + String repositoryId, final Holder objectId, final Boolean major, + final Properties properties, final ContentStream contentStream, final String checkinComment, + final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + // only accept a PWC + if (!info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisVersioningException("Object is not a PWC!"); + } + + // get object + final NodeRef nodeRef = info.getNodeRef(); + final TypeDefinitionWrapper type = info.getType(); + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + // check in + // update PWC + connector.setProperties(nodeRef, type, properties, + new String[] { PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + // handle content + if (contentStream != null) + { + // write content + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + + // check aspect + if (connector.getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + Map props = new HashMap(); + props.put(ContentModel.PROP_INITIAL_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, false); + connector.getNodeService().addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); + } + + // create version properties + Map versionProperties = new HashMap(5); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, major ? VersionType.MAJOR + : VersionType.MINOR); + if (checkinComment != null) + { + versionProperties.put(VersionModel.PROP_DESCRIPTION, checkinComment); + } + + // check in + NodeRef newNodeRef = connector.getCheckOutCheckInService().checkin(nodeRef, versionProperties); + + objectId.setValue(connector.createObjectId(newNodeRef)); + + removeTempFile(tempFile); + } + + @Override + public List getAllVersions( + String repositoryId, String objectId, String versionSeriesId, String filter, + Boolean includeAllowableActions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (versionSeriesId == null && objectId != null) + { + // it's a browser binding call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + if (versionSeriesId == null) + { + throw new CmisInvalidArgumentException("Object Id or Object Series Id must be set!"); + } + + List result = new ArrayList(); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); + + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION)) + { + // the version series id is the id of current version, which is a + // document + throw new CmisInvalidArgumentException("Version Series does not exist!"); + } + + // get current version and it's history + NodeRef nodeRef = info.getNodeRef(); + VersionHistory versionHistory = ((CMISNodeInfoImpl) info).getVersionHistory(); + + if (versionHistory == null) + { + // add current version + result.add(connector.createCMISObject(info, filter, includeAllowableActions, IncludeRelationships.NONE, + CMISConnector.RENDITION_NONE, false, false)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); + } + } + else + { + if (info.hasPWC()) + { + CMISNodeInfo pwcInfo = createNodeInfo(connector.getCheckOutCheckInService().getWorkingCopy(nodeRef)); + + result.add( + connector.createCMISObject( + pwcInfo, filter, includeAllowableActions, + IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, pwcInfo.getObjectId(), IncludeRelationships.NONE); + } + } + + // convert the version history + for (Version version : versionHistory.getAllVersions()) + { + CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef()); + + result.add( + connector.createCMISObject( + versionInfo, filter, includeAllowableActions, + IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, versionInfo.getObjectId(), IncludeRelationships.NONE); + } + } + } + + return result; + } + + @Override + public ObjectData getObjectOfLatestVersion( + String repositoryId, String objectId, String versionSeriesId, + Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); + CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major)); + + ObjectData object = connector.createCMISObject( + versionInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); + } + + return object; + } + + @Override + public Properties getPropertiesOfLatestVersion( + String repositoryId, String objectId, String versionSeriesId, + Boolean major, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return connector.getAssocProperties(info, filter); + } + else + { + CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major)); + addNodeInfo(versionInfo); + return connector.getNodeProperties(versionInfo, filter); + } + } + + // --- multifiling service --- + + @Override + public void addObjectToFolder( + String repositoryId, String objectId, String folderId, Boolean allVersions, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (!allVersions) + { + throw new CmisInvalidArgumentException("Only allVersions=true supported!"); + } + + // get node ref + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (!info.isDocument()) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + // get the folder node ref + final CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); + + connector.checkChildObjectType(folderInfo, info.getType().getTypeId()); + + final QName name = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName((String) connector.getNodeService().getProperty(nodeRef, + ContentModel.PROP_NAME))); + + connector.getNodeService().addChild( + folderInfo.getNodeRef(), nodeRef, ContentModel.ASSOC_CONTAINS, name); + } + + @Override + public void removeObjectFromFolder(String repositoryId, String objectId, String folderId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get node ref + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (!info.isDocument()) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + // get the folder node ref + final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + + // check primary parent + if (connector.getNodeService().getPrimaryParent(nodeRef).getParentRef().equals(folderNodeRef)) + { + throw new CmisConstraintException( + "Unfiling from primary parent folder is not supported! Use deleteObject() instead."); + } + + connector.getNodeService().removeChild(folderNodeRef, nodeRef); + } + + // --- discovery service --- + + @Override + public ObjectList getContentChanges( + String repositoryId, Holder changeLogToken, Boolean includeProperties, + String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + return connector.getContentChanges(changeLogToken, maxItems); + } + + @Override + public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (searchAllVersions.booleanValue()) + { + throw new CmisInvalidArgumentException("Search all version is not supported!"); + } + + return connector.query( + statement, includeAllowableActions, includeRelationships, renditionFilter, + maxItems, skipCount); + } + + // --- relationship service --- + + @Override + public ObjectList getObjectRelationships( + String repositoryId, String objectId, Boolean includeSubRelationshipTypes, + RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisInvalidArgumentException("Object is a relationship!"); + } + + if (info.isVariant(CMISObjectVariant.VERSION)) + { + throw new CmisInvalidArgumentException("Object is a document version!"); + } + + // check if the relationship base type is requested + if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId)) + { + boolean isrt = (includeSubRelationshipTypes == null ? false : includeSubRelationshipTypes.booleanValue()); + if (isrt) + { + // all relationships are a direct subtype of the base type in + // Alfresco -> remove filter + typeId = null; + } + else + { + // there are no relationships of the base type in Alfresco -> + // return empty list + ObjectListImpl result = new ObjectListImpl(); + result.setHasMoreItems(false); + result.setNumItems(BigInteger.ZERO); + result.setObjects(new ArrayList()); + return result; + } + } + + return connector.getObjectRelationships( + info.getNodeRef(), relationshipDirection, typeId, filter, includeAllowableActions, + maxItems, skipCount); + } + + // --- policy service --- + + @Override + public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + TypeDefinitionWrapper type = info.getType(); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + connector.applyPolicies(info.getNodeRef(), type, Collections.singletonList(policyId)); + } + + @Override + public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + TypeDefinitionWrapper type = info.getType(); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + throw new CmisConstraintException("Object is not policy controllable!"); + } + + @Override + public List getAppliedPolicies( + String repositoryId, String objectId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + getOrCreateNodeInfo(objectId, "Object"); + + // policies are not supported -> return empty list + return Collections.emptyList(); + } + + // --- ACL service --- + + @Override + public Acl applyAcl( + String repositoryId, String objectId, final Acl addAces, final Acl removeAces, + AclPropagation aclPropagation, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (aclPropagation == AclPropagation.OBJECTONLY) + { + throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); + } + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships don't have ACLs + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + final NodeRef nodeRef = info.getCurrentNodeNodeRef(); + final TypeDefinitionWrapper type = info.getType(); + + connector.applyACL(nodeRef, type, addAces, removeAces); + + return connector.getACL(nodeRef, false); + } + + @Override + public Acl applyAcl(String repositoryId, String objectId, final Acl aces, AclPropagation aclPropagation) + { + checkRepositoryId(repositoryId); + + if (aclPropagation == AclPropagation.OBJECTONLY) + { + throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); + } + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships don't have ACLs + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + final NodeRef nodeRef = info.getCurrentNodeNodeRef(); + final TypeDefinitionWrapper type = info.getType(); + + connector.applyACL(nodeRef, type, aces); + + return connector.getACL(nodeRef, false); + } + + @SuppressWarnings("unchecked") + @Override + public Acl getAcl(String repositoryId, String objectId, Boolean onlyBasicPermissions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships don't have ACLs + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return new AccessControlListImpl(Collections.EMPTY_LIST); + } + + // get the ACL + return connector.getACL(info.getCurrentNodeNodeRef(), onlyBasicPermissions); + } + + // -------------------------------------------------------- + + /** + * Collects the {@link ObjectInfo} about an object. + * + * (Provided by OpenCMIS, but optimized for Alfresco.) + */ + @Override + public ObjectInfo getObjectInfo(String repositoryId, String objectId) + { + return getObjectInfo(repositoryId, objectId, null, IncludeRelationships.BOTH); + } + + protected ObjectInfo getObjectInfo(String repositoryId, String objectId, IncludeRelationships includeRelationships) + { + return getObjectInfo(repositoryId, objectId, null, includeRelationships); + } + + protected ObjectInfo getObjectInfo( + String repositoryId, String objectId, String filter, + IncludeRelationships includeRelationships) + { + ObjectInfo info = objectInfoMap.get(objectId); + if (info == null) + { + CMISNodeInfo nodeInfo = getOrCreateNodeInfo(objectId); + + if (nodeInfo.getObjectVariant() == CMISObjectVariant.INVALID_ID + || nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_EXISTING + || nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_A_CMIS_OBJECT + || nodeInfo.getObjectVariant() == CMISObjectVariant.PERMISSION_DENIED) + { + info = null; + } else + { + // object info has not been found -> create one + try + { + if (filter == null) + { + filter = MIN_FILTER; + } + else if (!filter.equals("*")) + { + filter = filter + "," + MIN_FILTER; + } + + // get the object and its info + ObjectData object = connector.createCMISObject( + nodeInfo, filter, false, includeRelationships, null, false, false); + + info = getObjectInfoIntern(repositoryId, object); + + // add object info + objectInfoMap.put(objectId, info); + } + catch (Exception e) + { + e.printStackTrace(); + info = null; + } + } + } + + return info; + } + + /** + * Collects the {@link ObjectInfo} about an object. + * + * (Provided by OpenCMIS, but optimized for Alfresco.) + */ + @Override + protected ObjectInfo getObjectInfoIntern(String repositoryId, ObjectData object) + { + // if the object has no properties, stop here + if (object.getProperties() == null || object.getProperties().getProperties() == null) + { + throw new CmisRuntimeException("No properties!"); + } + + CMISNodeInfo ni = getOrCreateNodeInfo(object.getId()); + + ObjectInfoImpl info = new ObjectInfoImpl(); + + // general properties + info.setObject(object); + info.setId(object.getId()); + info.setName(ni.getName()); + info.setCreatedBy(getStringProperty(object, PropertyIds.CREATED_BY)); + info.setCreationDate(getDateTimeProperty(object, PropertyIds.CREATION_DATE)); + info.setLastModificationDate(getDateTimeProperty(object, PropertyIds.LAST_MODIFICATION_DATE)); + info.setTypeId(getIdProperty(object, PropertyIds.OBJECT_TYPE_ID)); + info.setBaseType(object.getBaseTypeId()); + + if (ni.isRelationship()) + { + // versioning + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(null); + info.setIsCurrentVersion(true); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + // content + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + + // parent + info.setHasParent(false); + + // policies and relationships + info.setSupportsRelationships(false); + info.setSupportsPolicies(false); + + // renditions + info.setRenditionInfos(null); + + // relationships + info.setRelationshipSourceIds(null); + info.setRelationshipTargetIds(null); + + // global settings + info.setHasAcl(false); + info.setSupportsDescendants(false); + info.setSupportsFolderTree(false); + } + else if (ni.isFolder()) + { + // versioning + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(null); + info.setIsCurrentVersion(true); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + // content + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + + // parent + info.setHasParent(!ni.isRootFolder()); + + // policies and relationships + info.setSupportsRelationships(true); + info.setSupportsPolicies(true); + + // renditions + info.setRenditionInfos(null); + + // relationships + setRelaionshipsToObjectInfo(object, info); + + // global settings + info.setHasAcl(true); + info.setSupportsDescendants(true); + info.setSupportsFolderTree(true); + } + else if (ni.isDocument()) + { + // versioning + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(ni.getCurrentNodeId()); + + if (ni.isPWC()) + { + info.setIsCurrentVersion(false); + info.setWorkingCopyId(ni.getObjectId()); + info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); + } + else + { + info.setIsCurrentVersion(ni.isCurrentVersion()); + + if (ni.hasPWC()) + { + info.setWorkingCopyId(ni.getCurrentNodeId() + CMISConnector.ID_SEPERATOR + + CMISConnector.PWC_VERSION_LABEL); + info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); + } else + { + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + } + } + + // content + String fileName = getStringProperty(object, PropertyIds.CONTENT_STREAM_FILE_NAME); + String mimeType = getStringProperty(object, PropertyIds.CONTENT_STREAM_MIME_TYPE); + String streamId = getIdProperty(object, PropertyIds.CONTENT_STREAM_ID); + BigInteger length = getIntegerProperty(object, PropertyIds.CONTENT_STREAM_LENGTH); + boolean hasContent = fileName != null || mimeType != null || streamId != null || length != null; + if (hasContent) + { + info.setHasContent(hasContent); + info.setContentType(mimeType); + info.setFileName(fileName); + } + else + { + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + } + + // parent + info.setHasParent(ni.isCurrentVersion() || ni.isPWC()); + + // policies and relationships + info.setSupportsRelationships(true); + info.setSupportsPolicies(true); + + // renditions + info.setRenditionInfos(null); + List renditions = object.getRenditions(); + if (renditions != null && renditions.size() > 0) + { + List renditionInfos = new ArrayList(); + for (RenditionData rendition : renditions) + { + RenditionInfoImpl renditionInfo = new RenditionInfoImpl(); + renditionInfo.setId(rendition.getStreamId()); + renditionInfo.setKind(rendition.getKind()); + renditionInfo.setContentType(rendition.getMimeType()); + renditionInfo.setTitle(rendition.getTitle()); + renditionInfo.setLength(rendition.getBigLength()); + renditionInfos.add(renditionInfo); + } + info.setRenditionInfos(renditionInfos); + } + + // relationships + setRelaionshipsToObjectInfo(object, info); + + // global settings + info.setHasAcl(true); + info.setSupportsDescendants(true); + info.setSupportsFolderTree(true); + } + + return info; + } + + private void setRelaionshipsToObjectInfo(ObjectData object, ObjectInfoImpl info) + { + info.setRelationshipSourceIds(null); + info.setRelationshipTargetIds(null); + + List relationships = object.getRelationships(); + if (relationships != null && relationships.size() > 0) + { + List sourceIds = new ArrayList(); + List targetIds = new ArrayList(); + for (ObjectData relationship : relationships) + { + String sourceId = getIdProperty(relationship, PropertyIds.SOURCE_ID); + String targetId = getIdProperty(relationship, PropertyIds.TARGET_ID); + if (object.getId().equals(sourceId)) + { + sourceIds.add(relationship.getId()); + } + if (object.getId().equals(targetId)) + { + targetIds.add(relationship.getId()); + } + } + if (sourceIds.size() > 0) + { + info.setRelationshipSourceIds(sourceIds); + } + if (targetIds.size() > 0) + { + info.setRelationshipTargetIds(targetIds); + } + } + } + + // -------------------------------------------------------- + + private void checkRepositoryId(String repositoryId) + { + if (!connector.getRepositoryId().equals(repositoryId)) + { + throw new CmisObjectNotFoundException("Unknown repository '" + repositoryId + "'!"); + } + } + + private Charset getEncoding(File tempFile, String mimeType) + { + Charset encoding = null; + + try + { + InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile)); + ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); + encoding = charsetFinder.getCharset(tfis, mimeType); + tfis.close(); + } catch (Exception e) + { + throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); + } + + return encoding; + } + + private File copyToTempFile(ContentStream contentStream) + { + File tempFile = null; + + if (contentStream == null) + { + return tempFile; + } + + int bufferSize = 40 * 1014; + long count = 0; + + try + { + tempFile = TempFileProvider.createTempFile("cmis", "content"); + if (contentStream.getStream() != null) + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); + InputStream in = new BufferedInputStream(contentStream.getStream(), bufferSize); + + byte[] buffer = new byte[bufferSize]; + int i; + while ((i = in.read(buffer)) > -1) + { + out.write(buffer, 0, i); + count += i; + } + + in.close(); + out.close(); + } + } + catch (Exception e) + { + removeTempFile(tempFile); + throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); + } + + if (contentStream.getLength() > -1 && contentStream.getLength() != count) + { + removeTempFile(tempFile); + throw new CmisStorageException( + "Expected " + contentStream.getLength() + " bytes but retrieved " + count + "bytes!"); + } + + return tempFile; + } + + private void removeTempFile(File tempFile) + { + if (tempFile == null) + { + return; + } + + try + { + tempFile.delete(); + } + catch (Exception e) + { + // ignore - file will be removed by TempFileProvider + } + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java new file mode 100644 index 0000000000..f91413c4a1 --- /dev/null +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco 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 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Interceptor to manage threads and perform other menial jobs that are common to all + * calls made to the service. It also provides detailed logging of values passing + * in and out of the service. + *

+ * DEBUG shows authentication and inbound arguments. TRACE shows full + * return results as well. + * + * @author Derek Hulley + * @since 4.0 + */ +public class AlfrescoCmisServiceInterceptor implements MethodInterceptor +{ + private static Log logger = LogFactory.getLog(AlfrescoCmisServiceInterceptor.class); + + public AlfrescoCmisServiceInterceptor() + { + } + + @Override + public synchronized Object invoke(MethodInvocation invocation) throws Throwable + { + // Keep note of whether debug is required + boolean debug = logger.isDebugEnabled(); + boolean trace = logger.isTraceEnabled(); + StringBuilder sb = null; + if (debug) + { + sb = new StringBuilder("\n" + + "CMIS invocation: \n" + + " Method: " + invocation.getMethod().getName() + "\n" + + " Arguments: \n"); + for (Object arg : invocation.getArguments()) + { + sb.append(" ").append(arg).append("\n"); + } + } + + Object ret = null; + AlfrescoCmisService service = (AlfrescoCmisService) invocation.getThis(); + try + { + // Wrap with pre- and post-method calls + try + { + sb.append( + " Pre-call authentication: \n" + + " Full auth: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" + + " Effective auth: " + AuthenticationUtil.getRunAsUser() + "\n"); + service.beforeCall(); + sb.append( + " In-call authentication: \n" + + " Full auth: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" + + " Effective auth: " + AuthenticationUtil.getRunAsUser() + "\n"); + ret = invocation.proceed(); + } + finally + { + service.afterCall(); + sb.append( + " Post-call authentication: \n" + + " Full auth: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" + + " Effective auth: " + AuthenticationUtil.getRunAsUser() + "\n"); + } + if (trace) + { + sb.append( + " Returning: ").append(ret).append("\n"); + logger.debug(sb); + } + // Done + return ret; + } + catch (Throwable e) + { + if (debug) + { + sb.append(" Throwing: " + e.getMessage()); + logger.debug(sb, e); + } + // Rethrow + throw e; + } + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java index adc7d02c8c..65cfff63b0 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java +++ b/source/java/org/alfresco/opencmis/AlfrescoLocalCmisServiceFactory.java @@ -61,14 +61,11 @@ public class AlfrescoLocalCmisServiceFactory extends AbstractServiceFactory CmisServiceWrapper wrapperService = THREAD_LOCAL_SERVICE.get(); if (wrapperService == null) { - wrapperService = new CmisServiceWrapper(new AlfrescoCmisService(CMIS_CONNECTOR), + wrapperService = new CmisServiceWrapper(new AlfrescoCmisServiceImpl(CMIS_CONNECTOR), CMIS_CONNECTOR.getTypesDefaultMaxItems(), CMIS_CONNECTOR.getTypesDefaultDepth(), CMIS_CONNECTOR.getObjectsDefaultMaxItems(), CMIS_CONNECTOR.getObjectsDefaultDepth()); THREAD_LOCAL_SERVICE.set(wrapperService); } - - wrapperService.getWrappedService().beginCall(context); - return wrapperService; } } diff --git a/source/java/org/alfresco/opencmis/BaseCMISTest.java b/source/java/org/alfresco/opencmis/BaseCMISTest.java index 6d2f0ff796..a0146dbd99 100644 --- a/source/java/org/alfresco/opencmis/BaseCMISTest.java +++ b/source/java/org/alfresco/opencmis/BaseCMISTest.java @@ -25,9 +25,6 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; -import org.alfresco.cmis.CMISAccessControlService; -import org.alfresco.cmis.CMISRenditionService; -import org.alfresco.cmis.CMISServices; import org.alfresco.opencmis.dictionary.CMISDictionaryService; import org.alfresco.opencmis.mapping.CMISMapping; import org.alfresco.opencmis.search.CMISQueryService; diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index d240b0c045..19de2b6d9f 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -174,6 +174,7 @@ import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl; +import org.apache.chemistry.opencmis.commons.server.CmisService; import org.apache.chemistry.opencmis.commons.spi.Holder; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; @@ -185,11 +186,14 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; /** * Bridge connecting Alfresco and OpenCMIS. + *

+ * This class provides many of the typical services that the {@link CmisService} implementation + * will need to use Alfresco. * * @author florian.mueller + * @author Derek Hulley */ -public class CMISConnector implements ApplicationContextAware, ApplicationListener, - TenantDeployer +public class CMISConnector implements ApplicationContextAware, ApplicationListener, TenantDeployer { public static final char ID_SEPERATOR = ';'; public static final String ASSOC_ID_PREFIX = "assoc:"; @@ -565,9 +569,14 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen return dictionaryService; } + /** + * Not implemented + * @throws UnsupportedOperationException always + */ public void setProxyUser(String proxyUser) { - this.proxyUser = proxyUser; +// this.proxyUser = proxyUser; + throw new UnsupportedOperationException("proxyUser setting not implemented. Please raise a JIRA request."); } public String getProxyUser() diff --git a/source/java/org/alfresco/opencmis/OpenCmisLocalTest.java b/source/java/org/alfresco/opencmis/OpenCmisLocalTest.java new file mode 100644 index 0000000000..facd94b223 --- /dev/null +++ b/source/java/org/alfresco/opencmis/OpenCmisLocalTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco 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 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; +import org.alfresco.util.TempFileProvider; +import org.apache.chemistry.opencmis.client.api.Folder; +import org.apache.chemistry.opencmis.client.api.Repository; +import org.apache.chemistry.opencmis.client.api.Session; +import org.apache.chemistry.opencmis.client.api.SessionFactory; +import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.SessionParameter; +import org.apache.chemistry.opencmis.commons.enums.BindingType; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; +import org.apache.chemistry.opencmis.commons.server.CmisServiceFactory; +import org.springframework.context.ApplicationContext; + +/** + * Tests basic local CMIS interaction + * + * @author Derek Hulley + * @since 4.0 + */ +public class OpenCmisLocalTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + /** + * Test class to provide the service factory + * + * @author Derek Hulley + * @since 4.0 + */ + public static class TestCmisServiceFactory implements CmisServiceFactory + { + private static AlfrescoCmisServiceFactory serviceFactory = (AlfrescoCmisServiceFactory) ctx.getBean("CMISServiceFactory"); + @Override + public void init(Map parameters) + { + } + + @Override + public void destroy() + { + } + + @Override + public CmisService getService(CallContext context) + { + return serviceFactory.getService(context); + } + + } + + private Repository getRepository(String user, String password) + { + // default factory implementation + SessionFactory sessionFactory = SessionFactoryImpl.newInstance(); + Map parameters = new HashMap(); + + // user credentials + parameters.put(SessionParameter.USER, "admin"); + parameters.put(SessionParameter.PASSWORD, "admin"); + + // connection settings + parameters.put(SessionParameter.BINDING_TYPE, BindingType.LOCAL.value()); + parameters.put(SessionParameter.LOCAL_FACTORY, "org.alfresco.opencmis.OpenCmisLocalTest$TestCmisServiceFactory"); + + // create session + List repositories = sessionFactory.getRepositories(parameters); + return repositories.size() > 0 ? repositories.get(0) : null; + } + + public void setUp() throws Exception + { + } + + public void testSetUp() throws Exception + { + Repository repository = getRepository("admin", "admin"); + assertNotNull("No repository available for testing", repository); + } + + public void testBasicFileOps() + { + Repository repository = getRepository("admin", "admin"); + Session session = repository.createSession(); + Folder rootFolder = session.getRootFolder(); + // create folder + Map folderProps = new HashMap(); + { + folderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder"); + folderProps.put(PropertyIds.NAME, getName() + "-" + GUID.generate()); + } + Folder folder = rootFolder.createFolder(folderProps, null, null, null, session.getDefaultContext()); + + Map fileProps = new HashMap(); + { + fileProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); + fileProps.put(PropertyIds.NAME, "mydoc-" + GUID.generate() + ".txt"); + } + ContentStreamImpl fileContent = new ContentStreamImpl(); + { + ContentWriter writer = new FileContentWriter(TempFileProvider.createTempFile(getName(), ".txt")); + writer.putContent("Ipsum and so on"); + ContentReader reader = writer.getReader(); + fileContent.setMimeType(MimetypeMap.MIMETYPE_TEXT_PLAIN); + fileContent.setStream(reader.getContentInputStream()); + } + folder.createDocument(fileProps, fileContent, VersioningState.MAJOR); + } +}