org.alfresco.opencmis.CMISConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.opencmis.CMISConnector.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.opencmis;

import java.io.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.service.cmr.activities.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.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.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.
 * <p/>
 * 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<ApplicationContextEvent>, 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<String, QName> SORT_PROPERTY_MAPPINGS = new HashMap<String, QName>();

    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 char REPLACEMENT_CHAR = '_';
    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<String, List<String>> kindToRenditionNames;

    // note: cache is tenant-aware (if using TransctionalCache impl)

    private SimpleCache<String, Object> 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<PermissionDefinition> repositoryPermissions;
    private Map<String, PermissionMapping> 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<String, List<String>> 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<String, Object> 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<QName, Boolean> getSortProperty(String cmisPropertyName, String direction) {
        QName sortPropName = null;
        Pair<QName, Boolean> 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<QName, Boolean>(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 NodeRef
     */
    public void createThumbnails(NodeRef nodeRef, Set<String> 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 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<NodeRef>() {
                public NodeRef doWork() throws Exception {
                    return transactionService.getRetryingTransactionHelper()
                            .doInTransaction(new RetryingTransactionCallback<NodeRef>() {
                                public NodeRef execute() throws Exception {
                                    NodeRef root = nodeService.getRootNode(storeRef);
                                    List<NodeRef> 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, null, null, true);
    }

    /**
     * Creates an object info object.
     */
    public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, QName nodeType, Map<QName, Serializable> nodeProps,
            VersionHistory versionHistory, boolean checkExists) {
        return new CMISNodeInfoImpl(this, nodeRef, nodeType, nodeProps, versionHistory, checkExists);
    }

    /**
     * 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<String, Serializable> versionProperties = new HashMap<String, Serializable>(2);
            versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR);
            versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial version");
            versionService.createVersion(nodeRef, versionProperties);
        }

        Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(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 <code>null</code> if no type
     * definition could be found.
     */
    public TypeDefinitionWrapper getType(NodeRef nodeRef) {
        try {
            QName typeQName = nodeService.getType(nodeRef);
            return getOpenCMISDictionaryService().findNodeType(typeQName);
        } catch (InvalidNodeRefException inre) {
            return null;
        }
    }

    /**
     * Returns the type definition of an association or <code>null</code> 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 <code>null</code> 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)) {
                nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null);
            }

            // MNT-14687 : Creating a document as checkedout and then cancelling the checkout should delete the document
            nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_CREATED_CHECKEDOUT, null);

            getCheckOutCheckInService().checkout(nodeRef);
        } else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR)) {
            if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) {
                nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null);
            }

            Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(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<String> childTypes = (List<String>) 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, 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<Ace>) Collections.EMPTY_LIST));
                result.setIsExactAcl(Boolean.FALSE);
            }
        } 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<RenditionData> 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<Void>() {
                    @Override
                    public Void doWork() throws Exception {
                        Acl acl = getACL(info.getCurrentNodeNodeRef(), false);
                        if (acl != null) {
                            result.setAcl(acl);
                            result.setIsExactAcl(acl.isExact());
                        }
                        return null;
                    }
                });
            }

            // add aspects
            List<CmisExtensionElement> 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 NodeRef
     * @param name String
     * @param mimeType String
     * @param contentSize long
     * @param encoding String
     * @param range String
     */
    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<QName, Serializable> props = new HashMap<QName, Serializable>();
            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<String> propertyIds = new HashSet<>();
            Set<String> filterSet = splitFilter(filter);

            Set<QName> aspects = info.getNodeAspects();
            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<String> 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 getAssocProperties(CMISNodeInfo info, String filter) {
        PropertiesImpl result = new PropertiesImpl();

        Set<String> 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<CmisExtensionElement> getAspectExtensions(CMISNodeInfo info, String filter,
            Set<String> alreadySetProperties) {
        List<CmisExtensionElement> extensions = new ArrayList<CmisExtensionElement>();
        Set<String> propertyIds = new HashSet<String>(alreadySetProperties);
        List<CmisExtensionElement> propertyExtensionList = new ArrayList<CmisExtensionElement>();
        Set<String> filterSet = splitFilter(filter);

        Set<QName> aspects = info.getNodeAspects();
        for (QName aspect : aspects) {
            TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect);
            if (aspectType == null) {
                continue;
            }

            AspectDefinition aspectDefinition = dictionaryService.getAspect(aspect);
            Map<QName, org.alfresco.service.cmr.dictionary.PropertyDefinition> 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<String, String> attributes = new HashMap<String, String>();
        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<CmisExtensionElement> propertyValues = new ArrayList<CmisExtensionElement>();
        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 MNT-15044
        // Filter for AtomPub and Web services bindings only. Browser/json binding already encodes.
        if (AlfrescoCmisServiceCall.get() != null
                && (CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding())
                        || CallContext.BINDING_WEBSERVICES.equals(AlfrescoCmisServiceCall.get().getBinding()))) {
            return filterXmlRestrictedCharacters(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<Boolean>) value);
            } else {
                ((PropertyBooleanImpl) result).setValue((Boolean) value);
            }
            break;
        case DATETIME:
            result = new PropertyDateTimeImpl();
            if (value instanceof List) {
                ((PropertyDateTimeImpl) result).setValues((List<GregorianCalendar>) 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<BigDecimal>) 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<String>) value);
            } else {
                ((PropertyHtmlImpl) result).setValue((String) value);
            }
            break;
        case ID:
            result = new PropertyIdImpl();
            if (value instanceof List) {
                ((PropertyIdImpl) result).setValues((List<String>) 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<BigInteger>) 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<String>) value);
            } else {
                // MNT-12496 MNT-15044
                // Filter for AtomPub and Web services bindings only. Browser/json binding already encodes.
                if (AlfrescoCmisServiceCall.get() != null && (CallContext.BINDING_ATOMPUB
                        .equals(AlfrescoCmisServiceCall.get().getBinding())
                        || CallContext.BINDING_WEBSERVICES.equals(AlfrescoCmisServiceCall.get().getBinding()))) {
                    ((PropertyStringImpl) result).setValue(filterXmlRestrictedCharacters((String) value));
                } else {
                    ((PropertyStringImpl) result).setValue((String) value);
                }
            }
            break;
        case URI:
            result = new PropertyUriImpl();
            if (value instanceof List) {
                ((PropertyUriImpl) result).setValues((List<String>) 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 filterXmlRestrictedCharacters(String origValue) {
        if (origValue == null) {
            return origValue;
        }
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < origValue.length(); i++) {
            char ch = origValue.charAt(i);
            boolean restricted = (ch < '\u0020') && !(ch == '\t' || ch == '\n' || ch == '\r');
            sb.append(restricted ? REPLACEMENT_CHAR : ch);
        }

        return sb.toString();
    }

    private Set<String> splitFilter(String filter) {
        if (filter == null) {
            return null;
        }

        if (filter.trim().length() == 0) {
            return null;
        }

        Set<String> result = new HashSet<String>();
        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<Action> allowableActions = new HashSet<Action>();
        result.setAllowableActions(allowableActions);

        for (CMISActionEvaluator evaluator : info.getType().getActionEvaluators().values()) {
            if (evaluator.isAllowed(info)) {
                allowableActions.add(evaluator.getAction());
            }
        }

        return result;
    }

    public List<ObjectData> getRelationships(NodeRef nodeRef,
            IncludeRelationships includeRelationships/*, CmisVersion cmisVersion*/) {
        List<ObjectData> result = new ArrayList<ObjectData>();

        if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) {
            // relationships from and to versions are not preserved
            return result;
        }

        // get relationships
        List<AssociationRef> assocs = new ArrayList<AssociationRef>();
        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<ObjectData>());

        if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) {
            // relationships from and to versions are not preserved
            return result;
        }

        // get relationships
        List<AssociationRef> assocs = new ArrayList<AssociationRef>();
        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<RenditionData> 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<AccessPermission> ordered = new ArrayList<AccessPermission>(
                permissionService.getAllSetPermissions(nodeRef));
        Collections.sort(ordered, new AccessPermissionComparator());

        // remove denied permissions and create OpenCMIS objects
        Map<String, Map<Boolean, AccessControlEntryImpl>> aceMap = new HashMap<String, Map<Boolean, AccessControlEntryImpl>>();
        for (AccessPermission entry : ordered) {
            if (entry.getAccessStatus() == AccessStatus.ALLOWED) {
                // add allowed entries
                Map<Boolean, AccessControlEntryImpl> directAce = aceMap.get(entry.getAuthority());
                if (directAce == null) {
                    directAce = new HashMap<Boolean, AccessControlEntryImpl>();
                    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<String>());
                    ace.setDirect(entry.isSetDirectly());
                    directAce.put(entry.isSetDirectly(), ace);
                }

                ace.getPermissions().add(entry.getPermission());
            } else if (entry.getAccessStatus() == AccessStatus.DENIED) {
                // remove denied entries
                Map<Boolean, AccessControlEntryImpl> 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<Ace> aces = new ArrayList<Ace>();
        result.setAces(aces);
        for (Map<Boolean, AccessControlEntryImpl> bothAces : aceMap.values()) {
            // get, translate and set direct ACE
            AccessControlEntryImpl directAce = bothAces.get(true);
            if ((directAce != null) && (!directAce.getPermissions().isEmpty())) {
                List<String> 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<String> 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<String> translatePermissionsToCMIS(List<String> permissions, boolean onlyBasicPermissions) {
        Set<String> result = new TreeSet<String>();

        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<PermissionReference> longForms = permissionModelDao.getGranteePermissions(permissionReference);

                HashSet<String> shortForms = new HashSet<String>();
                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<String>(result);
    }

    public static class AccessPermissionComparator implements Comparator<AccessPermission> {
        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<AccessPermission> permissions = permissionService.getAllSetPermissions(nodeRef);

            // get only direct ACE since only those can be removed
            Acl onlyDirectAcl = excludeInheritedAces(nodeRef, removeAces);

            for (Ace ace : onlyDirectAcl.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);
                }
            }
        }
    }

    /**
     * Converts Acl to map and ignore the indirect ACEs
     * 
     * @param acl Acl
     * @return Map
     */
    private Map<String, Set<String>> convertAclToMap(Acl acl) {
        Map<String, Set<String>> result = new HashMap<String, Set<String>>();

        if (acl == null || acl.getAces() == null) {
            return result;
        }

        for (Ace ace : acl.getAces()) {
            // don't consider indirect ACEs - we can't change them
            if (!ace.isDirect()) {
                // ignore
                continue;
            }

            // although a principal must not be null, check it
            if (ace.getPrincipal() == null || ace.getPrincipal().getId() == null) {
                // ignore
                continue;
            }

            Set<String> permissions = result.get(ace.getPrincipal().getId());
            if (permissions == null) {
                permissions = new HashSet<String>();
                result.put(ace.getPrincipal().getId(), permissions);
            }

            if (ace.getPermissions() != null) {
                permissions.addAll(ace.getPermissions());
            }
        }

        return result;
    }

    /**
     * Filter acl to ignore inherited ACEs
     * 
     * @param nodeRef NodeRef
     * @param acl Acl
     * @return Acl
     */
    protected Acl excludeInheritedAces(NodeRef nodeRef, Acl acl) {

        List<Ace> newAces = new ArrayList<Ace>();
        Acl allACLs = getACL(nodeRef, false);

        Map<String, Set<String>> originalsAcls = convertAclToMap(allACLs);
        Map<String, Set<String>> newAcls = convertAclToMap(acl);

        // iterate through the original ACEs
        for (Map.Entry<String, Set<String>> ace : originalsAcls.entrySet()) {

            // add permissions
            Set<String> addPermissions = newAcls.get(ace.getKey());
            if (addPermissions != null) {
                ace.getValue().addAll(addPermissions);
            }

            // create new ACE
            if (!ace.getValue().isEmpty()) {
                newAces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(ace.getKey()),
                        new ArrayList<String>(ace.getValue())));
            }
        }

        return new AccessControlListImpl(newAces);
    }

    /**
     * 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<String> permissions = translatePermissionsFromCMIS(ace.getPermissions());
            for (String permission : permissions) {
                permissionService.setPermission(nodeRef, principalId, permission, true);
            }
        }
    }

    private List<String> translatePermissionsFromCMIS(List<String> permissions) {
        List<String> result = new ArrayList<String>();

        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<String> 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<ObjectData>());

        // 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<String, Serializable> 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<RenditionData> 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<String, PropertyData<?>> incomingPropsMap = properties.getProperties();
        if (incomingPropsMap == null) {
            return;
        }

        // extract property data into an easier to use form
        Map<String, Pair<TypeDefinitionWrapper, Serializable>> propsMap = new HashMap<String, Pair<TypeDefinitionWrapper, Serializable>>();
        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<TypeDefinitionWrapper, Serializable> pair = new Pair<TypeDefinitionWrapper, Serializable>(propType,
                    value);
            propsMap.put(propertyId, pair);
        }

        // Need to do deal with secondary types first
        Pair<TypeDefinitionWrapper, Serializable> 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<CmisExtensionElement> 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<String, Pair<TypeDefinitionWrapper, Serializable>> propsToAdd) {
        // diff existing aspects and secondaryTypes/aspects list
        Set<QName> existingAspects = nodeService.getAspects(nodeRef);
        Set<QName> secondaryTypeAspects = new HashSet<QName>();
        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<QName> ignore = new HashSet<QName>();
        ignore.add(ContentModel.ASPECT_REFERENCEABLE);
        ignore.add(ContentModel.ASPECT_LOCALIZED);

        // aspects to add == the list of secondary types - existing aspects - ignored aspects
        Set<QName> toAdd = new HashSet<QName>(secondaryTypeAspects);
        toAdd.removeAll(existingAspects);
        toAdd.removeAll(ignore);

        // aspects to remove == existing aspects - secondary types
        Set<QName> aspectsToRemove = new HashSet<QName>();
        aspectsToRemove.addAll(existingAspects);
        aspectsToRemove.removeAll(ignore);
        Iterator<QName> 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<QName, org.alfresco.service.cmr.dictionary.PropertyDefinition> 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<TypeDefinitionWrapper, Serializable> pair = new Pair<TypeDefinitionWrapper, Serializable>(
                            propType, null);
                    propsToAdd.put(propertyId, pair);
                }
            }
        }
    }

    public void addSecondaryTypes(NodeRef nodeRef, List<String> 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<String> 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<String> aspectsToAdd = new ArrayList<String>();
        List<String> aspectsToRemove = new ArrayList<String>();
        Map<QName, List<Serializable>> aspectProperties = new HashMap<QName, List<Serializable>>();

        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<Serializable> values = new ArrayList<Serializable>();
                    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.<QName, Serializable>emptyMap());
                    }

                    //                   if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
                    //                   {
                    //                      nodeService.addAspect(nodeRef, type.getAlfrescoName(),
                    //                               Collections.<QName, Serializable> emptyMap());   
                    //                   }
                } else {
                    nodeService.addAspect(nodeRef, type.getAlfrescoName(),
                            Collections.<QName, Serializable>emptyMap());
                }
            }
        } catch (InvalidAspectException e) {
            throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
        } catch (InvalidNodeRefException e) {
            throw new CmisInvalidArgumentException("Invalid node: " + nodeRef);
        }

        // set property
        for (Map.Entry<QName, List<Serializable>> 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<String> changeLogToken, BigInteger maxItems) {
        final ObjectListImpl result = new ObjectListImpl();
        result.setObjects(new ArrayList<ObjectData>());

        EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true) {
            @Override
            public boolean handleAuditEntry(Long entryId, String user, long time,
                    Map<String, Serializable> 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<ObjectData> createChangeEvents(long time, Map<String, Serializable> values) {
        List<ObjectData> result = new ArrayList<ObjectData>();

        if ((values == null) || (values.size() == 0)) {
            return result;
        }

        GregorianCalendar changeTime = new GregorianCalendar();
        changeTime.setTimeInMillis(time);

        String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/";

        for (Entry<String, Serializable> 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<String, Serializable> valueMap = (Map<String, Serializable>) 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<String, Serializable> 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<String, Serializable> 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<PermissionDefinition> getRepositoryPermissions() {
        ArrayList<PermissionDefinition> result = new ArrayList<PermissionDefinition>();

        Set<PermissionReference> all = permissionModelDao.getAllPermissions();
        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<String, PermissionMapping> getPermissionMappings() {
        Map<String, PermissionMapping> result = new HashMap<String, PermissionMapping>();

        for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class)) {
            for (Map.Entry<String, List<String>> 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;
    }
}