/*******************************************************************************
* 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;
}
}