/*
 * #%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 .
 * #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 nodeInfoMap;
    private Map 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 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 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 list = new ArrayList();
        result.setList(list);
        // get the types from the dictionary
        List 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 getTypeDescendants(
            String repositoryId, String typeId, BigInteger depth,
            Boolean includePropertyDefinitions, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        List result = new ArrayList();
        // check depth
        int d = (depth == null ? -1 : depth.intValue());
        if (d == 0)
        {
            throw new CmisInvalidArgumentException("Depth must not be 0!");
        }
        if (typeId == null)
        {
            for (TypeDefinitionWrapper tdw : connector.getOpenCMISDictionaryService().getBaseTypes(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 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 children = connector.getOpenCMISDictionaryService().getChildren(typeId);
            if (children != null)
            {
                result.setChildren(new ArrayList());
                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 list = new ArrayList();
        result.setObjects(list);
        // get the children references
        NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef();
        // convert orderBy to sortProps
        List> sortProps = null;
        if (orderBy != null)
        {
            sortProps = new ArrayList>(1);
            String[] parts = orderBy.split(",");
            int len = parts.length;
            final int origLen = len;
            if (origLen > 0)
            {
                int maxSortProps = GetChildrenCannedQuery.MAX_FILTER_SORT_PROPS;
                if (len > maxSortProps)
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug(
                                "Too many sort properties in 'orderBy' - ignore those above max (max="
                                + maxSortProps + ",actual=" + len + ")");
                    }
                    len = maxSortProps;
                }
                for (int i = 0; i < len; i++)
                {
                    String[] sort = parts[i].split(" +");
                    
                    if (sort.length > 0)
                    {
                        // 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 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 typeqnames = new HashSet();
        List allTypes = connector.getOpenCMISDictionaryService().getAllTypes();
        for (TypeDefinitionWrapper type : allTypes)
        {
        	typeqnames.add(type.getAlfrescoClass());
        }
        PagingResults 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 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 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 result = new ArrayList();
        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 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 result = new ArrayList();
        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 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 list)
    {
        // get the children references
        List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef);
        for (ChildAssociationRef child : childrenList)
        {
            try
            {
                TypeDefinitionWrapper type = connector.getType(child.getChildRef());
                if (type == null)
                {
                    continue;
                }
                boolean isFolder = (type instanceof FolderTypeDefintionWrapper);
                if (foldersOnly && !isFolder)
                {
                    continue;
                }
                
                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());
                    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 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 getObjectParents(
            String repositoryId, String objectId, String filter,
            Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
            Boolean includeRelativePathSegment, ExtensionsData extension)
    {
        long start = System.currentTimeMillis();
        checkRepositoryId(repositoryId);
        List result = new ArrayList();
        // what kind of object is it?
        CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
        // relationships are not filed
        if (info.isRelationship())
        {
            throw new CmisConstraintException("Relationships are not fileable!");
        }
        if (info.isItem() || (info.isFolder() && !info.isRootFolder()))
        {
            List 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 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 nodeRefs;
        try
        {
            resultSet = connector.getSearchService().query(params);
            nodeRefs = resultSet.getNodeRefs();
        }
        finally
        {
            if (resultSet != null)
            {
                resultSet.close();
            }
        }
        // collect results
        ObjectListImpl result = new ObjectListImpl();
        List list = new ArrayList();
        result.setObjects(list);
        int skipCounter = skip;
        if (max > 0)
        {
            for (NodeRef nodeRef : nodeRefs)
            {
                // 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 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 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 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 props = new HashMap(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 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 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 policies,
            Acl addAces, Acl removeAces, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // get the parent folder
        getOrCreateFolderInfo(folderId, "Parent Folder");
        String objectTypeId = connector.getObjectTypeIdProperty(properties);
        connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_POLICY);
        // we should never get here - policies are not creatable!
        throw new CmisRuntimeException("Polcies cannot be created!");
    }
    @Override
    public String createRelationship(
            String repositoryId, Properties properties, List policies, Acl addAces,
            Acl removeAces, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // get type
        String objectTypeId = connector.getObjectTypeIdProperty(properties);
        final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_RELATIONSHIP);
        // get source object
        String sourceId = connector.getSourceIdProperty(properties);
        CMISNodeInfo sourceInfo = getOrCreateNodeInfo(sourceId, "Source");
        if (!sourceInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !sourceInfo.isVariant(CMISObjectVariant.FOLDER) && !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 objectId, Holder 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 objectId, Boolean overwriteFlag,
            Holder changeToken, final ContentStream contentStream, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
        if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC))
        {
            throw new CmisStreamNotSupportedException("Content can only be set ondocuments!");
        }
        final NodeRef nodeRef = info.getNodeRef();
        if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED)
        {
            throw new CmisStreamNotSupportedException("Document type doesn't allow content!");
        }
        boolean existed = connector.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT) != null;
        if (existed && !overwriteFlag)
        {
            throw new CmisContentAlreadyExistsException("Content already exists!");
        }
        if ((contentStream == null) || (contentStream.getStream() == null))
        {
            throw new CmisInvalidArgumentException("No content!");
        }
        // copy stream to temp file
        final File tempFile = copyToTempFile(contentStream);
        final Charset encoding = getEncoding(tempFile, contentStream.getMimeType());
        try
        {
            ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
            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 objectId, Holder changeToken,
            ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
        if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC))
        {
            throw new CmisStreamNotSupportedException("Content can only be deleted from ondocuments!");
        }
        final NodeRef nodeRef = info.getNodeRef();
        if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.REQUIRED)
        {
            throw new CmisInvalidArgumentException("Document type requires content!");
        }
        connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_CONTENT, null);
        
//        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 objectId, String targetFolderId, String sourceFolderId,
            ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // get object and source and target parent
        CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
        final NodeRef nodeRef = info.getCurrentNodeNodeRef();
        final CMISNodeInfo sourceInfo = getOrCreateFolderInfo(sourceFolderId, "Source Folder");
        final CMISNodeInfo targetInfo = getOrCreateFolderInfo(targetFolderId, "Target Folder");
        connector.checkChildObjectType(targetInfo, info.getType().getTypeId());
        ChildAssociationRef primaryParentRef = connector.getNodeService().getPrimaryParent(nodeRef);
        // if this is a primary child node, move it
        if (primaryParentRef.getParentRef().equals(sourceInfo.getNodeRef()))
        {
            connector.getNodeService().moveNode(
                    nodeRef, targetInfo.getNodeRef(),
                    primaryParentRef.getTypeQName(), primaryParentRef.getQName());
        }
        else
        {
            boolean found = false;
            // otherwise, reparent it
            for (ChildAssociationRef parent : connector.getNodeService().getParentAssocs(
                    nodeRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL))
            {
                if (parent.getParentRef().equals(sourceInfo.getNodeRef()))
                {
                    connector.getNodeService().removeChildAssociation(parent);
                    connector.getNodeService().addChild(
                            targetInfo.getNodeRef(), nodeRef,
                            ContentModel.ASSOC_CONTAINS, parent.getQName());
                    found = true;
                }
            }
            if (!found)
            {
                throw new IllegalArgumentException(
                        new CmisInvalidArgumentException(
                                "Document is not a child of the source folder that was specified!"));
            }
        }
    }
    @Override
    public void updateProperties(
            String repositoryId, Holder objectId, Holder changeToken,
            final Properties properties, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        final CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
        if (info.isVariant(CMISObjectVariant.ASSOC))
        {
            throw new CmisInvalidArgumentException("Relationship properties cannot be updated!");
        }
        else
        {
            if (info.isVariant(CMISObjectVariant.VERSION))
            {
                throw new CmisInvalidArgumentException("Document is not the latest version!");
            }
            final NodeRef nodeRef = info.getNodeRef();
            connector.setProperties(nodeRef, info.getType(), properties, new String[0]);
            
            objectId.setValue(connector.createObjectId(nodeRef));
        	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 ids = new ArrayList();
            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 bulkUpdateProperties(final String repositoryId,
            List objectIdAndChangeTokens, final Properties properties,
            final List addSecondaryTypeIds, final List 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
        {
            private final Iterator iterator;
            private final int size;
            private final int batchSize;
            private BulkUpdateContext context;
            public WorkProvider(List 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 getNextWork()
            {
                Collection results = new ArrayList(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 worker = new BatchProcessWorker()
        {
            @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 processor = new BatchProcessor(
                "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 addSecondaryTypeIds;
        private List removeSecondaryTypeIds;
        private boolean isObjectInfoRequired;
        BulkEntry(BulkUpdateContext bulkUpdateContext, BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken, Properties properties, List addSecondaryTypeIds,
                List 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 successes;
        
        BulkUpdateContext(int size)
        {
            this.successes = Collections.newSetFromMap(new ConcurrentHashMap());
        }
        
        void success(CMISNodeInfo info)
        {
            successes.add(info);
        }
        
        Set getSuccesses()
        {
            return successes;
        }
        List getChanges()
        {
            List changes = new ArrayList(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 getRenditions(String repositoryId, String objectId, String renditionFilter,
            BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // what kind of object is it?
        CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
        if (info.isVariant(CMISObjectVariant.ASSOC))
        {
            return Collections.emptyList();
        }
        else
        {
            return connector.getRenditions(info.getNodeRef(), renditionFilter, maxItems, skipCount);
        }
    }
    // --- versioning service ---
    @Override
    public void checkOut(
            String repositoryId, final Holder objectId, ExtensionsData extension,
            final Holder contentCopied)
    {
        checkRepositoryId(repositoryId);
        CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
        // Check for current version
        if (info.isVariant(CMISObjectVariant.CURRENT_VERSION))
        {
            // 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 objectId, final Boolean major,
            final Properties properties, final ContentStream contentStream, final String checkinComment,
            final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object");
        // only accept a PWC
        if (!info.isVariant(CMISObjectVariant.PWC))
        {
            throw new CmisVersioningException("Object is not a PWC!");
        }
        // get object
        final NodeRef nodeRef = info.getNodeRef();
        final TypeDefinitionWrapper type = info.getType();
        // copy stream to temp file
        final File tempFile = copyToTempFile(contentStream);
        final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType()));
        // check in
        // update PWC
        connector.setProperties(nodeRef, type, properties,
                new String[] { PropertyIds.OBJECT_TYPE_ID });
        connector.applyPolicies(nodeRef, type, policies);
        connector.applyACL(nodeRef, type, addAces, removeAces);
        // handle content
        if (contentStream != null)
        {
            // write content
            ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
            writer.setMimetype(parseMimeType(contentStream));
            writer.setEncoding(encoding.name());
            writer.putContent(tempFile);
        }
        // create version properties
        Map versionProperties = new HashMap(5);
        versionProperties.put(VersionModel.PROP_VERSION_TYPE, major ? VersionType.MAJOR
                : VersionType.MINOR);
        if (checkinComment != null)
        {
            versionProperties.put(VersionModel.PROP_DESCRIPTION, checkinComment);
        }
        // check in
        NodeRef newNodeRef = connector.getCheckOutCheckInService().checkin(nodeRef, versionProperties);
        connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), newNodeRef);
        objectId.setValue(connector.createObjectId(newNodeRef));
        
        removeTempFile(tempFile);
    }
    @Override
    public List getAllVersions(
            String repositoryId, String objectId, String versionSeriesId, String filter,
            Boolean includeAllowableActions, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        if (versionSeriesId == null && objectId != null)
        {
            // it's a browser binding call
            versionSeriesId = connector.getCurrentVersionId(objectId);
        }
        if (versionSeriesId == null)
        {
            throw new CmisInvalidArgumentException("Object Id or Object Series Id must be set!");
        }
        List result = new ArrayList();
        // what kind of object is it?
        CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series");
        // 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 changeLogToken, Boolean includeProperties,
            String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        return connector.getContentChanges(changeLogToken, maxItems);
    }
    @Override
    public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions,
            Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
            BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        if (searchAllVersions.booleanValue())
        {
            throw new CmisInvalidArgumentException("Search all version is not supported!");
        }
        return connector.query(
                statement, includeAllowableActions, includeRelationships, renditionFilter,
                maxItems, skipCount);
    }
    // --- relationship service ---
    @Override
    public ObjectList getObjectRelationships(
            String repositoryId, String objectId, Boolean includeSubRelationshipTypes,
            RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions,
            BigInteger maxItems, BigInteger skipCount, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // what kind of object is it?
        CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
        if (info.isVariant(CMISObjectVariant.ASSOC))
        {
            throw new CmisInvalidArgumentException("Object is a relationship!");
        }
        if (info.isVariant(CMISObjectVariant.VERSION))
        {
            throw new CmisInvalidArgumentException("Object is a document version!");
        }
        // check if the relationship base type is requested
        if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId))
        {
            boolean isrt = (includeSubRelationshipTypes == null ? false : includeSubRelationshipTypes.booleanValue());
            if (isrt)
            {
                // all relationships are a direct subtype of the base type in
                // Alfresco -> remove filter
                typeId = null;
            }
            else
            {
                // there are no relationships of the base type in Alfresco ->
                // return empty list
                ObjectListImpl result = new ObjectListImpl();
                result.setHasMoreItems(false);
                result.setNumItems(BigInteger.ZERO);
                result.setObjects(new ArrayList());
                return result;
            }
        }
        return connector.getObjectRelationships(
                info.getNodeRef(), relationshipDirection, typeId, filter, includeAllowableActions,
                maxItems, skipCount);
    }
    // --- policy service ---
    @Override
    public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // what kind of object is it?
        CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
        TypeDefinitionWrapper type = info.getType();
        if (type == null)
        {
            throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
        }
        connector.applyPolicies(info.getNodeRef(), type, Collections.singletonList(policyId));
    }
    @Override
    public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // what kind of object is it?
        CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object");
        TypeDefinitionWrapper type = info.getType();
        if (type == null)
        {
            throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
        }
        throw new CmisConstraintException("Object is not policy controllable!");
    }
    @Override
    public List getAppliedPolicies(
            String repositoryId, String objectId, String filter, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // what kind of object is it?
        getOrCreateNodeInfo(objectId, "Object");
        // policies are not supported -> return empty list
        return Collections.emptyList();
    }
    // --- ACL service ---
    @Override
    public Acl applyAcl(
            String repositoryId, String objectId, final Acl addAces, final Acl removeAces,
            AclPropagation aclPropagation, ExtensionsData extension)
    {
        checkRepositoryId(repositoryId);
        // 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 renditions = object.getRenditions();
            if (renditions != null && renditions.size() > 0)
            {
                List renditionInfos = new ArrayList();
                for (RenditionData rendition : renditions)
                {
                    RenditionInfoImpl renditionInfo = new RenditionInfoImpl();
                    renditionInfo.setId(rendition.getStreamId());
                    renditionInfo.setKind(rendition.getKind());
                    renditionInfo.setContentType(rendition.getMimeType());
                    renditionInfo.setTitle(rendition.getTitle());
                    renditionInfo.setLength(rendition.getBigLength());
                    renditionInfos.add(renditionInfo);
                }
                info.setRenditionInfos(renditionInfos);
            }
            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 relationships = object.getRelationships();
        if (relationships != null && relationships.size() > 0)
        {
            List sourceIds = new ArrayList();
            List targetIds = new ArrayList();
            for (ObjectData relationship : relationships)
            {
                String sourceId = getIdProperty(relationship, PropertyIds.SOURCE_ID);
                String targetId = getIdProperty(relationship, PropertyIds.TARGET_ID);
                if (object.getId().equals(sourceId))
                {
                    sourceIds.add(relationship.getId());
                }
                if (object.getId().equals(targetId))
                {
                    targetIds.add(relationship.getId());
                }
            }
            if (sourceIds.size() > 0)
            {
                info.setRelationshipSourceIds(sourceIds);
            }
            if (targetIds.size() > 0)
            {
                info.setRelationshipTargetIds(targetIds);
            }
        }
    }
    // --------------------------------------------------------
    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();
    }
}