/*
 * Copyright (C) 2005-2014 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package org.alfresco.opencmis;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.events.types.ContentEvent;
import org.alfresco.events.types.ContentEventImpl;
import org.alfresco.events.types.ContentReadRangeEvent;
import org.alfresco.events.types.Event;
import org.alfresco.model.ContentModel;
import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo;
import org.alfresco.opencmis.dictionary.CMISActionEvaluator;
import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum;
import org.alfresco.opencmis.dictionary.CMISDictionaryService;
import org.alfresco.opencmis.dictionary.CMISNodeInfo;
import org.alfresco.opencmis.dictionary.CMISObjectVariant;
import org.alfresco.opencmis.dictionary.CMISPropertyAccessor;
import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper;
import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper;
import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper;
import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper;
import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper;
import org.alfresco.opencmis.mapping.DirectProperty;
import org.alfresco.opencmis.search.CMISQueryOptions;
import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode;
import org.alfresco.opencmis.search.CMISQueryService;
import org.alfresco.opencmis.search.CMISResultSet;
import org.alfresco.opencmis.search.CMISResultSetColumn;
import org.alfresco.opencmis.search.CMISResultSetRow;
import org.alfresco.repo.Client;
import org.alfresco.repo.Client.ClientType;
import org.alfresco.repo.action.executer.ContentMetadataExtracter;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.events.EventPreparator;
import org.alfresco.repo.events.EventPublisher;
import org.alfresco.repo.model.filefolder.GetChildrenCannedQuery;
import org.alfresco.repo.model.filefolder.HiddenAspect;
import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.security.permissions.PermissionReference;
import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl;
import org.alfresco.repo.security.permissions.impl.ModelDAO;
import org.alfresco.repo.tenant.TenantAdminService;
import org.alfresco.repo.tenant.TenantDeployer;
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
import org.alfresco.repo.thumbnail.ThumbnailHelper;
import org.alfresco.repo.thumbnail.ThumbnailRegistry;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.version.VersionBaseModel;
import org.alfresco.repo.version.VersionModel;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.audit.AuditQueryParameters;
import org.alfresco.service.cmr.audit.AuditService;
import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.InvalidAspectException;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.Path.ChildAssocElement;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.descriptor.Descriptor;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.FileFilterMode;
import org.alfresco.util.Pair;
import org.alfresco.util.TempFileProvider;
import org.apache.chemistry.opencmis.commons.BasicPermissions;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectList;
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.data.PropertyId;
import org.apache.chemistry.opencmis.commons.data.PropertyString;
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.PermissionDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
import org.apache.chemistry.opencmis.commons.enums.ChangeType;
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.PropertyType;
import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
import org.apache.chemistry.opencmis.commons.enums.Updatability;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.util.StringUtils;
/**
 * Bridge connecting Alfresco and OpenCMIS.
 * 
 * This class  provides many of the typical services that the {@link CmisService} implementation
 * will need to use Alfresco.
 * 
 * @author florian.mueller
 * @author Derek Hulley
 * @author steveglover
 */
public class CMISConnector implements ApplicationContextAware, ApplicationListener, TenantDeployer
{
    private static Log logger = LogFactory.getLog(CMISConnector.class);
    // mappings from cmis property names to their Alfresco property name counterparts (used by getChildren)
    private static Map SORT_PROPERTY_MAPPINGS = new HashMap();
    
