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