/*******************************************************************************
* 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 Licensealong 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.aspect.impl;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.data.PropertyDecimal;
import org.apache.chemistry.opencmis.commons.data.PropertyString;
import org.apache.chemistry.opencmis.commons.definitions.Choice;
import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDecimalDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyIntegerDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyStringDefinition;
import org.apache.chemistry.opencmis.commons.definitions.RelationshipTypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
import org.apache.chemistry.opencmis.commons.definitions.TypeMutability;
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.CapabilityOrderBy;
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
import org.apache.chemistry.opencmis.commons.enums.PropertyType;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
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.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
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 jp.aegif.nemaki.businesslogic.ContentService;
import jp.aegif.nemaki.businesslogic.PrincipalService;
import jp.aegif.nemaki.cmis.aspect.ExceptionService;
import jp.aegif.nemaki.cmis.aspect.PermissionService;
import jp.aegif.nemaki.cmis.aspect.type.TypeManager;
import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap;
import jp.aegif.nemaki.model.Acl;
import jp.aegif.nemaki.model.Change;
import jp.aegif.nemaki.model.Content;
import jp.aegif.nemaki.model.Document;
import jp.aegif.nemaki.model.Folder;
import jp.aegif.nemaki.model.User;
import jp.aegif.nemaki.model.VersionSeries;
import jp.aegif.nemaki.util.DataUtil;
import jp.aegif.nemaki.util.constant.DomainType;
public class ExceptionServiceImpl implements ExceptionService,
ApplicationContextAware {
private TypeManager typeManager;
private ContentService contentService;
private PermissionService permissionService;
private RepositoryInfoMap repositoryInfoMap;
private PrincipalService principalService;
private static final Log log = LogFactory
.getLog(ExceptionServiceImpl.class);
private final BigInteger HTTP_STATUS_CODE_400 = BigInteger.valueOf(400);
private final BigInteger HTTP_STATUS_CODE_403 = BigInteger.valueOf(403);
private final BigInteger HTTP_STATUS_CODE_404 = BigInteger.valueOf(404);
private final BigInteger HTTP_STATUS_CODE_405 = BigInteger.valueOf(405);
private final BigInteger HTTP_STATUS_CODE_409 = BigInteger.valueOf(409);
private final BigInteger HTTP_STATUS_CODE_500 = BigInteger.valueOf(500);
@Override
public void invalidArgument(String msg) {
throw new CmisInvalidArgumentException(msg, HTTP_STATUS_CODE_400);
}
@Override
public void invalidArgumentRequired(String argumentName) {
throw new CmisInvalidArgumentException(argumentName + " must be set",
HTTP_STATUS_CODE_400);
}
@Override
public void invalidArgumentRequired(String argumentName, Object argument) {
if (argument == null) {
invalidArgumentRequired(argumentName);
}
}
@Override
public void invalidArgumentRequiredString(String argumentName,
String argument) {
if (isEmptyString(argument)) {
invalidArgumentRequired(argumentName);
}
}
@Override
public void invalidArgumentRequiredHolderString(String argumentName,
Holder<String> argument) {
if (argument == null || isEmptyString(argument.getValue())) {
invalidArgumentRequired(argumentName);
}
}
@Override
public void invalidArgumentRootFolder(String repositoryId, Content content) {
if (contentService.isRoot(repositoryId, content))
invalidArgument("Cannot specify the root folder as an input parameter");
}
@Override
public void invalidArgumentFolderId(Folder folder, String folderId) {
if (folder == null) {
String msg = "This objectId is not a folder id";
invalidArgument(buildMsgWithId(msg, folderId));
}
}
@Override
public void invalidArgumentDepth(BigInteger depth) {
if (depth == BigInteger.ZERO) {
invalidArgument("Depth must not be zero");
} else if (depth == BigInteger.valueOf(-1)) {
invalidArgument("Depth must not be less than -1");
}
}
private boolean isEmptyString(String s) {
if (s != null && s.length() != 0) {
return false;
} else {
return true;
}
}
@Override
public void invalidArgumentRequiredCollection(String argumentName,
Collection collection) {
if (CollectionUtils.isEmpty(collection))
invalidArgument(argumentName);
}
@Override
public void invalidArgumentRequiredParentFolderId(String repositoryId, String folderId) {
if (!repositoryInfoMap.get(repositoryId).getCapabilities().isMultifilingSupported())
invalidArgumentRequiredString("folderId", folderId);
}
@Override
public void invalidArgumentOrderBy(String repositoryId, String orderBy) {
if (repositoryInfoMap.get(repositoryId).getCapabilities().getOrderByCapability() == CapabilityOrderBy.NONE
&& orderBy != null)
invalidArgument("OrderBy capability is not supported");
}
@Override
public void invalidArgumentChangeEventNotAvailable(
String repositoryId, Holder<String> changeLogToken) {
if (changeLogToken != null && changeLogToken.getValue() != null) {
Change change = contentService.getChangeEvent(repositoryId, changeLogToken
.getValue());
if (change == null)
invalidArgument("changeLogToken:" + changeLogToken.getValue()
+ " does not exist");
}
}
@Override
public void invalidArgumentCreatableType(String repositoryId, TypeDefinition type) {
String msg = "";
String parentId = type.getParentTypeId();
if (typeManager.getTypeById(repositoryId, parentId) == null) {
msg = "Specified parent type does not exist";
} else {
TypeDefinition parent = typeManager.getTypeById(repositoryId, parentId)
.getTypeDefinition();
if (parent.getTypeMutability() == null) {
msg = "Specified parent type does not have TypeMutability";
} else {
boolean canCreate = (parent.getTypeMutability() == null) ? false
: true;
if (!canCreate) {
msg = "Specified parent type has TypeMutability.canCreate = false";
}
}
}
if (!StringUtils.isEmpty(msg)) {
msg = msg + " [objectTypeId = " + type.getId() + "]";
invalidArgument(msg);
}
}
@Override
public void invalidArgumentUpdatableType(TypeDefinition type) {
String msg = "";
TypeMutability typeMutability = type.getTypeMutability();
boolean canUpdate = (typeMutability.canUpdate() == null) ? false
: typeMutability.canUpdate();
if (!canUpdate) {
msg = "Specified type is not updatable";
msg = msg + " [objectTypeId = " + type.getId() + "]";
invalidArgument(msg);
}
}
@Override
public void invalidArgumentDeletableType(String repositoryId, String typeId) {
TypeDefinition type = typeManager.getTypeDefinition(repositoryId, typeId);
String msg = "";
TypeMutability typeMutability = type.getTypeMutability();
boolean canUpdate = (typeMutability.canDelete() == null) ? true
: typeMutability.canDelete();
if (!canUpdate) {
msg = "Specified type is not deletable";
msg = msg + " [objectTypeId = " + type.getId() + "]";
invalidArgument(msg);
}
}
@Override
public void invalidArgumentDoesNotExistType(String repositoryId, String typeId) {
String msg = "";
TypeDefinition type = typeManager.getTypeDefinition(repositoryId, typeId);
if (type == null) {
msg = "Specified type does not exist";
msg = msg + " [objectTypeId = " + typeId + "]";
invalidArgument(msg);
}
}
@Override
public void invalidArgumentSecondaryTypeIds(String repositoryId, Properties properties) {
if (properties == null)
return;
Map<String, PropertyData<?>> map = properties.getProperties();
if (MapUtils.isEmpty(map))
return;
List<String> results = new ArrayList<String>();
PropertyData<?> ids = map.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
if (ids == null || CollectionUtils.isEmpty(ids.getValues()))
return;
for (Object _id : ids.getValues()) {
String id = (String) _id;
TypeDefinitionContainer tdc = typeManager.getTypeById(repositoryId, id);
if (tdc == null) {
results.add(id);
}
}
if (CollectionUtils.isNotEmpty(results)) {
String msg = "Invalid cmis:SecondaryObjectTypeIds are provided:"
+ StringUtils.join(results, ",");
invalidArgument(msg);
}
}
@Override
public void objectNotFound(DomainType type, Object object, String id,
String msg) {
if (object == null)
throw new CmisObjectNotFoundException(msg, HTTP_STATUS_CODE_404);
}
@Override
public void objectNotFound(DomainType type, Object object, String id) {
String msg = "[" + type.value() + "Id:" + id + "]"
+ "The specified object is not found";
objectNotFound(type, object, id, msg);
}
@Override
public void objectNotFoundByPath(DomainType type, Object object, String path,
String msg) {
if (object == null)
throw new CmisObjectNotFoundException(msg, HTTP_STATUS_CODE_404);
}
@Override
public void objectNotFoundByPath(DomainType type, Object object, String path) {
String msg = "[" + type.value() + " path:" + path + "]"
+ "The specified object is not found";
objectNotFound(type, object, path, msg);
}
@Override
public void objectNotFoundVersionSeries(String id, Collection collection) {
if (CollectionUtils.isEmpty(collection)) {
String msg = "[VersionSeriesId:" + id + "]"
+ "The specified version series is not found";
throw new CmisObjectNotFoundException(msg, HTTP_STATUS_CODE_404);
}
}
@Override
public void objectNotFoundParentFolder(String repositoryId, String id, Content content) {
if (!repositoryInfoMap.get(repositoryId).getCapabilities().isMultifilingSupported())
objectNotFound(DomainType.OBJECT, content, id,
"The specified parent folder is not found");
}
/**
*
*/
// TODO Show also stack errors
@Override
public void permissionDenied(CallContext context, String repositoryId,
String key, Content content) {
if(content == null){
System.out.println();
}
String baseTypeId = content.getType();
Acl acl = contentService.calculateAcl(repositoryId, content);
permissionDeniedInternal(context, repositoryId, key, acl, baseTypeId, content);
permissionTopLevelFolder(context, repositoryId, key, content);
}
private void permissionDeniedInternal(CallContext callContext, String repositoryId,
String key, Acl acl, String baseTypeId, Content content) {
if (!permissionService.checkPermission(callContext, repositoryId, key, acl, baseTypeId, content)) {
String msg = String.format( "Permission Denied! repositoryId=%s key=%s acl=%s content={id:%s, name:%s} ", repositoryId, key, acl, content.getId(), content.getName()) ;
throw new CmisPermissionDeniedException(msg, HTTP_STATUS_CODE_403);
}
}
private void permissionTopLevelFolder(CallContext context, String repositoryId, String key, Content content){
boolean result = permissionService.checkPermissionAtTopLevel(context, repositoryId, key, content);
if(!result){
String msg = String.format( "Permission Denied to top level folders for non-admin user! repositoryId=%s key=%s userId=%s content={id:%s, name:%s} ", repositoryId, key, context.getUsername(), content.getId(), content.getName()) ;
throw new CmisPermissionDeniedException(msg, HTTP_STATUS_CODE_403);
}
}
@Override
public void perimissionAdmin(CallContext context, String repositoryId) {
List<User> admins = principalService.getAdmins(repositoryId);
if (!admins.contains(context.getUsername())) {
String msg = "This operation if permitted only for administrator";
throw new CmisPermissionDeniedException(msg, HTTP_STATUS_CODE_403);
}
}
/**
*
* NOTE:Check the condition before calling this method
*/
@Override
public void constraint(String objectId, String msg) {
throw new CmisConstraintException(buildMsgWithId(msg, objectId),
HTTP_STATUS_CODE_409);
}
private void constraint(String msg) {
throw new CmisConstraintException(msg, HTTP_STATUS_CODE_409);
}
@Override
public void constraintBaseTypeId(String repositoryId,
Properties properties, BaseTypeId baseTypeId) {
String objectTypeId = DataUtil.getObjectTypeId(properties);
TypeDefinition td = typeManager.getTypeDefinition(repositoryId, objectTypeId);
if (!td.getBaseTypeId().equals(baseTypeId))
constraint(null,
"cmis:objectTypeId is not an object type whose base tyep is "
+ baseTypeId);
}
@Override
public void constraintAllowedChildObjectTypeId(Folder folder,
Properties childProperties) {
List<String> allowedTypes = folder.getAllowedChildTypeIds();
// If cmis:allowedCHildTypeIds is not set, all types are allowed.
if (!CollectionUtils.isEmpty(allowedTypes)) {
String childType = DataUtil.getIdProperty(childProperties,
PropertyIds.OBJECT_TYPE_ID);
if (!allowedTypes.contains(childType)) {
String objectId = DataUtil.getIdProperty(childProperties,
PropertyIds.OBJECT_ID);
constraint(
objectId,
"cmis:objectTypeId="
+ childType
+ " is not in the list of AllowedChildObjectTypeIds of the parent folder");
}
}
}
@Override
public <T> void constraintPropertyValue(String repositoryId,
TypeDefinition typeDefinition, Properties properties, String objectId) {
Map<String, PropertyDefinition<?>> propertyDefinitions = typeDefinition
.getPropertyDefinitions();
// Adding secondary types and its properties MAY be done in the same
// operation
List<String> secIds = DataUtil.getIdListProperty(properties,
PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
if (CollectionUtils.isNotEmpty(secIds)) {
for (String secId : secIds) {
TypeDefinition sec = typeManager.getTypeById(repositoryId, secId)
.getTypeDefinition();
for (Entry<String, PropertyDefinition<?>> entry : sec
.getPropertyDefinitions().entrySet()) {
if (!propertyDefinitions.containsKey(entry.getKey())) {
propertyDefinitions.put(entry.getKey(),
entry.getValue());
}
}
}
}
for (PropertyData<?> _pd : properties.getPropertyList()) {
PropertyData<T> pd = (PropertyData<T>) _pd;
PropertyDefinition<T> propertyDefinition = (PropertyDefinition<T>) propertyDefinitions
.get(pd.getId());
// If an input property is not defined one, output error.
if (propertyDefinition == null)
constraint(objectId, "An undefined property is provided!");
// Check "required" flag
if (propertyDefinition.isRequired()
&& !DataUtil.valueExist(pd.getValues()))
constraint(objectId, "An required property is not provided!");
// Check choices
constraintChoices(propertyDefinition, pd, objectId);
// Check min/max length
switch (propertyDefinition.getPropertyType()) {
case STRING:
constraintStringPropertyValue(propertyDefinition, pd, objectId);
break;
case DECIMAL:
constraintDecimalPropertyValue(propertyDefinition, pd, objectId);
case INTEGER:
constraintIntegerPropertyValue(propertyDefinition, pd, objectId);
break;
default:
break;
}
}
}
private <T> void constraintChoices(PropertyDefinition<T> definition,
PropertyData<T> propertyData, String objectId) {
// Check OpenChoice
boolean openChoice = (definition.isOpenChoice() == null) ? true : false;
if (openChoice)
return;
List<T> data = propertyData.getValues();
// null or blank String value should be permitted within any choice list
if (CollectionUtils.isEmpty(data))
return;
List<Choice<T>> choices = definition.getChoices();
if (CollectionUtils.isEmpty(choices) || CollectionUtils.isEmpty(data))
return;
boolean included = false;
if (definition.getCardinality() == Cardinality.SINGLE) {
T d = data.get(0);
if (d instanceof String && StringUtils.isBlank((String) d)
|| d == null) {
return;
} else {
for (Choice<T> choice : choices) {
List<T> value = choice.getValue();
T v = value.get(0);
if (v.equals(d)) {
included = true;
break;
}
}
}
} else if (definition.getCardinality() == Cardinality.MULTI) {
List<T> values = new ArrayList<T>();
for (Choice<T> choice : choices) {
values.addAll(choice.getValue());
}
for (T d : data) {
if (values.contains(d)) {
included = true;
} else {
if (d instanceof String && StringUtils.isBlank((String) d)
|| d == null) {
included = true;
} else {
included = false;
break;
}
}
}
}
if (!included) {
constraint(objectId, propertyData.getId()
+ " property value must be one of choices");
}
}
private void constraintIntegerPropertyValue(
PropertyDefinition<?> definition, PropertyData<?> propertyData,
String objectId) {
final String msg = "AN INTEGER property violates the range constraints";
BigInteger val = BigInteger
.valueOf((Long) propertyData.getFirstValue());
BigInteger min = ((PropertyIntegerDefinition) definition).getMinValue();
if (min != null && min.compareTo(val) > 0) {
constraint(objectId, msg);
}
BigInteger max = ((PropertyIntegerDefinition) definition).getMinValue();
if (max != null && max.compareTo(val) < 0) {
constraint(objectId, msg);
}
}
private void constraintDecimalPropertyValue(
PropertyDefinition<?> definition, PropertyData<?> propertyData,
String objectId) {
final String msg = "An DECIMAL property violates the range constraints";
if (!(propertyData instanceof PropertyDecimal))
return;
BigDecimal val = ((PropertyDecimal) propertyData).getFirstValue();
BigDecimal min = ((PropertyDecimalDefinition) definition).getMinValue();
if (min != null && min.compareTo(val) > 0) {
constraint(objectId, msg);
}
BigDecimal max = ((PropertyDecimalDefinition) definition).getMaxValue();
if (max != null && max.compareTo(val) > 0) {
constraint(objectId, msg);
}
}
private void constraintStringPropertyValue(
PropertyDefinition<?> definition, PropertyData<?> propertyData,
String objectId) {
final String msg = "An STRING property violates the length constraints";
if (!(propertyData instanceof PropertyString))
return;
String val = ((PropertyString) propertyData).getFirstValue();
if (StringUtils.isEmpty(val))
return;
BigInteger length = BigInteger.valueOf(val.length());
BigInteger max = ((PropertyStringDefinition) definition).getMaxLength();
if (max != null && max.compareTo(length) < 0) {
constraint(objectId, msg);
}
}
@Override
public void constraintControllableVersionable(
DocumentTypeDefinition documentTypeDefinition,
VersioningState versioningState, String objectId) {
if (!documentTypeDefinition.isVersionable()
&& (versioningState != null && versioningState != VersioningState.NONE)) {
String msg = "Versioning state must not be set for a non-versionable object-type";
throw new CmisConstraintException(buildMsgWithId(msg, objectId),
HTTP_STATUS_CODE_409);
}
if (documentTypeDefinition.isVersionable()
&& versioningState == VersioningState.NONE) {
String msg = "Versioning state is set for a non-versionable object-type";
throw new CmisConstraintException(buildMsgWithId(msg, objectId),
HTTP_STATUS_CODE_409);
}
}
@Override
public void constraintCotrollablePolicies(TypeDefinition typeDefinition,
List<String> policies, Properties properties) {
if (!typeDefinition.isControllablePolicy()
&& !CollectionUtils.isEmpty(policies)) {
String msg = "Policies cannnot be provided to a non-controllablePolicy object-type";
constraint(getObjectId(properties), msg);
}
}
@Override
public void constraintCotrollableAcl(TypeDefinition typeDefinition,
org.apache.chemistry.opencmis.commons.data.Acl addAces,
org.apache.chemistry.opencmis.commons.data.Acl removeAces,
Properties properties) {
// TODO ignore removeAces?
boolean aclIsEmpty = (addAces == null)
|| (addAces != null && CollectionUtils.isEmpty(addAces
.getAces())) ? true : false;
if (!typeDefinition.isControllableAcl() && !aclIsEmpty) {
constraint(getObjectId(properties),
"Acl cannnot be provided to a non-controllableAcl object-type");
}
}
@Override
public void constraintPermissionDefined(
String repositoryId, org.apache.chemistry.opencmis.commons.data.Acl acl, String objectId) {
boolean aclIsEmpty = (acl == null)
|| (acl != null && CollectionUtils.isEmpty(acl.getAces())) ? true
: false;
List<PermissionDefinition> definitions = repositoryInfoMap.get(repositoryId)
.getAclCapabilities().getPermissions();
List<String> definedIds = new ArrayList<String>();
for (PermissionDefinition pdf : definitions) {
definedIds.add(pdf.getId());
}
if (!aclIsEmpty) {
for (org.apache.chemistry.opencmis.commons.data.Ace ace : acl
.getAces()) {
List<String> permissions = ace.getPermissions();
for (String p : permissions) {
if (!definedIds.contains(p)) {
constraint(objectId,
"A provided ACE includes an unsupported permission");
}
}
}
}
}
@Override
public void constraintAllowedSourceTypes(
RelationshipTypeDefinition relationshipTypeDefinition,
Content source) {
List<String> allowed = relationshipTypeDefinition
.getAllowedSourceTypeIds();
if (CollectionUtils.isNotEmpty(allowed)) {
if (!allowed.contains((source.getObjectType())))
constraint(source.getId(),
"The source object's type is not allowed for the relationship");
}
}
@Override
public void constraintAllowedTargetTypes(
RelationshipTypeDefinition relationshipTypeDefinition,
Content target) {
List<String> allowed = relationshipTypeDefinition
.getAllowedTargetTypeIds();
if (CollectionUtils.isNotEmpty(allowed)) {
if (!allowed.contains(target.getObjectType()))
constraint(target.getId(),
"The target object's type is not allowed for the relationship");
}
}
@Override
public void constraintVersionable(String repositoryId, String typeId) {
DocumentTypeDefinition type = (DocumentTypeDefinition) typeManager
.getTypeDefinition(repositoryId, typeId);
if (!type.isVersionable()) {
String msg = "Object type: " + type.getId() + " is not versionbale";
throw new CmisConstraintException(msg, HTTP_STATUS_CODE_409);
}
}
@Override
public void constraintAlreadyCheckedOut(String repositoryId, Document document) {
VersionSeries vs = contentService.getVersionSeries(repositoryId, document);
if (vs.isVersionSeriesCheckedOut()) {
if (!(document.isPrivateWorkingCopy())) {
constraint(document.getId(),
"The version series is already checked out");
}
}
}
@Override
public void constraintUpdateWhenCheckedOut(String repositoryId,
String currentUserId, Document document) {
VersionSeries vs = contentService.getVersionSeries(repositoryId, document);
if (vs.isVersionSeriesCheckedOut()) {
if (document.isPrivateWorkingCopy()) {
// Can update by only the use who has checked it out
String whoCheckedOut = vs.getVersionSeriesCheckedOutBy();
if (!currentUserId.equals(whoCheckedOut)) {
constraint(
document.getId(),
"This private working copy can be modified only by the user who has checked it out. ");
}
} else {
// All versions except for PWC are locked.
constraint(document.getId(),
"All versions except for PWC are locked when checked out.");
}
}
}
@Override
public void constraintAclPropagationDoesNotMatch(
AclPropagation aclPropagation) {
// Do nothing
}
@Override
public void constraintContentStreamRequired(String repositoryId, Document document) {
String objectTypeId = document.getObjectType();
DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager
.getTypeDefinition(repositoryId, objectTypeId);
if (td.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) {
if (document.getAttachmentNodeId() == null
|| contentService.getAttachment(repositoryId, document
.getAttachmentNodeId()) == null) {
constraint(document.getId(),
"This document type does not allow no content stream");
}
}
}
@Override
public void constraintContentStreamRequired(
DocumentTypeDefinition typeDefinition, ContentStream contentStream) {
if (ContentStreamAllowed.REQUIRED.equals(typeDefinition
.getContentStreamAllowed())) {
if (contentStream == null || contentStream.getStream() == null) {
constraint("[typeId="
+ typeDefinition.getId()
+ "]This document type does not allow no content stream");
}
}
}
@Override
public void constraintOnlyLeafTypeDefinition(String repositoryId, String objectTypeId) {
TypeDefinitionContainer tdc = typeManager.getTypeById(repositoryId, objectTypeId);
if (!CollectionUtils.isEmpty(tdc.getChildren())) {
String msg = "Cannot delete a type definition which has sub types"
+ " [objectTypeId = " + objectTypeId + "]";
throw new CmisConstraintException(msg, HTTP_STATUS_CODE_409);
}
}
@Override
public void constraintObjectsStillExist(String repositoryId, String objectTypeId) {
if (contentService.existContent(repositoryId, objectTypeId)) {
String msg = "There still exists objects of the specified object type"
+ " [objectTypeId = " + objectTypeId + "]";
throw new CmisConstraintException(msg, HTTP_STATUS_CODE_409);
}
}
@Override
public void constraintDuplicatePropertyDefinition(
String repositoryId, TypeDefinition typeDefinition) {
Map<String, PropertyDefinition<?>> props = typeDefinition
.getPropertyDefinitions();
if (MapUtils.isNotEmpty(props)) {
Set<String> keys = props.keySet();
TypeDefinition parent = typeManager
.getTypeDefinition(repositoryId, typeDefinition.getParentTypeId());
Map<String, PropertyDefinition<?>> parentProps = parent
.getPropertyDefinitions();
if (MapUtils.isNotEmpty(parentProps)) {
Set<String> parentKeys = parentProps.keySet();
for (String key : keys) {
if (parentKeys.contains(key)) {
String msg = "Duplicate property definition with parent type definition"
+ " [property id = " + key + "]";
throw new CmisConstraintException(msg,
HTTP_STATUS_CODE_409);
}
}
}
}
}
@Override
public void constraintUpdatePropertyDefinition(
PropertyDefinition<?> update, PropertyDefinition<?> old) {
constraintUpdatePropertyDefinitionHelper(update, old);
}
private <T> PropertyDefinition<T> constraintUpdatePropertyDefinitionHelper(
PropertyDefinition<T> update, PropertyDefinition<?> old) {
String msg = "";
// objField.setName(field.getName());
// objField.setValue(field.getValue());
// return objField;
if (!old.isInherited().equals(update.isInherited())) {
msg += "'inherited' cannot be modified";
}
if (typeManager.getSystemPropertyIds().contains(update.getId())) {
msg += "CMIS-defined property definition cannot be modified";
}
if (Boolean.FALSE.equals(old.isRequired())
&& Boolean.TRUE.equals(update.isRequired())) {
msg += "'required' cannot be modified from Optional to Required";
constraint(msg);
}
if (Boolean.TRUE.equals(old.isOpenChoice())
&& Boolean.FALSE.equals(update.isOpenChoice())) {
msg += "'openChoice' cannot be modified from true to false";
constraint(msg);
}
if (Boolean.FALSE.equals(update.isOpenChoice())) {
constraintChoicesRestriction(update, old);
}
constraintRestrictedValidation(update, old);
if (!old.getPropertyType().equals(update.getPropertyType())) {
msg += "'property type' cannot be modified";
constraint(msg);
}
if (!old.getCardinality().equals(update.getCardinality())) {
msg += "'cardinality' cannot be modified";
constraint(msg);
}
return update;
}
private void constraintChoicesRestriction(PropertyDefinition<?> update,
PropertyDefinition<?> old) {
String msg = update.getId() + ":";
List<?> updateValues = flattenChoiceValues(update.getChoices());
List<?> oldValues = flattenChoiceValues(old.getChoices());
if (!updateValues.containsAll(oldValues)) {
msg += "'choices' values must not be removed if 'openChoice' is false";
constraint(msg);
}
}
private <T> List<T> flattenChoiceValues(List<Choice<T>> choices) {
if (CollectionUtils.isEmpty(choices))
return null;
List<T> result = new ArrayList<T>();
for (Choice<T> choice : choices) {
List<T> value = choice.getValue();
if (CollectionUtils.isEmpty(value))
continue;
for (T v : value) {
result.add(v);
}
result.addAll(flattenChoiceValues(choice.getChoice()));
}
return result;
}
private void constraintRestrictedValidation(PropertyDefinition<?> update,
PropertyDefinition<?> old) {
switch (update.getPropertyType()) {
case BOOLEAN:
break;
case DATETIME:
break;
case DECIMAL:
constraintRestrictedDecimalValidation(update, old);
break;
case HTML:
break;
case ID:
break;
case INTEGER:
constraintRestrictedIntegerValidation(update, old);
break;
case STRING:
constraintRestrictedStringValidation(update, old);
break;
case URI:
break;
default:
break;
}
}
private void constraintRestrictedDecimalValidation(
PropertyDefinition<?> update, PropertyDefinition<?> old) {
PropertyDecimalDefinition _update = (PropertyDecimalDefinition) update;
PropertyDecimalDefinition _old = (PropertyDecimalDefinition) old;
// When minValue is restricted, throw an error
minRestriction(_update.getMinValue(), _old.getMinValue(),
_update.getId());
// When minValue is restricted, throw an error
maxRestriction(_update.getMaxValue(), _old.getMaxValue(),
_update.getId(), null);
}
private void constraintRestrictedIntegerValidation(
PropertyDefinition<?> update, PropertyDefinition<?> old) {
PropertyIntegerDefinition _update = (PropertyIntegerDefinition) update;
PropertyIntegerDefinition _old = (PropertyIntegerDefinition) old;
// When minValue is restricted, throw an error
minRestriction(_update.getMinValue(), _old.getMinValue(),
_update.getId());
// When minValue is restricted, throw an error
maxRestriction(_update.getMaxValue(), _old.getMaxValue(),
_update.getId(), _update.getPropertyType());
}
private void constraintRestrictedStringValidation(
PropertyDefinition<?> update, PropertyDefinition<?> old) {
PropertyStringDefinition _update = (PropertyStringDefinition) update;
PropertyStringDefinition _old = (PropertyStringDefinition) old;
// When minValue is restricted, throw an error
maxRestriction(_update.getMaxLength(), _old.getMaxLength(),
_update.getId(), _update.getPropertyType());
}
private void minRestriction(Comparable update, Comparable old,
String propertyId) {
// When minValue is restricted, throw an error
boolean flag = false;
String msg = propertyId + ":";
if (old == null) {
if (update != null) {
flag = true;
}
} else {
if (update != null) {
if (old.compareTo(update) < 0) {
flag = true;
}
}
}
if (flag) {
msg += "'minValue' cannot be further restricted";
constraint(msg);
}
}
private void maxRestriction(Comparable update, Comparable old,
String propertyId, PropertyType propertyType) {
// When minValue is restricted, throw an error
boolean flag = false;
String msg = propertyId + ":";
if (old == null) {
if (update != null) {
flag = true;
}
} else {
if (update != null) {
if (old.compareTo(update) > 0) {
flag = true;
}
}
}
if (flag) {
if (propertyType.equals(PropertyType.STRING)) {
msg += "'maxLength' cannot be further restricted";
} else {
msg += "'maxValue' cannot be further restricted";
}
constraint(msg);
}
}
@Override
public void constraintQueryName(PropertyDefinition<?> propertyDefinition) {
String msg = propertyDefinition.getId() + ":";
String queryName = propertyDefinition.getQueryName();
if (StringUtils.isEmpty(queryName))
constraint(msg + "'queryName' is null");
final String space = " ";
final String comma = ",";
final String dubleQuotation = "\"";
final String singleQuotaion = "\'";
final String backslash = "\\\\";
final String period = "\\.";
final String openParenthesis = "\\(";
final String closeParenthesis = "\\)";
if (queryName.matches(buildContainRegEx(space))
|| queryName.matches(buildContainRegEx(comma))
|| queryName.matches(buildContainRegEx(dubleQuotation))
|| queryName.matches(buildContainRegEx(singleQuotaion))
|| queryName.matches(buildContainRegEx(backslash))
|| queryName.matches(buildContainRegEx(period))
|| queryName.matches(buildContainRegEx(openParenthesis))
|| queryName.matches(buildContainRegEx(closeParenthesis))) {
constraint(msg
+ "invalid character for 'queryName'. See spec 2.1.2.1.3");
}
}
private String buildContainRegEx(String contained) {
return ".*" + contained + ".*";
}
@Override
public void constraintContentStreamDownload(String repositoryId, Document document) {
DocumentTypeDefinition documentTypeDefinition = (DocumentTypeDefinition) typeManager
.getTypeDefinition(repositoryId, document);
ContentStreamAllowed csa = documentTypeDefinition
.getContentStreamAllowed();
if (ContentStreamAllowed.NOTALLOWED == csa
|| ContentStreamAllowed.ALLOWED == csa
&& StringUtils.isBlank(document.getAttachmentNodeId())) {
constraint(document.getId(),
"This document has no ContentStream. getContentStream is not supported.");
}
}
@Override
public void constraintRenditionStreamDownload(Content content,
String streamId) {
List<String> renditions = content.getRenditionIds();
if (CollectionUtils.isEmpty(renditions)
|| !renditions.contains(streamId)) {
constraint(content.getId(),
"This document has no rendition specified with " + streamId);
}
}
@Override
public void constraintImmutable(String repositoryId,
Document document, TypeDefinition typeDefinition) {
Boolean defaultVal = (Boolean) typeManager.getSingleDefaultValue(
PropertyIds.IS_IMMUTABLE, typeDefinition.getId(), repositoryId);
boolean flag = false;
if (document.isImmutable() == null) {
if (defaultVal != null && defaultVal) {
flag = true;
}
} else {
if (document.isImmutable()) {
flag = true;
}
}
if (flag) {
constraint(document.getId(),
"Immutable document cannot be updated/deleted");
}
}
@Override
public void constraintPropertyDefinition(TypeDefinition typeDefinition,
PropertyDefinition<?> propertyDefinition) {
String typeId = typeDefinition.getId();
String propertyId = propertyDefinition.getId();
if (propertyDefinition.isOrderable()
&& propertyDefinition.getCardinality() == Cardinality.MULTI) {
String msg = DataUtil.buildPrefixTypeProperty(typeId, propertyId)
+ "PropertyDefinition violates the specification";
constraint(msg);
}
}
public void constraintDeleteRootFolder(String repositoryId, String objectId){
String rootFolderId = repositoryInfoMap.get(repositoryId).getRootFolderId();
if(rootFolderId.equals(objectId)){
constraint(objectId, "Cannot delete root folder");
}
}
@Override
public void contentAlreadyExists(Content content, Boolean overwriteFlag) {
if (!overwriteFlag) {
Document document = (Document) content; // FIXME
String attachmentNodeId = document.getAttachmentNodeId(); // FIXME
// getAttachmentNodes
if (attachmentNodeId != null) {
String msg = "Can't overwrite the content stream when overwriteFlag is false";
throw new CmisContentAlreadyExistsException(buildMsgWithId(msg,
content.getId()), HTTP_STATUS_CODE_409);
}
}
}
@Override
public void streamNotSupported(
DocumentTypeDefinition documentTypeDefinition,
ContentStream contentStream) {
if (documentTypeDefinition.getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED
&& contentStream != null) {
String msg = "A Content stream must not be included";
throw new CmisStreamNotSupportedException(msg, HTTP_STATUS_CODE_403);
}
}
/**
*
*/
@Override
public void versioning(Document doc) {
if (!doc.isLatestVersion() && !doc.isPrivateWorkingCopy()) {
String msg = "The operation is not allowed on a non-current version of a document";
throw new CmisVersioningException(buildMsgWithId(msg, doc.getId()),
HTTP_STATUS_CODE_409);
}
}
// TODO implement!
@Override
public void nameConstraintViolation(Properties properties,
Folder parentFolder) {
// If name conflicts, modify names by the repository without outputting
// error
if (parentFolder == null) {
} else {
}
}
@Override
public void updateConflict(Content content, Holder<String> changeToken) {
if ((changeToken == null || changeToken.getValue() == null)) {
throw new CmisUpdateConflictException(
"Change token is required to update", HTTP_STATUS_CODE_409);
} else if (!changeToken.getValue().equals(content.getChangeToken())) {
throw new CmisUpdateConflictException(
"Cannot update because the changeToken conflicts",
HTTP_STATUS_CODE_409);
}
}
private String buildMsgWithId(String msg, String objectId) {
if (objectId == null)
objectId = "";
return msg + " [cmis:objectId = " + objectId + "]";
}
private String getObjectId(Properties properties) {
return DataUtil.getIdProperty(properties, PropertyIds.OBJECT_ID);
}
/**
* Setter
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
}
public void setTypeManager(TypeManager typeManager) {
this.typeManager = typeManager;
}
public void setContentService(ContentService contentService) {
this.contentService = contentService;
}
public void setPermissionService(PermissionService permissionService) {
this.permissionService = permissionService;
}
public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) {
this.repositoryInfoMap = repositoryInfoMap;
}
public void setPrincipalService(PrincipalService principalService) {
this.principalService = principalService;
}
}