/*
* 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.myfaces.commons.validator;
import java.util.Comparator;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;
import javax.faces.validator.ValidatorException;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFValidator;
import org.apache.myfaces.commons.util.MessageUtils;
/**
*
* Validates this component against another component.
* <p>
* Specify the foreign component with the for={foreign-component-id} attribute.
* </p>
* <p>
* Valid operator attribute values:
* </p>
* <ul>
* <li>equals: eq, ==, =,</li>
* <li>not equals: ne, !=,</li>
* <li>greater than: gt, >,</li>
* <li>less than: lt, <,</li>
* <li>greater than or equals: ge, >=,</li>
* <li>less than or equals: le, <=</li>
* </ul>
* <p>
* If the comparator attribute is specified, the component values are compared
* using the specified java.util.Comparator object.
* If no comparator is specified, the component values must implement Comparable
* and are compared using compareTo().
* If either value or foreign value does not implement Comparable and no Comparator
* is specified, validation always succeeds.
* </p>
* <p>
* Put this validator on the bottom-most component to insure that
* the foreign component's value has been converted and validated first.
* </p>
* <p>
* However, this validator will attempt to convert and validate the foreign
* component's value if this has not already occurred. This process may not
* be identical to the standard JSF conversion and validation process.
* </p><p>
* The validation error message key is currently hardcoded as
* </p>
* <p>
* "{0} value <{1}> must be {2} {3} value <{4}>"
* </p>
* where
* <ul>
* <li>{0} is the parent component id,</li>
* <li>{1} is the parent component value,</li>
* <li>{2} is the operator name,</li>
* <li>{3} is the foreign component id, and</li>
* <li>{4} is the foreign component value.</li>
* </ul>
* <p>
* The alternateOperatorName attribute can specify a custom operator name.
* For example, use "after" instead of "greater than" when comparing dates.
*
* The message attribute can specify an alternate validation error message key.
* For example, use "{0} must be {2} {3}" to remove values from the message.
* </p>
* <p>
* Known issues:
* </p>
* <ul>
* <li> Operator names should be localized.</li>
* <li> The default message key should be localized.</li>
* <li> Perhaps an exception should be thrown if the two values are not Comparable and no Comparator is specified.</li>
* </ul>
*
*
* @author Mike Kienenberger (latest modification by $Author: lu4242 $)
* @version $Revision: 1099842 $ $Date: 2011-05-05 10:28:24 -0500 (Thu, 05 May 2011) $
*/
@JSFValidator(
name = "mcv:validateCompareTo",
clazz = "org.apache.myfaces.commons.validator.CompareToValidator",
tagClass = "org.apache.myfaces.commons.validator.ValidateCompareToTag",
serialuidtag = "-8879289182242196266L")
public abstract class AbstractCompareToValidator extends ValidatorBase {
/**
* <p>The standard converter id for this converter.</p>
*/
public static final String VALIDATOR_ID = "org.apache.myfaces.commons.validator.CompareTo";
/**
* <p>The message identifier of the {@link FacesMessage} to be created if
* the comparison check fails.</p>
*/
public static final String COMPARE_TO_MESSAGE_ID = "org.apache.myfaces.commons.validator.CompareTo.INVALID";
public AbstractCompareToValidator(){
super();
}
public static final String OPERATOR_EQUALS = "eq";
public static final String OPERATOR_NOT_EQUALS = "ne";
public static final String OPERATOR_GREATER_THAN = "gt";
public static final String OPERATOR_LESS_THAN = "lt";
public static final String OPERATOR_GREATER_THAN_OR_EQUALS = "ge";
public static final String OPERATOR_LESS_THAN_OR_EQUALS = "le";
public static final String OPERATOR_EQUALS_ALT = "==";
public static final String OPERATOR_NOT_EQUALS_ALT = "!=";
public static final String OPERATOR_GREATER_THAN_ALT = ">";
public static final String OPERATOR_LESS_THAN_ALT = "<";
public static final String OPERATOR_GREATER_THAN_OR_EQUALS_ALT = ">=";
public static final String OPERATOR_LESS_THAN_OR_EQUALS_ALT = "<=";
public static final String OPERATOR_EQUALS_ALT2 = "=";
protected String getOperatorForString(String operatorSpecified)
{
if (OPERATOR_EQUALS.equalsIgnoreCase(operatorSpecified))
return OPERATOR_EQUALS;
else if (OPERATOR_NOT_EQUALS.equalsIgnoreCase(operatorSpecified))
return OPERATOR_NOT_EQUALS;
else if (OPERATOR_GREATER_THAN.equalsIgnoreCase(operatorSpecified))
return OPERATOR_GREATER_THAN;
else if (OPERATOR_LESS_THAN.equalsIgnoreCase(operatorSpecified))
return OPERATOR_LESS_THAN;
else if (OPERATOR_GREATER_THAN_OR_EQUALS.equalsIgnoreCase(operatorSpecified))
return OPERATOR_GREATER_THAN_OR_EQUALS;
else if (OPERATOR_LESS_THAN_OR_EQUALS.equalsIgnoreCase(operatorSpecified))
return OPERATOR_LESS_THAN_OR_EQUALS;
else if (OPERATOR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
return OPERATOR_EQUALS;
else if (OPERATOR_NOT_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
return OPERATOR_NOT_EQUALS;
else if (OPERATOR_GREATER_THAN_ALT.equalsIgnoreCase(operatorSpecified))
return OPERATOR_GREATER_THAN;
else if (OPERATOR_LESS_THAN_ALT.equalsIgnoreCase(operatorSpecified))
return OPERATOR_LESS_THAN;
else if (OPERATOR_GREATER_THAN_OR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
return OPERATOR_GREATER_THAN_OR_EQUALS;
else if (OPERATOR_LESS_THAN_OR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
return OPERATOR_LESS_THAN_OR_EQUALS;
else if (OPERATOR_EQUALS_ALT2.equalsIgnoreCase(operatorSpecified))
return OPERATOR_EQUALS;
throw new IllegalStateException("Operator has unknown value of '" + operatorSpecified + "'");
}
protected String nameForOperator(String operator)
{
if (OPERATOR_EQUALS.equals(operator))
return "equal to";
else if (OPERATOR_NOT_EQUALS.equals(operator))
return "inequal to";
else if (OPERATOR_GREATER_THAN.equals(operator))
return "greater than";
else if (OPERATOR_LESS_THAN.equals(operator))
return "less than";
else if (OPERATOR_GREATER_THAN_OR_EQUALS.equals(operator))
return "greater than or equal to";
else if (OPERATOR_LESS_THAN_OR_EQUALS.equals(operator))
return "less than or equal to";
throw new IllegalStateException("Operator has unknown value of '" + operator + "'");
}
protected boolean validateOperatorOnComparisonResult(String operator, int result)
{
if (OPERATOR_EQUALS.equals(operator))
return result == 0;
else if (OPERATOR_NOT_EQUALS.equals(operator))
return result != 0;
else if (OPERATOR_GREATER_THAN.equals(operator))
return result > 0;
else if (OPERATOR_LESS_THAN.equals(operator))
return result < 0;
else if (OPERATOR_GREATER_THAN_OR_EQUALS.equals(operator))
return result >= 0;
else if (OPERATOR_LESS_THAN_OR_EQUALS.equals(operator))
return result <= 0;
throw new IllegalStateException("Operator has unknown value of '" + operator + "'");
}
public void validate(
FacesContext facesContext,
UIComponent uiComponent,
Object value)
throws ValidatorException {
if (facesContext == null) throw new NullPointerException("facesContext");
if (uiComponent == null) throw new NullPointerException("uiComponent");
// Don't perform validation if the value is null
if (value == null)
{
return;
}
String foreignComponentName = getFor();
foreignComponentName = (foreignComponentName != null && foreignComponentName.length() > 0) ? foreignComponentName : getForId();
if (foreignComponentName == null) {
throw new FacesException("No id set to compare. Use 'for' in jsp mode and 'forId' in facelets mode");
}
UIComponent foreignComponent = (UIComponent) uiComponent.getParent().findComponent(foreignComponentName);
if(foreignComponent == null)
throw new FacesException("Unable to find component '" + foreignComponentName + "' (calling findComponent on component '" + uiComponent.getId() + "')");
if(false == foreignComponent instanceof EditableValueHolder)
throw new FacesException("Component '" + foreignComponent.getId() + "' does not implement EditableValueHolder");
EditableValueHolder foreignEditableValueHolder = (EditableValueHolder)foreignComponent;
if (foreignEditableValueHolder.isRequired() && foreignEditableValueHolder.getValue()== null ) {
return;
}
Object foreignValue;
if (foreignEditableValueHolder.isValid())
{
foreignValue = foreignEditableValueHolder.getValue();
}
else
{
try
{
foreignValue = getConvertedValueNonValid(facesContext, foreignComponent);
}
catch(ConverterException e)
{
/*
* If the value cannot be converted this should return,
* because does not have sense compare one
* foreign invalid value with other value.
* this force end the validation but do not continue
* with the next phases, because the converter
* of the foreign component fails and show a validation error.
*/
return;
}
}
// Don't perform validation if the foreign value is null
if (null == foreignValue)
{
return;
}
String operator = getOperatorForString(getOperator());
String alternateOperatorName = getAlternateOperatorName();
Object[] args = {
uiComponent.getId(),
value.toString(),
(alternateOperatorName == null) ? nameForOperator(operator) : alternateOperatorName,
foreignComponent.getId(),
foreignValue.toString()
};
String message = getMessage();
if (null == message) message = COMPARE_TO_MESSAGE_ID;
Comparator comparator = createComparator();
if (null != comparator)
{
if (false == validateOperatorOnComparisonResult(operator, comparator.compare(value, foreignValue)))
{
throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message, args));
}
}
else if ( (value instanceof Comparable) && (foreignValue instanceof Comparable) )
{
try
{
if (false == validateOperatorOnComparisonResult(operator, ((Comparable)value).compareTo(foreignValue)))
{
throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message, args));
}
}
catch (RuntimeException exception)
{
if (exception instanceof ValidatorException)
{
throw exception;
}
else
{
throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message + ": " + exception.getLocalizedMessage(), args));
}
}
}
else if (value instanceof Comparable)
{
throw new ClassCastException(getClassCastExceptionMessage(foreignComponent.getId(), Comparable.class, foreignValue));
}
else if (foreignValue instanceof Comparable)
{
throw new ClassCastException(getClassCastExceptionMessage(uiComponent.getId(), Comparable.class, value));
}
}
protected String getClassCastExceptionMessage(String name, Class clazz, Object object)
{
if (null == object)
return name + " must be type " + clazz + " but is null";
else return name + " must be type " + clazz + " but is type " + object.getClass();
}
protected Comparator createComparator()
{
Object comparator = getComparator();
if (null == comparator) return null;
if (false == comparator instanceof Comparator)
{
throw new ClassCastException(getClassCastExceptionMessage("comparator", Comparator.class, comparator));
}
return (Comparator)comparator;
}
// -------------------------------------------------------- GETTER & SETTER
/**
* The JSF id of the component with which to compare values.
*
* In JSF 2.0 facelets mode is used to identify the components this
* validator should be applied to when using composite components.
* Please use forId in that case instead.
*
* @return the foreign component_id, on which a value should be validated
*/
@JSFProperty
public abstract String getFor();
/**
* @param string the foreign component_id, on which a value should be validated
*/
public abstract void setFor(String string);
/**
* The JSF id of the component with which to compare values.
*
* @return
*/
@JSFProperty(faceletsOnly=true)
public abstract String getForId();
/**
*
* @param string the foreign component_id, on which a value should be validated
*/
public abstract void setForId(String string);
/**
* Operator for comparison: equals: eq, ==, =, not equals: ne, !=, greater than: gt, >, less than: lt, <, greater than or equals: ge, >=, less than or equals: le, <=
*
* @return
*/
@JSFProperty
public abstract String getOperator();
public abstract void setOperator(String operator);
/**
* Value binding for an alternate java.util.Comparator object if component
* values don't implement Comparable
*
* @return
*/
@JSFProperty
public abstract Object getComparator();
public abstract void setComparator(Object comparator);
/**
* custom operator name in error message (ie "after" instead of "greater than" for dates)
*
* @return
*/
@JSFProperty
public abstract String getAlternateOperatorName();
public abstract void setAlternateOperatorName(String alternateOperatorName);
// ---------------- Borrowed to convert foreign submitted values
private Renderer getRenderer(FacesContext context, UIComponent foreignComponent)
{
if (context == null) throw new NullPointerException("context");
String rendererType = foreignComponent.getRendererType();
if (rendererType == null) return null;
String renderKitId = context.getViewRoot().getRenderKitId();
RenderKitFactory rkf = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit renderKit = rkf.getRenderKit(context, renderKitId);
Renderer renderer = renderKit.getRenderer(foreignComponent.getFamily(), rendererType);
if (renderer == null)
{
getFacesContext().getExternalContext().log("No Renderer found for component " + foreignComponent + " (component-family=" + foreignComponent.getFamily() + ", renderer-type=" + rendererType + ")");
}
return renderer;
}
private Converter findUIOutputConverter(FacesContext facesContext, UIComponent component)
{
Converter converter = null;
if (component instanceof ValueHolder)
{
converter = ((ValueHolder)component).getConverter();
}
if (converter != null) return converter;
//Try to find out by value binding
ValueExpression vb = component.getValueExpression("value");
if (vb == null) return null;
Class valueType = vb.getType(facesContext.getELContext());
if (valueType == null) return null;
if (String.class.equals(valueType)) return null; //No converter needed for String type
if (Object.class.equals(valueType)) return null; //There is no converter for Object class
try
{
return facesContext.getApplication().createConverter(valueType);
}
catch (FacesException e)
{
getFacesContext().getExternalContext().log("No Converter for type " + valueType.getName() + " found", e);
return null;
}
}
// --------------------- borrowed and modified from UIInput ------------
private Object getConvertedValueNonValid(FacesContext facesContext, UIComponent component)
throws ConverterException
{
Object componentValueObject;
//If the component does not implements EditableValueHolder
//we don't have any way to get the submitted value, so
//just return null.
if (!(component instanceof EditableValueHolder))
{
return null;
}
Object submittedValue = ((EditableValueHolder) component).getSubmittedValue();
if (submittedValue == null)
{
componentValueObject = null;
}
else
{
Renderer renderer = getRenderer(facesContext, component);
if (renderer != null)
{
componentValueObject = renderer.getConvertedValue(facesContext, component, submittedValue);
}
else if (submittedValue instanceof String)
{
Converter converter = findUIOutputConverter(facesContext, component);
if (converter != null)
{
componentValueObject = converter.getAsObject(facesContext, component, (String)submittedValue);
}
else
{
componentValueObject = submittedValue;
}
}else{
componentValueObject = submittedValue;
}
}
return componentValueObject;
}
}