blob: 0c740d19f4d70d02b13080a5966b744dcc887caa [file] [log] [blame]
/*
* 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;
}
}
}