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: 1099842 $ $Date: 2011-05-05 10:28:24 -0500 (Thu, 05 May 2011) $
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         foreignComponentName = (foreignComponentName != null && foreignComponentName.length() > 0) ? foreignComponentName : getForId();
233         if (foreignComponentName == null) {
234             throw new FacesException("No id set to compare. Use 'for' in jsp mode and 'forId' in facelets mode");
235         }
236 
237         UIComponent foreignComponent = (UIComponent) uiComponent.getParent().findComponent(foreignComponentName);
238         if(foreignComponent == null)
239             throw new FacesException("Unable to find component '" + foreignComponentName + "' (calling findComponent on component '" + uiComponent.getId() + "')");
240 
241         if(false == foreignComponent instanceof EditableValueHolder)
242             throw new FacesException("Component '" + foreignComponent.getId() + "' does not implement EditableValueHolder");
243         EditableValueHolder foreignEditableValueHolder = (EditableValueHolder)foreignComponent;
244 
245         if (foreignEditableValueHolder.isRequired() && foreignEditableValueHolder.getValue()== null ) {
246             return;
247         }
248 
249         Object foreignValue;
250         if (foreignEditableValueHolder.isValid())
251         {
252             foreignValue = foreignEditableValueHolder.getValue();
253         }
254         else
255         {
256             try 
257             {
258                 foreignValue = getConvertedValueNonValid(facesContext, foreignComponent);
259             }
260             catch(ConverterException e)
261             {
262                 /*
263                  * If the value cannot be converted this should return,
264                  * because does not have sense compare one
265                  * foreign invalid value with other value.
266                  * this force end the validation but do not continue
267                  * with the next phases, because the converter
268                  * of the foreign component fails and show a validation error.
269                  */
270                 return;
271             }
272         }
273 
274         // Don't perform validation if the foreign value is null
275         if (null == foreignValue)
276         {
277             return;
278         }
279 
280         String operator = getOperatorForString(getOperator());
281 
282         String alternateOperatorName = getAlternateOperatorName();
283         Object[] args = {
284                 uiComponent.getId(),
285                 value.toString(),
286                 (alternateOperatorName == null) ? nameForOperator(operator) : alternateOperatorName,
287                 foreignComponent.getId(),
288                 foreignValue.toString()
289         };
290 
291         String message = getMessage();
292         if (null == message)  message = COMPARE_TO_MESSAGE_ID;
293 
294         Comparator comparator = createComparator();
295 
296         if (null != comparator)
297         {
298             if (false == validateOperatorOnComparisonResult(operator, comparator.compare(value, foreignValue)))
299             {
300                 throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message, args));
301             }
302         }
303         else if ( (value instanceof Comparable) && (foreignValue instanceof Comparable) )
304         {
305             try
306             {
307                 if (false == validateOperatorOnComparisonResult(operator, ((Comparable)value).compareTo(foreignValue)))
308                 {
309                     throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message, args));
310                 }
311             }
312             catch (RuntimeException exception)
313             {
314                 if (exception instanceof ValidatorException)
315                 {
316                     throw exception;
317                 }
318                 else
319                 {
320                     throw new ValidatorException(MessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, message + ": " + exception.getLocalizedMessage(), args));
321                 }
322             }
323         }
324         else if (value instanceof Comparable)
325         {
326             throw new ClassCastException(getClassCastExceptionMessage(foreignComponent.getId(), Comparable.class, foreignValue));
327         }
328         else if (foreignValue instanceof Comparable)
329         {
330             throw new ClassCastException(getClassCastExceptionMessage(uiComponent.getId(), Comparable.class, value));
331         }
332     }
333 
334     protected String getClassCastExceptionMessage(String name, Class clazz, Object object)
335     {
336         if (null == object)
337             return name + " must be type " + clazz + " but is null";
338         else return name + " must be type " + clazz + " but is type " + object.getClass();
339     }
340 
341     protected Comparator createComparator()
342     {
343         Object comparator = getComparator();
344 
345         if (null == comparator)  return null;
346 
347         if (false == comparator instanceof Comparator)
348         {
349             throw new ClassCastException(getClassCastExceptionMessage("comparator", Comparator.class, comparator));
350         }
351 
352         return (Comparator)comparator;
353     }
354 
355     // -------------------------------------------------------- GETTER & SETTER
356 
357     /**
358      * The JSF id of the component with which to compare values.
359      * 
360      * In JSF 2.0 facelets mode is used to identify the components this 
361      * validator should be applied to when using composite components.
362      * Please use forId in that case instead.
363      * 
364      * @return the foreign component_id, on which a value should be validated
365      */
366     @JSFProperty
367     public abstract String getFor();
368 
369     /**
370      * @param string the foreign component_id, on which a value should be validated
371      */
372     public abstract void setFor(String string);
373     
374     /**
375      * The JSF id of the component with which to compare values.
376      * 
377      * @return
378      */
379     @JSFProperty(faceletsOnly=true)
380     public abstract String getForId();
381     
382     /**
383      * 
384      * @param string the foreign component_id, on which a value should be validated
385      */
386     public abstract void setForId(String string);
387 
388     /**
389      * 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;=
390      * 
391      * @return
392      */
393     @JSFProperty
394     public abstract String getOperator();
395 
396     public abstract void setOperator(String operator);
397 
398     /**
399      * Value binding for an alternate java.util.Comparator object if component 
400      * values don't implement Comparable
401      * 
402      * @return
403      */
404     @JSFProperty
405     public abstract Object getComparator();
406 
407     public abstract void setComparator(Object comparator);
408 
409     /**
410      * custom operator name in error message (ie "after" instead of "greater than" for dates)
411      * 
412      * @return
413      */
414     @JSFProperty
415     public abstract String getAlternateOperatorName();
416 
417     public abstract void setAlternateOperatorName(String alternateOperatorName);
418 
419     // ---------------- Borrowed to convert foreign submitted values
420 
421     private Renderer getRenderer(FacesContext context, UIComponent foreignComponent)
422     {
423         if (context == null) throw new NullPointerException("context");
424         String rendererType = foreignComponent.getRendererType();
425         if (rendererType == null) return null;
426         String renderKitId = context.getViewRoot().getRenderKitId();
427         RenderKitFactory rkf = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
428         RenderKit renderKit = rkf.getRenderKit(context, renderKitId);
429         Renderer renderer = renderKit.getRenderer(foreignComponent.getFamily(), rendererType);
430         if (renderer == null)
431         {
432             getFacesContext().getExternalContext().log("No Renderer found for component " + foreignComponent + " (component-family=" + foreignComponent.getFamily() + ", renderer-type=" + rendererType + ")");
433         }
434         return renderer;
435     }
436 
437     private Converter findUIOutputConverter(FacesContext facesContext, UIComponent component)
438     {
439         Converter converter = null;
440         if (component instanceof ValueHolder)
441         {
442             converter = ((ValueHolder)component).getConverter();
443         }
444         if (converter != null) return converter;
445 
446         //Try to find out by value binding
447         ValueExpression vb = component.getValueExpression("value");
448         if (vb == null) return null;
449 
450         Class valueType = vb.getType(facesContext.getELContext());
451         if (valueType == null) return null;
452 
453         if (String.class.equals(valueType)) return null;    //No converter needed for String type
454         if (Object.class.equals(valueType)) return null;    //There is no converter for Object class
455 
456         try
457         {
458             return facesContext.getApplication().createConverter(valueType);
459         }
460         catch (FacesException e)
461         {
462             getFacesContext().getExternalContext().log("No Converter for type " + valueType.getName() + " found", e);
463             return null;
464         }
465     }
466 
467 
468     // --------------------- borrowed and modified from UIInput ------------
469 
470     private Object getConvertedValueNonValid(FacesContext facesContext, UIComponent component)
471         throws ConverterException
472     {
473         Object componentValueObject;
474         //If the component does not implements EditableValueHolder
475         //we don't have any way to get the submitted value, so
476         //just return null.
477         if (!(component instanceof EditableValueHolder))
478         {
479             return null;
480         }
481         Object submittedValue = ((EditableValueHolder) component).getSubmittedValue();
482         if (submittedValue == null)
483         {
484             componentValueObject = null;
485         }
486         else
487         {
488             Renderer renderer = getRenderer(facesContext, component);
489             if (renderer != null)
490             {
491                 componentValueObject = renderer.getConvertedValue(facesContext, component, submittedValue);
492             }
493             else if (submittedValue instanceof String)
494             {
495                 Converter converter = findUIOutputConverter(facesContext, component);
496                 if (converter != null)
497                 {
498                     componentValueObject = converter.getAsObject(facesContext, component, (String)submittedValue);
499                 }
500                 else
501                 {
502                     componentValueObject = submittedValue;
503                 }
504             }else{
505                 componentValueObject = submittedValue;
506             }
507         }
508         return componentValueObject;
509     }
510 }