    static
    {
    	SORT_PROPERTY_MAPPINGS.put(PropertyIds.LAST_MODIFICATION_DATE, ContentModel.PROP_MODIFIED);
    	SORT_PROPERTY_MAPPINGS.put(PropertyIds.CREATION_DATE, ContentModel.PROP_CREATED);
    }
    public static final char ID_SEPERATOR = ';';
    public static final String ASSOC_ID_PREFIX = "assoc:";
    public static final String PWC_VERSION_LABEL = "pwc";
    public static final String UNVERSIONED_VERSION_LABEL = "1.0";
    public static final String RENDITION_NONE = "cmis:none";
    public static final String CMIS_CHANGELOG_AUDIT_APPLICATION = "CMISChangeLog";
    public static final String ALFRESCO_EXTENSION_NAMESPACE = "http://www.alfresco.org";
    public static final String CMIS_NAMESPACE = "http://docs.oasis-open.org/ns/cmis/core/200908/";
    public static final String ASPECTS = "aspects";
    public static final String SET_ASPECTS = "setAspects";
    public static final String APPLIED_ASPECTS = "appliedAspects";
    public static final String ASPECTS_TO_ADD = "aspectsToAdd";
    public static final String ASPECTS_TO_REMOVE = "aspectsToRemove";
    public static final String PROPERTIES = "properties";
    private static final BigInteger TYPES_DEFAULT_MAX_ITEMS = BigInteger.valueOf(50);
    private static final BigInteger TYPES_DEFAULT_DEPTH = BigInteger.valueOf(-1);
    private static final BigInteger OBJECTS_DEFAULT_MAX_ITEMS = BigInteger.valueOf(200);
    private static final BigInteger OBJECTS_DEFAULT_DEPTH = BigInteger.valueOf(10);
    private static final String QUERY_NAME_OBJECT_ID = "cmis:objectId";
    private static final String QUERY_NAME_OBJECT_TYPE_ID = "cmis:objectTypeId";
    private static final String QUERY_NAME_BASE_TYPE_ID = "cmis:baseTypeId";
    private static final String CMIS_USER = "cmis:user";
	private static final BigInteger maxInt = BigInteger.valueOf(Integer.MAX_VALUE);
	private static final BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE);
	private static final BigInteger maxLong  = BigInteger.valueOf(Long.MAX_VALUE);
	private static final BigInteger minLong  = BigInteger.valueOf(Long.MIN_VALUE);
    // lifecycle
    private ProcessorLifecycle lifecycle = new ProcessorLifecycle();
    // Alfresco objects
    private DescriptorService descriptorService;
    private NodeService nodeService;
    private VersionService versionService;
    private CheckOutCheckInService checkOutCheckInService;
    private LockService lockService;
    private ContentService contentService;
    private RenditionService renditionService;
    private FileFolderService fileFolderService;
    private TenantAdminService tenantAdminService;
    private TransactionService transactionService;
    private AuthenticationService authenticationService;
    private PermissionService permissionService;
    private ModelDAO permissionModelDao;
    private CMISDictionaryService cmisDictionaryService;
    private CMISDictionaryService cmisDictionaryService11;
    private CMISQueryService cmisQueryService;
    private CMISQueryService cmisQueryService11;
    private MimetypeService mimetypeService;
    private AuditService auditService;
    private NamespaceService namespaceService;
    private SearchService searchService;
    private DictionaryService dictionaryService;
    private SiteService siteService;
    private ActionService actionService;
    private ThumbnailService thumbnailService;
    private ServiceRegistry serviceRegistry;
    private EventPublisher eventPublisher;
    private CmisActivityPoster activityPoster;
    private BehaviourFilter behaviourFilter;
    private HiddenAspect hiddenAspect;
    private StoreRef storeRef;
    private String rootPath;
    private Map> kindToRenditionNames;
    
    // note: cache is tenant-aware (if using TransctionalCache impl)
    
    private SimpleCache singletonCache; // eg. for cmisRootNodeRef, cmisRenditionMapping
    private final String KEY_CMIS_ROOT_NODEREF = "key.cmisRoot.noderef";
    private final String KEY_CMIS_RENDITION_MAPPING_NODEREF = "key.cmisRenditionMapping.noderef";
    
    private String proxyUser;
    private boolean openHttpSession = false;
    // OpenCMIS objects
    private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS;
    private BigInteger typesDefaultDepth = TYPES_DEFAULT_DEPTH;
    private BigInteger objectsDefaultMaxItems = OBJECTS_DEFAULT_MAX_ITEMS;
    private BigInteger objectsDefaultDepth = OBJECTS_DEFAULT_DEPTH;
    private List repositoryPermissions;
    private Map permissionMappings;
    private ObjectFilter objectFilter;
    // Bulk update properties 
    private int bulkMaxItems = 1000;
    private int bulkBatchSize = 20;
    private int bulkWorkerThreads = 2;
    
    // --------------------------------------------------------------
    // Configuration
    // --------------------------------------------------------------
    public void setObjectFilter(ObjectFilter objectFilter)
    {
		this.objectFilter = objectFilter;
	}
	/**
     * Sets the root store.
     * 
     * @param store
     *            store_type://store_id
     */
    public void setStore(String store)
    {
        this.storeRef = new StoreRef(store);
    }
    public void setSiteService(SiteService siteService)
    {
		this.siteService = siteService;
	}
	public void setActivityPoster(CmisActivityPoster activityPoster)
    {
		this.activityPoster = activityPoster;
	}
    
	public CmisActivityPoster getActivityPoster()
	{
		return activityPoster;
	}
	public void setHiddenAspect(HiddenAspect hiddenAspect)
    {
		this.hiddenAspect = hiddenAspect;
	}
    public boolean isHidden(NodeRef nodeRef)
    {
        final FileFilterMode.Client client = FileFilterMode.getClient();
    	return (hiddenAspect.getVisibility(client, nodeRef) == Visibility.NotVisible);
    }
    /**
     * Sets the root path.
     * 
     * @param path
     *            path within default store
     */
    public void setRootPath(String path)
    {
        rootPath = path;
    }
    public BigInteger getTypesDefaultMaxItems()
    {
        return typesDefaultMaxItems;
    }
    public void setTypesDefaultMaxItems(BigInteger typesDefaultMaxItems)
    {
        this.typesDefaultMaxItems = typesDefaultMaxItems;
    }
    public BigInteger getTypesDefaultDepth()
    {
        return typesDefaultDepth;
    }
    public void setTypesDefaultDepth(BigInteger typesDefaultDepth)
    {
        this.typesDefaultDepth = typesDefaultDepth;
    }
    public BigInteger getObjectsDefaultMaxItems()
    {
        return objectsDefaultMaxItems;
    }
    public void setObjectsDefaultMaxItems(BigInteger objectsDefaultMaxItems)
    {
        this.objectsDefaultMaxItems = objectsDefaultMaxItems;
    }
    public BigInteger getObjectsDefaultDepth()
    {
        return objectsDefaultDepth;
    }
    public void setObjectsDefaultDepth(BigInteger objectsDefaultDepth)
    {
        this.objectsDefaultDepth = objectsDefaultDepth;
    }
    /**
     * Set rendition kind mapping.
     */
    public void setRenditionKindMapping(Map> renditionKinds)
    {
        this.kindToRenditionNames = renditionKinds;
    }
    public void setOpenHttpSession(boolean openHttpSession)
    {
        this.openHttpSession = openHttpSession;
    }
    public boolean openHttpSession()
    {
        return openHttpSession;
    }
    public void setThumbnailService(ThumbnailService thumbnailService)
    {
		this.thumbnailService = thumbnailService;
	}
    
	public void setServiceRegistry(ServiceRegistry serviceRegistry)
	{
		this.serviceRegistry = serviceRegistry;
	}
	/**
     * Sets the descriptor service.
     */
    public void setDescriptorService(DescriptorService descriptorService)
    {
        this.descriptorService = descriptorService;
    }
    public DescriptorService getDescriptorService()
    {
        return descriptorService;
    }
    public void setBehaviourFilter(BehaviourFilter behaviourFilter)
    {
        this.behaviourFilter = behaviourFilter;
    }
    /**
     * Sets the node service.
     */
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
    public NodeService getNodeService()
    {
        return nodeService;
    }
	public void setActionService(ActionService actionService)
	{
		this.actionService = actionService;
	}
	/**
     * Sets the version service.
     */
    public void setVersionService(VersionService versionService)
    {
        this.versionService = versionService;
    }
    public VersionService getVersionService()
    {
        return versionService;
    }
    /**
     * Sets the checkOut/checkIn service.
     */
    public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService)
    {
        this.checkOutCheckInService = checkOutCheckInService;
    }
    public CheckOutCheckInService getCheckOutCheckInService()
    {
        return checkOutCheckInService;
    }
    /**
     * Sets the lock service.
     */
    public LockService getLockService()
    {
        return lockService;
    }
    public void setLockService(LockService lockService)
    {
        this.lockService = lockService;
    }
    /**
     * Sets the content service.
     */
    public void setContentService(ContentService contentService)
    {
        this.contentService = contentService;
    }
    public ContentService getContentService()
    {
        return contentService;
    }
    
    /**
     * Sets the event publisher
     */
    public void setEventPublisher(EventPublisher eventPublisher)
    {
        this.eventPublisher = eventPublisher;
    }
    
    /**
     * Sets the rendition service.
     */
    public void setrenditionService(RenditionService renditionService)
    {
        this.renditionService = renditionService;
    }
    /**
     * Sets the file folder service.
     */
    public void setFileFolderService(FileFolderService fileFolderService)
    {
        this.fileFolderService = fileFolderService;
    }
    public FileFolderService getFileFolderService()
    {
        return fileFolderService;
    }
    /**
     * Sets the tenant admin service.
     */
    public void setTenantAdminService(TenantAdminService tenantAdminService)
    {
        this.tenantAdminService = tenantAdminService;
    }
    
    public void setSingletonCache(SimpleCache singletonCache)
    {
        this.singletonCache = singletonCache;
    }
    
    /**
     * Sets the transaction service.
     */
    public void setTransactionService(TransactionService transactionService)
    {
        this.transactionService = transactionService;
    }
    public TransactionService getTransactionService()
    {
        return transactionService;
    }
    /**
     * Sets the authentication service.
     */
    public void setAuthenticationService(AuthenticationService authenticationService)
    {
        this.authenticationService = authenticationService;
    }
    public AuthenticationService getAuthenticationService()
    {
        return authenticationService;
    }
    /**
     * Sets the permission service.
     */
    public void setPermissionService(PermissionService permissionService)
    {
        this.permissionService = permissionService;
    }
    /**
     * Sets the permission model DAO.
     */
    public void setPermissionModelDao(ModelDAO permissionModelDao)
    {
        this.permissionModelDao = permissionModelDao;
    }
    public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService)
    {
        this.cmisDictionaryService = cmisDictionaryService;
    }
    public void setOpenCMISDictionaryService11(CMISDictionaryService cmisDictionaryService)
    {
        this.cmisDictionaryService11 = cmisDictionaryService;
    }
    public CMISDictionaryService getOpenCMISDictionaryService()
    {
        CmisVersion cmisVersion = getRequestCmisVersion();
        if(cmisVersion.equals(CmisVersion.CMIS_1_0))
        {
            return cmisDictionaryService;
        }
        else
        {
            return cmisDictionaryService11;
        }
    }
    /**
     * Sets the OpenCMIS query service.
     */
    public void setOpenCMISQueryService(CMISQueryService cmisQueryService)
    {
        this.cmisQueryService = cmisQueryService;
    }
    public void setOpenCMISQueryService11(CMISQueryService cmisQueryService)
    {
        this.cmisQueryService11 = cmisQueryService;
    }
    
    public CMISQueryService getOpenCMISQueryService()
    {
        CmisVersion cmisVersion = getRequestCmisVersion();
        if(cmisVersion.equals(CmisVersion.CMIS_1_0))
        {
            return cmisQueryService;
        }
        else
        {
            return cmisQueryService11;
        }
    }
    /**
     * Sets the MIME type service.
     */
    public void setMimetypeService(MimetypeService mimetypeService)
    {
        this.mimetypeService = mimetypeService;
    }
    public MimetypeService getMimetypeService()
    {
        return mimetypeService;
    }
    /**
     * Sets the audit service.
     */
    public void setAuditService(AuditService auditService)
    {
        this.auditService = auditService;
    }
    /**
     * Sets the namespace service.
     */
    public void setNamespaceService(NamespaceService namespaceService)
    {
        this.namespaceService = namespaceService;
    }
    /**
     * Sets the search service.
     */
    public void setSearchService(SearchService searchService)
    {
        this.searchService = searchService;
    }
    public SearchService getSearchService()
    {
        return searchService;
    }
    public void setDictionaryService(DictionaryService dictionaryService)
    {
        this.dictionaryService = dictionaryService;
    }
    public DictionaryService getDictionaryService()
    {
        return dictionaryService;
    }
    /**
     * Not implemented
     * @throws UnsupportedOperationException always
     */
    public void setProxyUser(String proxyUser)
    {
//        this.proxyUser = proxyUser;
        throw new UnsupportedOperationException("proxyUser setting not implemented.  Please raise a JIRA request.");
    }
    public String getProxyUser()
    {
        return proxyUser;
    }
    /**
     * Sets bulk update properties.
     */
    public void setBulkMaxItems(int size)
    {
        bulkMaxItems = size;
    }
    
    public int getBulkMaxItems()
    {
        return bulkMaxItems;
    }
    
    public void setBulkBatchSize(int size)
    {
        bulkBatchSize = size;
    }
    
    public int getBulkBatchSize()
    {
        return bulkBatchSize;
    }
    
    public void setBulkWorkerThreads(int threads)
    {
        bulkWorkerThreads = threads;
    }
    
    public int getBulkWorkerThreads()
    {
        return bulkWorkerThreads;
    }
    
    // --------------------------------------------------------------
    // Lifecycle methods
    // --------------------------------------------------------------
    private File tmp;
    
    public void setup()
    {
    	File tempDir = TempFileProvider.getTempDir();
    	this.tmp = new File(tempDir, "CMISAppend");
    	if(!this.tmp.exists() && !this.tmp.mkdir())
    	{
    		throw new AlfrescoRuntimeException("Failed to create CMIS temporary directory");
    	}
    }
    
    public void init()
    {
        // register as tenant deployer
        tenantAdminService.register(this);
        // set up and cache rendition mapping
        getRenditionMapping();
        // cache root node ref
        getRootNodeRef();
        // cache permission definitions and permission mappings
        repositoryPermissions = getRepositoryPermissions();
        permissionMappings = getPermissionMappings();
    }
    public void destroy()
    {
        singletonCache.remove(KEY_CMIS_ROOT_NODEREF);
        singletonCache.remove(KEY_CMIS_RENDITION_MAPPING_NODEREF);
    }
    public void onEnableTenant()
    {
        init();
    }
    public void onDisableTenant()
    {
        destroy();
    }
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        lifecycle.setApplicationContext(applicationContext);
    }
    public void onApplicationEvent(ApplicationContextEvent event)
    {
        lifecycle.onApplicationEvent(event);
    }
    /**
     * Hooks into Spring Application Lifecycle.
     */
    private class ProcessorLifecycle extends AbstractLifecycleBean
    {
        @Override
        protected void onBootstrap(ApplicationEvent event)
        {
            init();
        }
        @Override
        protected void onShutdown(ApplicationEvent event)
        {
        }
    }
    // --------------------------------------------------------------
    // Alfresco methods
    // --------------------------------------------------------------
    /*
     * For the given cmis property name get the corresponding Alfresco property name.
     * 
     * Certain CMIS properties (e.g. cmis:creationDate and cmis:lastModifiedBy) don't
     * have direct mappings to Alfresco properties through the CMIS dictionary, because
     * these mappings should not be exposed outside the repository through CMIS. For these,
     * however, this method provides the mapping so that the sort works.
     * 
     */
    public Pair getSortProperty(String cmisPropertyName, String direction)
    {
    	QName sortPropName = null;
    	Pair sortProp = null;
        PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findPropertyByQueryName(cmisPropertyName);
        if (propDef != null)
        {
	        if (propDef.getPropertyId().equals(PropertyIds.BASE_TYPE_ID))
	        {
	            // special-case (see also ALF-13968) - for getChildren, using "cmis:baseTypeId" allows sorting of folders first and vice-versa (cmis:folder <-> cmis:document)
	        	sortPropName = GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER;
	        }
	        else
	        {
	        	sortPropName = propDef.getPropertyAccessor().getMappedProperty();
	        }
	        if (sortPropName == null)
	        {
	        	// ok to map these properties because we are always getting current versions of nodes
	        	sortPropName = SORT_PROPERTY_MAPPINGS.get(cmisPropertyName);
	        }
	    }
    	if (sortPropName != null)
    	{
    		boolean sortAsc = (direction == null ? true : direction.equalsIgnoreCase("asc"));
    		sortProp = new Pair(sortPropName, sortAsc);
    	}
    	else
    	{
    		if (logger.isDebugEnabled())
    		{
    			logger.debug("Ignore sort property '" + cmisPropertyName + " - mapping not found");
    		}
    	}
        return sortProp;
    }
    /**
     * Asynchronously generates thumbnails for the given node.
     *  
     * @param nodeRef
     */
    public void createThumbnails(NodeRef nodeRef, Set thumbnailNames)
    {
    	if(thumbnailNames == null || thumbnailNames.size() == 0)
    	{
    		return;
    	}
        ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry();
        // If there's nothing currently registered to generate thumbnails for the
        //  specified mimetype, then log a message and bail out
        Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
        ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
        if (contentData == null)
        {
        	logger.info("Unable to create thumbnails as there is no content");
        	return;
        }
    	long size = contentData.getSize();
    	String mimeType = contentData.getMimetype();
    	for(String thumbnailName : thumbnailNames)
    	{
	        // Use the thumbnail registy to get the details of the thumbail
	        ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName);
	        if(details == null)
	        {
	            // Throw exception 
	            logger.warn("The thumbnail name '" + thumbnailName + "' is not registered");
	            continue;
	        }
	        else
	        {
		        if(registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), mimeType, size, nodeRef, details))
		        {
		            org.alfresco.service.cmr.action.Action action = ThumbnailHelper.createCreateThumbnailAction(details, serviceRegistry);
		            
		            // Queue async creation of thumbnail
		            actionService.executeAction(action, nodeRef, true, true);
		        }
		        else
		        {
		        	logger.info("Unable to create thumbnail '" + details.getName() + "' for " +
		        			mimeType + " as no transformer is currently available");
		        }
	        }
    	}
    }
    /**
     * Extracts metadata for the node.
     *  
     * @param nodeRef
     */
    public void extractMetadata(NodeRef nodeRef)
    {
    	org.alfresco.service.cmr.action.Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME);
    	actionService.executeAction(action, nodeRef, true, false);
    }
    public SiteInfo getSite(NodeRef nodeRef)
    {
    	return siteService.getSite(nodeRef);
    }
    
    /**
     *  Should the node be filtered?
     */
	public boolean filter(NodeRef nodeRef)
    {
    	return objectFilter.filter(nodeRef);
    }
    public boolean disableBehaviour(QName className)
    {
        boolean wasEnabled = behaviourFilter.isEnabled(className);
        if(wasEnabled)
        {
        	behaviourFilter.disableBehaviour(className);
        }
        return wasEnabled;
    }
	
    public boolean enableBehaviour(QName className)
    {
        boolean isEnabled = behaviourFilter.isEnabled(className);
        if(!isEnabled)
        {
            behaviourFilter.enableBehaviour(className);
        }
        return isEnabled;
    }
    
    public boolean disableBehaviour(QName className, NodeRef nodeRef)
    {
        boolean wasEnabled = behaviourFilter.isEnabled(nodeRef, className);
        if(wasEnabled)
        {
            behaviourFilter.disableBehaviour(nodeRef, className);
        }
        return wasEnabled;
    }
    
    public boolean enableBehaviour(QName className, NodeRef nodeRef)
    {
        boolean isEnabled = behaviourFilter.isEnabled(nodeRef, className);
        if(!isEnabled)
        {
            behaviourFilter.enableBehaviour(nodeRef, className);
        }
        return isEnabled;
    }
    public StoreRef getRootStoreRef()
    {
        return getRootNodeRef().getStoreRef();
    }
    public void deleteNode(NodeRef nodeRef, boolean postActivity)
    {
        ActivityInfo activityInfo = null;
        // post activity after removal of the node
        postActivity &= !hiddenAspect.hasHiddenAspect(nodeRef);
    	if(postActivity)
    	{
        	// get this information before the node is deleted
	    	activityInfo = activityPoster.getActivityInfo(nodeRef);
    	}
        getNodeService().deleteNode(nodeRef);
        // post activity after removal of the node
        if(postActivity && activityInfo != null)
        {
        	activityPoster.postFileFolderDeleted(activityInfo);
        }
    }
    public RetryingTransactionHelper getRetryingTransactionHelper()
    {
    	return transactionService.getRetryingTransactionHelper();
    }
    /**
     * Returns the root folder node ref.
     */
    public NodeRef getRootNodeRef()
    {
        NodeRef rootNodeRef = (NodeRef)singletonCache.get(KEY_CMIS_ROOT_NODEREF);
        if (rootNodeRef == null)
        {
            rootNodeRef = AuthenticationUtil.runAs(new RunAsWork()
            {
                public NodeRef doWork() throws Exception
                {
                    return transactionService.getRetryingTransactionHelper().doInTransaction(
                            new RetryingTransactionCallback()
                            {
                                public NodeRef execute() throws Exception
                                {
                                    NodeRef root = nodeService.getRootNode(storeRef);
                                    List rootNodes = searchService.selectNodes(root, rootPath, null,
                                            namespaceService, false);
                                    if (rootNodes.size() != 1)
                                    {
                                        throw new CmisRuntimeException("Unable to locate CMIS root path " + rootPath);
                                    }
                                    return rootNodes.get(0);
                                };
                            }, true);
                }
            }, AuthenticationUtil.getSystemUserName());
            if (rootNodeRef == null)
            {
                throw new CmisObjectNotFoundException("Root folder path '" + rootPath + "' not found!");
            }
            singletonCache.put(KEY_CMIS_ROOT_NODEREF, rootNodeRef);
        }
        return rootNodeRef;
    }
    public String getName(NodeRef nodeRef)
    {
        try
        {
            Object name = nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
            return (name instanceof String ? (String) name : null);
        }
        catch(InvalidNodeRefException inre)
        {
            return null;
        }
    }
    /**
     * Cuts of the version information from an object id.
     */
    public String getCurrentVersionId(String objectId)
    {
        if (objectId == null)
        {
            return null;
        }
        int sepIndex = objectId.lastIndexOf(ID_SEPERATOR);
        if (sepIndex > -1)
        {
            return objectId.substring(0, sepIndex);
        }
        return objectId;
    }
    /**
     * Creates an object info object.
     */
    public CMISNodeInfoImpl createNodeInfo(String objectId)
    {
        return new CMISNodeInfoImpl(this, objectId);
    }
    /**
     * Creates an object info object.
     */
    public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef)
    {
        return createNodeInfo(nodeRef, null);
    }
    /**
     * Creates an object info object.
     */
    public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory)
    {
        return new CMISNodeInfoImpl(this, nodeRef, versionHistory);
    }
    /**
     * Creates an object info object.
     */
    public CMISNodeInfoImpl createNodeInfo(AssociationRef assocRef)
    {
        return new CMISNodeInfoImpl(this, assocRef);
    }
    /*
     * Strip store ref from the id, if there is one.
     */
    private String getGuid(String id)
    {
    	int idx = id.lastIndexOf("/");
    	if(idx != -1)
    	{
    		return id.substring(idx + 1);
    	}
    	else
    	{
    		return id;
    	}
    }
    
    /*
     * Construct an object id based on the incoming assocRef and versionLabel. The object id will always
     * be the assocRef guid.
     */
    public String constructObjectId(AssociationRef assocRef, String versionLabel)
    {
        return constructObjectId(assocRef, versionLabel, isPublicApi());
    }
    
    public String constructObjectId(AssociationRef assocRef, String versionLabel, boolean dropStoreRef)
    {
    	StringBuilder sb = new StringBuilder(CMISConnector.ASSOC_ID_PREFIX);
    	if(dropStoreRef)
    	{
    		// always return the guid
    		sb.append(assocRef.getId());
    	}
    	else
    	{
    		sb.append(assocRef.toString());
    	}
    	if(versionLabel != null)
    	{
    		sb.append(CMISConnector.ID_SEPERATOR);
    		sb.append(versionLabel);
    	}
    	return sb.toString();
    }
    
    /*
     * Construct an object id based on the incoming incomingObjectId. The object id will always
     * be the node guid.
     */
    public String constructObjectId(String incomingObjectId)
    {
    	return constructObjectId(incomingObjectId, null);
    }
    /*
     * Construct an object id based on the incoming incomingNodeId and versionLabel. The object id will always
     * be the node guid.
     */
    public String constructObjectId(String incomingNodeId, String versionLabel)
    {
        return constructObjectId(incomingNodeId, versionLabel, isPublicApi());
    }
    
    public String constructObjectId(String incomingNodeId, String versionLabel, boolean dropStoreRef)
    {
    	StringBuilder sb = new StringBuilder();
    	if(dropStoreRef)
    	{
    		// always return the guid
    		sb.append(getGuid(incomingNodeId));
    	}
    	else
    	{
    	    sb.append(incomingNodeId);
    	}
    	if(versionLabel != null)
    	{
    		sb.append(CMISConnector.ID_SEPERATOR);
    		sb.append(versionLabel);
    	}
    	return sb.toString();
    }
    public void createVersion(NodeRef nodeRef, VersionType versionType, String reason)
    {
    	if(versionService.getVersionHistory(nodeRef) == null)
    	{
    		// no version history. Make sure we have an initial major version 1.0.
            Map versionProperties = new HashMap(2);
            versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR);
            versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial version");
            versionService.createVersion(nodeRef, versionProperties);
    	}
        Map versionProperties = new HashMap(2);
        versionProperties.put(VersionModel.PROP_VERSION_TYPE, versionType);
        versionProperties.put(VersionModel.PROP_DESCRIPTION, reason);
        versionService.createVersion(nodeRef, versionProperties);
    }
    public CmisVersion getRequestCmisVersion()
    {
        CallContext callContext = AlfrescoCmisServiceCall.get();
        CmisVersion cmisVersion = (callContext != null ? callContext.getCmisVersion() : CmisVersion.CMIS_1_0);
        return cmisVersion;
    }
    private boolean isPublicApi()
    {
    	boolean isPublicApi = false;
    	CallContext callContext = AlfrescoCmisServiceCall.get();
    	if(callContext != null)
    	{
	    	String value = (String)callContext.get("isPublicApi");
	    	isPublicApi = (value == null ? false : Boolean.parseBoolean(value));
    	}
    	return isPublicApi;
    }
    /*
     * Construct an object id based on the incoming incomingNodeRef and versionLabel. The object id will always
     * be the incomingNodeRef guid.
     */
    public String constructObjectId(NodeRef incomingNodeRef, String versionLabel)
    {
        return constructObjectId(incomingNodeRef, versionLabel, isPublicApi());
    }
    
    public String constructObjectId(NodeRef incomingNodeRef, String versionLabel, boolean dropStoreRef)
    {
    	StringBuilder sb = new StringBuilder();
    	sb.append(dropStoreRef ? incomingNodeRef.getId() : incomingNodeRef.toString());
    	if(versionLabel != null)
    	{
    		sb.append(CMISConnector.ID_SEPERATOR);
    		sb.append(versionLabel);
    	}
    	return sb.toString();
    }
    /**
     * Compiles a CMIS object if for a live node.
     */
    public String createObjectId(NodeRef nodeRef)
    {
        return createObjectId(nodeRef, isPublicApi());
    }
    
    public String createObjectId(NodeRef nodeRef, boolean dropStoreRef)
    {
    	QName typeQName = nodeService.getType(nodeRef);
        TypeDefinitionWrapper type = getOpenCMISDictionaryService().findNodeType(typeQName);
    
    	if(type instanceof ItemTypeDefinitionWrapper)
    	{
    		return constructObjectId(nodeRef, null);
    	}
    	
        if(type instanceof FolderTypeDefintionWrapper)
        {
            return constructObjectId(nodeRef, null, dropStoreRef);
        }
        
        Serializable versionLabel = getNodeService()
                .getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
        if (versionLabel == null)
        {
        	versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL;
        }
        return constructObjectId(nodeRef, (String)versionLabel, dropStoreRef);
    }
    
    private boolean isFolder(NodeRef nodeRef)
    {
        return getType(nodeRef) instanceof FolderTypeDefintionWrapper;
    }
    /**
     * Returns the type definition of a node or null if no type
     * definition could be found.
     */
    public TypeDefinitionWrapper getType(NodeRef nodeRef)
    {
        try
        {
            QName typeQName = nodeService.getType(nodeRef);
            return getType(typeQName);
        }
        catch(InvalidNodeRefException inre)
        {
            return null;
        }
    }
    private TypeDefinitionWrapper getType(QName typeQName)
    {
        return getOpenCMISDictionaryService().findNodeType(typeQName);
    }
    /**
     * Returns the type definition of an association or null if no
     * type definition could be found.
     */
    public TypeDefinitionWrapper getType(AssociationRef assocRef)
    {
        QName typeQName = assocRef.getTypeQName();
        return getOpenCMISDictionaryService().findAssocType(typeQName);
    }
    /**
     * Returns the type definition of a node or null if no type
     * definition could be found.
     */
    public TypeDefinitionWrapper getType(String cmisTypeId)
    {
        return getOpenCMISDictionaryService().findType(cmisTypeId);
    }
    /**
     * Returns the definition after it has checked if the type can be used for
     * object creation.
     */
    public TypeDefinitionWrapper getTypeForCreate(String cmisTypeId, BaseTypeId baseTypeId)
    {
        TypeDefinitionWrapper type = getType(cmisTypeId);
        if ((type == null) || (type.getBaseTypeId() != baseTypeId))
        {
            switch (baseTypeId)
            {
            case CMIS_DOCUMENT:
                throw new CmisConstraintException("Type is not a document type!");
            case CMIS_FOLDER:
                throw new CmisConstraintException("Type is not a folder type!");
            case CMIS_RELATIONSHIP:
                throw new CmisConstraintException("Type is not a relationship type!");
            case CMIS_POLICY:
                throw new CmisConstraintException("Type is not a policy type!");
            case CMIS_ITEM:
                throw new CmisConstraintException("Type is not an item type!");    
            }
        }
        if (!type.getTypeDefinition(false).isCreatable())
        {
            throw new CmisConstraintException("Type is not creatable!");
        }
        return type;
    }
    /**
     * Applies a versioning state to a document.
     */
    public void applyVersioningState(NodeRef nodeRef, VersioningState versioningState)
    {
        if (versioningState == VersioningState.CHECKEDOUT)
        {
            if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
            {
                Map props = new HashMap();
                props.put(ContentModel.PROP_INITIAL_VERSION, false);
                props.put(ContentModel.PROP_AUTO_VERSION, false);
                props.put(ContentModel.PROP_AUTO_VERSION_PROPS, false);
                nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
            }
            Map versionProperties = new HashMap(5);
            versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR);
            versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version");
            getCheckOutCheckInService().checkout(nodeRef);
        }
        else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR))
        {
            if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
            {
                Map props = new HashMap();
                props.put(ContentModel.PROP_INITIAL_VERSION, false);
                props.put(ContentModel.PROP_AUTO_VERSION, false);
                props.put(ContentModel.PROP_AUTO_VERSION_PROPS, false);
                nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
            }
            Map versionProperties = new HashMap(5);
            versionProperties.put(
                    VersionModel.PROP_VERSION_TYPE,
                    versioningState == VersioningState.MAJOR ? VersionType.MAJOR : VersionType.MINOR);
            versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version");
            versionService.createVersion(nodeRef, versionProperties);
        }
    }
    /**
     * Checks if a child of a given type can be added to a given folder.
     */
    @SuppressWarnings("unchecked")
    public void checkChildObjectType(CMISNodeInfo folderInfo, String childType)
    {
        TypeDefinitionWrapper targetType = folderInfo.getType();
        PropertyDefinitionWrapper allowableChildObjectTypeProperty = targetType
                .getPropertyById(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS);
        List childTypes = (List) allowableChildObjectTypeProperty.getPropertyAccessor().getValue(
                folderInfo);
        if ((childTypes == null) || childTypes.isEmpty())
        {
            return;
        }
        
        if (!childTypes.contains(childType))
        {
            throw new CmisConstraintException("Objects of type '" + childType + "' cannot be added to this folder!");
        }
    }
    /**
     * Creates the CMIS object for a node.
     */
    public ObjectData createCMISObject(CMISNodeInfo info, FileInfo node, String filter,
            boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
            boolean includePolicyIds, boolean includeAcl)
    {
        if (info.getType() == null)
        {
            throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
        }
        Properties nodeProps = getNodeProperties(info, node, filter, info.getType());
        return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships,
                renditionFilter, includePolicyIds, includeAcl);
    }
    public ObjectData createCMISObject(CMISNodeInfo info, String filter, boolean includeAllowableActions,
            IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds,
            boolean includeAcl)
    {
        if (info.getType() == null)
        {
            throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
        }
        Properties nodeProps = (info.isRelationship() ? getAssocProperties(info, filter) : getNodeProperties(info,
                filter));
        return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships,
                renditionFilter, includePolicyIds, includeAcl);
    }
    @SuppressWarnings("unchecked")
    private ObjectData createCMISObjectImpl(final CMISNodeInfo info, Properties nodeProps, String filter,
            boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
            boolean includePolicyIds, boolean includeAcl)
    {
        final ObjectDataImpl result = new ObjectDataImpl();
        // set allowable actions
        if (includeAllowableActions)
        {
            result.setAllowableActions(getAllowableActions(info));
        }
        // set policy ids
        if (includePolicyIds)
        {
            result.setPolicyIds(new PolicyIdListImpl());
        }
        if (info.isRelationship())
        {
            // set properties
            result.setProperties(getAssocProperties(info, filter));
            // set ACL
            if (includeAcl)
            {
                // association have no ACL - return an empty list of ACEs
                result.setAcl(new AccessControlListImpl((List) Collections.EMPTY_LIST));
            }
        }
        else
        {
            // set properties
            result.setProperties(nodeProps);
            // set relationships
            if (includeRelationships != IncludeRelationships.NONE)
            {
                result.setRelationships(getRelationships(info.getNodeRef(), includeRelationships));
            }
            // set renditions
            if (!RENDITION_NONE.equals(renditionFilter))
            {
                List renditions = getRenditions(info.getNodeRef(), renditionFilter, null, null);
                if ((renditions != null) && (!renditions.isEmpty()))
                {
                    result.setRenditions(renditions);
                }
                else
                {
                	result.setRenditions(Collections.EMPTY_LIST);
                }
            }
            // set ACL
            if (includeAcl)
            {
            	AuthenticationUtil.runAsSystem(new RunAsWork()
            	{
					@Override
					public Void doWork() throws Exception
					{
		                result.setAcl(getACL(info.getCurrentNodeNodeRef(), false));
						return null;
					}
            	});
            }
            // add aspects
            List extensions = getAspectExtensions(info, filter, result.getProperties()
                    .getProperties().keySet());
            if (!extensions.isEmpty())
            {
                result.getProperties().setExtensions(
                        Collections.singletonList((CmisExtensionElement) new CmisExtensionElementImpl(
                                ALFRESCO_EXTENSION_NAMESPACE, ASPECTS, null, extensions)));
            }
        }
        return result;
    }
    public String getPath(NodeRef nodeRef)
    {
        try
        {
            Path path = nodeService.getPath(nodeRef);
            // skip to CMIS root path
            NodeRef rootNode = getRootNodeRef();
            int i = 0;
            while (i < path.size())
            {
                Path.Element element = path.get(i);
                if (element instanceof ChildAssocElement)
                {
                    ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef();
                    NodeRef node = assocRef.getChildRef();
                    if (node.equals(rootNode))
                    {
                        break;
                    }
                }
                i++;
            }
            StringBuilder displayPath = new StringBuilder(64);
            if (path.size() - i == 1)
            {
                // render root path
                displayPath.append("/");
            }
            else
            {
                // render CMIS scoped path
                i++;
                while (i < path.size())
                {
                    Path.Element element = path.get(i);
                    if (element instanceof ChildAssocElement)
                    {
                        ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef();
                        NodeRef node = assocRef.getChildRef();
                        displayPath.append("/");
                        displayPath.append(nodeService.getProperty(node, ContentModel.PROP_NAME));
                    }
                    i++;
                }
            }
            return displayPath.toString();
        }
        catch(InvalidNodeRefException inre)
        {
            return null;
        }
    }
    /**
     * Gets the content from the repository.
     */
    public ContentStream getContentStream(CMISNodeInfo info, String streamId, BigInteger offset, BigInteger length)
    {
        // get the type and check if the object can have content
        TypeDefinitionWrapper type = info.getType();
        checkDocumentTypeForContent(type);
        // looks like a document, now get the content
        ContentStreamImpl result = new ContentStreamImpl();
        result.setFileName(info.getName());
        // if streamId is set, fetch other content
        NodeRef streamNodeRef = info.getNodeRef();
        if ((streamId != null) && (streamId.length() > 0))
        {
            CMISNodeInfo streamInfo = createNodeInfo(streamId);
            if (!streamInfo.isVariant(CMISObjectVariant.CURRENT_VERSION))
            {
                throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId + ", expected variant " + CMISObjectVariant.CURRENT_VERSION + ", got variant " + streamInfo.getObjectVariant());
            }
            streamNodeRef = streamInfo.getNodeRef();
            type = streamInfo.getType();
            checkDocumentTypeForContent(type);
        }
        // get the stream now
        try
        {
            ContentReader contentReader = contentService.getReader(streamNodeRef, ContentModel.PROP_CONTENT);
            if (contentReader == null)
            {
                throw new CmisConstraintException("Document has no content!");
            }
            result.setMimeType(contentReader.getMimetype());
            long contentSize = contentReader.getSize();
            
            if ((offset == null) && (length == null))
            {
                result.setStream(contentReader.getContentInputStream());
                result.setLength(BigInteger.valueOf(contentSize));
                publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), null);
            }
            else
            {
                long off = (offset == null ? 0 : offset.longValue());
                long len = (length == null ? contentSize : length.longValue());
                if (off + len > contentSize)
                {
                    len = contentReader.getSize() - off;
                }
                result.setStream(new RangeInputStream(contentReader.getContentInputStream(), off, len));
                result.setLength(BigInteger.valueOf(len));
                publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), off+" - "+len);
            }
        }
        catch (Exception e)
        {
            if (e instanceof CmisBaseException)
            {
                throw (CmisBaseException) e;
            }
            else
            {
                StringBuilder msg = new StringBuilder("Failed to retrieve content: " + e.getMessage());
                Throwable cause = e.getCause();
                if(cause != null)
                {
                    // add the cause to the CMIS exception
                    msg.append(", ");
                    msg.append(cause.getMessage());
                }
                throw new CmisRuntimeException(msg.toString(), e);
            }
        }
        return result;
    }
    /**
     * Notifies listeners that a read has taken place.
     * 
     * @param nodeRef
     * @param name
     * @param mimeType
     * @param contentSize
     * @param encoding
     * @param range
     */
    protected void publishReadEvent(final NodeRef nodeRef, final String name, final String mimeType, final long contentSize, final String encoding, final String range)
    {
        final QName nodeType = nodeRef==null?null:nodeService.getType(nodeRef);
        
        eventPublisher.publishEvent(new EventPreparator(){
            @Override
            public Event prepareEvent(String user, String networkId, String transactionId)
            {
                if (StringUtils.hasText(range))
                {
                    return new ContentReadRangeEvent(user, networkId, transactionId,
                                nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding, range); 
                } 
                else 
                {
                    return new ContentEventImpl(ContentEvent.DOWNLOAD, user, networkId, transactionId,
                                nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding);            
                }
            }
        });
        
    }
    public void appendContent(CMISNodeInfo nodeInfo, ContentStream contentStream, boolean isLastChunk) throws IOException
    {
    	NodeRef nodeRef = nodeInfo.getNodeRef();
    	this.disableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef);
		
    	if(!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT))
    	{
    		Map props = new HashMap();
    		props.put(ContentModel.PROP_GOT_FIRST_CHUNK, true);
    		nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT, props);
    	}
		ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
		InputStream existingContentInput = (reader != null ? reader.getContentInputStream() : null);
		InputStream input = contentStream.getStream();
    	ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
    	OutputStream out = new BufferedOutputStream(writer.getContentOutputStream());
    	
    	InputStream in = null;
    	if(existingContentInput != null)
    	{
    		in = new SequenceInputStream(existingContentInput, input);
    	}
    	else
    	{
    		in = input;
    	}
		try
		{
	    	IOUtils.copy(in, out);
	    	if(isLastChunk)
	    	{
    			nodeService.removeAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT);
	    		getActivityPoster().postFileFolderUpdated(nodeInfo.isFolder(), nodeRef);
	    		createVersion(nodeRef, VersionType.MINOR, "Appended content stream");
	    	}
		}
		finally
		{
			if(in != null)
			{
				in.close();
			}
			if(out != null)
			{
    	    	out.close();	
			}
			
			this.enableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef);
		}
    }
    private void checkDocumentTypeForContent(TypeDefinitionWrapper type)
    {
        if (type == null)
        {
            throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
        }
        if (!(type instanceof DocumentTypeDefinitionWrapper))
        {
            throw new CmisStreamNotSupportedException("Object is not a document!");
        }
        if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED)
        {
            throw new CmisConstraintException("Document cannot have content!");
        }
    }
    private void addAspectProperties(CMISNodeInfo info, String filter, PropertiesImpl result)
    {
        if(getRequestCmisVersion().equals(CmisVersion.CMIS_1_1))
        {
            Set propertyIds = new HashSet();
            Set filterSet = splitFilter(filter);
    
            Set aspects = nodeService.getAspects(info.getNodeRef());
            for (QName aspect : aspects)
            {
                TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect);
                if (aspectType == null)
                {
                    continue;
                }
    
                for (PropertyDefinitionWrapper propDef : aspectType.getProperties())
                {
                    if (propertyIds.contains(propDef.getPropertyId()))
                    {
                        // skip properties that have already been added
                        continue;
                    }
    
                    if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
                    {
                        // skip properties that are not in the filter
                        continue;
                    }
    
                    Serializable value = propDef.getPropertyAccessor().getValue(info);
                    result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
    
                    // mark property as 'added'
                    propertyIds.add(propDef.getPropertyId());
                }
            }
        }
    }
    public Properties getNodeProperties(CMISNodeInfo info, String filter)
    {
        PropertiesImpl result = new PropertiesImpl();
        Set filterSet = splitFilter(filter);
        for (PropertyDefinitionWrapper propDef : info.getType().getProperties())
        {
            if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID))
            {
                // don't filter the object id
                if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
                {
                    // skip properties that are not in the filter
                    continue;
                }
            }
            Serializable value = propDef.getPropertyAccessor().getValue(info);
            result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
        }
        addAspectProperties(info, filter, result);
        return result;
    }
    public Properties getNodeProperties(CMISNodeInfo info, FileInfo node, String filter, TypeDefinitionWrapper type)
    {
        PropertiesImpl result = new PropertiesImpl();
        Set filterSet = splitFilter(filter);
        Map nodeProps = node.getProperties();
        for (PropertyDefinitionWrapper propDef : type.getProperties())
        {
            if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID))
            {
                // don't filter the object id
                if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
                {
                    // skip properties that are not in the filter
                    continue;
                }
            }
            Serializable value = null;
            CMISPropertyAccessor accessor = propDef.getPropertyAccessor();
            if (accessor instanceof DirectProperty)
            {
                value = nodeProps.get(accessor.getMappedProperty());
            }
            else
            {
                value = propDef.getPropertyAccessor().getValue(info);
            }
            result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
        }
        addAspectProperties(info, filter, result);
        return result;
    }
    public Properties getAssocProperties(CMISNodeInfo info, String filter)
    {
        PropertiesImpl result = new PropertiesImpl();
        Set filterSet = splitFilter(filter);
        for (PropertyDefinitionWrapper propDefWrap : info.getType().getProperties())
        {
            PropertyDefinition> propDef = propDefWrap.getPropertyDefinition();
            if ((filterSet != null) && (!filterSet.contains(propDef.getQueryName())))
            {
                // skip properties that are not in the filter
                continue;
            }
            CMISPropertyAccessor cmisPropertyAccessor = propDefWrap.getPropertyAccessor();
            Serializable value = cmisPropertyAccessor.getValue(info);
            PropertyType propType = propDef.getPropertyType();
            PropertyData> propertyData = getProperty(propType, propDefWrap, value);
            result.addProperty(propertyData);
        }
        return result;
    }
    /**
     * Builds aspect extension.
     */
    public List getAspectExtensions(CMISNodeInfo info, String filter,
            Set alreadySetProperties)
    {
        List extensions = new ArrayList();
        Set propertyIds = new HashSet(alreadySetProperties);
        List propertyExtensionList = new ArrayList();
        Set filterSet = splitFilter(filter);
        Set aspects = nodeService.getAspects(info.getNodeRef());
        for (QName aspect : aspects)
        {
            TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect);
            if (aspectType == null)
            {
                continue;
            }
            
            AspectDefinition aspectDefinition = dictionaryService.getAspect(aspect);
            Map aspectProperties = aspectDefinition.getProperties();
            extensions.add(new CmisExtensionElementImpl(ALFRESCO_EXTENSION_NAMESPACE, APPLIED_ASPECTS, null, aspectType
                    .getTypeId()));
            for (PropertyDefinitionWrapper propDef : aspectType.getProperties())
            {
                boolean addPropertyToExtensionList = getRequestCmisVersion().equals(CmisVersion.CMIS_1_1) && aspectProperties.keySet().contains(propDef.getAlfrescoName());
                // MNT-11876 : add property to extension even if it has been returned (CMIS 1.1)
                if (propertyIds.contains(propDef.getPropertyId()) && !addPropertyToExtensionList)
                {
                    // skip properties that have already been added
                    continue;
                }
                if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
                {
                    // skip properties that are not in the filter
                    continue;
                }
                Serializable value = propDef.getPropertyAccessor().getValue(info);
                propertyExtensionList.add(createAspectPropertyExtension(propDef.getPropertyDefinition(), value));
                // mark property as 'added'
                propertyIds.add(propDef.getPropertyId());
            }
        }
        if (!propertyExtensionList.isEmpty())
        {
            CmisExtensionElementImpl propertiesExtension = new CmisExtensionElementImpl(
                    ALFRESCO_EXTENSION_NAMESPACE, "properties", null, propertyExtensionList);
            extensions.addAll(Collections.singletonList(propertiesExtension));
        }
        return extensions;
    }
    /**
     * Creates a property extension element.
     */
    @SuppressWarnings("rawtypes")
    private CmisExtensionElement createAspectPropertyExtension(PropertyDefinition> propertyDefinition, Object value)
    {
        String name;
        switch (propertyDefinition.getPropertyType())
        {
        case BOOLEAN:
            name = "propertyBoolean";
            break;
        case DATETIME:
            name = "propertyDateTime";
            break;
        case DECIMAL:
            name = "propertyDecimal";
            break;
        case INTEGER:
            name = "propertyInteger";
            break;
        case ID:
            name = "propertyId";
            break;
        default:
            name = "propertyString";
        }
        Map attributes = new HashMap();
        attributes.put("propertyDefinitionId", propertyDefinition.getId());
        attributes.put("queryName", propertyDefinition.getQueryName());
        // optional value
        if (propertyDefinition.getDisplayName() !=null && propertyDefinition.getDisplayName().trim().length() > 0)
        {
            attributes.put("displayName", propertyDefinition.getDisplayName());
        }
        // optional value
        if (propertyDefinition.getLocalName() !=null && propertyDefinition.getLocalName().trim().length() > 0)
        {
            attributes.put("localName", propertyDefinition.getLocalName());
        }
        
        List propertyValues = new ArrayList();
        if (value != null)
        {
            if (value instanceof List)
            {
                for (Object o : ((List) value))
                {
                	if(o != null)
                	{
                		propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null,
                				convertAspectPropertyValue(o)));
                	}
                	else
                	{
                		logger.warn("Unexpected null entry in list value for property " + propertyDefinition.getDisplayName()
                				+ ", value = " + value);
                	}
                }
            }
            else
            {
                propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null,
                        convertAspectPropertyValue(value)));
            }
        }
        return new CmisExtensionElementImpl(CMIS_NAMESPACE, name, attributes, propertyValues);
    }
    private String convertAspectPropertyValue(Object value)
    {
        if (value instanceof Date)
        {
            GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
            cal.setTime((Date) value);
            value = cal;
        }
        if (value instanceof GregorianCalendar)
        {
            DatatypeFactory df;
            try
            {
                df = DatatypeFactory.newInstance();
            }
            catch (DatatypeConfigurationException e)
            {
                throw new IllegalArgumentException("Aspect conversation exception: " + e.getMessage(), e);
            }
            return df.newXMLGregorianCalendar((GregorianCalendar) value).toXMLFormat();
        }
        // MNT-12496
        // Encode for AtomPub only. Browser/json binding already encodes.
        if (AlfrescoCmisServiceCall.get() != null && CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()))
        {
        	return escapeControlCharacters(value.toString());
        }
        else
        {
        	return value.toString();
        }
    }
    @SuppressWarnings("unchecked")
    private AbstractPropertyData> getProperty(PropertyType propType, PropertyDefinitionWrapper propDef,
            Serializable value)
    {
        AbstractPropertyData> result = null;
        switch (propType)
        {
        case BOOLEAN:
            result = new PropertyBooleanImpl();
            if (value instanceof List)
            {
                ((PropertyBooleanImpl) result).setValues((List) value);
            }
            else
            {
                ((PropertyBooleanImpl) result).setValue((Boolean) value);
            }
            break;
        case DATETIME:
            result = new PropertyDateTimeImpl();
            if (value instanceof List)
            {
                ((PropertyDateTimeImpl) result).setValues((List) DefaultTypeConverter.INSTANCE
                        .convert(GregorianCalendar.class, (List>) value));
            }
            else
            {
                ((PropertyDateTimeImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(GregorianCalendar.class,
                        value));
            }
            break;
        case DECIMAL:
            result = new PropertyDecimalImpl();
            if (value instanceof List)
            {
                ((PropertyDecimalImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert(
                        BigDecimal.class, (List>) value));
            }
            else
            {
                ((PropertyDecimalImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigDecimal.class, value));
            }
            break;
        case HTML:
            result = new PropertyHtmlImpl();
            if (value instanceof List)
            {
                ((PropertyHtmlImpl) result).setValues((List) value);
            }
            else
            {
                ((PropertyHtmlImpl) result).setValue((String) value);
            }
            break;
        case ID:
            result = new PropertyIdImpl();
            if (value instanceof List)
            {
                ((PropertyIdImpl) result).setValues((List) value);
            }
            else
            {
                if (value instanceof NodeRef)
                {
                    ((PropertyIdImpl) result).setValue(constructObjectId((NodeRef)value, null));
                }
                else
                {
                    ((PropertyIdImpl) result).setValue((String) value);
                }
            }
            break;
        case INTEGER:
            result = new PropertyIntegerImpl();
            if (value instanceof List)
            {
                ((PropertyIntegerImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert(
                        BigInteger.class, (List>) value));
            }
            else
            {
                ((PropertyIntegerImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigInteger.class, value));
            }
            break;
        case STRING:
            result = new PropertyStringImpl();
            if (value instanceof List)
            {
                ((PropertyStringImpl) result).setValues((List) value);
            }
            else
            {
               // MNT-12496
               // Encode for AtomPub only. Browser/json binding already encodes.
            	if (AlfrescoCmisServiceCall.get() != null && CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()))
            	{
            		((PropertyStringImpl) result).setValue(escapeControlCharacters((String) value));
            	}
            	else
            	{
            		((PropertyStringImpl) result).setValue((String) value);
            	}
            }
            break;
        case URI:
            result = new PropertyUriImpl();
            if (value instanceof List)
            {
                ((PropertyUriImpl) result).setValues((List) value);
            }
            else
            {
                ((PropertyUriImpl) result).setValue((String) value);
            }
            break;
        default:
            throw new RuntimeException("Unknown datatype! Spec change?");
        }
        if (propDef != null)
        {
            result.setId(propDef.getPropertyDefinition().getId());
            result.setQueryName(propDef.getPropertyDefinition().getQueryName());
            result.setDisplayName(propDef.getPropertyDefinition().getDisplayName());
            result.setLocalName(propDef.getPropertyDefinition().getLocalName());
        }
        return result;
    }
    
    private String escapeControlCharacters(String origValue)
    {
        if (origValue == null)
        {
            return origValue;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < origValue.length(); i++)
        {
            char ch = origValue.charAt(i);
            switch (ch)
            {
            case '\b':
            case '\f':
            case '\n':
            case '\r':
            case '\t':
                sb.append(ch);
                break;
            default:
                // Reference: http://www.unicode.org/versions/Unicode5.1.0/
                if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF'))
                {
                    String ss = Integer.toHexString(ch);
                    sb.append("\\u");
                    for (int k = 0; k < 4 - ss.length(); k++)
                    {
                        sb.append('0');
                    }
                    sb.append(ss.toUpperCase());
                }
                else
                {
                    sb.append(ch);
                }
            }
        }
        return sb.toString();
    }
    private Set splitFilter(String filter)
    {
        if (filter == null)
        {
            return null;
        }
        if (filter.trim().length() == 0)
        {
            return null;
        }
        Set result = new HashSet();
        for (String s : filter.split(","))
        {
            s = s.trim();
            if (s.equals("*"))
            {
                return null;
            }
            else if (s.length() > 0)
            {
                result.add(s);
            }
        }
        // set a few base properties
        result.add(QUERY_NAME_OBJECT_ID);
        result.add(QUERY_NAME_OBJECT_TYPE_ID);
        result.add(QUERY_NAME_BASE_TYPE_ID);
        return result;
    }
    public AllowableActions getAllowableActions(CMISNodeInfo info)
    {
        AllowableActionsImpl result = new AllowableActionsImpl();
        Set allowableActions = new HashSet();
        result.setAllowableActions(allowableActions);
        for (CMISActionEvaluator evaluator : info.getType().getActionEvaluators().values())
        {
            if (evaluator.isAllowed(info))
            {
                allowableActions.add(evaluator.getAction());
            }
        }
        return result;
    }
    public List getRelationships(NodeRef nodeRef, IncludeRelationships includeRelationships/*, CmisVersion cmisVersion*/)
    {
        List result = new ArrayList();
        if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL))
        {
            // relationships from and to versions are not preserved
            return result;
        }
        // get relationships
        List assocs = new ArrayList();
        if (includeRelationships == IncludeRelationships.SOURCE || includeRelationships == IncludeRelationships.BOTH)
        {
            assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
        }
        if (includeRelationships == IncludeRelationships.TARGET || includeRelationships == IncludeRelationships.BOTH)
        {
            assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
        }
        // filter relationships that not map the CMIS domain model
        for (AssociationRef assocRef : assocs)
        {
            TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName());
            if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null
                    || getType(assocRef.getTargetRef()) == null)
            {
                continue;
            }
            try
            {
	            result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE,
	                    RENDITION_NONE, false, false/*, cmisVersion*/));
            }
            catch(AccessDeniedException e)
            {
            	// PUBLICAPI-110
            	// ok, just skip it
            }
            catch(CmisObjectNotFoundException e)
            {
                // ignore objects that have not been found (perhaps because their type is unknown to CMIS)
            }
            // TODO: Somewhere this has not been wrapped correctly
            catch (net.sf.acegisecurity.AccessDeniedException e)
            {
                // skip
            }
        }
        return result;
    }
    public ObjectList getObjectRelationships(NodeRef nodeRef, RelationshipDirection relationshipDirection,
            String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount)
    {
        ObjectListImpl result = new ObjectListImpl();
        result.setHasMoreItems(false);
        result.setNumItems(BigInteger.ZERO);
        result.setObjects(new ArrayList());
        if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL))
        {
            // relationships from and to versions are not preserved
            return result;
        }
        // get relationships
        List assocs = new ArrayList();
        if (relationshipDirection == RelationshipDirection.SOURCE
                || relationshipDirection == RelationshipDirection.EITHER)
        {
            assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
        }
        if (relationshipDirection == RelationshipDirection.TARGET
                || relationshipDirection == RelationshipDirection.EITHER)
        {
            assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
        }
        int skip = (skipCount == null ? 0 : skipCount.intValue());
        int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
        int counter = 0;
        boolean hasMore = false;
        if (max > 0)
        {
            // filter relationships that not map the CMIS domain model
            for (AssociationRef assocRef : assocs)
            {
                TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName());
                if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null
                        || getType(assocRef.getTargetRef()) == null)
                {
                    continue;
                }
                if ((typeId != null) && !assocTypeDef.getTypeId().equals(typeId))
                {
                    continue;
                }
                counter++;
                if (skip > 0)
                {
                    skip--;
                    continue;
                }
                max--;
                if (max > 0)
                {
                	try
                	{
	                    result.getObjects().add(
	                            createCMISObject(createNodeInfo(assocRef), filter, includeAllowableActions,
	                                    IncludeRelationships.NONE, RENDITION_NONE, false, false/*, cmisVersion*/));
                    }
                    catch(CmisObjectNotFoundException e)
                    {
                        // ignore objects that have not been found (perhaps because their type is unknown to CMIS)
                    }
                }
                else
                {
                    hasMore = true;
                }
            }
        }
        result.setNumItems(BigInteger.valueOf(counter));
        result.setHasMoreItems(hasMore);
        return result;
    }
    public List getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems,
            BigInteger skipCount)
    {
        CMISRenditionMapping mapping = getRenditionMapping();
        return mapping.getRenditions(nodeRef, renditionFilter, maxItems, skipCount);
    }
    public Acl getACL(NodeRef nodeRef, boolean onlyBasicPermissions)
    {
        AccessControlListImpl result = new AccessControlListImpl();
        // get the permissions and sort them
        ArrayList ordered = new ArrayList(
                permissionService.getAllSetPermissions(nodeRef));
        Collections.sort(ordered, new AccessPermissionComparator());
        // remove denied permissions and create OpenCMIS objects
        Map> aceMap = new HashMap>();
        for (AccessPermission entry : ordered)
        {
            if (entry.getAccessStatus() == AccessStatus.ALLOWED)
            {
                // add allowed entries
                Map directAce = aceMap.get(entry.getAuthority());
                if (directAce == null)
                {
                    directAce = new HashMap();
                    aceMap.put(entry.getAuthority(), directAce);
                }
                AccessControlEntryImpl ace = directAce.get(entry.isSetDirectly());
                if (ace == null)
                {
                    ace = new AccessControlEntryImpl();
                    ace.setPrincipal(new AccessControlPrincipalDataImpl(entry.getAuthority()));
                    ace.setPermissions(new ArrayList());
                    ace.setDirect(entry.isSetDirectly());
                    directAce.put(entry.isSetDirectly(), ace);
                }
                ace.getPermissions().add(entry.getPermission());
            }
            else if (entry.getAccessStatus() == AccessStatus.DENIED)
            {
                // remove denied entries
                Map directAce = aceMap.get(entry.getAuthority());
                if (directAce != null)
                {
                    for (AccessControlEntryImpl ace : directAce.values())
                    {
                        ace.getPermissions().remove(entry.getPermission());
                    }
                }
            }
        }
        // adjust permissions, add CMIS permissions and add ACEs to ACL
        List aces = new ArrayList();
        result.setAces(aces);
        for (Map bothAces : aceMap.values())
        {
            // get, translate and set direct ACE
            AccessControlEntryImpl directAce = bothAces.get(true);
            if ((directAce != null) && (!directAce.getPermissions().isEmpty()))
            {
            	List permissions = translatePermissionsToCMIS(directAce.getPermissions(), onlyBasicPermissions);
            	if(permissions != null && !permissions.isEmpty())
            	{
                	// tck doesn't like empty permissions list
	                directAce.setPermissions(permissions);
	                aces.add(directAce);
            	}
            }
            // get, translate, remove duplicate permissions and set indirect ACE
            AccessControlEntryImpl indirectAce = bothAces.get(false);
            if ((indirectAce != null) && (!indirectAce.getPermissions().isEmpty()))
            {
            	List permissions = translatePermissionsToCMIS(indirectAce.getPermissions(), onlyBasicPermissions);
                indirectAce.setPermissions(permissions);
                // remove permissions that are already set in the direct ACE
                if ((directAce != null) && (!directAce.getPermissions().isEmpty()))
                {
                    indirectAce.getPermissions().removeAll(directAce.getPermissions());
                }
                if(!indirectAce.getPermissions().isEmpty())
                {
                	// tck doesn't like empty permissions list
                	aces.add(indirectAce);
                }
            }
        }
        result.setExact(!onlyBasicPermissions);
        return result;
    }
    private List translatePermissionsToCMIS(List permissions, boolean onlyBasicPermissions)
    {
        Set result = new TreeSet();
        for (String permission : permissions)
        {
            PermissionReference permissionReference = permissionModelDao.getPermissionReference(null, permission);
            if (onlyBasicPermissions)
            {
                // check for full permissions
                if (permissionModelDao.hasFull(permissionReference))
                {
                    result.add(BasicPermissions.ALL);
                }
                // check short forms
                Set longForms = permissionModelDao.getGranteePermissions(permissionReference);
                HashSet shortForms = new HashSet();
                for (PermissionReference longForm : longForms)
                {
                    shortForms.add(permissionModelDao.isUnique(longForm) ? longForm.getName() : longForm.toString());
                }
                for (String perm : shortForms)
                {
                    if (PermissionService.READ.equals(perm))
                    {
                        result.add(BasicPermissions.READ);
                    }
                    else if (PermissionService.WRITE.equals(perm))
                    {
                        result.add(BasicPermissions.WRITE);
                    }
                    else if (PermissionService.ALL_PERMISSIONS.equals(perm))
                    {
                        result.add(BasicPermissions.ALL);
                    }
                }
                // check the permission
                if (PermissionService.READ.equals(permission))
                {
                    result.add(BasicPermissions.READ);
                }
                else if (PermissionService.WRITE.equals(permission))
                {
                    result.add(BasicPermissions.WRITE);
                }
                else if (PermissionService.ALL_PERMISSIONS.equals(permission))
                {
                    result.add(BasicPermissions.ALL);
                }
            }
            else
            {
                // ACE-2224: only repository specific permissions should be reported
                if (permission.startsWith("{"))
                {
                    result.add(permission);
                }
                // do revert conversion for basic permissions that have exact matching
                else if (PermissionService.READ.equals(permission))
                {
                    result.add(BasicPermissions.READ);
                }
                else if (PermissionService.WRITE.equals(permission))
                {
                    result.add(BasicPermissions.WRITE);
                }
                else if (PermissionService.ALL_PERMISSIONS.equals(permission))
                {
                    result.add(BasicPermissions.ALL);
                }
                else
                {
                    result.add(permissionReference.toString());
                }
            }
        }
        return new ArrayList(result);
    }
    public static class AccessPermissionComparator implements Comparator
    {
        public int compare(AccessPermission left, AccessPermission right)
        {
            if (left.getPosition() != right.getPosition())
            {
                return right.getPosition() - left.getPosition();
            }
            else
            {
                if (left.getAccessStatus() != right.getAccessStatus())
                {
                    return (left.getAccessStatus() == AccessStatus.DENIED) ? -1 : 1;
                }
                else
                {
                    int compare = left.getAuthority().compareTo(right.getAuthority());
                    if (compare != 0)
                    {
                        return compare;
                    }
                    else
                    {
                        return (left.getPermission().compareTo(right.getPermission()));
                    }
                }
            }
        }
    }
    /**
     * Applies the given ACLs.
     */
    public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl addAces, Acl removeAces)
    {
        boolean hasAdd = (addAces != null) && (addAces.getAces() != null) && !addAces.getAces().isEmpty();
        boolean hasRemove = (removeAces != null) && (removeAces.getAces() != null) && !removeAces.getAces().isEmpty();
        if (!hasAdd && !hasRemove)
        {
            return;
        }
        if (!type.getTypeDefinition(false).isControllableAcl())
        {
            throw new CmisConstraintException("Object is not ACL controllable!");
        }
        // remove permissions
        if (hasRemove)
        {
            Set permissions = permissionService.getAllSetPermissions(nodeRef);
            for (Ace ace : removeAces.getAces())
            {
                String principalId = ace.getPrincipalId();
                if (CMIS_USER.equals(principalId))
                {
                    principalId = AuthenticationUtil.getFullyAuthenticatedUser();
                }
                for (String permission : translatePermissionsFromCMIS(ace.getPermissions()))
                {
                    AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, principalId,
                            0);
                    if (!permissions.contains(toCheck))
                    {
                        throw new CmisConstraintException("No matching ACE found to remove!");
                    }
                    permissionService.deletePermission(nodeRef, principalId, permission);
                }
            }
        }
        // add permissions
        if (hasAdd)
        {
            for (Ace ace : addAces.getAces())
            {
                String principalId = ace.getPrincipalId();
                if (CMIS_USER.equals(principalId))
                {
                    principalId = AuthenticationUtil.getFullyAuthenticatedUser();
                }
                for (String permission : translatePermissionsFromCMIS(ace.getPermissions()))
                {
                    permissionService.setPermission(nodeRef, principalId, permission, true);
                }
            }
        }
    }
    /**
     * Sets the given ACL.
     */
    public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl aces)
    {
        boolean hasAces = (aces != null) && (aces.getAces() != null) && !aces.getAces().isEmpty();
        if (!hasAces && !permissionService.getInheritParentPermissions(nodeRef))
        {
            return;
        }
        if (!type.getTypeDefinition(false).isControllableAcl())
        {
            throw new CmisConstraintException("Object is not ACL controllable!");
        }
        // remove all permissions
        permissionService.deletePermissions(nodeRef);
        // set new permissions
        for (Ace ace : aces.getAces())
        {
            String principalId = ace.getPrincipalId();
            if (CMIS_USER.equals(principalId))
            {
                principalId = AuthenticationUtil.getFullyAuthenticatedUser();
            }
            List permissions = translatePermissionsFromCMIS(ace.getPermissions());
            for (String permission : permissions)
            {
                permissionService.setPermission(nodeRef, principalId, permission, true);
            }
        }
    }
    private List translatePermissionsFromCMIS(List permissions)
    {
        List result = new ArrayList();
        if (permissions == null)
        {
            return result;
        }
        for (String permission : permissions)
        {
            if (permission == null)
            {
                throw new CmisConstraintException("Invalid null permission!");
            }
            if (BasicPermissions.READ.equals(permission))
            {
                result.add(PermissionService.READ);
            }
            else if (BasicPermissions.WRITE.equals(permission))
            {
                result.add(PermissionService.WRITE);
            }
            else if (BasicPermissions.ALL.equals(permission))
            {
                result.add(PermissionService.ALL_PERMISSIONS);
            }
            else if (!permission.startsWith("{"))
            {
                result.add(permission);
            }
            else
            {
                int sepIndex = permission.lastIndexOf('.');
                if (sepIndex == -1)
                {
                    result.add(permission);
                }
                else
                {
                    result.add(permission.substring(sepIndex + 1));
                }
            }
        }
        return result;
    }
    public void applyPolicies(NodeRef nodeRef, TypeDefinitionWrapper type, List policies)
    {
        if ((policies == null) || (policies.isEmpty()))
        {
            return;
        }
        if (!type.getTypeDefinition(false).isControllablePolicy())
        {
            throw new CmisConstraintException("Object is not policy controllable!");
        }
        // nothing else to do...
    }
    @SuppressWarnings("unchecked")
	public ObjectList query(String statement, Boolean includeAllowableActions,
            IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount/*, CmisVersion cmisVersion*/)
    {
        // prepare results
        ObjectListImpl result = new ObjectListImpl();
        result.setObjects(new ArrayList());
        // prepare query
        CMISQueryOptions options = new CMISQueryOptions(statement, getRootStoreRef());
        CmisVersion cmisVersion = getRequestCmisVersion();
        options.setCmisVersion(cmisVersion);
        options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS);
        int skip = 0;
        if ((skipCount != null) && (skipCount.intValue() >= 0))
        {
            skip = skipCount.intValue();
            options.setSkipCount(skip);
        }
        if ((maxItems != null) && (maxItems.intValue() >= 0))
        {
            options.setMaxItems(maxItems.intValue());
        }
        boolean fetchObject = includeAllowableActions || (includeRelationships != IncludeRelationships.NONE)
                || (!RENDITION_NONE.equals(renditionFilter));
        // query
        CMISResultSet rs = getOpenCMISQueryService().query(options);
        try
        {
            CMISResultSetColumn[] columns = rs.getMetaData().getColumns();
            for (CMISResultSetRow row : rs)
            {
                NodeRef nodeRef = row.getNodeRef();
                
                if(!nodeService.exists(nodeRef) || filter(nodeRef))
                {
                    continue;
                }
                
                TypeDefinitionWrapper type = getType(nodeRef);
                if (type == null)
                {
                    continue;
                }
                
                ObjectDataImpl hit = new ObjectDataImpl();
                PropertiesImpl properties = new PropertiesImpl();
                hit.setProperties(properties);
                Map values = row.getValues();
                for (CMISResultSetColumn column : columns)
                {
                    AbstractPropertyData> property = getProperty(column.getCMISDataType(),
                            column.getCMISPropertyDefinition(), values.get(column.getName()));
                    property.setQueryName(column.getName());
                    properties.addProperty(property);
                }
                if (fetchObject)
                {
                    // set allowable actions
                    if (includeAllowableActions)
                    {
                        CMISNodeInfoImpl nodeInfo = createNodeInfo(nodeRef);
                        if(!nodeInfo.getObjectVariant().equals(CMISObjectVariant.NOT_EXISTING))
                        {
                            hit.setAllowableActions(getAllowableActions(nodeInfo));
                        }
                    }
                    // set relationships
                    if (includeRelationships != IncludeRelationships.NONE)
                    {
                        hit.setRelationships(getRelationships(nodeRef, includeRelationships/*, cmisVersion*/));
                    }
                    // set renditions
                    if (!RENDITION_NONE.equals(renditionFilter))
                    {
                        List renditions = getRenditions(nodeRef, renditionFilter, null, null);
                        if ((renditions != null) && (!renditions.isEmpty()))
                        {
                            hit.setRenditions(renditions);
                        }
                        else
                        {
                        	hit.setRenditions(Collections.EMPTY_LIST);
                        }
                    }
                }
                result.getObjects().add(hit);
            }
            long numberFound = rs.getNumberFound();
            if(numberFound != -1)
            {
            	result.setNumItems(BigInteger.valueOf(numberFound));
            }
            result.setHasMoreItems(rs.hasMore());
        } finally
        {
            rs.close();
        }
        return result;
    }
    /**
     * Sets property values.
     */
    @SuppressWarnings({ "rawtypes" })
    public void setProperties(NodeRef nodeRef, TypeDefinitionWrapper type, Properties properties, String... exclude)
    {
        if (properties == null)
        {
            return;
        }
        
        Map> incomingPropsMap = properties.getProperties();
        if (incomingPropsMap == null)
        {
            return;
        }
        // extract property data into an easier to use form
        Map> propsMap = new HashMap>();
        for (String propertyId : incomingPropsMap.keySet())
        {
            PropertyData> property = incomingPropsMap.get(propertyId);
            PropertyDefinitionWrapper propDef = type.getPropertyById(property.getId());
            if (propDef == null)
            {
                propDef = getOpenCMISDictionaryService().findProperty(propertyId);
                if (propDef == null)
                {
                    throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!");
                }
            }
            Updatability updatability = propDef.getPropertyDefinition().getUpdatability();
            if ((updatability == Updatability.READONLY)
                    || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef)))
            {
                throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!");
            }
            TypeDefinitionWrapper propType = propDef.getOwningType();
            Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI);
            Pair pair = new Pair(propType, value);
            propsMap.put(propertyId, pair);
        }
        // Need to do deal with secondary types first
        Pair pair = propsMap.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
        Serializable secondaryTypesProperty = (pair != null ? pair.getSecond() : null);
        if(secondaryTypesProperty != null)
        {
            if (!(secondaryTypesProperty instanceof List))
            {
                throw new CmisInvalidArgumentException("Secondary types must be a list!");
            }
            List secondaryTypes = (List)secondaryTypesProperty;
            if(secondaryTypes != null && secondaryTypes.size() > 0)
            {
                // add/remove secondary types/aspects
                processSecondaryTypes(nodeRef, secondaryTypes, propsMap);
            }
        }
        for (String propertyId : propsMap.keySet())
        {
            if(propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS))
            {
                // already handled above
                continue;
            }
            pair = propsMap.get(propertyId);
            TypeDefinitionWrapper propType = pair.getFirst();
            Serializable value = pair.getSecond();
            if (Arrays.binarySearch(exclude, propertyId) < 0)
            {
                setProperty(nodeRef, propType, propertyId, value);
            }
        }
        List extensions = properties.getExtensions();
        if (extensions != null)
        {
        	boolean isNameChanging = properties.getProperties().containsKey(PropertyIds.NAME);
            for (CmisExtensionElement extension : extensions)
            {
                if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace())
                        && SET_ASPECTS.equals(extension.getName()))
                {
                    setAspectProperties(nodeRef, isNameChanging, extension);
                    break;
                }
            }
        }
    }
    @SuppressWarnings("rawtypes")
    private void processSecondaryTypes(NodeRef nodeRef, List secondaryTypes, Map> propsToAdd)
    {
        // diff existing aspects and secondaryTypes/aspects list
        Set existingAspects = nodeService.getAspects(nodeRef);
        Set secondaryTypeAspects = new HashSet();
        for(Object o : secondaryTypes)
        {
            String secondaryType = (String)o;
            
            TypeDefinitionWrapper wrapper = getOpenCMISDictionaryService().findType(secondaryType);
            if(wrapper != null)
            {
                QName aspectQName = wrapper.getAlfrescoName();
                secondaryTypeAspects.add(aspectQName);
            }
            else
            {
                throw new CmisInvalidArgumentException("Invalid secondary type id " + secondaryType);
            }
        }
        Set ignore = new HashSet();
        ignore.add(ContentModel.ASPECT_REFERENCEABLE);
        ignore.add(ContentModel.ASPECT_LOCALIZED);
        // aspects to add == the list of secondary types - existing aspects - ignored aspects
        Set toAdd = new HashSet(secondaryTypeAspects);
        toAdd.removeAll(existingAspects);
        toAdd.removeAll(ignore);
        // aspects to remove == existing aspects - secondary types
        Set aspectsToRemove = new HashSet();
        aspectsToRemove.addAll(existingAspects);
        aspectsToRemove.removeAll(ignore);
        Iterator it = aspectsToRemove.iterator();
        while(it.hasNext())
        {
            QName aspectQName = it.next();
            TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName);
            if(w == null || secondaryTypeAspects.contains(aspectQName))
            {
                // the type is not exposed or is in the secondary types to set, so remove it from the to remove set
                it.remove();
            }
        }
        // first, remove aspects
        for(QName aspectQName : aspectsToRemove)
        {
            nodeService.removeAspect(nodeRef, aspectQName);
            // aspect is being removed so remove all of its properties from the propsToAdd map
            TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName);
            for(PropertyDefinitionWrapper wr : w.getProperties())
            {
                String propertyId = wr.getPropertyId();
                propsToAdd.remove(propertyId);
            }
        }
        // add aspects and properties
        for(QName aspectQName : toAdd)
        {
            nodeService.addAspect(nodeRef, aspectQName, null);
            
            // get aspect properties
            AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName);
            Map aspectPropDefs = aspectDef.getProperties();
            TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName);
            // for each aspect property...
            for(QName propQName : aspectPropDefs.keySet())
            {
                // find CMIS property id
                PropertyDefinitionWrapper property = w.getPropertyByQName(propQName);
                String propertyId = property.getPropertyId();
                if(!propsToAdd.containsKey(propertyId))
                {
                    TypeDefinitionWrapper propType = property.getOwningType();
                    // CMIS 1.1 secondary types specification requires that all secondary type properties are set
                    // property not included in propsToAdd, add it with null value
                    Pair pair = new Pair(propType, null);
                    propsToAdd.put(propertyId, pair);
                }
            }
        }
    }
    public void addSecondaryTypes(NodeRef nodeRef, List secondaryTypes)
    {
    	if(secondaryTypes != null && secondaryTypes.size() > 0)
    	{
	    	for(String secondaryType : secondaryTypes)
	    	{
	            TypeDefinitionWrapper type = getType(secondaryType);
	            if (type == null)
	            {
	                throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType);
	            }
	    		nodeService.addAspect(nodeRef, type.getAlfrescoName(), null);
	    	}
    	}
    }
    
    public void removeSecondaryTypes(NodeRef nodeRef, List secondaryTypes)
    {
    	if(secondaryTypes != null && secondaryTypes.size() > 0)
    	{
	    	for(String secondaryType : secondaryTypes)
	    	{
	            TypeDefinitionWrapper type = getType(secondaryType);
	            if (type == null)
	            {
	                throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType);
	            }
	    		nodeService.removeAspect(nodeRef, type.getAlfrescoName());
	    	}
    	}
    }
    private void setAspectProperties(NodeRef nodeRef, boolean isNameChanging, CmisExtensionElement aspectExtension)
    {
        if (aspectExtension.getChildren() == null)
        {
            return;
        }
        List aspectsToAdd = new ArrayList();
        List aspectsToRemove = new ArrayList();
        Map> aspectProperties = new HashMap>();
        for (CmisExtensionElement extension : aspectExtension.getChildren())
        {
            if (!ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()))
            {
                continue;
            }
            if (ASPECTS_TO_ADD.equals(extension.getName()) && (extension.getValue() != null))
            {
                aspectsToAdd.add(extension.getValue());
            }
            else if (ASPECTS_TO_REMOVE.equals(extension.getName()) && (extension.getValue() != null))
            {
                aspectsToRemove.add(extension.getValue());
            }
            else if (PROPERTIES.equals(extension.getName()) && (extension.getChildren() != null))
            {
                for (CmisExtensionElement property : extension.getChildren())
                {
                    if (!property.getName().startsWith("property"))
                    {
                        continue;
                    }
                    String propertyId = (property.getAttributes() == null ? null : property.getAttributes().get(
                            "propertyDefinitionId"));
                    if ((propertyId == null) || (property.getChildren() == null))
                    {
                        continue;
                    }
                    PropertyType propertyType = PropertyType.STRING;
                    DatatypeFactory df = null;
                    if (property.getName().equals("propertyBoolean"))
                    {
                        propertyType = PropertyType.BOOLEAN;
                    }
                    else if (property.getName().equals("propertyInteger"))
                    {
                        propertyType = PropertyType.INTEGER;
                    }
                    else if (property.getName().equals("propertyDateTime"))
                    {
                        propertyType = PropertyType.DATETIME;
                        try
                        {
                            df = DatatypeFactory.newInstance();
                        }
                        catch (DatatypeConfigurationException e)
                        {
                            throw new CmisRuntimeException("Aspect conversation exception: " + e.getMessage(), e);
                        }
                    }
                    else if (property.getName().equals("propertyDecimal"))
                    {
                        propertyType = PropertyType.DECIMAL;
                    }
                    ArrayList values = new ArrayList();
                    if (property.getChildren() != null)
                    {
//                        try
//                        {
                            for (CmisExtensionElement valueElement : property.getChildren())
                            {
                                if ("value".equals(valueElement.getName()))
                                {
                                    switch (propertyType)
                                    {
                                    case BOOLEAN:
                                    	try
                                    	{
                                    		values.add(Boolean.parseBoolean(valueElement.getValue()));
	                                	}
	                                	catch (Exception e)
	                                	{
	                                		throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
	                                	}
                                        break;
                                    case DATETIME:
                                    	try
                                    	{
	                                        values.add(df.newXMLGregorianCalendar(valueElement.getValue())
	                                                .toGregorianCalendar());
                                    	}
                                    	catch (Exception e)
                                    	{
                                    		throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
                                    	}
                                        break;
                                    case INTEGER:
                                    	BigInteger value = null;
                                    	try
                                    	{
                                    		value = new BigInteger(valueElement.getValue());
                                    	}
                                    	catch (Exception e)
                                    	{
                                    		throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
                                    	}
                                    	// overflow check
                                    	PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findProperty(propertyId);
                                        if(propDef == null)
                                        {
                                            throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!");
                                        }
                                        
                                        QName propertyQName = propDef.getPropertyAccessor().getMappedProperty();
                                        if (propertyQName == null)
                                        {
                                            throw new CmisConstraintException("Unable to set property " + propertyId + "!");
                                        }
                                		org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName);
                                		QName dataDef = def.getDataType().getName();
                                		if (dataDef.equals(DataTypeDefinition.INT) && (value.compareTo(maxInt) > 0 || value.compareTo(minInt) < 0))
                                		{
                                			throw new CmisConstraintException("Value is out of range for property " + propertyId);
                                		}
                                		if (dataDef.equals(DataTypeDefinition.LONG) && (value.compareTo(maxLong) > 0 || value.compareTo(minLong) < 0 ))
                                		{
                                			throw new CmisConstraintException("Value is out of range for property " + propertyId);
                                		}
                                    	
                                        values.add(value);
                                        break;
                                    case DECIMAL:
                                    	try
                                    	{
                                    		values.add(new BigDecimal(valueElement.getValue()));
                                    	}
                                    	catch (Exception e)
                                    	{
                                    		throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
                                    	}
                                        break;
                                    default:
                                        values.add(valueElement.getValue());
                                    }
                                }
                            }
                    }
                    aspectProperties.put(QName.createQName(propertyId, namespaceService), values);
                }
            }
        }
        // remove and add aspects
        String aspectType = null;
        try
        {
            for (String aspect : aspectsToRemove)
            {
                aspectType = aspect;
                TypeDefinitionWrapper type = getType(aspect);
                if (type == null)
                {
                    throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
                }
                QName typeName = type.getAlfrescoName();
                // if aspect is hidden aspect, remove only if hidden node is not client controlled
                if(typeName.equals(ContentModel.ASPECT_HIDDEN))
                {
                	if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
                	{
                		// manipulate hidden aspect only if client controlled
                		nodeService.removeAspect(nodeRef, typeName);	
                	}
                	
                	
                	
//                	if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
//                	{
//                    	nodeService.removeAspect(nodeRef, typeName);	
//                	}
                }
                else
                {
                	nodeService.removeAspect(nodeRef, typeName);
                }
            }
            for (String aspect : aspectsToAdd)
            {
                aspectType = aspect;
                TypeDefinitionWrapper type = getType(aspect);
                if (type == null)
                {
                    throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
                }
                QName typeName = type.getAlfrescoName();
                // if aspect is hidden aspect, remove only if hidden node is not client controlled
                if(typeName.equals(ContentModel.ASPECT_HIDDEN))
                {
                	if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
                	{
                		// manipulate hidden aspect only if client controlled
                		nodeService.addAspect(nodeRef, type.getAlfrescoName(),
    	                        Collections. emptyMap());	
                	}
                	
//                	if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
//                	{
//                		nodeService.addAspect(nodeRef, type.getAlfrescoName(),
//    	                        Collections. emptyMap());	
//                	}
                }
                else
                {
                	nodeService.addAspect(nodeRef, type.getAlfrescoName(),
	                        Collections. emptyMap());
                }
            }
        }
        catch (InvalidAspectException e)
        {
            throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
        }
        catch (InvalidNodeRefException e)
        {
            throw new CmisInvalidArgumentException("Invalid node: " + nodeRef);
        }
        // set property
        for (Map.Entry> property : aspectProperties.entrySet())
        {
        	QName propertyQName = property.getKey();
            if (property.getValue().isEmpty())
            {
                if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey()))
                {
                	if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
                	{
                		// manipulate hidden aspect property only if client controlled
                        nodeService.removeProperty(nodeRef, propertyQName);
                	}
                }
                else
                {
                	nodeService.removeProperty(nodeRef, property.getKey());	
                }
            }
            else
            {
                if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey()))
                {
                	if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
                	{
                		// manipulate hidden aspect property only if client controlled
                    	nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property
                    			.getValue().get(0) : (Serializable) property.getValue());
                	}
                }
                else
                {
            	Serializable value = (Serializable)property.getValue();
                	nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property
                        .getValue().get(0) : value);
                }
            }
        }
    }
    /**
     * Sets a property value.
     */
	public void setProperty(NodeRef nodeRef, TypeDefinitionWrapper type, String propertyId, Serializable value)
    {
        if (propertyId == null)
        {
            throw new CmisInvalidArgumentException("Cannot process not null property!");
        }
        PropertyDefinitionWrapper propDef = type.getPropertyById(propertyId);
        if (propDef == null)
        {
            throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!");
        }
        Updatability updatability = propDef.getPropertyDefinition().getUpdatability();
        if ((updatability == Updatability.READONLY)
                || (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef)))
        {
            throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!");
        }
        if(propDef.getPropertyId().equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS))
        {
            throw new IllegalArgumentException("Cannot process " + PropertyIds.SECONDARY_OBJECT_TYPE_IDS + " in setProperty");
        }
        else
        {
        	QName propertyQName = propDef.getPropertyAccessor().getMappedProperty();
        	if (propertyQName == null)
        	{
        		throw new CmisConstraintException("Unable to set property " + propertyId + "!");
        	}
        
        	if (propertyId.equals(PropertyIds.NAME))
        	{
        		if (!(value instanceof String))
        		{
        			throw new CmisInvalidArgumentException("Object name must be a string!");
        		}
        		try
        		{
        			fileFolderService.rename(nodeRef, value.toString());
        		}
        		catch (FileExistsException e)
        		{
        			throw new CmisContentAlreadyExistsException("An object with this name already exists!", e);
        		}
        		catch (FileNotFoundException e)
        		{
        			throw new CmisInvalidArgumentException("Object with id " + nodeRef.getId() + " not found!");
        		}
        	}
        	else
            {
                // overflow check
                if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.INTEGER && value instanceof BigInteger)
                {
                    org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName);
                    QName dataDef = def.getDataType().getName();
                    BigInteger bigValue = (BigInteger) value;
                    if ((bigValue.compareTo(maxInt) > 0 || bigValue.compareTo(minInt) < 0) && dataDef.equals(DataTypeDefinition.INT))
                    {
                        throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName());
                    }
                    if ((bigValue.compareTo(maxLong) > 0 || bigValue.compareTo(minLong) < 0) && dataDef.equals(DataTypeDefinition.LONG))
                    {
                        throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName());
                    }
                }
                nodeService.setProperty(nodeRef, propertyQName, value);
            }
        }
    }
    private Serializable getValue(PropertyData> property, boolean isMultiValue)
    {
        if ((property.getValues() == null) || (property.getValues().isEmpty()))
        {
            return null;
        }
        if (isMultiValue)
        {
            return (Serializable) property.getValues();
        }
        return (Serializable) property.getValues().get(0);
    }
    /**
     * Returns content changes.
     */
    public ObjectList getContentChanges(Holder changeLogToken, BigInteger maxItems)
    {
        final ObjectListImpl result = new ObjectListImpl();
        result.setObjects(new ArrayList());
        EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true)
        {
            @Override
            public boolean handleAuditEntry(Long entryId, String user, long time, Map values)
            {
                result.getObjects().addAll(createChangeEvents(time, values));
                return super.handleAuditEntry(entryId, user, time, values);
            }
        };
        Long from = null;
        if ((changeLogToken != null) && (changeLogToken.getValue() != null))
        {
            try
            {
                from = Long.parseLong(changeLogToken.getValue());
            }
            catch (NumberFormatException e)
            {
                throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken);
            }
        }
        AuditQueryParameters params = new AuditQueryParameters();
        params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);
        params.setForward(true);
        params.setFromId(from);
        int maxResults = (maxItems == null ? 0 : maxItems.intValue());
        maxResults = (maxResults < 1 ? 0 : maxResults + 1);
        auditService.auditQuery(changeLogCollectingCallback, params, maxResults);
        String newChangeLogToken = null;
        if (maxResults > 0)
        {
            if (result.getObjects().size() >= maxResults)
            {
            	StringBuilder clt = new StringBuilder();
                newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString());
                result.getObjects().remove(result.getObjects().size() - 1).getId();
                result.setHasMoreItems(true);
            }
            else
            {
                result.setHasMoreItems(false);
            }
        }
        if (changeLogToken != null)
        {
            changeLogToken.setValue(newChangeLogToken);
        }
        return result;
    }
    @SuppressWarnings("unchecked")
    private List createChangeEvents(long time, Map values)
    {
        List result = new ArrayList();
        if ((values == null) || (values.size() == 0))
        {
            return result;
        }
        GregorianCalendar changeTime = new GregorianCalendar();
        changeTime.setTimeInMillis(time);
        String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/";
        for (Entry entry : values.entrySet())
        {
            if ((entry.getKey() == null) || (!(entry.getValue() instanceof Map)))
            {
                continue;
            }
            String path = entry.getKey();
            if (!path.startsWith(appPath))
            {
                continue;
            }
            ChangeType changeType = null;
            String changePath = path.substring(appPath.length()).toLowerCase();
            for (ChangeType c : ChangeType.values())
            {
                if (changePath.startsWith(c.value().toLowerCase()))
                {
                    changeType = c;
                    break;
                }
            }
            if (changeType == null)
            {
                continue;
            }
            Map valueMap = (Map) entry.getValue();
            String objectId = (String) valueMap.get(CMISChangeLogDataExtractor.KEY_OBJECT_ID);
            // build object
            ObjectDataImpl object = new ObjectDataImpl();
            result.add(object);
            PropertiesImpl properties = new PropertiesImpl();
            object.setProperties(properties);
            PropertyIdImpl objectIdProperty = new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId);
            properties.addProperty(objectIdProperty);
            ChangeEventInfoDataImpl changeEvent = new ChangeEventInfoDataImpl();
            object.setChangeEventInfo(changeEvent);
            changeEvent.setChangeType(changeType);
            changeEvent.setChangeTime(changeTime);
        }
        return result;
    }
    private class EntryIdCallback implements AuditQueryCallback
    {
        private final boolean valuesRequired;
        private Long entryId;
        public EntryIdCallback(boolean valuesRequired)
        {
            this.valuesRequired = valuesRequired;
        }
        public String getEntryId()
        {
            return entryId == null ? null : entryId.toString();
        }
        public boolean valuesRequired()
        {
            return this.valuesRequired;
        }
        public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time,
                Map values)
        {
            if (applicationName.equals(CMIS_CHANGELOG_AUDIT_APPLICATION))
            {
                return handleAuditEntry(entryId, user, time, values);
            }
            return true;
        }
        public boolean handleAuditEntry(Long entryId, String user, long time, Map values)
        {
            this.entryId = entryId;
            return true;
        }
        public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error)
        {
            throw new CmisRuntimeException("Audit entry " + entryId + ": " + errorMsg, error);
        }
    };
    // --------------------------------------------------------------
    // OpenCMIS methods
    // --------------------------------------------------------------
    /**
     * Returns the value of the given property if it exists and is of the
     * correct type.
     */
    public String getStringProperty(Properties properties, String propertyId)
    {
        if ((properties == null) || (properties.getProperties() == null))
        {
            return null;
        }
        PropertyData> property = properties.getProperties().get(propertyId);
        if (!(property instanceof PropertyString))
        {
            return null;
        }
        return ((PropertyString) property).getFirstValue();
    }
    /**
     * Returns the value of the given property if it exists and is of the
     * correct type.
     */
    public String getIdProperty(Properties properties, String propertyId)
    {
        if ((properties == null) || (properties.getProperties() == null))
        {
            return null;
        }
        PropertyData> property = properties.getProperties().get(propertyId);
        if (!(property instanceof PropertyId))
        {
            return null;
        }
        return ((PropertyId) property).getFirstValue();
    }
    public String getNameProperty(Properties properties, String fallback)
    {
        String name = getStringProperty(properties, PropertyIds.NAME);
        if ((name == null) || (name.trim().length() == 0))
        {
            if (fallback == null)
            {
                throw new CmisInvalidArgumentException("Property " + PropertyIds.NAME + " must be set!");
            }
            else
            {
                name = fallback;
            }
        }
        return name;
    }
    public String getObjectTypeIdProperty(Properties properties)
    {
        String objectTypeId = getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID);
        if ((objectTypeId == null) || (objectTypeId.trim().length() == 0))
        {
            throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!");
        }
        return objectTypeId;
    }
    public String getSourceIdProperty(Properties properties)
    {
        String id = getIdProperty(properties, PropertyIds.SOURCE_ID);
        if ((id == null) || (id.trim().length() == 0))
        {
            throw new CmisInvalidArgumentException("Property " + PropertyIds.SOURCE_ID + " must be set!");
        }
        return id;
    }
    public String getTargetIdProperty(Properties properties)
    {
        String id = getIdProperty(properties, PropertyIds.TARGET_ID);
        if ((id == null) || (id.trim().length() == 0))
        {
            throw new CmisInvalidArgumentException("Property " + PropertyIds.TARGET_ID + " must be set!");
        }
        return id;
    }
    /**
     * Returns the repository info object.
     */
    public RepositoryInfo getRepositoryInfo(CmisVersion cmisVersion)
    {
        return createRepositoryInfo(cmisVersion);
    }
    /**
     * Returns the repository id.
     */
    public String getRepositoryId()
    {
        return descriptorService.getCurrentRepositoryDescriptor().getId();
    }
    /**
     * Creates the repository info object.
     */
    private RepositoryInfo createRepositoryInfo(CmisVersion cmisVersion)
    {
        Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor();
        // get change token
        boolean auditEnabled = auditService.isAuditEnabled(CMIS_CHANGELOG_AUDIT_APPLICATION, "/"
                + CMIS_CHANGELOG_AUDIT_APPLICATION);
        String latestChangeLogToken = null;
        if (auditEnabled)
        {
            EntryIdCallback auditQueryCallback = new EntryIdCallback(false);
            AuditQueryParameters params = new AuditQueryParameters();
            params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);
            params.setForward(false);
            auditService.auditQuery(auditQueryCallback, params, 1);
            String entryId = auditQueryCallback.getEntryId();
            // MNT-13529
            // add initial change log token
            latestChangeLogToken = entryId == null ? "0" : entryId;
        }
        // compile repository info
        RepositoryInfoImpl ri = new RepositoryInfoImpl();
        ri.setId(currentDescriptor.getId());
        ri.setName(currentDescriptor.getName());
        ri.setDescription(currentDescriptor.getName());
        ri.setVendorName("Alfresco");
        ri.setProductName("Alfresco " + descriptorService.getServerDescriptor().getEdition());
        ri.setProductVersion(currentDescriptor.getVersion());
        NodeRef rootNodeRef = getRootNodeRef();
        ri.setRootFolder(constructObjectId(rootNodeRef, null));
        ri.setCmisVersion(cmisVersion);
        ri.setChangesIncomplete(true);
        ri.setChangesOnType(Arrays.asList(new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER }));
        ri.setLatestChangeLogToken(latestChangeLogToken);
        ri.setPrincipalAnonymous(AuthenticationUtil.getGuestUserName());
        ri.setPrincipalAnyone(PermissionService.ALL_AUTHORITIES);
        RepositoryCapabilitiesImpl repCap = new RepositoryCapabilitiesImpl();
        ri.setCapabilities(repCap);
        repCap.setAllVersionsSearchable(false);
        repCap.setCapabilityAcl(CapabilityAcl.MANAGE);
        repCap.setCapabilityChanges(auditEnabled ? CapabilityChanges.OBJECTIDSONLY : CapabilityChanges.NONE);
        repCap.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME);
        repCap.setCapabilityJoin(CapabilityJoin.NONE);
        repCap.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
        repCap.setCapabilityRendition(CapabilityRenditions.READ);
        repCap.setIsPwcSearchable(false);
        repCap.setIsPwcUpdatable(true);
        repCap.setSupportsGetDescendants(true);
        repCap.setSupportsGetFolderTree(true);
        repCap.setSupportsMultifiling(true);
        repCap.setSupportsUnfiling(false);
        repCap.setSupportsVersionSpecificFiling(false);
        AclCapabilitiesDataImpl aclCap = new AclCapabilitiesDataImpl();
        ri.setAclCapabilities(aclCap);
        aclCap.setAclPropagation(AclPropagation.PROPAGATE);
        aclCap.setSupportedPermissions(SupportedPermissions.BOTH);
        aclCap.setPermissionDefinitionData(repositoryPermissions);
        aclCap.setPermissionMappingData(permissionMappings);
        return ri;
    }
    private List getRepositoryPermissions()
    {
        ArrayList result = new ArrayList();
        Set all = permissionModelDao.getAllExposedPermissions();
        for (PermissionReference pr : all)
        {
            result.add(createPermissionDefinition(pr));
        }
        PermissionReference allPermission = permissionModelDao.getPermissionReference(null,
                PermissionService.ALL_PERMISSIONS);
        result.add(createPermissionDefinition(allPermission));
        PermissionDefinitionDataImpl cmisPermission;
        cmisPermission = new PermissionDefinitionDataImpl();
        cmisPermission.setId(BasicPermissions.READ);
        cmisPermission.setDescription("CMIS Read");
        result.add(cmisPermission);
        cmisPermission = new PermissionDefinitionDataImpl();
        cmisPermission.setId(BasicPermissions.WRITE);
        cmisPermission.setDescription("CMIS Write");
        result.add(cmisPermission);
        cmisPermission = new PermissionDefinitionDataImpl();
        cmisPermission.setId(BasicPermissions.ALL);
        cmisPermission.setDescription("CMIS All");
        result.add(cmisPermission);
        return result;
    }
    private PermissionDefinition createPermissionDefinition(PermissionReference pr)
    {
        PermissionDefinitionDataImpl permission = new PermissionDefinitionDataImpl();
        permission.setId(pr.getQName().toString() + "." + pr.getName());
        permission.setDescription(permission.getId());
        return permission;
    }
    private Map getPermissionMappings()
    {
        Map result = new HashMap();
        for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class))
        {
            for (Map.Entry> m : e.getPermissionMapping().entrySet())
            {
                PermissionMappingDataImpl mapping = new PermissionMappingDataImpl();
                mapping.setKey(m.getKey());
                mapping.setPermissions(m.getValue());
                result.put(mapping.getKey(), mapping);
            }
        }
        return result;
    }
    
    private CMISRenditionMapping getRenditionMapping()
    {
        CMISRenditionMapping renditionMapping = (CMISRenditionMapping)singletonCache.get(KEY_CMIS_RENDITION_MAPPING_NODEREF);
        if (renditionMapping == null)
        {
            renditionMapping = new CMISRenditionMapping(nodeService, contentService, renditionService,
                    transactionService, kindToRenditionNames);
            
            singletonCache.put(KEY_CMIS_RENDITION_MAPPING_NODEREF, renditionMapping);
        }
        return renditionMapping;
    }
}