jp.aegif.nemaki.cmis.service.impl.ObjectServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for jp.aegif.nemaki.cmis.service.impl.ObjectServiceImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2013 aegif.
 *
 * This file is part of NemakiWare.
 *
 * NemakiWare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NemakiWare 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with NemakiWare.
 * If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     linzhixing(https://github.com/linzhixing) - initial API and implementation
 ******************************************************************************/
package jp.aegif.nemaki.cmis.service.impl;

import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.BulkUpdateObjectIdAndChangeToken;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.RenditionData;
import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.FolderTypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.RelationshipTypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.BulkUpdateObjectIdAndChangeTokenImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import jp.aegif.nemaki.businesslogic.ContentService;
import jp.aegif.nemaki.cmis.aspect.CompileService;
import jp.aegif.nemaki.cmis.aspect.ExceptionService;
import jp.aegif.nemaki.cmis.aspect.query.solr.SolrUtil;
import jp.aegif.nemaki.cmis.aspect.type.TypeManager;
import jp.aegif.nemaki.cmis.service.ObjectService;
import jp.aegif.nemaki.cmis.service.ObjectServiceInternal;
import jp.aegif.nemaki.model.AttachmentNode;
import jp.aegif.nemaki.model.Content;
import jp.aegif.nemaki.model.Document;
import jp.aegif.nemaki.model.Folder;
import jp.aegif.nemaki.model.Item;
import jp.aegif.nemaki.model.Policy;
import jp.aegif.nemaki.model.Relationship;
import jp.aegif.nemaki.model.Rendition;
import jp.aegif.nemaki.model.VersionSeries;
import jp.aegif.nemaki.util.DataUtil;
import jp.aegif.nemaki.util.cache.NemakiCachePool;
import jp.aegif.nemaki.util.constant.DomainType;
import jp.aegif.nemaki.util.lock.ThreadLockService;

public class ObjectServiceImpl implements ObjectService {

    private static final Log log = LogFactory.getLog(ObjectServiceImpl.class);

    private TypeManager typeManager;
    private ObjectServiceInternal objectServiceInternal;
    private ContentService contentService;
    private ExceptionService exceptionService;
    private CompileService compileService;
    private SolrUtil solrUtil;
    private NemakiCachePool nemakiCachePool;
    private ThreadLockService threadLockService;
    private int threadMax;

    @Override
    public ObjectData getObjectByPath(CallContext callContext, String repositoryId, String path, String filter,
            Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
            Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) {
        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.invalidArgumentRequired("objectId", path);
        // FIXME path is not preserved in db.
        Content content = contentService.getContentByPath(repositoryId, path);

        // TODO create objectNotFoundByPath method
        exceptionService.objectNotFoundByPath(DomainType.OBJECT, content, path);

        Lock lock = threadLockService.getReadLock(repositoryId, content.getId());
        try {
            lock.lock();

            exceptionService.permissionDenied(callContext, repositoryId,
                    PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content);

            // //////////////////
            // Body of the method
            // //////////////////
            return compileService.compileObjectData(callContext, repositoryId, content, filter,
                    includeAllowableActions, includeRelationships, renditionFilter, includeAcl);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public ObjectData getObject(CallContext callContext, String repositoryId, String objectId, String filter,
            Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
            Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) {

        exceptionService.invalidArgumentRequired("objectId", objectId);

        Lock lock = threadLockService.getReadLock(repositoryId, objectId);
        try {
            lock.lock();

            // //////////////////
            // General Exception
            // //////////////////
            Content content = contentService.getContent(repositoryId, objectId);
            // WORK AROUND: getObject(versionSeriesId) is interpreted as
            // getDocumentOflatestVersion
            if (content == null) {
                VersionSeries versionSeries = contentService.getVersionSeries(repositoryId, objectId);
                if (versionSeries != null) {
                    content = contentService.getDocumentOfLatestVersion(repositoryId, objectId);
                }
            }
            exceptionService.objectNotFound(DomainType.OBJECT, content, objectId);
            exceptionService.permissionDenied(callContext, repositoryId,
                    PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content);

            // //////////////////
            // Body of the method
            // //////////////////
            ObjectData object = compileService.compileObjectData(callContext, repositoryId, content, filter,
                    includeAllowableActions, includeRelationships, null, includeAcl);

            return object;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public ContentStream getContentStream(CallContext callContext, String repositoryId, String objectId,
            String streamId, BigInteger offset, BigInteger length) {

        exceptionService.invalidArgumentRequired("objectId", objectId);

        Lock lock = threadLockService.getReadLock(repositoryId, objectId);
        try {
            lock.lock();

            // //////////////////
            // General Exception
            // //////////////////
            Content content = contentService.getContent(repositoryId, objectId);
            exceptionService.objectNotFound(DomainType.OBJECT, content, objectId);
            exceptionService.permissionDenied(callContext, repositoryId,
                    PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content);

            // //////////////////
            // Body of the method
            // //////////////////
            if (streamId == null) {
                return getContentStreamInternal(repositoryId, content, offset, length);
            } else {
                return getRenditionStream(repositoryId, content, streamId);
            }
        } finally {
            lock.unlock();
        }
    }

    // TODO Implement HTTP range(offset and length of stream), though it is not
    // obligatory.
    private ContentStream getContentStreamInternal(String repositoryId, Content content, BigInteger rangeOffset,
            BigInteger rangeLength) {
        if (!content.isDocument()) {
            exceptionService.constraint(content.getId(),
                    "getContentStream cannnot be invoked to other than document type.");
        }
        Document document = (Document) content;
        exceptionService.constraintContentStreamDownload(repositoryId, document);
        AttachmentNode attachment = contentService.getAttachment(repositoryId, document.getAttachmentNodeId());
        attachment.setRangeOffset(rangeOffset);
        attachment.setRangeLength(rangeLength);

        // Set content stream
        BigInteger length = BigInteger.valueOf(attachment.getLength());
        String name = attachment.getName();
        String mimeType = attachment.getMimeType();
        InputStream is = attachment.getInputStream();
        ContentStream cs = new ContentStreamImpl(name, length, mimeType, is);

        return cs;
    }

    private ContentStream getRenditionStream(String repositoryId, Content content, String streamId) {
        if (!content.isDocument() && !content.isFolder()) {
            exceptionService.constraint(content.getId(),
                    "getRenditionStream cannnot be invoked to other than document or folder type.");
        }

        exceptionService.constraintRenditionStreamDownload(content, streamId);

        Rendition rendition = contentService.getRendition(repositoryId, streamId);

        BigInteger length = BigInteger.valueOf(rendition.getLength());
        String mimeType = rendition.getMimetype();
        InputStream is = rendition.getInputStream();
        ContentStream cs = new ContentStreamImpl("preview_" + streamId, length, mimeType, is);

        return cs;
    }

    @Override
    public List<RenditionData> getRenditions(CallContext callContext, String repositoryId, String objectId,
            String renditionFilter, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {

        Lock lock = threadLockService.getReadLock(repositoryId, objectId);
        try {
            lock.lock();

            List<Rendition> renditions = contentService.getRenditions(repositoryId, objectId);

            List<RenditionData> results = new ArrayList<RenditionData>();
            for (Rendition rnd : renditions) {
                RenditionDataImpl data = new RenditionDataImpl(rnd.getId(), rnd.getMimetype(),
                        BigInteger.valueOf(rnd.getLength()), rnd.getKind(), rnd.getTitle(),
                        BigInteger.valueOf(rnd.getWidth()), BigInteger.valueOf(rnd.getHeight()),
                        rnd.getRenditionDocumentId());
                results.add(data);
            }
            return results;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public AllowableActions getAllowableActions(CallContext callContext, String repositoryId, String objectId) {

        exceptionService.invalidArgumentRequired("objectId", objectId);

        Lock lock = threadLockService.getReadLock(repositoryId, objectId);

        try {
            lock.lock();

            // //////////////////
            // General Exception
            // //////////////////
            Content content = contentService.getContent(repositoryId, objectId);
            exceptionService.objectNotFound(DomainType.OBJECT, content, objectId);
            // NOTE: The permission key doesn't exist according to CMIS
            // specification.

            // //////////////////
            // Body of the method
            // //////////////////
            return compileService.compileAllowableActions(callContext, repositoryId, content);

        } finally {
            lock.unlock();
        }
    }

    @Override
    public ObjectData create(CallContext callContext, String repositoryId, Properties properties, String folderId,
            ContentStream contentStream, VersioningState versioningState, List<String> policies,
            ExtensionsData extension) {

        String typeId = DataUtil.getObjectTypeId(properties);
        TypeDefinition type = typeManager.getTypeDefinition(repositoryId, typeId);
        if (type == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        String objectId = null;
        // TODO ACE can be set !
        if (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) {
            objectId = createDocument(callContext, repositoryId, properties, folderId, contentStream,
                    versioningState, null, null, null);
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER) {
            objectId = createFolder(callContext, repositoryId, properties, folderId, policies, null, null,
                    extension);
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_RELATIONSHIP) {
            objectId = createRelationship(callContext, repositoryId, properties, policies, null, null, extension);
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_POLICY) {
            objectId = createPolicy(callContext, repositoryId, properties, folderId, policies, null, null,
                    extension);
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_ITEM) {
            objectId = createItem(callContext, repositoryId, properties, folderId, policies, null, null, extension);
        } else {
            throw new CmisObjectNotFoundException("Cannot create object of type '" + typeId + "'!");
        }

        ObjectData object = compileService.compileObjectData(callContext, repositoryId,
                contentService.getContent(repositoryId, objectId), null, false, IncludeRelationships.NONE, null,
                false);

        return object;
    }

    @Override
    public String createFolder(CallContext callContext, String repositoryId, Properties properties, String folderId,
            List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
        FolderTypeDefinition td = (FolderTypeDefinition) typeManager.getTypeDefinition(repositoryId,
                DataUtil.getObjectTypeId(properties));
        Folder parentFolder = contentService.getFolder(repositoryId, folderId);

        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parentFolder);
        exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_CREATE_FOLDER_FOLDER,
                parentFolder);

        // //////////////////
        // Specific Exception
        // //////////////////
        exceptionService.constraintBaseTypeId(repositoryId, properties, BaseTypeId.CMIS_FOLDER);
        exceptionService.constraintAllowedChildObjectTypeId(parentFolder, properties);
        exceptionService.constraintPropertyValue(repositoryId, td, properties,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID));
        exceptionService.constraintCotrollablePolicies(td, policies, properties);
        exceptionService.constraintCotrollableAcl(td, addAces, removeAces, properties);
        exceptionService.constraintPermissionDefined(repositoryId, addAces, null);
        exceptionService.constraintPermissionDefined(repositoryId, removeAces, null);
        exceptionService.nameConstraintViolation(properties, parentFolder);

        // //////////////////
        // Body of the method
        // //////////////////
        Folder folder = contentService.createFolder(callContext, repositoryId, properties, parentFolder);
        return folder.getId();
    }

    @Override
    public String createDocument(CallContext callContext, String repositoryId, Properties properties,
            String folderId, ContentStream contentStream, VersioningState versioningState, List<String> policies,
            Acl addAces, Acl removeAces) {
        String objectTypeId = DataUtil.getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID);
        DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager.getTypeDefinition(repositoryId,
                objectTypeId);
        Folder parentFolder = contentService.getFolder(repositoryId, folderId);

        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.invalidArgumentRequired("properties", properties);
        exceptionService.invalidArgumentRequiredParentFolderId(repositoryId, folderId);
        exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parentFolder);
        exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_CREATE_FOLDER_FOLDER,
                parentFolder);

        // //////////////////
        // Specific Exception
        // //////////////////
        exceptionService.constraintBaseTypeId(repositoryId, properties, BaseTypeId.CMIS_DOCUMENT);
        exceptionService.constraintAllowedChildObjectTypeId(parentFolder, properties);
        exceptionService.constraintPropertyValue(repositoryId, td, properties,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID));
        exceptionService.constraintContentStreamRequired(td, contentStream);
        exceptionService.constraintControllableVersionable(td, versioningState, null);
        versioningState = (td.isVersionable() && versioningState == null) ? VersioningState.MAJOR : versioningState;
        exceptionService.constraintCotrollablePolicies(td, policies, properties);
        exceptionService.constraintCotrollableAcl(td, addAces, removeAces, properties);
        exceptionService.constraintPermissionDefined(repositoryId, addAces, null);
        exceptionService.constraintPermissionDefined(repositoryId, removeAces, null);
        exceptionService.streamNotSupported(td, contentStream);
        exceptionService.nameConstraintViolation(properties, parentFolder);

        // //////////////////
        // Body of the method
        // //////////////////
        Document document = contentService.createDocument(callContext, repositoryId, properties, parentFolder,
                contentStream, versioningState, null);
        return document.getId();
    }

    @Override
    public String createDocumentFromSource(CallContext callContext, String repositoryId, String sourceId,
            Properties properties, String folderId, VersioningState versioningState, List<String> policies,
            Acl addAces, Acl removeAces) {
        Document original = contentService.getDocument(repositoryId, sourceId);
        DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager.getTypeDefinition(repositoryId,
                original.getObjectType());

        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.invalidArgumentRequired("properties", properties);
        exceptionService.invalidArgumentRequiredParentFolderId(repositoryId, folderId);
        Folder parentFolder = contentService.getFolder(repositoryId, folderId);
        exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parentFolder);
        exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_CREATE_FOLDER_FOLDER,
                parentFolder);

        // //////////////////
        // Specific Exception
        // //////////////////
        exceptionService.constraintBaseTypeId(repositoryId, properties, BaseTypeId.CMIS_DOCUMENT);
        exceptionService.constraintAllowedChildObjectTypeId(parentFolder, properties);
        exceptionService.constraintPropertyValue(repositoryId, td, properties,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID));
        exceptionService.constraintControllableVersionable(td, versioningState, null);
        versioningState = (td.isVersionable() && versioningState == null) ? VersioningState.MAJOR : versioningState;
        exceptionService.constraintCotrollablePolicies(td, policies, properties);
        exceptionService.constraintCotrollableAcl(td, addAces, removeAces, properties);
        exceptionService.constraintPermissionDefined(repositoryId, addAces, null);
        exceptionService.constraintPermissionDefined(repositoryId, removeAces, null);
        exceptionService.nameConstraintViolation(properties, parentFolder);

        // //////////////////
        // Body of the method
        // //////////////////
        Document document = contentService.createDocumentFromSource(callContext, repositoryId, properties,
                parentFolder, original, versioningState, policies, addAces, removeAces);
        return document.getId();
    }

    @Override
    public void setContentStream(CallContext callContext, String repositoryId, Holder<String> objectId,
            boolean overwriteFlag, ContentStream contentStream, Holder<String> changeToken) {

        exceptionService.invalidArgumentRequiredHolderString("objectId", objectId);

        Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue());
        try {
            lock.lock();
            // //////////////////
            // General Exception
            // //////////////////

            exceptionService.invalidArgumentRequired("contentStream", contentStream);
            Document doc = (Document) contentService.getContent(repositoryId, objectId.getValue());
            exceptionService.objectNotFound(DomainType.OBJECT, doc, objectId.getValue());
            exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_SET_CONTENT_DOCUMENT,
                    doc);
            DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager.getTypeDefinition(repositoryId,
                    doc.getObjectType());
            exceptionService.constraintImmutable(repositoryId, doc, td);

            // //////////////////
            // Specific Exception
            // //////////////////
            exceptionService.contentAlreadyExists(doc, overwriteFlag);
            exceptionService.streamNotSupported(td, contentStream);
            exceptionService.updateConflict(doc, changeToken);
            exceptionService.versioning(doc);
            Folder parent = contentService.getParent(repositoryId, objectId.getValue());
            exceptionService.objectNotFoundParentFolder(repositoryId, objectId.getValue(), parent);

            // //////////////////
            // Body of the method
            // //////////////////
            String oldId = objectId.getValue();

            // TODO Externalize versioningState
            if (doc.isPrivateWorkingCopy()) {
                Document result = contentService.replacePwc(callContext, repositoryId, doc, contentStream);
                objectId.setValue(result.getId());
            } else {
                Document result = contentService.createDocumentWithNewStream(callContext, repositoryId, doc,
                        contentStream);
                objectId.setValue(result.getId());
            }

            nemakiCachePool.get(repositoryId).removeCmisCache(oldId);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void deleteContentStream(CallContext callContext, String repositoryId, Holder<String> objectId,
            Holder<String> changeToken, ExtensionsData extension) {

        exceptionService.invalidArgumentRequiredHolderString("objectId", objectId);

        Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue());
        try {
            lock.lock();

            // //////////////////
            // Exception
            // //////////////////
            Document document = contentService.getDocument(repositoryId, objectId.getValue());
            exceptionService.objectNotFound(DomainType.OBJECT, document, document.getId());
            exceptionService.constraintContentStreamRequired(repositoryId, document);

            // //////////////////
            // Body of the method
            // //////////////////
            contentService.deleteContentStream(callContext, repositoryId, objectId);

            nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue());

        } finally {
            lock.unlock();
        }
    }

    @Override
    public void appendContentStream(CallContext callContext, String repositoryId, Holder<String> objectId,
            Holder<String> changeToken, ContentStream contentStream, boolean isLastChunk,
            ExtensionsData extension) {

        exceptionService.invalidArgumentRequiredHolderString("objectId", objectId);

        Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue());
        try {
            lock.lock();

            // //////////////////
            // General Exception
            // //////////////////

            exceptionService.invalidArgumentRequired("contentStream", contentStream);
            Document doc = (Document) contentService.getContent(repositoryId, objectId.getValue());
            exceptionService.objectNotFound(DomainType.OBJECT, doc, objectId.getValue());
            exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_SET_CONTENT_DOCUMENT,
                    doc);
            DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager.getTypeDefinition(repositoryId,
                    doc.getObjectType());
            exceptionService.constraintImmutable(repositoryId, doc, td);

            // //////////////////
            // Specific Exception
            // //////////////////
            exceptionService.streamNotSupported(td, contentStream);
            exceptionService.updateConflict(doc, changeToken);
            exceptionService.versioning(doc);

            // //////////////////
            // Body of the method
            // //////////////////
            contentService.appendAttachment(callContext, repositoryId, objectId, changeToken, contentStream,
                    isLastChunk, extension);

            nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue());
        } finally {
            lock.unlock();
        }
    }

    @Override
    public String createRelationship(CallContext callContext, String repositoryId, Properties properties,
            List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
        String objectTypeId = DataUtil.getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID);
        RelationshipTypeDefinition td = (RelationshipTypeDefinition) typeManager.getTypeDefinition(repositoryId,
                objectTypeId);
        // //////////////////
        // Exception
        // //////////////////
        exceptionService.invalidArgumentRequiredCollection("properties", properties.getPropertyList());
        String sourceId = DataUtil.getIdProperty(properties, PropertyIds.SOURCE_ID);
        if (sourceId != null) {
            Content source = contentService.getContent(repositoryId, sourceId);
            if (source == null)
                exceptionService.constraintAllowedSourceTypes(td, source);
            exceptionService.permissionDenied(callContext, repositoryId,
                    PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE, source);
        }
        String targetId = DataUtil.getIdProperty(properties, PropertyIds.TARGET_ID);
        if (targetId != null) {
            Content target = contentService.getContent(repositoryId, targetId);
            if (target == null)
                exceptionService.constraintAllowedTargetTypes(td, target);
            exceptionService.permissionDenied(callContext, repositoryId,
                    PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET, target);
        }

        exceptionService.constraintBaseTypeId(repositoryId, properties, BaseTypeId.CMIS_RELATIONSHIP);
        exceptionService.constraintPropertyValue(repositoryId, td, properties,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID));
        exceptionService.constraintCotrollablePolicies(td, policies, properties);
        exceptionService.constraintCotrollableAcl(td, addAces, removeAces, properties);
        exceptionService.constraintPermissionDefined(repositoryId, addAces, null);
        exceptionService.constraintPermissionDefined(repositoryId, removeAces, null);
        exceptionService.nameConstraintViolation(properties, null);

        // //////////////////
        // Body of the method
        // //////////////////
        Relationship relationship = contentService.createRelationship(callContext, repositoryId, properties,
                policies, addAces, removeAces, extension);
        nemakiCachePool.get(repositoryId).removeCmisCache(relationship.getSourceId());
        nemakiCachePool.get(repositoryId).removeCmisCache(relationship.getTargetId());

        return relationship.getId();
    }

    @Override
    public String createPolicy(CallContext callContext, String repositoryId, Properties properties, String folderId,
            List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.invalidArgumentRequiredCollection("properties", properties.getPropertyList());
        // NOTE: folderId is ignored because policy is not filable in Nemaki
        TypeDefinition td = typeManager.getTypeDefinition(repositoryId,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID));
        exceptionService.constraintPropertyValue(repositoryId, td, properties,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID));

        // //////////////////
        // Specific Exception
        // //////////////////
        exceptionService.constraintBaseTypeId(repositoryId, properties, BaseTypeId.CMIS_POLICY);
        // exceptionService.constraintAllowedChildObjectTypeId(parent,
        // properties);
        exceptionService.constraintCotrollablePolicies(td, policies, properties);
        exceptionService.constraintCotrollableAcl(td, addAces, removeAces, properties);
        // exceptionService.nameConstraintViolation(properties, parent);

        // //////////////////
        // Body of the method
        // //////////////////
        Policy policy = contentService.createPolicy(callContext, repositoryId, properties, policies, addAces,
                removeAces, extension);
        return policy.getId();
    }

    @Override
    public String createItem(CallContext callContext, String repositoryId, Properties properties, String folderId,
            List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
        // //////////////////
        // General Exception
        // //////////////////
        TypeDefinition td = typeManager.getTypeDefinition(repositoryId, DataUtil.getObjectTypeId(properties));
        Folder parentFolder = contentService.getFolder(repositoryId, folderId);
        exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parentFolder);
        exceptionService.invalidArgumentRequiredCollection("properties", properties.getPropertyList());

        // //////////////////
        // Specific Exception
        // //////////////////
        exceptionService.constraintBaseTypeId(repositoryId, properties, BaseTypeId.CMIS_ITEM);
        exceptionService.constraintPropertyValue(repositoryId, td, properties,
                DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID));
        exceptionService.constraintCotrollablePolicies(td, policies, properties);
        exceptionService.constraintCotrollableAcl(td, addAces, removeAces, properties);

        // //////////////////
        // Body of the method
        // //////////////////
        Item item = contentService.createItem(callContext, repositoryId, properties, folderId, policies, addAces,
                removeAces, extension);
        return item.getId();
    }

    @Override
    public void updateProperties(CallContext callContext, String repositoryId, Holder<String> objectId,
            Properties properties, Holder<String> changeToken) {

        exceptionService.invalidArgumentRequiredHolderString("objectId", objectId);

        Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue());
        try {
            lock.lock();

            // //////////////////
            // Exception
            // //////////////////
            Content content = checkExceptionBeforeUpdateProperties(callContext, repositoryId, objectId, properties,
                    changeToken);

            // //////////////////
            // Body of the method
            // //////////////////
            contentService.updateProperties(callContext, repositoryId, properties, content);

            nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue());
        } finally {
            lock.unlock();
        }
    }

    private Content checkExceptionBeforeUpdateProperties(CallContext callContext, String repositoryId,
            Holder<String> objectId, Properties properties, Holder<String> changeToken) {
        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.invalidArgumentRequiredCollection("properties", properties.getPropertyList());
        Content content = contentService.getContent(repositoryId, objectId.getValue());
        exceptionService.objectNotFound(DomainType.OBJECT, content, objectId.getValue());
        if (content.isDocument()) {
            Document d = (Document) content;
            exceptionService.versioning(d);
            exceptionService.constraintUpdateWhenCheckedOut(repositoryId, callContext.getUsername(), d);
            TypeDefinition typeDef = typeManager.getTypeDefinition(repositoryId, d);
            exceptionService.constraintImmutable(repositoryId, d, typeDef);
        }
        exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT,
                content);
        exceptionService.updateConflict(content, changeToken);

        TypeDefinition tdf = typeManager.getTypeDefinition(repositoryId, content);
        exceptionService.constraintPropertyValue(repositoryId, tdf, properties, objectId.getValue());

        return content;
    }

    @Override
    public List<BulkUpdateObjectIdAndChangeToken> bulkUpdateProperties(CallContext callContext, String repositoryId,
            List<BulkUpdateObjectIdAndChangeToken> objectIdAndChangeTokenList, Properties properties,
            List<String> addSecondaryTypeIds, List<String> removeSecondaryTypeIds, ExtensionsData extension) {
        // //////////////////
        // General Exception
        // //////////////////
        // Each permission is checked at each execution
        exceptionService.invalidArgumentRequiredCollection("objectIdAndChangeToken", objectIdAndChangeTokenList);
        exceptionService.invalidArgumentSecondaryTypeIds(repositoryId, properties);

        // //////////////////
        // Body of the method
        // //////////////////
        List<BulkUpdateObjectIdAndChangeToken> results = new ArrayList<BulkUpdateObjectIdAndChangeToken>();

        ExecutorService executor = Executors.newCachedThreadPool();
        List<BulkUpdateTask> tasks = new ArrayList<>();
        for (BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken : objectIdAndChangeTokenList) {
            tasks.add(new BulkUpdateTask(callContext, repositoryId, objectIdAndChangeToken, properties,
                    addSecondaryTypeIds, removeSecondaryTypeIds, extension));
        }

        try {
            List<Future<BulkUpdateObjectIdAndChangeToken>> _results = executor.invokeAll(tasks);
            for (Future<BulkUpdateObjectIdAndChangeToken> _result : _results) {
                try {
                    BulkUpdateObjectIdAndChangeToken result = _result.get();
                    results.add(result);
                } catch (Exception e) {
                    //TODO log
                    //do nothing
                }
            }
        } catch (InterruptedException e1) {
            //TODO log
            e1.printStackTrace();
        }

        return results;
    }

    private class BulkUpdateTask implements Callable<BulkUpdateObjectIdAndChangeToken> {

        private CallContext callContext;
        private String repositoryId;
        private BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken;
        private Properties properties;
        private List<String> addSecondaryTypeIds;
        private List<String> removeSecondaryTypeIds;
        private ExtensionsData extension;

        public BulkUpdateTask(CallContext callContext, String repositoryId,
                BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken, Properties properties,
                List<String> addSecondaryTypeIds, List<String> removeSecondaryTypeIds, ExtensionsData extension) {
            super();
            this.callContext = callContext;
            this.repositoryId = repositoryId;
            this.objectIdAndChangeToken = objectIdAndChangeToken;
            this.properties = properties;
            this.addSecondaryTypeIds = addSecondaryTypeIds;
            this.removeSecondaryTypeIds = removeSecondaryTypeIds;
            this.extension = extension;
        }

        @Override
        public BulkUpdateObjectIdAndChangeToken call() throws Exception {
            exceptionService.invalidArgumentRequiredString("objectId", objectIdAndChangeToken.getId());

            Lock lock = threadLockService.getWriteLock(repositoryId, objectIdAndChangeToken.getId());
            try {
                lock.lock();

                Content content = checkExceptionBeforeUpdateProperties(callContext, repositoryId,
                        new Holder<String>(objectIdAndChangeToken.getId()), properties,
                        new Holder<String>(objectIdAndChangeToken.getChangeToken()));
                contentService.updateProperties(callContext, repositoryId, properties, content);
                nemakiCachePool.get(repositoryId).removeCmisCache(content.getId());

                BulkUpdateObjectIdAndChangeToken result = new BulkUpdateObjectIdAndChangeTokenImpl(
                        objectIdAndChangeToken.getId(), content.getId(), String.valueOf(content.getChangeToken()));
                return result;
            } catch (Exception e) {
                // Don't throw an error
                // Don't return any BulkUpdateObjectIdAndChangetoken
            } finally {
                lock.unlock();
            }

            // TODO Auto-generated method stub
            return null;
        }

    }

    @Override
    public void moveObject(CallContext callContext, String repositoryId, Holder<String> objectId,
            String sourceFolderId, String targetFolderId) {

        exceptionService.invalidArgumentRequiredHolderString("objectId", objectId);

        Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue());
        try {
            lock.lock();
            // //////////////////
            // General Exception
            // //////////////////
            exceptionService.invalidArgumentRequiredString("sourceFolderId", sourceFolderId);
            exceptionService.invalidArgumentRequiredString("targetFolderId", targetFolderId);
            Content content = contentService.getContent(repositoryId, objectId.getValue());
            exceptionService.objectNotFound(DomainType.OBJECT, content, objectId.getValue());
            Folder source = contentService.getFolder(repositoryId, sourceFolderId);
            exceptionService.objectNotFound(DomainType.OBJECT, source, sourceFolderId);
            Folder target = contentService.getFolder(repositoryId, targetFolderId);
            exceptionService.objectNotFound(DomainType.OBJECT, target, targetFolderId);
            exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_MOVE_OBJECT,
                    content);
            exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_MOVE_SOURCE, source);
            exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_MOVE_TARGET, target);

            // //////////////////
            // Body of the method
            // //////////////////
            contentService.move(callContext, repositoryId, content, target);

            nemakiCachePool.get(repositoryId).removeCmisCache(content.getId());
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void deleteObject(CallContext callContext, String repositoryId, String objectId, Boolean allVersions) {
        objectServiceInternal.deleteObjectInternal(callContext, repositoryId, objectId, allVersions, false);
    }

    @Override
    public FailedToDeleteData deleteTree(CallContext callContext, String repositoryId, String folderId,
            Boolean allVersions, UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension) {
        // //////////////////
        // Inner classes
        // //////////////////
        class DeleteTask implements Callable<Boolean> {
            private CallContext callContext;
            private String repositoryId;
            private Content content;
            private Boolean allVersions;

            public DeleteTask() {
            }

            public DeleteTask(CallContext callContext, String repositoryId, Content content, Boolean allVersions) {
                this.callContext = callContext;
                this.repositoryId = repositoryId;
                this.content = content;
                this.allVersions = allVersions;
            }

            @Override
            public Boolean call() throws Exception {
                try {
                    objectServiceInternal.deleteObjectInternal(callContext, repositoryId, content, allVersions,
                            true);
                    return false;
                } catch (Exception e) {
                    return true;
                }
            }
        }

        class WrappedExecutorService {
            private ExecutorService service;
            private Folder folder;

            private WrappedExecutorService() {
            };

            public WrappedExecutorService(ExecutorService service, Folder folder) {
                this.service = service;
                this.folder = folder;
            }

            public ExecutorService getService() {
                return service;
            }

            public Folder getFolder() {
                return folder;
            }
        }

        class DeleteService {
            private Map<String, Future<Boolean>> failureIds;
            private WrappedExecutorService parentService;
            private CallContext callContext;
            private String repositoryId;
            private Content content;
            private Boolean allVersions;

            public DeleteService() {
            }

            public DeleteService(Map<String, Future<Boolean>> failureIds, WrappedExecutorService parentService,
                    CallContext callContext, String repositoryId, Content content, Boolean allVersions) {
                super();
                this.failureIds = failureIds;
                this.parentService = parentService;
                this.callContext = callContext;
                this.repositoryId = repositoryId;
                this.content = content;
                this.allVersions = allVersions;
            }

            public void execute() {
                if (content.isDocument()) {
                    Future<Boolean> result = parentService.getService()
                            .submit(new DeleteTask(callContext, repositoryId, content, allVersions));
                    failureIds.put(content.getId(), result);
                } else if (content.isFolder()) {
                    WrappedExecutorService childrenService = new WrappedExecutorService(
                            Executors.newFixedThreadPool(threadMax), (Folder) content);

                    List<Content> children = contentService.getChildren(repositoryId, content.getId());
                    if (CollectionUtils.isNotEmpty(children)) {
                        for (Content child : children) {
                            DeleteService deleteService = new DeleteService(this.failureIds, childrenService,
                                    callContext, repositoryId, child, allVersions);
                            deleteService.execute();
                        }
                    }

                    //wait til newService ends
                    childrenService.getService().shutdown();
                    try {
                        childrenService.getService().awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
                    } catch (InterruptedException e) {
                        log.error(e, e);
                    }

                    //Lastly, delete self
                    Future<Boolean> result = parentService.getService()
                            .submit(new DeleteTask(callContext, repositoryId, content, allVersions));
                    failureIds.put(content.getId(), result);
                }

            }
        }

        // //////////////////
        // General Exception
        // //////////////////
        exceptionService.invalidArgumentRequiredString("objectId", folderId);
        Folder folder = contentService.getFolder(repositoryId, folderId);
        exceptionService.permissionDenied(callContext, repositoryId, PermissionMapping.CAN_DELETE_TREE_FOLDER,
                folder);
        exceptionService.constraintDeleteRootFolder(repositoryId, folderId);

        // //////////////////
        // Specific Exception
        // //////////////////
        if (folder == null)
            exceptionService.constraint(folderId, "deleteTree cannot be invoked on a non-folder object");

        // //////////////////
        // Body of the method
        // //////////////////
        // Delete descendants
        Map<String, Future<Boolean>> failureIds = new HashMap<String, Future<Boolean>>();

        DeleteService deleteService = new DeleteService(failureIds,
                new WrappedExecutorService(Executors.newFixedThreadPool(threadMax), folder), callContext,
                repositoryId, folder, allVersions);
        deleteService.execute();

        solrUtil.callSolrIndexing(repositoryId);

        // Check FailedToDeleteData
        // FIXME Consider orphans that was failed to be deleted
        FailedToDeleteDataImpl fdd = new FailedToDeleteDataImpl();
        List<String> ids = new ArrayList<String>();
        for (Entry<String, Future<Boolean>> entry : failureIds.entrySet()) {
            Boolean failed;
            try {
                failed = entry.getValue().get();
                if (failed) {
                    ids.add(entry.getKey());
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        fdd.setIds(ids);
        return fdd;
    }

    public void setObjectServiceInternal(ObjectServiceInternal objectServiceInternal) {
        this.objectServiceInternal = objectServiceInternal;
    }

    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    public void setExceptionService(ExceptionService exceptionService) {
        this.exceptionService = exceptionService;
    }

    public void setCompileService(CompileService compileService) {
        this.compileService = compileService;
    }

    public void setTypeManager(TypeManager typeManager) {
        this.typeManager = typeManager;
    }

    public void setSolrUtil(SolrUtil solrUtil) {
        this.solrUtil = solrUtil;
    }

    public void setNemakiCachePool(NemakiCachePool nemakiCachePool) {
        this.nemakiCachePool = nemakiCachePool;
    }

    public void setThreadLockService(ThreadLockService threadLockService) {
        this.threadLockService = threadLockService;
    }

    public void setThreadMax(int threadMax) {
        this.threadMax = threadMax;
    }
}