/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.services.extension; import java.util.Collection; import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.eclipse.skalli.commons.HtmlUtils; import org.eclipse.skalli.model.ExtensionEntityBase; import org.eclipse.skalli.model.Issue; import org.eclipse.skalli.model.Issuer; import org.eclipse.skalli.model.PropertyName; import org.eclipse.skalli.model.Severity; ; /** * Abstract base class for the implementation of {@link PropertyValidator property validators}. * It simplifies the implementation of property validators that perform simple yes/no validations, * for example check whether a given value matches a regular expression or has a certain minimum * length. * <p> * Validators derived from this class must implement {@link PropertyValidatorBase#isValid(Object, Severity)}. */ public abstract class PropertyValidatorBase implements PropertyValidator, Issuer { protected Class<? extends ExtensionEntityBase> extension; protected String property; protected String caption; protected String invalidValueMessage; protected String undefinedValueMessage; protected boolean valueRequired; protected Severity severity; /** * Returns a validator instance for a property of a given <code>entity</code>. * Derived classes using this constructor should overwrite {@link #getDefaultMessage(Object)} * or {@link #getMessage(Object)} to provide a meaningful "validation failed" message. * * @param severity the severity that should be assigned to reported issues. * @param extension the class of the model extension the property belongs to. * @param property the name of a property (see {@link PropertyName}). */ protected PropertyValidatorBase(Severity severity, Class<? extends ExtensionEntityBase> extension, String property) { if (severity == null) { throw new IllegalArgumentException("argument 'severity' must not be null"); //$NON-NLS-1$ } if (extension == null) { throw new IllegalArgumentException("argument 'extension' must not be null"); //$NON-NLS-1$ } if (StringUtils.isBlank(property)) { throw new IllegalArgumentException("argument 'property' must not be null or an empty string"); //$NON-NLS-1$ } this.severity = severity; this.extension = extension; this.property = property; } /** * Returns a validator instance for a property of a given <code>entity</code>. * The <code>caption</code> is used to construct "validation failed" messages * of the form <tt>"<value> is not a valid >caption>"</tt> or * <tt>">caption> must have a value"</tt>.<br> * * @param severity the severity that should be assigned to reported issues. * @param extension the class of the model extension the property belongs to, or <code>null</code>. * @param property the name of a property (see {@link PropertyName}). * @param caption the caption of the property as shown to the user in the UI form. */ protected PropertyValidatorBase(Severity severity, Class<? extends ExtensionEntityBase> extension, String property, String caption) { this(severity, extension, property); this.caption = caption; } /** * Returns a validator instance for a property of a given <code>entity</code>. * This constructor allows to define custom "validation failed" and "value undefined" * messages, respectively. Both <code>invalidValueMessage</code> and <code>undefinedValueMessage</code> * may contain the placeholder <tt>"{0}"</tt> which then is substituted by the actual value * of the property in case of a validation failing. * * @param severity the severity that should be assigned to reported issues. * @param extension the class of the model extension the property belongs to, or <code>null</code>. * @param property the name of a property (see {@link PropertyName}). * @param invalidValueMessage the message to return in case the value invalid. * @param undefinedValueMessage the message to return in case the value is undefined. */ protected PropertyValidatorBase(Severity severity, Class<? extends ExtensionEntityBase> extension, String property, String invalidValueMessage, String undefinedValueMessage) { this(severity, extension, property); this.invalidValueMessage = invalidValueMessage; this.undefinedValueMessage = undefinedValueMessage; } /** * Returns <code>true</code>, if the validation should fail in case * the value passed to {@link #validate(UUID,Object,Severity)} is <code>null</code> * or an empty string. */ public boolean isValueRequired() { return valueRequired; } /** * Specifies whether the validation should fail in case the value passed to * {@link #validate(UUID,Object,Severity)} is <code>null</code> or an empty string. * * @param valueRequired if <code>true</code>, a value is required for the property. */ public void setValueRequired(boolean valueRequired) { this.valueRequired = valueRequired; } /** * Returns a custom "invalid value" validation message. * In case a caption has been defined, {@link #getInvalidMessageFromCaption(Object)} is called * to construct a message. Otherwise {@link #getCustomInvalidMessage(Object)} is called to retrieve * a custom "validation failed" message.<br> * Replaces <tt>"{0}"</tt> placeholders in custom messages with <code>value</code>. * <p> * <em>Important note</em>: if you overwrite this method ensure that <code>value</code> * is properly escaped (see {@link HtmlUtils#formatEscaped(String, Object...)}) * if it appears in the result. * * @param value the value of the property. * @return a validation message, if either a caption or custom messages have been * defined, <code>null</code> otherwise. */ protected String getInvalidMessage(Object value) { String message = null; if (StringUtils.isNotBlank(caption)) { message = getInvalidMessageFromCaption(value); } else { message = getCustomInvalidMessage(value); if (StringUtils.isNotBlank(message)) { int n = message.indexOf("{0}"); //$NON-NLS-1$ if (n >= 0) { message = HtmlUtils.formatEscaped(message, value); } } } return message; } /** * Returns a custom "value undefined" validation message. * In case a caption has been defined, {@link #getUndefinedMessageFromCaption()} is called * to construct a message. Otherwise {@link #getCustomUndefinedMessage()} is called to retrieve * a custom "value undefined" message. * * @return a validation message, if either a caption or custom messages have been * defined, <code>null</code> otherwise. */ protected String getUndefinedMessage() { String message = null; if (StringUtils.isNotBlank(caption)) { message = getUndefinedMessageFromCaption(); } else { message = getCustomUndefinedMessage(); } return message; } /** * Constructs a "invalid value" message from the caption of the property. * <p> * <em>Important note</em>: if you overwrite this method ensure that <code>value</code> * is properly escaped (see {@link HtmlUtils#formatEscaped(String, Object...)}) * if it appears in the result. * * @param value the value of the property. */ protected String getInvalidMessageFromCaption(Object value) { return HtmlUtils.formatEscaped("''{0}'' is not a valid {1}", value, caption); } /** * Constructs a "value undefined" message from the caption of the property. */ protected String getUndefinedMessageFromCaption() { return HtmlUtils.formatEscaped("{0} must have a value", caption); } /** * Returns a custom "invalid value" message. * <p> * <em>Important note</em>: if you overwrite this method ensure that <code>value</code> * is properly escaped (see {@link HtmlUtils#formatEscaped(String, Object...)}) * if it appears in the result. * * @param value the value of the property. */ protected String getCustomInvalidMessage(Object value) { return invalidValueMessage; } /** * Returns a custom "value undefined" message. */ protected String getCustomUndefinedMessage() { return undefinedValueMessage; } /** * Returns a default "invalid value" message constructed from the * property name and the actual property value. * <p> * <em>Important note</em>: if you overwrite this method ensure that <code>value</code> * is properly escaped (see {@link HtmlUtils#formatEscaped(String, Object...)}) * if it appears in the result. * * @param value the value of the property. */ protected String getDefaultInvalidMessage(Object value) { return extension != null ? HtmlUtils.formatEscaped("''{0}'' is not a valid value for property ''{1}'' of extension ''{2}''", value, property, extension.getName()) : HtmlUtils.formatEscaped("''{0}'' is not a valid value for property ''{1}''", value, property); } /** * Returns a default "value undefined" message constructed from the * property name and the actual property value. */ protected String getDefaultUndefinedMessage() { return extension != null ? HtmlUtils.formatEscaped("Property ''{0}'' of extension ''{1}'' must have a value", property, extension.getName()) : HtmlUtils.formatEscaped("Property ''{0}'' must have a value", property); } /** * Returns <code>true</code> if the given <code>value</code> is <code>null</code> * or, in case of a string, an empty string. * * @param value the value to check. */ protected boolean isUndefinedOrBlank(Object value) { if (value instanceof String) { return StringUtils.isBlank((String) value); } return value == null; } /** * Returns <code>true</code>, if the given value is invalid * This method is called from within {@link #validate(Object, Severity)} * and implementations can assume that this method is never called * with <code>value=null</code> or <code>value=""</code>. * * @param entity the unique identifier of the entity to validate. * @param value the property value to validate. */ protected abstract boolean isValid(UUID entity, Object value); /** * Calls {@link PropertyValidatorBase#isValid(Object, Severity)} to determine whether * <code>value</code> is valid. If the value is a Collection {@link PropertyValidatorBase#isValid(Object, Severity)} * is called for each item of the collection. <p> * In case the value is invalid, {@link #getInvalidMessage(Object)} * is called to build a suitable "invalid value" validation message. * In case a value is required but not provided, {@link #getUndefinedMessage()} is called to * build a suitable "value undefined" validation message. * <p> * If no suitable custom message is available, the method tries to construct a default message * by calling {@link #getDefaultInvalidMessage(Object)} or {@link #getDefaultUndefinedMessage()}, * respectively. * <p> * The result set of this method contains exactly one {@link Issue} entry, * if the validation failed, but is empty otherwise. * <p> * <em>Important note</em>: if you overwrite this method ensure that <code>value</code> * is properly escaped (see {@link HtmlUtils#formatEscaped(String, Object...)}) * if it appears in any of the messages of the result issue set. */ @Override public SortedSet<Issue> validate(UUID entity, Object value, Severity minSeverity) { TreeSet<Issue> issues = new TreeSet<Issue>(); if (severity.compareTo(minSeverity) <= 0) { if (isUndefinedOrBlank(value)) { if (valueRequired) { String message = getUndefinedMessage(); if (StringUtils.isBlank(message)) { message = getDefaultUndefinedMessage(); } issues.add(new Issue(severity, getClass(), entity, extension, property, 0, message)); } } else if (value instanceof Collection) { int item = 0; for (Object entry : (Collection<?>) value) { validate(entity, entry, minSeverity, item, issues); ++item; } } else { validate(entity, value, minSeverity, 0, issues); } } return issues; } private void validate(UUID entity, Object value, Severity minSeverity, int item, TreeSet<Issue> issues) { String message = null; if (!isValid(entity, value)) { message = getInvalidMessage(value); if (StringUtils.isBlank(message)) { message = getDefaultInvalidMessage(value); } } if (StringUtils.isNotBlank(message)) { issues.add(new Issue(severity, getClass(), entity, extension, property, item, message)); } } }