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