Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * 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 <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.opencmis; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.math.BigInteger; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import net.sf.acegisecurity.Authentication; 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.batch.BatchProcessWorkProvider; import org.alfresco.repo.batch.BatchProcessor; import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker; import org.alfresco.repo.content.encoding.ContentCharsetFinder; import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.Authorization; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.version.VersionModel; 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.ContentIOException; 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.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.BulkUpdateObjectIdAndChangeToken; 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.CmisVersion; 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.BulkUpdateObjectIdAndChangeTokenImpl; 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 * @author janv * * @since 4.0 */ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements AlfrescoCmisService { private static Log logger = LogFactory.getLog(AlfrescoCmisService.class); 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 Authentication authentication; private Map<String, CMISNodeInfo> nodeInfoMap; private Map<String, ObjectInfo> objectInfoMap; public AlfrescoCmisServiceImpl(CMISConnector connector) { this.connector = connector; nodeInfoMap = new HashMap<>(); objectInfoMap = new HashMap<>(); } @Override public void open(CallContext context) { AlfrescoCmisServiceCall.set(context); } protected CallContext getContext() { CallContext context = AlfrescoCmisServiceCall.get(); return context; } @Override public void close() { AlfrescoCmisServiceCall.clear(); // Put these resources on the transactions nodeInfoMap.clear(); objectInfoMap.clear(); } protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef) { return createNodeInfo(nodeRef, null, true); } protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory, boolean checkExists) { return createNodeInfo(nodeRef, null, null, versionHistory, checkExists); } protected CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, QName nodeType, Map<QName, Serializable> nodeProps, VersionHistory versionHistory, boolean checkExists) { CMISNodeInfoImpl result = connector.createNodeInfo(nodeRef, nodeType, nodeProps, versionHistory, checkExists); 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<RepositoryInfo> getRepositoryInfos(ExtensionsData extension) { CmisVersion cmisVersion = getContext().getCmisVersion(); return Collections.singletonList(connector.getRepositoryInfo(cmisVersion)); } @Override public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) { checkRepositoryId(repositoryId); CmisVersion cmisVersion = getContext().getCmisVersion(); return connector.getRepositoryInfo(cmisVersion); } @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<TypeDefinition> list = new ArrayList<TypeDefinition>(); result.setList(list); // get the types from the dictionary List<TypeDefinitionWrapper> childrenList; if (typeId == null) { childrenList = connector.getOpenCMISDictionaryService().getBaseTypes(true); } else { TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); if (tdw == null) { throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); } childrenList = connector.getOpenCMISDictionaryService().getChildren(typeId); // 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<TypeDefinitionContainer> getTypeDescendants(String repositoryId, String typeId, BigInteger depth, Boolean includePropertyDefinitions, ExtensionsData extension) { checkRepositoryId(repositoryId); List<TypeDefinitionContainer> result = new ArrayList<TypeDefinitionContainer>(); // 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(true)) { result.add(getTypesDescendants(d, tdw, includePropertyDefinitions)); } } else { TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); if (tdw == null) { throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); } List<TypeDefinitionWrapper> children = connector.getOpenCMISDictionaryService().getChildren(typeId); if (children != null) { for (TypeDefinitionWrapper child : children) { 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) { String typeId = tdw.getTypeId(); List<TypeDefinitionWrapper> children = connector.getOpenCMISDictionaryService().getChildren(typeId); if (children != null) { result.setChildren(new ArrayList<TypeDefinitionContainer>()); for (TypeDefinitionWrapper tdc : children) { 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<ObjectInFolderData> list = new ArrayList<ObjectInFolderData>(); result.setObjects(list); // get the children references NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); // convert orderBy to sortProps List<Pair<QName, Boolean>> sortProps = null; if (orderBy != null) { sortProps = new ArrayList<Pair<QName, Boolean>>(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) { // MNT-10529: Leading spaces result in an empty string value after the split operation. Leading spaces may occur in the 'orderBy' statement when the // elements of statement separated by a comma and a space(s) int index = (sort[0].isEmpty()) ? (1) : (0); Pair<QName, Boolean> sortProp = connector.getSortProperty(sort[index], sort.length > (index + 1) ? sort[index + 1] : null); sortProps.add(sortProp); } } } 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 Set<QName> typeqnames = new HashSet<QName>(); List<TypeDefinitionWrapper> allTypes = connector.getOpenCMISDictionaryService().getAllTypes(); for (TypeDefinitionWrapper type : allTypes) { typeqnames.add(type.getAlfrescoClass()); } PagingResults<FileInfo> pageOfNodeInfos = connector.getFileFolderService().list(folderNodeRef, typeqnames, null, //ignoreAspectQNames, sortProps, pageRequest); if (max > 0) { for (FileInfo child : pageOfNodeInfos.getPage()) { try { // TODO this will break the paging if filtering is performed... if (connector.filter(child.getNodeRef())) { continue; } // create a child CMIS object CMISNodeInfo ni = createNodeInfo(child.getNodeRef(), child.getType(), child.getProperties(), null, false); // note: checkExists=false (don't need to check again) // ignore invalid children // Skip non-cmis objects if (ni.getObjectVariant() == CMISObjectVariant.INVALID_ID || ni.getObjectVariant() == CMISObjectVariant.NOT_EXISTING || ni.getObjectVariant() == CMISObjectVariant.NOT_A_CMIS_OBJECT || ni.getObjectVariant() == CMISObjectVariant.PERMISSION_DENIED) { continue; } ObjectData object = connector.createCMISObject(ni, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false/*, getContext().getCmisVersion()*/); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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 } catch (CmisObjectNotFoundException e) { // ignore objects that have not been found (perhaps because their type is unknown to CMIS) } } } // has more ? result.setHasMoreItems(pageOfNodeInfos.hasMoreItems()); // total count ? Pair<Integer, Integer> 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)); } } logGetObjectsCall("getChildren", start, folderId, list.size(), filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, extension, skipCount, maxItems, orderBy, null); return result; } @Override public List<ObjectInFolderContainer> getDescendants(String repositoryId, String folderId, BigInteger depth, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePathSegment, ExtensionsData extension) { long start = System.currentTimeMillis(); checkRepositoryId(repositoryId); List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>(); getDescendantsTree(repositoryId, getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), depth.intValue(), filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, false, result); logGetObjectsCall("getDescendants", start, folderId, countDescendantsTree(result), filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, extension, null, null, null, depth); return result; } @Override public List<ObjectInFolderContainer> getFolderTree(String repositoryId, String folderId, BigInteger depth, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePathSegment, ExtensionsData extension) { long start = System.currentTimeMillis(); checkRepositoryId(repositoryId); List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>(); getDescendantsTree(repositoryId, getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), depth.intValue(), filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, true, result); logGetObjectsCall("getFolderTree", start, folderId, countDescendantsTree(result), filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, extension, null, null, null, depth); return result; } protected void logGetObjectsCall(String methodName, long start, String folderId, int itemCount, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePathSegment, ExtensionsData extensionsData, BigInteger skipCount, BigInteger maxItems, String orderBy, BigInteger depth) { if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append(methodName).append(": ").append(folderId).append(" - ").append(itemCount).append(" in ") .append(System.currentTimeMillis() - start).append(" ms").append(" [includeAllowableActions=") .append(includeAllowableActions).append(",includeRelationships=").append(includeRelationships) .append(",renditionFilter=").append(renditionFilter); if (includePathSegment != null) { sb.append((methodName.equals("getObjectParents") ? ",includeRelativePathSegment=" : ",includePathSegment=")).append(includePathSegment); } if (filter != null) { sb.append(",filter=").append(filter); } if (skipCount != null) { sb.append(",skipCount=").append(skipCount); } if (maxItems != null) { sb.append(",maxItems=").append(maxItems); } if (orderBy != null) { sb.append(",orderBy=").append(orderBy); } if (depth != null) { sb.append(",depth=").append(depth); } if (extensionsData != null) { sb.append(",extensionsData=").append(extensionsData); } sb.append("]"); logger.debug(sb.toString()); } else if (logger.isInfoEnabled()) { StringBuilder sb = new StringBuilder(); sb.append(methodName).append(": ").append(folderId).append(" - ").append(itemCount).append(" in ") .append(System.currentTimeMillis() - start).append(" ms"); logger.info(sb.toString()); } } private int countDescendantsTree(List<ObjectInFolderContainer> tree) { int c = tree.size(); for (ObjectInFolderContainer o : tree) { c += countDescendantsTree(o.getChildren()); } return c; } protected void logGetObjectCall(String methodName, long start, String idOrPath, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, Boolean isObjectInfoRequired, ExtensionsData extensionsData) { if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append(methodName).append(": ").append(idOrPath).append(" in ") .append(System.currentTimeMillis() - start).append(" ms").append(" [includeAllowableActions=") .append(includeAllowableActions).append(",includeRelationships=").append(includeRelationships) .append(",renditionFilter=").append(renditionFilter).append(",includePolicyIds=") .append(includePolicyIds).append(",includeAcl=").append(includeAcl) .append(",isObjectInfoRequired=").append(isObjectInfoRequired); if (filter != null) { sb.append(",filter=").append(filter); } if (extensionsData != null) { sb.append(",extensionsData=").append(extensionsData); } sb.append("]"); logger.debug(sb.toString()); } else if (logger.isInfoEnabled()) { StringBuilder sb = new StringBuilder(); sb.append(methodName).append(": ").append(idOrPath).append(" in ") .append(System.currentTimeMillis() - start).append(" ms"); logger.info(sb.toString()); } } private void getDescendantsTree(String repositoryId, NodeRef folderNodeRef, int depth, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePathSegment, boolean foldersOnly, List<ObjectInFolderContainer> list) { // get the children references List<ChildAssociationRef> 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; } if (isFolder && type.getAlfrescoClass().equals(ContentModel.TYPE_SYSTEM_FOLDER)) { continue; } if (connector.isHidden(child.getChildRef())) { continue; } if (connector.filter(child.getChildRef())) { continue; } boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); // create a child CMIS object ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); CMISNodeInfo ni = createNodeInfo(child.getChildRef(), null, false); // note: checkExists=false (don't need to check again) object.setObject(connector.createCMISObject(ni, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false)); if (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<ObjectInFolderContainer>()); 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 } catch (CmisObjectNotFoundException e) { // ignore objects that have not been found (perhaps because their type is unknown to CMIS) } } } @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<CMISNodeInfo> 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); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, parentInfo.getObjectId(), IncludeRelationships.NONE); } return result; } @Override public List<ObjectParentData> getObjectParents(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includeRelativePathSegment, ExtensionsData extension) { long start = System.currentTimeMillis(); checkRepositoryId(repositoryId); List<ObjectParentData> result = new ArrayList<ObjectParentData>(); // 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.isItem() || (info.isFolder() && !info.isRootFolder())) { List<CMISNodeInfo> parentInfos = info.getParents(); if (!parentInfos.isEmpty()) { for (CMISNodeInfo parent : parentInfos) { CMISNodeInfo parentInfo = addNodeInfo(parent); ObjectData object = connector.createCMISObject(parentInfo, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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<CMISNodeInfo> parentInfos = info.getParents(); for (CMISNodeInfo parentInfo : parentInfos) { addNodeInfo(parentInfo); ObjectData object = connector.createCMISObject(parentInfo, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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); } } logGetObjectsCall("getObjectParents", start, objectId, result.size(), filter, includeAllowableActions, includeRelationships, renditionFilter, includeRelativePathSegment, extension, null, null, null, null); 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) { 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()); // prepare query SearchParameters params = new SearchParameters(); params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); if (folderId == null) { params.setQuery("+=cm:workingCopyOwner:\"" + AuthenticationUtil.getFullyAuthenticatedUser() + "\""); params.addStore(connector.getRootStoreRef()); } else { CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); params.setQuery("+=cm:workingCopyOwner:\"" + AuthenticationUtil.getFullyAuthenticatedUser() + "\" AND +=PARENT:\"" + folderInfo.getNodeRef().toString() + "\""); params.addStore(folderInfo.getNodeRef().getStoreRef()); } // 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<NodeRef> nodeRefs; try { resultSet = connector.getSearchService().query(params); nodeRefs = resultSet.getNodeRefs(); } finally { if (resultSet != null) { resultSet.close(); } } // collect results ObjectListImpl result = new ObjectListImpl(); List<ObjectData> list = new ArrayList<ObjectData>(); result.setObjects(list); int skipCounter = skip; if (max > 0) { for (NodeRef nodeRef : nodeRefs) { // TODO - perhaps filter by path in the query instead? if (connector.filter(nodeRef)) { continue; } 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); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); } // add it list.add(object); } catch (InvalidNodeRefException e) { // ignore invalid objects } catch (CmisObjectNotFoundException e) { // ignore objects that have not been found (perhaps because their type is unknown to CMIS) } } } // has more ? result.setHasMoreItems(nodeRefs.size() - skip > list.size()); logGetObjectsCall("getCheckedOutDocs", start, folderId, list.size(), filter, includeAllowableActions, includeRelationships, renditionFilter, null, extension, skipCount, maxItems, orderBy, null); return result; } // --- object service --- @Override public String create(String repositoryId, Properties properties, String folderId, ContentStream contentStream, VersioningState versioningState, List<String> 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; case CMIS_ITEM: newId = createItem(repositoryId, properties, folderId, policies, null, null, extension); } // check new object id if (newId == null) { throw new CmisRuntimeException("Creation failed!"); } boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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<String> 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 FileInfo fileInfo = connector.getFileFolderService().create(parentInfo.getNodeRef(), name, type.getAlfrescoClass()); NodeRef nodeRef = fileInfo.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.getActivityPoster().postFileFolderAdded(nodeRef); String objectId = connector.createObjectId(nodeRef); return objectId; } @Override public String createItem(String repositoryId, Properties properties, String folderId, List<String> policies, Acl addAces, 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_ITEM); connector.checkChildObjectType(parentInfo, type.getTypeId()); /** * The above code specifies a folder - so the contents of a folder (as defined by the alfresco model) are * ASSOC cm:contains to a TYPE sys:base */ QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)); Map<QName, Serializable> props = new HashMap<QName, Serializable>(11); props.put(ContentModel.PROP_NAME, (Serializable) assocQName.getLocalName()); ChildAssociationRef newRef = connector.getNodeService().createNode(parentInfo.getNodeRef(), ContentModel.ASSOC_CONTAINS, assocQName, type.getAlfrescoClass(), props); NodeRef nodeRef = newRef.getChildRef(); connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_NAME, assocQName.getLocalName()); connector.applyPolicies(nodeRef, type, policies); connector.applyACL(nodeRef, type, addAces, removeAces); String objectId = connector.createObjectId(nodeRef); return objectId; } private String parseMimeType(ContentStream contentStream) { String mimeType = null; String tmp = contentStream.getMimeType(); if (tmp != null) { int idx = tmp.indexOf(";"); if (idx != -1) { mimeType = tmp.substring(0, idx).trim(); } else { mimeType = tmp; } } return mimeType; } @Override public String createDocument(String repositoryId, final Properties properties, String folderId, final ContentStream contentStream, final VersioningState versioningState, final List<String> 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!"); } FileInfo fileInfo = connector.getFileFolderService().create(parentInfo.getNodeRef(), name, type.getAlfrescoClass()); NodeRef nodeRef = fileInfo.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 File tempFile = null; try { if (contentStream != null) { // write content String mimeType = parseMimeType(contentStream); // copy stream to temp file // OpenCMIS does this for us .... tempFile = copyToTempFile(contentStream); final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); writer.setMimetype(mimeType); writer.setEncoding(encoding.name()); writer.putContent(tempFile); } } finally { if (tempFile != null) { removeTempFile(tempFile); } } connector.extractMetadata(nodeRef); // generate "doclib" thumbnail asynchronously connector.createThumbnails(nodeRef, Collections.singleton("doclib")); connector.applyVersioningState(nodeRef, versioningState); removeTempFile(tempFile); String objectId = connector.createObjectId(nodeRef); connector.getActivityPoster().postFileFolderAdded(nodeRef); return objectId; } @Override public String createDocumentFromSource(String repositoryId, String sourceId, final Properties properties, String folderId, final VersioningState versioningState, final List<String> 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 { FileInfo fileInfo = connector.getFileFolderService().copy(sourceNodeRef, parentInfo.getNodeRef(), name); NodeRef nodeRef = fileInfo.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.extractMetadata(nodeRef); connector.createThumbnails(nodeRef, Collections.singleton("doclib")); connector.applyVersioningState(nodeRef, versioningState); connector.getActivityPoster().postFileFolderAdded(nodeRef); 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<String> 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<String> 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) && !sourceInfo.isVariant(CMISObjectVariant.ITEM)) { throw new CmisInvalidArgumentException( "Source is not the latest version of a document, a folder or an item 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) && !targetInfo.isVariant(CMISObjectVariant.ITEM)) { throw new CmisInvalidArgumentException( "Target is not the latest version of a document, a folder or an item 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 // ALF-10085 : disable auditing behaviour for this use case boolean wasEnabled = connector.disableBehaviour(ContentModel.ASPECT_AUDITABLE, sourceNodeRef); // Lasts for txn try { AssociationRef assocRef = connector.getNodeService().createAssociation(sourceNodeRef, targetNodeRef, type.getAlfrescoClass()); return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId(); } finally { if (wasEnabled) { connector.enableBehaviour(ContentModel.ASPECT_AUDITABLE, sourceNodeRef); } } } @Override public void appendContentStream(String repositoryId, Holder<String> objectId, Holder<String> changeToken, ContentStream contentStream, boolean isLastChunk, ExtensionsData extension) { if ((contentStream == null) || (contentStream.getStream() == null)) { throw new CmisInvalidArgumentException("No content!"); } checkRepositoryId(repositoryId); CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); NodeRef nodeRef = info.getNodeRef(); if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)) .getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) { throw new CmisStreamNotSupportedException("Document type doesn't allow content!"); } try { connector.appendContent(info, contentStream, isLastChunk); objectId.setValue(connector.createObjectId(nodeRef)); } catch (IOException e) { throw new ContentIOException("", e); } } @Override public void setContentStream(String repositoryId, Holder<String> objectId, Boolean overwriteFlag, Holder<String> 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); String mimeType = parseMimeType(contentStream); writer.setMimetype(mimeType); writer.setEncoding(encoding.name()); writer.putContent(tempFile); } finally { removeTempFile(tempFile); } objectId.setValue(connector.createObjectId(nodeRef)); connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef); } @Override public void deleteContentStream(String repositoryId, Holder<String> objectId, Holder<String> 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); // connector.createVersion(nodeRef, VersionType.MINOR, "Delete content"); connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef); objectId.setValue(connector.createObjectId(nodeRef)); } @Override public void moveObject(String repositoryId, Holder<String> 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<String> objectId, Holder<String> 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)); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, objectId.getValue(), "*", IncludeRelationships.NONE); } connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef); } } @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()) { // Check if there is at least one child if (connector.getNodeService() .getChildAssocs(nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL, 1, false) .size() > 0) { throw new CmisConstraintException("Could not delete folder with at least one child!"); } connector.deleteNode(nodeRef, true); break; // Reason for do-while } if (info.hasPWC()) { // is a checked out document. If a delete, don't allow unless checkout is canceled. If a cancel // checkout, not allowed. throw new CmisConstraintException( "Could not delete/cancel checkout on the original checked out document"); } // 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(); } // attempt to delete the node if (allVersions) { connector.deleteNode(nodeRef, true); } else { CMISNodeInfoImpl infoImpl = ((CMISNodeInfoImpl) info); Version version = infoImpl.getVersion(); if (infoImpl.getVersionHistory().getPredecessor(version) == null) { connector.deleteNode(nodeRef, true); } else { connector.getVersionService().deleteVersion(nodeRef, version); // MNT-10032 revert node version to predecessor connector.getVersionService().revert(nodeRef); } } } 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(); try { connector.deleteNode(folderNodeRef, true); } catch (Exception e) { List<String> ids = new ArrayList<String>(); ids.add(folderId); result.setIds(ids); } return result; } @Override public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) { long start = System.currentTimeMillis(); 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); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); } logGetObjectCall("getObject", start, objectId, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl, isObjectInfoRequired, extension); return object; } @Override public List<BulkUpdateObjectIdAndChangeToken> bulkUpdateProperties(final String repositoryId, List<BulkUpdateObjectIdAndChangeToken> objectIdAndChangeTokens, final Properties properties, final List<String> addSecondaryTypeIds, final List<String> removeSecondaryTypeIds, ExtensionsData extension) { checkRepositoryId(repositoryId); if (objectIdAndChangeTokens.size() > connector.getBulkMaxItems()) { throw new CmisConstraintException( "Bulk update not supported for more than " + connector.getBulkMaxItems() + " objects."); } // MNT-16376 We need the CMIS call context from this thread to set // to the working threads below, in order to correctly identify // the CMIS version protocol used for this CMIS call final CallContext cmisCallContext = AlfrescoCmisServiceCall.get(); // WorkProvider class WorkProvider implements BatchProcessWorkProvider<BulkEntry> { private final Iterator<BulkUpdateObjectIdAndChangeToken> iterator; private final int size; private final int batchSize; private BulkUpdateContext context; public WorkProvider(List<BulkUpdateObjectIdAndChangeToken> objectIdAndChangeTokens, BulkUpdateContext context, int batchSize) { this.iterator = objectIdAndChangeTokens.iterator(); this.size = objectIdAndChangeTokens.size(); this.context = context; this.batchSize = batchSize; } @Override public synchronized int getTotalEstimatedWorkSize() { return size; } @Override public synchronized Collection<BulkEntry> getNextWork() { Collection<BulkEntry> results = new ArrayList<BulkEntry>(batchSize); while (results.size() < batchSize && iterator.hasNext()) { results.add(new BulkEntry(context, iterator.next(), properties, addSecondaryTypeIds, removeSecondaryTypeIds, getContext().isObjectInfoRequired())); } return results; } } BulkUpdateContext context = new BulkUpdateContext(objectIdAndChangeTokens.size()); RetryingTransactionHelper helper = connector.getRetryingTransactionHelper(); final String runAsUser = AuthenticationUtil.getRunAsUser(); // Worker BatchProcessWorker<BulkEntry> worker = new BatchProcessWorker<BulkEntry>() { @Override public void process(final BulkEntry entry) throws Throwable { entry.update(); } public String getIdentifier(BulkEntry entry) { return entry.getObjectIdAndChangeToken().getId(); } @Override public void beforeProcess() throws Throwable { // Authentication AuthenticationUtil.pushAuthentication(); AuthenticationUtil.setFullyAuthenticatedUser(runAsUser); AlfrescoCmisServiceCall.set(cmisCallContext); } @Override public void afterProcess() throws Throwable { // Clear authentication AuthenticationUtil.popAuthentication(); } }; // Processor BatchProcessor<BulkEntry> processor = new BatchProcessor<BulkEntry>("CMISbulkUpdateProperties", helper, new WorkProvider(objectIdAndChangeTokens, context, connector.getBulkBatchSize()), connector.getBulkWorkerThreads(), connector.getBulkBatchSize(), null, logger, 100); processor.process(worker, true); for (CMISNodeInfo info : context.getSuccesses()) { NodeRef nodeRef = info.getNodeRef(); connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef); } return context.getChanges(); } private class BulkEntry { private String repositoryId; private BulkUpdateContext bulkUpdateContext; private BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken; private Properties properties; private List<String> addSecondaryTypeIds; private List<String> removeSecondaryTypeIds; private boolean isObjectInfoRequired; BulkEntry(BulkUpdateContext bulkUpdateContext, BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken, Properties properties, List<String> addSecondaryTypeIds, List<String> removeSecondaryTypeIds, boolean isObjectInfoRequired) { this.bulkUpdateContext = bulkUpdateContext; this.objectIdAndChangeToken = objectIdAndChangeToken; this.properties = properties; this.addSecondaryTypeIds = addSecondaryTypeIds; this.removeSecondaryTypeIds = removeSecondaryTypeIds; this.isObjectInfoRequired = isObjectInfoRequired; } public void update() { String objectId = objectIdAndChangeToken.getId(); final CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); if (!info.isVariant(CMISObjectVariant.ASSOC) && !info.isVariant(CMISObjectVariant.VERSION)) { final NodeRef nodeRef = info.getNodeRef(); connector.setProperties(nodeRef, info.getType(), properties, new String[0]); if (isObjectInfoRequired) { getObjectInfo(repositoryId, objectId, "*", IncludeRelationships.NONE); } connector.addSecondaryTypes(nodeRef, addSecondaryTypeIds); connector.removeSecondaryTypes(nodeRef, removeSecondaryTypeIds); if (properties.getProperties().size() > 0 || addSecondaryTypeIds.size() > 0 || removeSecondaryTypeIds.size() > 0) { bulkUpdateContext.success(info); } } }; public BulkUpdateObjectIdAndChangeToken getObjectIdAndChangeToken() { return objectIdAndChangeToken; } }; private static class BulkUpdateContext { private Set<CMISNodeInfo> successes; BulkUpdateContext(int size) { this.successes = Collections.newSetFromMap(new ConcurrentHashMap<CMISNodeInfo, Boolean>()); } void success(CMISNodeInfo info) { successes.add(info); } Set<CMISNodeInfo> getSuccesses() { return successes; } List<BulkUpdateObjectIdAndChangeToken> getChanges() { List<BulkUpdateObjectIdAndChangeToken> changes = new ArrayList<BulkUpdateObjectIdAndChangeToken>( successes.size()); for (CMISNodeInfo info : successes) { BulkUpdateObjectIdAndChangeTokenImpl a = new BulkUpdateObjectIdAndChangeTokenImpl(); a.setId(info.getObjectId()); // a.setNewId(info.getObjectId()); changes.add(a); } return changes; } } @Override public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) { long start = System.currentTimeMillis(); boolean isObjectInfoRequired = false; checkRepositoryId(repositoryId); // start at the root node NodeRef rootNodeRef = connector.getRootNodeRef(); ObjectData object; if (path.equals("/")) { object = 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("/"))); if (connector.filter(fileInfo.getNodeRef())) { throw new CmisObjectNotFoundException("Object not found: " + path); } CMISNodeInfo info = createNodeInfo(fileInfo.getNodeRef(), fileInfo.getType(), fileInfo.getProperties(), null, false); object = connector.createCMISObject(info, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl); isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); } } catch (FileNotFoundException e) { throw new CmisObjectNotFoundException("Object not found: " + path); } } logGetObjectCall("getObjectByPath", start, path, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl, isObjectInfoRequired, extension); return object; } @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"); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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<RenditionData> 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<String> objectId, ExtensionsData extension, final Holder<Boolean> contentCopied) { checkRepositoryId(repositoryId); CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); // Check for current version if (info.isVariant(CMISObjectVariant.CURRENT_VERSION)) { // Good } else if (info.isVariant(CMISObjectVariant.VERSION)) { throw new CmisInvalidArgumentException("Can't check out an old version of a document"); } else { throw new CmisInvalidArgumentException( "Only documents can be checked out! Object was a " + info.getObjectVariant().toString()); } // 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)) { NodeRef nodeRef = info.getNodeRef(); NodeRef workingCopyNodeRef = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); info = getOrCreateNodeInfo(workingCopyNodeRef.getId()); 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<String> objectId, final Boolean major, final Properties properties, final ContentStream contentStream, final String checkinComment, final List<String> 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(parseMimeType(contentStream)); writer.setEncoding(encoding.name()); writer.putContent(tempFile); } // create version properties Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(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); connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), newNodeRef); objectId.setValue(connector.createObjectId(newNodeRef)); removeTempFile(tempFile); } @Override public List<ObjectData> 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<ObjectData> result = new ArrayList<ObjectData>(); // what kind of object is it? CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); // when webservices binding is used, objectId points to null and versionSeriesId points to original node instead of PWC // see MNT-13839 if (objectId == null) { info = getOrCreateNodeInfo(versionSeriesId); if (info.hasPWC()) { NodeRef nodeRef = info.getNodeRef(); info = getOrCreateNodeInfo( connector.getCheckOutCheckInService().getWorkingCopy(nodeRef).toString()); } } 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)); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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)); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, pwcInfo.getObjectId(), IncludeRelationships.NONE); } } // convert the version history for (Version version : versionHistory.getAllVersions()) { CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef(), versionHistory, true); // TODO do we need to check existence ? // MNT-9557 fix. Replace head version with current node info if (versionHistory.getHeadVersion().equals(version)) { versionInfo = createNodeInfo(nodeRef); } result.add(connector.createCMISObject(versionInfo, filter, includeAllowableActions, IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (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) { long start = System.currentTimeMillis(); 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); boolean isObjectInfoRequired = getContext().isObjectInfoRequired(); if (isObjectInfoRequired) { getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); } StringBuilder sb = new StringBuilder(); sb.append(objectId).append("-").append(versionSeriesId); logGetObjectCall("getObjectOfLatestVersion", start, sb.toString(), filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl, isObjectInfoRequired, extension); 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<String> 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<ObjectData>()); 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<ObjectData> 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); // We are spec compliant if we just let it through and the tck will not fail 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); // We are spec compliant if we just let it through and the tck will not fail 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.) */ @SuppressWarnings("unchecked") @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(getStringProperty(object, PropertyIds.NAME)); 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<RenditionData> renditions = object.getRenditions(); if (renditions != null && renditions.size() > 0) { List<RenditionInfo> renditionInfos = new ArrayList<RenditionInfo>(); 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); } else { info.setRenditionInfos(Collections.EMPTY_LIST); } // relationships setRelaionshipsToObjectInfo(object, info); // global settings info.setHasAcl(true); info.setSupportsDescendants(true); info.setSupportsFolderTree(true); } else if (ni.isItem()) { info.setHasAcl(true); info.setHasContent(false); } return info; } private void setRelaionshipsToObjectInfo(ObjectData object, ObjectInfoImpl info) { info.setRelationshipSourceIds(null); info.setRelationshipTargetIds(null); List<ObjectData> relationships = object.getRelationships(); if (relationships != null && relationships.size() > 0) { List<String> sourceIds = new ArrayList<String>(); List<String> targetIds = new ArrayList<String>(); 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); } } } // -------------------------------------------------------- protected 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) { if (contentStream == null) { return null; } File result = null; try { result = TempFileProvider.createTempFile(contentStream.getStream(), "cmis", "content"); } catch (Exception e) { throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); } if ((contentStream.getLength() > -1) && (result == null || contentStream.getLength() != result.length())) { removeTempFile(result); throw new CmisStorageException("Expected " + contentStream.getLength() + " bytes but retrieved " + (result == null ? -1 : result.length()) + " bytes!"); } return result; } private void removeTempFile(File tempFile) { if (tempFile == null) { return; } try { tempFile.delete(); } catch (Exception e) { // ignore - file will be removed by TempFileProvider } } @Override public void beforeCall() { AuthenticationUtil.pushAuthentication(); if (authentication != null) { // Use the previously-obtained authentication AuthenticationUtil.setFullAuthentication(authentication); } else { CallContext context = getContext(); 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 { // First check if we already are authenticated if (AuthenticationUtil.getFullyAuthenticatedUser() == null) { // 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 = getContext().getUsername(); // String password = getContext().getPassword(); // if (currentUser != null && currentUser.equals(connector.getProxyUser())) // { // if (user != null && user.length() > 0) // { // AuthenticationUtil.setFullyAuthenticatedUser(user); // } // } } } @Override public void afterCall() { AuthenticationUtil.popAuthentication(); } }