View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.commons.validator;
20  
21  import java.util.Comparator;
22  
23  import javax.el.ValueExpression;
24  import javax.faces.FacesException;
25  import javax.faces.FactoryFinder;
26  import javax.faces.application.FacesMessage;
27  import javax.faces.component.EditableValueHolder;
28  import javax.faces.component.UIComponent;
29  import javax.faces.component.ValueHolder;
30  import javax.faces.context.FacesContext;
31  import javax.faces.convert.Converter;
32  import javax.faces.convert.ConverterException;
33  import javax.faces.render.RenderKit;
34  import javax.faces.render.RenderKitFactory;
35  import javax.faces.render.Renderer;
36  import javax.faces.validator.ValidatorException;
37  
38  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
39  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFValidator;
40  import org.apache.myfaces.commons.util.MessageUtils;
41  
42  /**
43   * 
44   * Validates this component against another component.
45   * <p>
46   * Specify the foreign component with the for={foreign-component-id} attribute.
47   * </p>
48   * <p>
49   * Valid operator attribute values:
50   * </p>
51   * <ul>
52   *   <li>equals:                  eq, ==, =,</li>
53   *      <li>not equals:              ne, !=,</li>
54   *   <li>greater than:            gt, &gt;,</li>
55   *   <li>less than:               lt, &lt;,</li>
56   *   <li>greater than or equals:  ge, &gt;=,</li>
57   *   <li>less than or equals:     le, &lt;=</li>
58   * </ul>
59   * <p>
60   * If the comparator attribute is specified, the component values are compared
61   * using the specified java.util.Comparator object.
62   * If no comparator is specified, the component values must implement Comparable
63   * and are compared using compareTo().
64   * If either value or foreign value does not implement Comparable and no Comparator
65   * is specified, validation always succeeds.
66   * </p>
67   * <p>
68   * Put this validator on the bottom-most component to insure that
69   * the foreign component's value has been converted and validated first.
70   * </p>
71   * <p>
72   * However, this validator will attempt to convert and validate the foreign
73   * component's value if this has not already occurred.  This process may not
74   * be identical to the standard JSF conversion and validation process.
75   * </p><p>
76   * The validation error message key is currently hardcoded as
77   * </p>
78   * <p>
79   *     "{0} value &lt;{1}&gt; must be {2} {3} value &lt;{4}&gt;"
80   * </p>
81   * where
82   * <ul>
83   *       <li>{0} is the parent component id,</li>
84   *       <li>{1} is the parent component value,</li>
85   *       <li>{2} is the operator name,</li>
86   *       <li>{3} is the foreign component id, and</li>
87   *       <li>{4} is the foreign component value.</li>
88   * </ul>
89   * <p>
90   * The alternateOperatorName attribute can specify a custom operator name.
91   * For example, use "after" instead of "greater than" when comparing dates.
92   * 
93   * The message attribute can specify an alternate validation error message key.
94   * For example, use "{0} must be {2} {3}" to remove values from the message.
95   * </p>
96   * <p>
97   * Known issues:
98   * </p>
99   * <ul>
100  *   <li> Operator names should be localized.</li>
101  *   <li> The default message key should be localized.</li>
102  *   <li> Perhaps an exception should be thrown if the two values are not Comparable and no Comparator is specified.</li>
103  * </ul>
104  *   
105  *   
106  * @author Mike Kienenberger (latest modification by $Author: lu4242 $)
107  * @version $Revision: 1021620 $ $Date: 2010-10-11 23:09:48 -0500 (Mon, 11 Oct 2010) $
108  */
109 @JSFValidator(
110    name = "mcv:validateCompareTo",
111    clazz = "org.apache.myfaces.commons.validator.CompareToValidator",
112    tagClass = "org.apache.myfaces.commons.validator.ValidateCompareToTag",
113    serialuidtag = "-8879289182242196266L")
114 public abstract class AbstractCompareToValidator extends ValidatorBase {
115     /**
116      * <p>The standard converter id for this converter.</p>
117      */
118     public static final String     VALIDATOR_ID        = "org.apache.myfaces.commons.validator.CompareTo";
119 
120     /**
121      * <p>The message identifier of the {@link FacesMessage} to be created if
122      * the comparison check fails.</p>
123      */
124     public static final String COMPARE_TO_MESSAGE_ID = "org.apache.myfaces.commons.validator.CompareTo.INVALID";
125     
126     public AbstractCompareToValidator(){
127         super();
128     }
129 
130     public static final String OPERATOR_EQUALS = "eq";
131     public static final String OPERATOR_NOT_EQUALS = "ne";
132     public static final String OPERATOR_GREATER_THAN = "gt";
133     public static final String OPERATOR_LESS_THAN = "lt";
134     public static final String OPERATOR_GREATER_THAN_OR_EQUALS = "ge";
135     public static final String OPERATOR_LESS_THAN_OR_EQUALS = "le";
136 
137     public static final String OPERATOR_EQUALS_ALT = "==";
138     public static final String OPERATOR_NOT_EQUALS_ALT = "!=";
139     public static final String OPERATOR_GREATER_THAN_ALT = ">";
140     public static final String OPERATOR_LESS_THAN_ALT = "<";
141     public static final String OPERATOR_GREATER_THAN_OR_EQUALS_ALT = ">=";
142     public static final String OPERATOR_LESS_THAN_OR_EQUALS_ALT = "<=";
143 
144     public static final String OPERATOR_EQUALS_ALT2 = "=";
145 
146     protected String getOperatorForString(String operatorSpecified)
147     {
148         if (OPERATOR_EQUALS.equalsIgnoreCase(operatorSpecified))
149             return OPERATOR_EQUALS;
150         else if (OPERATOR_NOT_EQUALS.equalsIgnoreCase(operatorSpecified))
151             return OPERATOR_NOT_EQUALS;
152         else if (OPERATOR_GREATER_THAN.equalsIgnoreCase(operatorSpecified))
153             return OPERATOR_GREATER_THAN;
154         else if (OPERATOR_LESS_THAN.equalsIgnoreCase(operatorSpecified))
155             return OPERATOR_LESS_THAN;
156         else if (OPERATOR_GREATER_THAN_OR_EQUALS.equalsIgnoreCase(operatorSpecified))
157             return OPERATOR_GREATER_THAN_OR_EQUALS;
158         else if (OPERATOR_LESS_THAN_OR_EQUALS.equalsIgnoreCase(operatorSpecified))
159             return OPERATOR_LESS_THAN_OR_EQUALS;
160 
161         else if (OPERATOR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
162             return OPERATOR_EQUALS;
163         else if (OPERATOR_NOT_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
164             return OPERATOR_NOT_EQUALS;
165         else if (OPERATOR_GREATER_THAN_ALT.equalsIgnoreCase(operatorSpecified))
166             return OPERATOR_GREATER_THAN;
167         else if (OPERATOR_LESS_THAN_ALT.equalsIgnoreCase(operatorSpecified))
168             return OPERATOR_LESS_THAN;
169         else if (OPERATOR_GREATER_THAN_OR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
170             return OPERATOR_GREATER_THAN_OR_EQUALS;
171         else if (OPERATOR_LESS_THAN_OR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
172             return OPERATOR_LESS_THAN_OR_EQUALS;
173 
174         else if (OPERATOR_EQUALS_ALT2.equalsIgnoreCase(operatorSpecified))
175             return OPERATOR_EQUALS;
176 
177         throw new IllegalStateException("Operator has unknown value of '" + operatorSpecified + "'");
178     }
179 
180     protected String nameForOperator(String operator)
181     {
182         if (OPERATOR_EQUALS.equals(operator))
183             return "equal to";
184         else if (OPERATOR_NOT_EQUALS.equals(operator))
185             return "inequal to";
186         else if (OPERATOR_GREATER_THAN.equals(operator))
187             return "greater than";
188         else if (OPERATOR_LESS_THAN.equals(operator))
189             return "less than";
190         else if (OPERATOR_GREATER_THAN_OR_EQUALS.equals(operator))
191             return "greater than or equal to";
192         else if (OPERATOR_LESS_THAN_OR_EQUALS.equals(operator))
193             return "less than or equal to";
194 
195         throw new IllegalStateException("Operator has unknown value of '" + operator + "'");
196     }
197 
198     protected boolean validateOperatorOnComparisonResult(String operator, int result)
199     {
200         if (OPERATOR_EQUALS.equals(operator))
201             return result == 0;
202         else if (OPERATOR_NOT_EQUALS.equals(operator))
203             return result != 0;
204         else if (OPERATOR_GREATER_THAN.equals(operator))
205             return result > 0;
206         else if (OPERATOR_LESS_THAN.equals(operator))
207             return result < 0;
208         else if (OPERATOR_GREATER_THAN_OR_EQUALS.equals(operator))
209             return result >= 0;
210         else if (OPERATOR_LESS_THAN_OR_EQUALS.equals(operator))
211             return result <= 0;
212 
213         throw new IllegalStateException("Operator has unknown value of '" + operator + "'");
214     }
215 
216     public void validate(
217         FacesContext facesContext,
218         UIComponent uiComponent,
219         Object value)
220         throws ValidatorException {
221 
222         if (facesContext == null) throw new NullPointerException("facesContext");
223         if (uiComponent == null) throw new NullPointerException("uiComponent");
224 
225         // Don't perform validation if the value is null
226         if (value == null)
227         {
228             return;
229         }
230 
231         String foreignComponentName = getFor();
232 
233         UIComponent foreignComponent = (UIComponent) uiComponent.getParent().findComponent(foreignComponentName);
234         if(foreignComponent == null)
235             throw new FacesException("Unable to find component '" + foreignComponentName + "' (calling findComponent on component '" + uiComponent.getId() + "')");
236 
237         if(false == foreignComponent instanceof EditableValueHolder)
238             throw new FacesException("Component '" + foreignComponent.getId() + "' does not implement EditableValueHolder");
239         EditableValueHolder foreignEditableValueHolder = (EditableValueHolder)foreignComponent;
240 
241         if (foreignEditableValueHolder.isRequired() && foreignEditableValueHolder.getValue()== null ) {
242             return;
243         }
244 
245         Object foreignValue;
246         if (foreignEditableValueHolder.isValid())
247         {
248             foreignValue = foreignEditableValueHolder.getValue();
249         }
250         else
251         {
252             try 
253             {
254                 foreignValue = getConvertedValueNonValid(facesContext, foreignComponent);
255             }
256             catch(ConverterException e)
257             {
258                 /*
259                  * If the value cannot be converted this should return,
260                  * because does not have sense compare one
261                  * foreign invalid value with other value.
262                  * this force end the validation but do not continue
263                  * with the next phases, because the converter
264                  * of the foreign component fails and show a validation error.
265                  */
266                 return;
267             }
268         }
269 
270         // Don't perform validation if the foreign value is null
271         if (null == foreignValue)
272         {
273             return;
274         }
275 
276         String operator = getOperatorForString(getOperator());
277 
278         String alternateOperatorName = getAlternateOperatorName();
279         Object[] args = {
280                 uiComponent.getId(),
281                 value.toString(),
282                 (alternateOperatorName == null) ? nameForOperator(operator) : alternateOperatorName,
283                 foreignComponent.getId(),
284                 foreignValue.toString()
285         };
286 
287         String message = getMessage();
288         if (null == message)  message = COMPARE_TO_MESSAGE_ID;
289 
290         Comparator comparator = createComparator();
291 
292         if (null != comparator)
293         {
294             if (false == validateOperatorOnComparisonResult(operator, comparator.compare(value, foreignValue)))
295             {
296                 throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message, args));
297             }
298         }
299         else if ( (value instanceof Comparable) && (foreignValue instanceof Comparable) )
300         {
301             try
302             {
303                 if (false == validateOperatorOnComparisonResult(operator, ((Comparable)value).compareTo(foreignValue)))
304                 {
305                     throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message, args));
306                 }
307             }
308             catch (RuntimeException exception)
309             {
310                 if (exception instanceof ValidatorException)
311                 {
312                     throw exception;
313                 }
314                 else
315                 {
316                     throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message + ": " + exception.getLocalizedMessage(), args));
317                 }
318             }
319         }
320         else if (value instanceof Comparable)
321         {
322             throw new ClassCastException(getClassCastExceptionMessage(foreignComponent.getId(), Comparable.class, foreignValue));
323         }
324         else if (foreignValue instanceof Comparable)
325         {
326             throw new ClassCastException(getClassCastExceptionMessage(uiComponent.getId(), Comparable.class, value));
327         }
328     }
329 
330     protected String getClassCastExceptionMessage(String name, Class clazz, Object object)
331     {
332         if (null == object)
333             return name + " must be type " + clazz + " but is null";
334         else return name + " must be type " + clazz + " but is type " + object.getClass();
335     }
336 
337     protected Comparator createComparator()
338     {
339         Object comparator = getComparator();
340 
341         if (null == comparator)  return null;
342 
343         if (false == comparator instanceof Comparator)
344         {
345             throw new ClassCastException(getClassCastExceptionMessage("comparator", Comparator.class, comparator));
346         }
347 
348         return (Comparator)comparator;
349     }
350 
351     // -------------------------------------------------------- GETTER & SETTER
352 
353     /**
354      * The JSF id of the component with which to compare values.
355      * 
356      * @return the foreign component_id, on which a value should be validated
357      */
358     @JSFProperty
359     public abstract String getFor();
360 
361     /**
362      * @param string the foreign component_id, on which a value should be validated
363      */
364     public abstract void setFor(String string);
365 
366     /**
367      * Operator for comparison: equals: eq, ==, =, not equals: ne, !=, greater than: gt, &gt;, less than: lt, &lt;, greater than or equals: ge, &gt;=, less than or equals: le, &lt;=
368      * 
369      * @return
370      */
371     @JSFProperty
372     public abstract String getOperator();
373 
374     public abstract void setOperator(String operator);
375 
376     /**
377      * Value binding for an alternate java.util.Comparator object if component 
378      * values don't implement Comparable
379      * 
380      * @return
381      */
382     @JSFProperty
383     public abstract Object getComparator();
384 
385     public abstract void setComparator(Object comparator);
386 
387     /**
388      * custom operator name in error message (ie "after" instead of "greater than" for dates)
389      * 
390      * @return
391      */
392     @JSFProperty
393     public abstract String getAlternateOperatorName();
394 
395     public abstract void setAlternateOperatorName(String alternateOperatorName);
396 
397     // ---------------- Borrowed to convert foreign submitted values
398 
399     private Renderer getRenderer(FacesContext context, UIComponent foreignComponent)
400     {
401         if (context == null) throw new NullPointerException("context");
402         String rendererType = foreignComponent.getRendererType();
403         if (rendererType == null) return null;
404         String renderKitId = context.getViewRoot().getRenderKitId();
405         RenderKitFactory rkf = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
406         RenderKit renderKit = rkf.getRenderKit(context, renderKitId);
407         Renderer renderer = renderKit.getRenderer(foreignComponent.getFamily(), rendererType);
408         if (renderer == null)
409         {
410             getFacesContext().getExternalContext().log("No Renderer found for component " + foreignComponent + " (component-family=" + foreignComponent.getFamily() + ", renderer-type=" + rendererType + ")");
411         }
412         return renderer;
413     }
414 
415     private Converter findUIOutputConverter(FacesContext facesContext, UIComponent component)
416     {
417         Converter converter = null;
418         if (component instanceof ValueHolder)
419         {
420             converter = ((ValueHolder)component).getConverter();
421         }
422         if (converter != null) return converter;
423 
424         //Try to find out by value binding
425         ValueExpression vb = component.getValueExpression("value");
426         if (vb == null) return null;
427 
428         Class valueType = vb.getType(facesContext.getELContext());
429         if (valueType == null) return null;
430 
431         if (String.class.equals(valueType)) return null;    //No converter needed for String type
432         if (Object.class.equals(valueType)) return null;    //There is no converter for Object class
433 
434         try
435         {
436             return facesContext.getApplication().createConverter(valueType);
437         }
438         catch (FacesException e)
439         {
440             getFacesContext().getExternalContext().log("No Converter for type " + valueType.getName() + " found", e);
441             return null;
442         }
443     }
444 
445 
446     // --------------------- borrowed and modified from UIInput ------------
447 
448     private Object getConvertedValueNonValid(FacesContext facesContext, UIComponent component)
449         throws ConverterException
450     {
451         Object componentValueObject;
452         //If the component does not implements EditableValueHolder
453         //we don't have any way to get the submitted value, so
454         //just return null.
455         if (!(component instanceof EditableValueHolder))
456         {
457             return null;
458         }
459         Object submittedValue = ((EditableValueHolder) component).getSubmittedValue();
460         if (submittedValue == null)
461         {
462             componentValueObject = null;
463         }
464         else
465         {
466             Renderer renderer = getRenderer(facesContext, component);
467             if (renderer != null)
468             {
469                 componentValueObject = renderer.getConvertedValue(facesContext, component, submittedValue);
470             }
471             else if (submittedValue instanceof String)
472             {
473                 Converter converter = findUIOutputConverter(facesContext, component);
474                 if (converter != null)
475                 {
476                     componentValueObject = converter.getAsObject(facesContext, component, (String)submittedValue);
477                 }
478                 else
479                 {
480                     componentValueObject = submittedValue;
481                 }
482             }else{
483                 componentValueObject = submittedValue;
484             }
485         }
486         return componentValueObject;
487     }
488 }