/** * Class responsible to validate cms properties of a content object * * @author Savvas Triantafyllou (striantafillou@betaconcept.gr) * */ /* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Astroboa 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.console.jsf.visitor; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.faces.application.FacesMessage; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.BinaryChannel; import org.betaconceptframework.astroboa.api.model.BinaryProperty; import org.betaconceptframework.astroboa.api.model.BooleanProperty; import org.betaconceptframework.astroboa.api.model.CmsProperty; import org.betaconceptframework.astroboa.api.model.ComplexCmsProperty; import org.betaconceptframework.astroboa.api.model.ComplexCmsRootProperty; import org.betaconceptframework.astroboa.api.model.ContentObject; import org.betaconceptframework.astroboa.api.model.SimpleCmsProperty; import org.betaconceptframework.astroboa.api.model.ValueType; import org.betaconceptframework.astroboa.api.model.definition.BinaryPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.CmsPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.ComplexCmsPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.ContentObjectTypeDefinition; import org.betaconceptframework.astroboa.api.model.definition.DoublePropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.LongPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.SimpleCmsPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.StringPropertyDefinition; import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType; import org.betaconceptframework.astroboa.api.service.DefinitionService; import org.betaconceptframework.astroboa.console.jsf.edit.SimpleCmsPropertyValueWrapper; import org.betaconceptframework.astroboa.util.CmsConstants; import org.betaconceptframework.astroboa.util.PropertyPath; import org.betaconceptframework.ui.jsf.utility.JSFUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ public class CmsPropertyValidatorVisitor { private static final String PATH_DELIMITER = " > "; private final Logger logger = LoggerFactory.getLogger(getClass()); private DefinitionService definitionService; private boolean mandatoryPropertiesAreMissing = false; private boolean hasErrors = false; private Deque<Map<String, String>> mandatoryErrorMessagesPerPropertyPath = new ArrayDeque<Map<String,String>>(); private Map<String, List<SimpleCmsPropertyValueWrapper>> simpleCmsPropertyValuesOfPasswordType; private Map<String, List<SimpleCmsPropertyValueWrapper>> simpleCmsPropertyValuesOfInvalidTempValue; public CmsPropertyValidatorVisitor(DefinitionService definitionService) { this.definitionService = definitionService; } public void validateContentObjectProperties(ContentObject contentObject, ContentObjectTypeDefinition contentObjectTypeDefinition){ reset(false); if (contentObject == null || contentObject.getComplexCmsRootProperty() == null || contentObjectTypeDefinition == null){ hasErrors = true; logger.error("No contentObject or content object type definition provided. Unable to validate"); return; } //Validate system name if (StringUtils.isNotBlank(contentObject.getSystemName())){ if (!CmsConstants.SystemNamePattern.matcher(contentObject.getSystemName()).matches()){ JSFUtilities.addMessage(null, "object.edit.invalid.system.name", new String[]{contentObject.getSystemName()}, FacesMessage.SEVERITY_WARN); hasErrors = true; } } validateComplexCmsProperty(contentObject.getComplexCmsRootProperty(), contentObjectTypeDefinition.getPropertyDefinitions(), contentObject.getComplexCmsRootProperty().getAspects()); validatePropertiesWithTempValue(); } private void validatePropertiesWithTempValue() { if (MapUtils.isNotEmpty(simpleCmsPropertyValuesOfInvalidTempValue)){ for (Entry<String, List<SimpleCmsPropertyValueWrapper>> simpleCmsPropertyValueWrapperEntry : simpleCmsPropertyValuesOfInvalidTempValue.entrySet()){ for (SimpleCmsPropertyValueWrapper simpleCmsPropertyValueWrapper : simpleCmsPropertyValueWrapperEntry.getValue()){ //It may be the case that user has corrected wrong value if (simpleCmsPropertyValueWrapper.hasTempValue()){ SimpleCmsProperty simpleCmsProperty = simpleCmsPropertyValueWrapper.getSimpleCmsProperty(); Object value = simpleCmsPropertyValueWrapper.getValue(); if (ValueType.Double == simpleCmsProperty.getValueType()){ validateDoubleProperty(Arrays.asList(value), simpleCmsProperty); } else if (ValueType.Long == simpleCmsProperty.getValueType()){ validateLongProperty(Arrays.asList(value), simpleCmsProperty); } else if (ValueType.String == simpleCmsProperty.getValueType()){ validateStringProperty(Arrays.asList(value), simpleCmsProperty); } else{ logger.warn("Found property {} with temp value of type {}", simpleCmsProperty.getFullPath(), simpleCmsProperty.getValueType()); } } } } } } /** * @param asList * @param simpleCmsProperty */ private void validateStringProperty(List<Object> values,SimpleCmsProperty simpleCmsProperty) { int valueIndex = -1; for (Object value : values){ ++valueIndex; if (value != null){ if (value instanceof String){ if (StringUtils.isNotBlank((String)value)){ if (simpleCmsProperty.getPropertyDefinition() != null && ! ((SimpleCmsPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).isValueValid(value)){ if (((StringPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).getPattern() != null){ addErrorMessage(simpleCmsProperty.getFullPath(), "errors.pattern", getLocalizedLabelOfFullPathFromCmsProperty(simpleCmsProperty), ((StringPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).getPattern()); } else{ String min = ((StringPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).getMinLength() == null ? "0" : String.valueOf(((StringPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).getMinLength()); String max = ((StringPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).getMaxLength() == null ? "0" :String.valueOf(((StringPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).getMaxLength()); addErrorMessage(simpleCmsProperty.getFullPath(), "errors.range", getLocalizedLabelOfFullPathFromCmsProperty(simpleCmsProperty), min, max); } } } } } else { // There is a case that the property is multi-value and its list of values contains nulls. // This may happen if the user deletes one or more values from the object form by cutting the text instead of using the (X) button to remove the field. // In such a case when the user submits the form (i.e. save the object) then the property will contain an ArrayList one or more null values. // We should identify such case and not raise an error but instead remove the null value from the list. if (simpleCmsProperty.getPropertyDefinition().isMultiple()) { if (values.size() == 1) { // if there is only one value and is null remove the list itself simpleCmsProperty.removeValues(); } else { simpleCmsProperty.removeSimpleTypeValue(valueIndex); } } else { addErrorMessage(simpleCmsProperty.getFullPath(), "errors.invalid", getLocalizedLabelOfFullPathFromCmsProperty(simpleCmsProperty)); } } } } public void reset(boolean clearSimpleCmsPropertyValueOfPasswordType) { mandatoryPropertiesAreMissing = false; hasErrors = false; mandatoryErrorMessagesPerPropertyPath.clear(); if (clearSimpleCmsPropertyValueOfPasswordType){ if (simpleCmsPropertyValuesOfPasswordType == null){ simpleCmsPropertyValuesOfPasswordType = new HashMap<String, List<SimpleCmsPropertyValueWrapper>>(); } else{ simpleCmsPropertyValuesOfPasswordType.clear(); } } } private boolean validateComplexCmsProperty( ComplexCmsProperty complexCmsProperty, Map<String, CmsPropertyDefinition> childPropertyDefinitions, List<String> aspects) { Map<String, List<CmsProperty>> childCmsProperties = complexCmsProperty.getChildProperties(); mandatoryErrorMessagesPerPropertyPath.push(new HashMap<String, String>()); //Holds all child cms properties that have been processed List<String> childPropertiesValidated = new ArrayList<String>(); boolean atLeastOnePropertyIsAboutToBeSaved = false; boolean foundAtLeastOneNonBooleanPropertyWhoseValueMustBeSaved = false; //validate only if there are any properties if (MapUtils.isNotEmpty(childCmsProperties)){ //Validate each property for (Map.Entry<String, List<CmsProperty>> cmsPropertyEntry : childCmsProperties.entrySet()){ String childCmsPropertyName = cmsPropertyEntry.getKey(); List<CmsProperty> childCmsPropertyValues = cmsPropertyEntry.getValue(); childPropertiesValidated.add(childCmsPropertyName); //Get its definition CmsPropertyDefinition childCmsPropertyDefinition = childPropertyDefinitions.get(childCmsPropertyName); if (childCmsPropertyDefinition == null){ //Check if this is an aspect, if an aspect list is provided if (CollectionUtils.isNotEmpty(aspects) && aspects.contains(childCmsPropertyName)){ //Load aspect definition childCmsPropertyDefinition = (CmsPropertyDefinition) definitionService.getCmsDefinition(childCmsPropertyName, ResourceRepresentationType.DEFINITION_INSTANCE, false); //Still is null if (childCmsPropertyDefinition == null){ logger.error("Aspect "+ childCmsPropertyName + " exists in content object but its definition could not be found"); JSFUtilities.addMessage(null, "Σφάλμα κατά την αποθήκευση των στοιχείων του "+ getLocalizedLabelOfFullPathFromCmsProperty(complexCmsProperty), FacesMessage.SEVERITY_WARN); hasErrors = true; break; } } else if (CollectionUtils.isEmpty(childCmsPropertyValues)) { //Child Property definition is missing and aspect list does not contain its name //Probably an aspect which was removed //Do not validate continue; } else{ logger.error("Property "+ childCmsPropertyName + " exists in content object but its definition could not be found in content object type definition and it is not an aspect"); JSFUtilities.addMessage(null, "Σφάλμα κατά την αποθήκευση των στοιχείων του "+ getLocalizedLabelOfFullPathFromCmsProperty(complexCmsProperty), FacesMessage.SEVERITY_WARN); hasErrors = true; break; } } //Check if property is obsolete if (childCmsPropertyDefinition.isObsolete()){ logger.warn("Found values for obsolete definition for property "+ childCmsPropertyDefinition.getFullPath()); continue; } else{ //Check if it is mandatory but yet no values exist if (childCmsPropertyDefinition.isMandatory() && CollectionUtils.isEmpty(childCmsPropertyValues)){ addErrorMessageForRequiredPropertyUsingParentPropertyAndChildDefinition(complexCmsProperty, childCmsPropertyDefinition); continue; } else{ //Validation for Complex Cms property if (ValueType.Complex == childCmsPropertyDefinition.getValueType()){ //Check if it is single value but more than one values exist if (!childCmsPropertyDefinition.isMultiple() && (childCmsPropertyValues != null && childCmsPropertyValues.size() > 1)){ addErrorMessageForPropertyUsingParentPropertyAndChildDefinition(complexCmsProperty, childCmsPropertyDefinition, "cms.property.single.value"); continue; } else{ for (CmsProperty childCmsProperty : childCmsPropertyValues){ atLeastOnePropertyIsAboutToBeSaved = validateComplexCmsProperty((ComplexCmsProperty)childCmsProperty, ((ComplexCmsPropertyDefinition)childCmsPropertyDefinition).getChildCmsPropertyDefinitions(), aspects) || atLeastOnePropertyIsAboutToBeSaved; } } } else if (ValueType.ContentType != childCmsPropertyDefinition.getValueType()){ //Validate Simple Cms Property for (CmsProperty childCmsProperty : childCmsPropertyValues){ SimpleCmsProperty simpleProperty = (SimpleCmsProperty)childCmsProperty; atLeastOnePropertyIsAboutToBeSaved = validateSimpleCmsProperty(simpleProperty, ((SimpleCmsPropertyDefinition)childCmsPropertyDefinition)) || atLeastOnePropertyIsAboutToBeSaved; if (!foundAtLeastOneNonBooleanPropertyWhoseValueMustBeSaved && simpleProperty.hasValues()){ if (childCmsProperty.getValueType() != ValueType.Boolean){ foundAtLeastOneNonBooleanPropertyWhoseValueMustBeSaved = true; } else{ if (childCmsProperty.getPropertyDefinition().isMultiple() && simpleProperty.getSimpleTypeValues().size()>1){ foundAtLeastOneNonBooleanPropertyWhoseValueMustBeSaved = true; } else if (//Property is a boolean property and its value is not the same with the default value or (((BooleanProperty)childCmsProperty).getPropertyDefinition() != null && ((BooleanProperty)childCmsProperty).getPropertyDefinition().isSetDefaultValue() && ((BooleanProperty)childCmsProperty).getPropertyDefinition().getDefaultValue() != ((BooleanProperty)childCmsProperty).getFirstValue() ) || //no default value is set and its value is TRUE (((BooleanProperty)childCmsProperty).getFirstValue())){ foundAtLeastOneNonBooleanPropertyWhoseValueMustBeSaved = true; } } } } } } } } } //Check all child properties that are mandatory but are not processed at all if (atLeastOnePropertyIsAboutToBeSaved){ if (! foundAtLeastOneNonBooleanPropertyWhoseValueMustBeSaved && ! (complexCmsProperty instanceof ComplexCmsRootProperty)) { atLeastOnePropertyIsAboutToBeSaved = false; if (! mandatoryErrorMessagesPerPropertyPath.isEmpty() && mandatoryErrorMessagesPerPropertyPath.peek() != null){ mandatoryErrorMessagesPerPropertyPath.peek().clear(); } } else{ for (CmsPropertyDefinition childPropertyDefinition : childPropertyDefinitions.values()){ if (childPropertyDefinition.isMandatory() && !childPropertiesValidated.contains(childPropertyDefinition.getName())){ if (complexCmsProperty.getId() == null){ //New complex cms property and one of its child does not even exists addErrorMessageForRequiredPropertyUsingParentPropertyAndChildDefinition(complexCmsProperty, childPropertyDefinition); } } } } } //1. Mandatory Error Messages exist and parent property is mandatory //2. Mandatory Error Messages exist and parent property is an aspect //3. Mandatory Error Messages exist and parent property has at least one child property with value which will be saved Map<String, String> currentMandatoryErrorMessagesPerPropertyPath = mandatoryErrorMessagesPerPropertyPath.pollFirst(); //Mandatory Error Messages should be displayed in the following cases: //1. There is at least one child property which must be saved //2. Complex Cms Property is Mandatory and at the same time it is a root property or an aspect if (MapUtils.isNotEmpty(currentMandatoryErrorMessagesPerPropertyPath)){ //Mandatory errors exist. if (atLeastOnePropertyIsAboutToBeSaved || ( complexCmsProperty.getPropertyDefinition().isMandatory() && ( ( complexCmsProperty.getParentProperty() != null && complexCmsProperty.getParentProperty() instanceof ComplexCmsRootProperty ) ) ) ){ //Print messages for (Map.Entry<String, String> errorMessages : currentMandatoryErrorMessagesPerPropertyPath.entrySet()){ JSFUtilities.addMessage(errorMessages.getKey(), errorMessages.getValue(), FacesMessage.SEVERITY_WARN); } mandatoryPropertiesAreMissing = true; atLeastOnePropertyIsAboutToBeSaved = true; currentMandatoryErrorMessagesPerPropertyPath = null; } else{ if (complexCmsProperty.getParentProperty() == null && ! (complexCmsProperty.getParentProperty() instanceof ComplexCmsRootProperty ) ) { //Copy error messages to the first on the queue if (! mandatoryErrorMessagesPerPropertyPath.isEmpty()){ mandatoryErrorMessagesPerPropertyPath.peekFirst().putAll(currentMandatoryErrorMessagesPerPropertyPath); } } } } return atLeastOnePropertyIsAboutToBeSaved; } private void addErrorMessageForPropertyUsingParentPropertyAndChildDefinition( ComplexCmsProperty parentComplexCmsProperty, CmsPropertyDefinition childCmsPropertyDefinition, String errorMessageKey) { String childPropertyFullPath = PropertyPath.createFullPropertyPath(parentComplexCmsProperty.getFullPath(), childCmsPropertyDefinition.getName()); String childLocalizedLabel = PropertyPath.createFullPropertyPathWithDelimiter(getLocalizedLabelOfFullPathFromCmsProperty(parentComplexCmsProperty), childCmsPropertyDefinition.getDisplayName().getLocalizedLabelForLocale(JSFUtilities.getLocaleAsString()), PATH_DELIMITER); addErrorMessage(childPropertyFullPath, errorMessageKey, childLocalizedLabel); } private void addErrorMessageForRequiredPropertyUsingParentPropertyAndChildDefinition( ComplexCmsProperty parentComplexCmsProperty, CmsPropertyDefinition childCmsPropertyDefinition) { String childPropertyFullPath = PropertyPath.createFullPropertyPath(parentComplexCmsProperty.getFullPath(), childCmsPropertyDefinition.getName()); String childLocalizedLabel = PropertyPath.createFullPropertyPathWithDelimiter(getLocalizedLabelOfFullPathFromCmsProperty(parentComplexCmsProperty), childCmsPropertyDefinition.getDisplayName().getLocalizedLabelForLocale(JSFUtilities.getLocaleAsString()), PATH_DELIMITER); addErrorMessageForRequiredField(childPropertyFullPath, childLocalizedLabel); } private boolean validateSimpleCmsProperty(SimpleCmsProperty simpleCmsProperty, SimpleCmsPropertyDefinition simpleCmsPropertyDefinition) { boolean valueExists = hasValue(simpleCmsPropertyDefinition, simpleCmsProperty); if (! valueExists // No value found && simpleCmsPropertyDefinition.isMandatory() //Is Mandatory && simpleCmsPropertyDefinition.getDefaultValue() == null) { //No default value is defined addErrorMessageForRequiredField(simpleCmsProperty.getFullPath(), getLocalizedLabelOfFullPathFromCmsProperty(simpleCmsProperty)); return false; } else if (valueExists){ //If this property is o password type first we have to verify provided password if (simpleCmsPropertyDefinition != null && simpleCmsPropertyDefinition instanceof StringPropertyDefinition && ((StringPropertyDefinition)simpleCmsPropertyDefinition).isPasswordType() && simpleCmsPropertyValuesOfPasswordType.containsKey(simpleCmsPropertyDefinition.getFullPath())){ for (SimpleCmsPropertyValueWrapper simpleCmsPropertyValueWrapper : simpleCmsPropertyValuesOfPasswordType.get(simpleCmsPropertyDefinition.getFullPath())){ List<String> errorMessages = simpleCmsPropertyValueWrapper.verifyPassword(); if (CollectionUtils.isNotEmpty(errorMessages)){ for (String errorMessage : errorMessages){ addFinalErrorMessage(simpleCmsProperty.getFullPath(), errorMessage); } } simpleCmsPropertyValueWrapper.clearPasswords(); } } //Value exists. Get all values and validate According to type final List<Object> values = new ArrayList<Object>(); if (simpleCmsPropertyDefinition.isMultiple()) values.addAll(simpleCmsProperty.getSimpleTypeValues()); else{ Object simpleTypeValue = simpleCmsProperty.getSimpleTypeValue(); if (simpleTypeValue != null) values.add(simpleTypeValue); } switch (simpleCmsPropertyDefinition.getValueType()) { case Date: //No validation necessary since in UI we make sure that //only Date objects are set. See SimpleCmsProeprtyValueWrapper.setVale() break; case Double: validateDoubleProperty(values, simpleCmsProperty); break; case Long: validateLongProperty(values, simpleCmsProperty); break; case Binary: validateBinaryProperty(values, simpleCmsProperty); break; case String: validateStringProperty(values, simpleCmsProperty); break; case Boolean: case ObjectReference: case TopicReference: //Nothing to validate for these yet. break; case Complex: case ContentType: //This should never happen logger.warn("Tried to validate invalid type for simple cms property "+ simpleCmsPropertyDefinition.getValueType() + " for property path "+ simpleCmsProperty.getFullPath() + " " + "whereas definition's full path is "+ simpleCmsPropertyDefinition.getFullPath()); break; default: break; } //There is at least one value that will be saved return true; } else{ return false; } } private void validateBinaryProperty(List<Object> values, SimpleCmsProperty simpleCmsProperty) { //If binary property is unmanaged then content must exist to //UnmanagedDataStore directory if (simpleCmsProperty.getPropertyDefinition() != null && simpleCmsProperty.getPropertyDefinition() instanceof BinaryPropertyDefinition && ((BinaryPropertyDefinition)simpleCmsProperty.getPropertyDefinition()).isBinaryChannelUnmanaged() ){ for (Object value : values){ if (value != null){ if (value instanceof BinaryChannel){ if (! ((BinaryChannel)value).contentExists()){ addErrorMessage(simpleCmsProperty.getFullPath(), "object.edit.validation.invalid.path.to.unmanaged.binary.channel", getLocalizedLabelOfFullPathFromCmsProperty(simpleCmsProperty), ((BinaryChannel)value).getRelativeFileSystemPath()); } } } } } } private void addErrorMessageForRequiredField(String propertyFullPath, String propertyLocalizedLabel) { //Store message to map and parent property will decide whether it should be announced mandatoryErrorMessagesPerPropertyPath.getFirst().put(propertyFullPath, JSFUtilities.getLocalizedMessage("errors.required", new String[]{propertyLocalizedLabel})); } private void addFinalErrorMessage(String propertyFullPath, String message) { JSFUtilities.addMessage(propertyFullPath, message, FacesMessage.SEVERITY_WARN); hasErrors = true; } private void addErrorMessage(String propertyFullPath, String messageKey, String... messageParameters) { JSFUtilities.addMessage(propertyFullPath, messageKey, messageParameters, FacesMessage.SEVERITY_WARN); hasErrors = true; } private String getLocalizedLabelOfFullPathFromCmsProperty(CmsProperty cmsProperty){ return (cmsProperty == null ? "" : cmsProperty.getLocalizedLabelOfFullPathforLocaleWithDelimiter(JSFUtilities.getLocaleAsString(), PATH_DELIMITER)); } public <T> void visitSimplePropertyDefinition( SimpleCmsPropertyDefinition<T> simplePropertyDefinition) { } private void validateLongProperty(List<Object> values, SimpleCmsProperty cmsProperty) { int valueIndex = -1; for ( Object value : values){ ++valueIndex; if (value != null){ //usually all values are Strings if they are coming from UI forms. // So we should check if we can convert them to longs Long longValue = null; if (value instanceof String){ if (StringUtils.isNotBlank((String)value)){ try{ //If value is not a valid long then an exception is thrown longValue = Long.valueOf((String)value); } catch(Exception e){ addErrorMessage(cmsProperty.getFullPath(), "errors.integerNumber", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty)); return; } } } else if ( ! (value instanceof Long)) { addErrorMessage(cmsProperty.getFullPath(), "errors.integerNumber", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty)); return; } if (longValue == null) { longValue = (Long) value; } // check if the number is within range if (cmsProperty.getPropertyDefinition() != null && ! ((LongPropertyDefinition)cmsProperty.getPropertyDefinition()).isValueValid(longValue)){ // the isValueValid checks for both range and out of enumerated values errors. // Here we assume without checking that it is a range error. // If there is an enumeration in property definition then // it could be an error because the value is outside the enumerated ones but the interface always // provides selection lists for enumerated values and thus this type of error is not possible addErrorMessage(cmsProperty.getFullPath(), "errors.range", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty), String.valueOf(((LongPropertyDefinition)cmsProperty.getPropertyDefinition()).getMinValue()), String.valueOf(((LongPropertyDefinition)cmsProperty.getPropertyDefinition()).getMaxValue())); } } else { // There is a case that the property is multi-value and its list of values contains nulls. // This may happen if the user deletes one or more values from the object form by cutting the number digits instead of using the (X) button to remove the field. // In such a case when the user submits the form (i.e. save the object) then the property will contain an ArrayList with one or more null values. // We should identify such case and not raise an error but instead remove the null value from the list. if (cmsProperty.getPropertyDefinition().isMultiple()) { if (values.size() == 1) { // if there is only one value and is null remove the list itself cmsProperty.removeValues(); } else { cmsProperty.removeSimpleTypeValue(valueIndex); } } else { addErrorMessage(cmsProperty.getFullPath(), "errors.invalid", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty)); } } } } private void validateDoubleProperty(List<Object> values, SimpleCmsProperty cmsProperty) { int valueIndex = -1; for (Object value : values){ ++valueIndex; if (value != null){ //usually all values are Strings if they are coming from UI forms. // So we should check if we can convert them to doubles Double doubleValue = null; if (value instanceof String){ if (StringUtils.isNotBlank((String)value)){ try{ //If value is not a valid double then an exception is thrown doubleValue = Double.valueOf((String)value); } catch(Exception e){ addErrorMessage(cmsProperty.getFullPath(), "errors.decimalNumber", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty)); return; } } } else if ( ! (value instanceof Double)) { addErrorMessage(cmsProperty.getFullPath(), "errors.decimalNumber", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty)); return; } if (doubleValue == null) { doubleValue = (Double) value; } // check if number is within range if (cmsProperty.getPropertyDefinition() != null && ! ((DoublePropertyDefinition)cmsProperty.getPropertyDefinition()).isValueValid(doubleValue)){ // the isValueValid checks for both range and out of enumerated values errors. // Here we assume without checking that it is a range error. // If there is an enumeration in property definition then // it could be an error because the value is outside the enumerated ones but the interface always // provides selection lists for enumerated values and thus this type of error is not possible addErrorMessage(cmsProperty.getFullPath(), "errors.range", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty), String.valueOf(((DoublePropertyDefinition)cmsProperty.getPropertyDefinition()).getMinValue()), String.valueOf(((DoublePropertyDefinition)cmsProperty.getPropertyDefinition()).getMaxValue())); } } else { // There is a case that the property is multi-value and its list of values contains nulls. // This may happen if the user deletes one or more values from the object form by cutting the number digits instead of using the (X) button to remove the field. // In such a case when the user submits the form (i.e. save the object) then the property will contain an ArrayList one or more null values. // We should identify such case and not raise an error but instead remove the null value from the list. if (cmsProperty.getPropertyDefinition().isMultiple()) { if (values.size() == 1) { // if there is only one value and is null remove the list itself cmsProperty.removeValues(); } else { cmsProperty.removeSimpleTypeValue(valueIndex); } } else { addErrorMessage(cmsProperty.getFullPath(), "errors.invalid", getLocalizedLabelOfFullPathFromCmsProperty(cmsProperty)); } } } } private boolean hasValue(SimpleCmsPropertyDefinition simplePropertyDefinition, SimpleCmsProperty simpleProperty){ if (ValueType.Binary == simplePropertyDefinition.getValueType()){ boolean hasValue = false; List<BinaryChannel> tempBinaryChannelList = new ArrayList<BinaryChannel>(); if (simplePropertyDefinition.isMultiple()) tempBinaryChannelList.addAll(((BinaryProperty)simpleProperty).getSimpleTypeValues()); else tempBinaryChannelList.add(((BinaryProperty)simpleProperty).getSimpleTypeValue()); if (CollectionUtils.isNotEmpty(tempBinaryChannelList)) { //All binaryChannels must have content or binary channel id for (BinaryChannel binaryChannel: tempBinaryChannelList) { if (binaryChannel != null)//It may be the case that binaryChannel is a single and has no value. Thus list will contain one element which is null hasValue = binaryChannelHasValue(binaryChannel); if (!hasValue) return false; } } return hasValue; } else{ boolean hasValue = simpleProperty.hasValues(); if (!hasValue) { //No value found. Check if property is of type password. //In this case check if new password is provided in the special //PasswordVerifier bean if (simplePropertyDefinition != null && simplePropertyDefinition instanceof StringPropertyDefinition && ((StringPropertyDefinition)simplePropertyDefinition).isPasswordType() && simpleCmsPropertyValuesOfPasswordType.containsKey(simplePropertyDefinition.getFullPath())){ List<SimpleCmsPropertyValueWrapper> simpleCmsPropertyValueWrappers = simpleCmsPropertyValuesOfPasswordType.get(simplePropertyDefinition.getFullPath()); for (SimpleCmsPropertyValueWrapper simpleCmsPropertyValueWrapper : simpleCmsPropertyValueWrappers) { if (StringUtils.isNotBlank(simpleCmsPropertyValueWrapper.getPasswordVerifier().getNewPassword())) { return true; } } } else{ //Property is no password type. If it is StringProperty //blank strings are considered non values if (! simplePropertyDefinition.isMultiple() && simplePropertyDefinition.getValueType() == ValueType.String) { return StringUtils.isNotBlank((String)simpleProperty.getSimpleTypeValue()); } } } return hasValue; /*if (simplePropertyDefinition.isMultiple()) return CollectionUtils.isNotEmpty(simpleProperty.getSimpleTypeValues()); else // it is Single Value if (simplePropertyDefinition.getValueType() == ValueType.String) return StringUtils.isNotBlank((String)simpleProperty.getSimpleTypeValue()); else return (simpleProperty.getSimpleTypeValue() != null);*/ } } private boolean binaryChannelHasValue(BinaryChannel binaryChannel) { if (binaryChannel.getContent() != null) return true; //No Binary Content exists. Check BinaryChannelUUID if (binaryChannel.getId() != null) return true; //No Content and no uuid. return false; } public boolean mandatoryPropertiesAreMissing() { return mandatoryPropertiesAreMissing; } public boolean hasErrors(){ return hasErrors || mandatoryPropertiesAreMissing; } public void registerSimpleCmsPropertyOfPasswordType( SimpleCmsPropertyValueWrapper simpleCmsPropertyValueWrapper) { if (simpleCmsPropertyValueWrapper == null){ return ; } if (simpleCmsPropertyValuesOfPasswordType == null){ simpleCmsPropertyValuesOfPasswordType = new HashMap<String, List<SimpleCmsPropertyValueWrapper>>(); } String fullPropertyPath = simpleCmsPropertyValueWrapper.getFullPropertyPath(); if (!simpleCmsPropertyValuesOfPasswordType.containsKey(fullPropertyPath)){ simpleCmsPropertyValuesOfPasswordType.put(fullPropertyPath, new ArrayList<SimpleCmsPropertyValueWrapper>()); } //In case property is single value then only one wrapper must exist if (!simpleCmsPropertyValueWrapper.isMultiple()) { simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).clear(); simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).add(simpleCmsPropertyValueWrapper); } else{ //Replace simpleCmsPropertyWrapper with the same index int valueIndex = simpleCmsPropertyValueWrapper.getValueIndex(); if (valueIndex < 0) { //Index -1. Add to list although index is not valid simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).add(simpleCmsPropertyValueWrapper); } else { boolean addedToList = false; for (int i=0;i<simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).size();i++) { if (simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).get(i).getValueIndex() == valueIndex) { simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).set(i, simpleCmsPropertyValueWrapper); addedToList = true; break; } } //Add to the end of the list if (! addedToList) { simpleCmsPropertyValuesOfPasswordType.get(fullPropertyPath).add(simpleCmsPropertyValueWrapper); } } } } /** * @param simpleCmsPropertyValueWrapper */ public void registerInvalidSimpleCmsPropertyWithTempValue( SimpleCmsPropertyValueWrapper simpleCmsPropertyValueWrapper) { if (simpleCmsPropertyValueWrapper == null){ return ; } if (simpleCmsPropertyValuesOfInvalidTempValue == null){ simpleCmsPropertyValuesOfInvalidTempValue = new HashMap<String, List<SimpleCmsPropertyValueWrapper>>(); } String fullPropertyPath = simpleCmsPropertyValueWrapper.getFullPropertyPath(); if (!simpleCmsPropertyValuesOfInvalidTempValue.containsKey(fullPropertyPath)){ simpleCmsPropertyValuesOfInvalidTempValue.put(fullPropertyPath, new ArrayList<SimpleCmsPropertyValueWrapper>()); } //In case property is single value then only one wrapper must exist if (!simpleCmsPropertyValueWrapper.isMultiple()){ simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).clear(); simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).add(simpleCmsPropertyValueWrapper); } else{ //Replace simpleCmsPropertyWrapper with the same index int valueIndex = simpleCmsPropertyValueWrapper.getValueIndex(); if (valueIndex < 0){ //Index -1. Add to list although index is not valid simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).add(simpleCmsPropertyValueWrapper); } else{ boolean addedToList = false; for (int i=0;i<simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).size();i++){ if (simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).get(i).getValueIndex() == valueIndex){ simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).set(i, simpleCmsPropertyValueWrapper); addedToList = true; break; } } //Add to the end of the list if (! addedToList){ simpleCmsPropertyValuesOfInvalidTempValue.get(fullPropertyPath).add(simpleCmsPropertyValueWrapper); } } } } }