| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.chemistry.opencmis.server.support; |
| |
| import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.*; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.chemistry.opencmis.commons.PropertyIds; |
| import org.apache.chemistry.opencmis.commons.data.Acl; |
| import org.apache.chemistry.opencmis.commons.data.Properties; |
| import org.apache.chemistry.opencmis.commons.data.PropertyData; |
| import org.apache.chemistry.opencmis.commons.definitions.Choice; |
| import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; |
| 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.enums.BaseTypeId; |
| import org.apache.chemistry.opencmis.commons.enums.Cardinality; |
| import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; |
| import org.apache.chemistry.opencmis.commons.enums.VersioningState; |
| import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; |
| import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; |
| |
| public final class TypeValidator { |
| |
| private TypeValidator() { |
| } |
| |
| public static void validateRequiredSystemProperties(Properties properties) { |
| if (properties == null || properties.getProperties() == null) { |
| throw new CmisInvalidArgumentException("Cannot create object, no properties are given"); |
| } |
| |
| if (!properties.getProperties().containsKey(PropertyIds.OBJECT_TYPE_ID)) { |
| throw new CmisInvalidArgumentException("Cannot create object, type id is missing"); |
| } |
| } |
| |
| private static boolean isMandatorySystemProperty(String propertyId) { |
| return propertyId.equals(PropertyIds.OBJECT_TYPE_ID); |
| } |
| |
| @SuppressWarnings("unchecked") |
| static <T> PropertyValidator<T> createPropertyValidator(PropertyDefinition<?> propDef) { |
| PropertyValidator<T> result = null; |
| if (propDef instanceof PropertyIntegerDefinition) { |
| result = (PropertyValidator<T>) new PropertyValidatorInteger(); |
| } else if (propDef instanceof PropertyDecimalDefinition) { |
| result = (PropertyValidator<T>) new PropertyValidatorDecimal(); |
| } else if (propDef instanceof PropertyStringDefinition) { |
| result = (PropertyValidator<T>) new PropertyValidatorString(); |
| } else { |
| result = new PropertyValidator<T>(); |
| } |
| return result; |
| } |
| |
| /* |
| * property validations: not readonly, all required are given, all are known |
| * in type cardinality: no multi values for single value, def min max check |
| * for Integer and Decimal, choices and in list Strings, max length set |
| * default value for omitted properties |
| */ |
| static class PropertyValidator<T> { |
| |
| public void validate(PropertyDefinition<T> propDef, PropertyData<T> prop) { |
| |
| // check general constraints for all property types |
| if (propDef.getCardinality() == Cardinality.SINGLE) { |
| if (prop.getValues() != null && prop.getValues().size() > 1) { |
| throw new CmisConstraintException("The property with id " + propDef.getId() |
| + " is single valued, but multiple values are passed " + prop.getValues()); |
| } |
| } |
| |
| if (isNotEmpty(propDef.getChoices())) { |
| validateChoices(propDef, prop); |
| } |
| } |
| |
| private void validateChoices(PropertyDefinition<T> propDef, PropertyData<T> prop) { |
| boolean isAllowedValue = true; |
| boolean hasMultiValueChoiceLists = false; |
| for (Choice<?> allowedValue : propDef.getChoices()) { |
| if (allowedValue.getValue() != null && allowedValue.getValue().size() > 1) { |
| hasMultiValueChoiceLists = true; |
| } |
| } |
| |
| if (propDef.isOpenChoice() != null && propDef.isOpenChoice()) { |
| isAllowedValue = true; |
| } else if (hasMultiValueChoiceLists) { |
| // check if value is in list |
| |
| // do a complex check if this combination of actual values is |
| // allowed check if value is in list |
| isAllowedValue = false; |
| List<?> actualValues = prop.getValues(); |
| for (Choice<?> allowedValue : propDef.getChoices()) { |
| if (allowedValue.getValue().size() == actualValues.size()) { |
| boolean listValuesAreEqual = true; |
| Iterator<?> it = allowedValue.getValue().iterator(); |
| for (Object actualValue : actualValues) { |
| if (!actualValue.equals(it.next())) { |
| listValuesAreEqual = false; |
| break; |
| } |
| } |
| if (listValuesAreEqual) { |
| isAllowedValue = true; |
| } |
| } |
| |
| if (isAllowedValue) { |
| break; |
| } |
| } |
| |
| } else { |
| List<T> allowedValues = getAllowedValues(propDef.getChoices()); |
| // do a simpler check if all values are choice elements |
| |
| for (Object actualValue : prop.getValues()) { |
| if (!allowedValues.contains(actualValue)) { |
| isAllowedValue = false; |
| break; |
| } |
| } |
| } |
| |
| if (!isAllowedValue) { |
| throw new CmisConstraintException("The property with id " + propDef.getId() |
| + " has a fixed set of values. Value(s) " + prop.getValues() + " are not listed."); |
| } |
| } |
| |
| /** |
| * Calculate the list of allowed values for this property definition by |
| * recursively collecting all choice values from property definition |
| * |
| * @param propDef |
| * property definition |
| * @return list of possible values in complete hierarchy |
| */ |
| private List<T> getAllowedValues(List<Choice<T>> choices) { |
| List<T> allowedValues = new ArrayList<T>(choices.size()); |
| for (Choice<T> choice : choices) { |
| if (isNotEmpty(choice.getValue())) { |
| allowedValues.add(choice.getValue().get(0)); |
| } |
| if (isNotEmpty(choice.getChoice())) { |
| List<Choice<T>> x = choice.getChoice(); |
| allowedValues.addAll(getAllowedValues(x)); |
| } |
| } |
| return allowedValues; |
| } |
| } |
| |
| static class PropertyValidatorInteger extends PropertyValidator<BigInteger> { |
| |
| @Override |
| public void validate(PropertyDefinition<BigInteger> propDef, PropertyData<BigInteger> property) { |
| |
| super.validate(propDef, property); |
| |
| BigInteger propVal = property.getFirstValue(); |
| BigInteger minVal = ((PropertyIntegerDefinition) propDef).getMinValue(); |
| BigInteger maxVal = ((PropertyIntegerDefinition) propDef).getMaxValue(); |
| |
| // check min and max |
| if (minVal != null && propVal != null && propVal.compareTo(minVal) == -1) { |
| throw new CmisConstraintException("For property with id " + propDef.getId() + " the value " + propVal |
| + " is less than the minimum value " + minVal); |
| } |
| if (maxVal != null && propVal != null && propVal.compareTo(maxVal) == 1) { |
| throw new CmisConstraintException("For property with id " + propDef.getId() + " the value " + propVal |
| + " is bigger than the maximum value " + maxVal); |
| } |
| } |
| } |
| |
| static class PropertyValidatorDecimal extends PropertyValidator<BigDecimal> { |
| |
| @Override |
| public void validate(PropertyDefinition<BigDecimal> propDef, PropertyData<BigDecimal> property) { |
| |
| super.validate(propDef, property); |
| |
| BigDecimal propVal = property.getFirstValue(); |
| BigDecimal minVal = ((PropertyDecimalDefinition) propDef).getMinValue(); |
| BigDecimal maxVal = ((PropertyDecimalDefinition) propDef).getMaxValue(); |
| |
| // check min and max |
| if (minVal != null && propVal != null && propVal.compareTo(minVal) == -1) { |
| throw new CmisConstraintException("For property with id " + propDef.getId() + " the value " + propVal |
| + " is less than the minimum value " + minVal); |
| } |
| if (maxVal != null && propVal != null && propVal.compareTo(maxVal) == 1) { |
| throw new CmisConstraintException("For property with id " + propDef.getId() + " the value " + propVal |
| + " is bigger than the maximum value " + maxVal); |
| } |
| } |
| } |
| |
| static class PropertyValidatorString extends PropertyValidator<String> { |
| |
| @Override |
| public void validate(PropertyDefinition<String> propDef, PropertyData<String> property) { |
| |
| super.validate(propDef, property); |
| |
| long maxLen = ((PropertyStringDefinition) propDef).getMaxLength() == null ? -1 |
| : ((PropertyStringDefinition) propDef).getMaxLength().longValue(); |
| long len = property.getFirstValue() == null ? -1 : property.getFirstValue().length(); |
| |
| // check max length |
| if (maxLen >= 0 && len >= 0 && maxLen < len) { |
| throw new CmisConstraintException("For property with id " + propDef.getId() + " the length of " + len |
| + " is bigger than the maximum allowed length " + maxLen); |
| } |
| } |
| } |
| |
| public static <T> void validateProperties(TypeDefinition typeDef, Properties properties, boolean checkMandatory) { |
| validateProperties(typeDef, properties, checkMandatory, false); |
| } |
| |
| public static <T> void validateProperties(TypeDefinition typeDef, Properties properties, boolean checkMandatory, |
| boolean cmis11) { |
| List<String> propDefsRequired = getMandatoryPropDefs(typeDef.getPropertyDefinitions()); |
| |
| if (properties != null) { |
| for (PropertyData<?> prop : properties.getProperties().values()) { |
| String propertyId = prop.getId(); |
| if (null == propertyId) { |
| throw new CmisInvalidArgumentException("Property id cannot be null"); |
| } |
| BaseTypeId baseTypeId = typeDef.getBaseTypeId(); |
| |
| // check that all mandatory attributes are present |
| if (checkMandatory && propDefsRequired.contains(propertyId)) { |
| propDefsRequired.remove(propertyId); |
| } |
| |
| if (isSystemProperty(baseTypeId, propertyId, cmis11)) { |
| continue; // ignore system properties for validation |
| } |
| |
| // Check if all properties are known in the type |
| if (typeContainsProperty(typeDef, propertyId)) { |
| // check all type specific constraints: |
| PropertyDefinition<T> propDef = getPropertyDefinition(typeDef, propertyId); |
| PropertyValidator<T> validator = createPropertyValidator(propDef); |
| validator.validate(propDef, (PropertyData<T>) prop); |
| } else { |
| throw new CmisConstraintException("Unknown property " + propertyId + " in type " + typeDef.getId()); |
| } |
| } |
| } |
| |
| if (checkMandatory && !propDefsRequired.isEmpty()) { |
| throw new CmisConstraintException("The following mandatory properties are missing: " + propDefsRequired); |
| } |
| } |
| |
| public static <T> void validateProperties(List<TypeDefinition> typeDefs, Properties properties, |
| boolean checkMandatory) { |
| if (properties == null) { |
| return; |
| } |
| |
| Map<String, Boolean> checkedProperties = new HashMap<String, Boolean>(); |
| for (String propId : properties.getProperties().keySet()) { |
| checkedProperties.put(propId, false); |
| } |
| |
| for (TypeDefinition typeDef : typeDefs) { |
| |
| List<String> propDefsRequired = getMandatoryPropDefs(typeDef.getPropertyDefinitions()); |
| |
| for (PropertyData<?> prop : properties.getProperties().values()) { |
| String propertyId = prop.getId(); |
| if (null == propertyId) { |
| throw new CmisInvalidArgumentException("Property id cannot be null"); |
| } |
| BaseTypeId baseTypeId = typeDef.getBaseTypeId(); |
| |
| // check that all mandatory attributes are present |
| if (checkMandatory && propDefsRequired.contains(propertyId)) { |
| propDefsRequired.remove(propertyId); |
| } |
| |
| if (isSystemProperty(baseTypeId, propertyId, true)) { |
| checkedProperties.put(prop.getId(), true); // ignore system |
| // properties for |
| // validation |
| } else if (typeContainsProperty(typeDef, propertyId)) { |
| // Check if all properties are known in the type |
| // marked the property as found in a type of primary or |
| // secondary types |
| checkedProperties.put(prop.getId(), true); |
| |
| // check all type specific constraints: |
| PropertyDefinition<T> propDef = getPropertyDefinition(typeDef, propertyId); |
| PropertyValidator<T> validator = createPropertyValidator(propDef); |
| validator.validate(propDef, (PropertyData<T>) prop); |
| } |
| } |
| |
| if (checkMandatory && !propDefsRequired.isEmpty()) { |
| throw new CmisConstraintException("The following mandatory properties are missing: " + propDefsRequired); |
| } |
| } |
| |
| // check if all properties are known in a type definition |
| List<String> unknownProperties = new ArrayList<String>(); |
| for (String propId : properties.getProperties().keySet()) { |
| if (!checkedProperties.get(propId)) { |
| unknownProperties.add(propId); |
| } |
| } |
| if (!unknownProperties.isEmpty()) { |
| throw new CmisConstraintException( |
| "The following properties are not known in any of the types of this object: " + unknownProperties); |
| } |
| } |
| |
| public static void validateVersionStateForCreate(DocumentTypeDefinition typeDef, VersioningState verState) { |
| if (null == verState) { |
| return; |
| } |
| if (typeDef.isVersionable() && verState.equals(VersioningState.NONE) || !typeDef.isVersionable() |
| && !verState.equals(VersioningState.NONE)) { |
| throw new CmisConstraintException("The versioning state flag is imcompatible to the type definition."); |
| } |
| |
| } |
| |
| public static void validateAllowedChildObjectTypes(TypeDefinition childTypeDef, List<String> allowedChildTypes) { |
| |
| validateAllowedTypes(childTypeDef, allowedChildTypes, "in this folder"); |
| } |
| |
| public static void validateAllowedRelationshipTypes(RelationshipTypeDefinition relationshipTypeDef, |
| TypeDefinition sourceTypeDef, TypeDefinition targetTypeDef) { |
| List<String> allowedSourceTypes = relationshipTypeDef.getAllowedSourceTypeIds(); |
| validateAllowedTypes(sourceTypeDef, allowedSourceTypes, " as source type in this relationship"); |
| List<String> allowedTargetTypes = relationshipTypeDef.getAllowedTargetTypeIds(); |
| validateAllowedTypes(targetTypeDef, allowedTargetTypes, " as target type in this relationship"); |
| } |
| |
| protected static void validateAllowedTypes(TypeDefinition typeDef, List<String> allowedTypes, String description) { |
| if (isNullOrEmpty(allowedTypes)) { |
| return; // all types are allowed |
| } |
| |
| for (String allowedType : allowedTypes) { |
| if (allowedType.equals(typeDef.getId())) { |
| return; |
| } |
| } |
| throw new CmisConstraintException("The requested type " + typeDef.getId() + " is not allowed " + description); |
| } |
| |
| public static void validateAcl(TypeDefinition typeDef, Acl addACEs, Acl removeACEs) { |
| if (!typeDef.isControllableAcl() && (addACEs != null || removeACEs != null)) { |
| throw new CmisConstraintException("acl set for type: " + typeDef.getDisplayName() |
| + " that is not controllableACL"); |
| } |
| } |
| |
| public static void validateContentAllowed(DocumentTypeDefinition typeDef, boolean hasContent) { |
| ContentStreamAllowed contentAllowed = typeDef.getContentStreamAllowed(); |
| if (ContentStreamAllowed.REQUIRED == contentAllowed && !hasContent) { |
| throw new CmisConstraintException("Type " + typeDef.getId() |
| + " requires content but document has no content."); |
| } else if (ContentStreamAllowed.NOTALLOWED == contentAllowed && hasContent) { |
| throw new CmisConstraintException("Type " + typeDef.getId() |
| + " does not allow content but document has content."); |
| } |
| } |
| |
| private static List<String> getMandatoryPropDefs(Map<String, PropertyDefinition<?>> propDefs) { |
| List<String> res = new ArrayList<String>(); |
| if (null != propDefs) { |
| for (PropertyDefinition<?> propDef : propDefs.values()) { |
| if (propDef.isRequired() && !isMandatorySystemProperty(propDef.getId())) { |
| res.add(propDef.getId()); |
| } |
| } |
| } |
| return res; |
| } |
| |
| public static boolean typeContainsProperty(TypeDefinition typeDef, String propertyId) { |
| |
| Map<String, PropertyDefinition<?>> propDefs = typeDef.getPropertyDefinitions(); |
| if (null == propDefs) { |
| return false; |
| } |
| |
| PropertyDefinition<?> propDef = propDefs.get(propertyId); |
| |
| return propDef != null; |
| } |
| |
| public static boolean typeContainsPropertyWithQueryName(TypeDefinition typeDef, String propertyQueryName) { |
| |
| Map<String, PropertyDefinition<?>> propDefs = typeDef.getPropertyDefinitions(); |
| if (null == propDefs) { |
| return false; |
| } |
| |
| for (PropertyDefinition<?> propDef : propDefs.values()) { |
| if (propDef.getQueryName().equalsIgnoreCase(propertyQueryName)) { |
| return true; |
| } |
| } |
| |
| return false; // unknown property query name in this type |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static <T> PropertyDefinition<T> getPropertyDefinition(TypeDefinition typeDef, String propertyId) { |
| |
| Map<String, PropertyDefinition<?>> propDefs = typeDef.getPropertyDefinitions(); |
| if (null == propDefs) { |
| return null; |
| } |
| |
| PropertyDefinition<?> propDef = propDefs.get(propertyId); |
| |
| if (null == propDef) { |
| return null; // not found |
| } else { |
| return (PropertyDefinition<T>) propDef; |
| } |
| } |
| |
| private static boolean isSystemProperty(BaseTypeId baseTypeId, String propertyId, boolean cmis11) { |
| |
| if (propertyId.equals(PropertyIds.NAME)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.OBJECT_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.OBJECT_TYPE_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.BASE_TYPE_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CREATED_BY)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CREATION_DATE)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.LAST_MODIFIED_BY)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.LAST_MODIFICATION_DATE)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CHANGE_TOKEN)) { |
| return true; |
| } else if (cmis11 && propertyId.equals(PropertyIds.DESCRIPTION)) { |
| return true; |
| } else if (cmis11 && propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) { |
| return true; |
| } |
| |
| if (baseTypeId.equals(BaseTypeId.CMIS_DOCUMENT)) { |
| if (propertyId.equals(PropertyIds.IS_IMMUTABLE)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.IS_LATEST_VERSION)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.IS_MAJOR_VERSION)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.VERSION_SERIES_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.IS_LATEST_MAJOR_VERSION)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.VERSION_LABEL)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.VERSION_SERIES_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CHECKIN_COMMENT)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CONTENT_STREAM_LENGTH)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CONTENT_STREAM_MIME_TYPE)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CONTENT_STREAM_FILE_NAME)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.CONTENT_STREAM_ID)) { |
| return true; |
| } |
| |
| return false; |
| } else if (baseTypeId.equals(BaseTypeId.CMIS_FOLDER)) { |
| if (propertyId.equals(PropertyIds.PARENT_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.PATH)) { |
| return true; |
| } |
| |
| return false; |
| } else if (baseTypeId.equals(BaseTypeId.CMIS_POLICY)) { |
| if (propertyId.equals(PropertyIds.SOURCE_ID)) { |
| return true; |
| } else if (propertyId.equals(PropertyIds.TARGET_ID)) { |
| return true; |
| } |
| return false; |
| } else { // relationship |
| if (propertyId.equals(PropertyIds.POLICY_TEXT)) { |
| return true; |
| } |
| |
| return false; |
| } |
| } |
| } |