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.custom.comparetovalidator;
20  
21  import java.util.Comparator;
22  
23  import javax.faces.FacesException;
24  import javax.faces.FactoryFinder;
25  import javax.faces.application.FacesMessage;
26  import javax.faces.component.EditableValueHolder;
27  import javax.faces.component.UIComponent;
28  import javax.faces.context.FacesContext;
29  import javax.faces.convert.Converter;
30  import javax.faces.convert.ConverterException;
31  import javax.faces.el.ValueBinding;
32  import javax.faces.render.RenderKit;
33  import javax.faces.render.RenderKitFactory;
34  import javax.faces.render.Renderer;
35  import javax.faces.validator.ValidatorException;
36  
37  import org.apache.myfaces.shared_tomahawk.util.MessageUtils;
38  import org.apache.myfaces.shared_tomahawk.util._ComponentUtils;
39  import org.apache.myfaces.tomahawk.util.Constants;
40  import org.apache.myfaces.validator.ValidatorBase;
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   * </p><p>
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  * @JSFValidator
106  *   name = "s:validateCompareTo"
107  *   class = "org.apache.myfaces.custom.comparetovalidator.CompareToValidator"
108  *   tagClass = "org.apache.myfaces.custom.comparetovalidator.ValidateCompareToTag"
109  *   serialuidtag = "-8879289182242196266L"
110  *   
111  * @author Mike Kienenberger (latest modification by $Author: lu4242 $)
112  * @version $Revision: 940142 $ $Date: 2010-05-01 20:36:31 -0500 (Sat, 01 May 2010) $
113  */
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.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.CompareTo.INVALID";
125     public static final String COMPARE_TO_MESSAGE_ID = "{0} value <{1}> must be {2} {3} value <{4}>";
126 
127     public AbstractCompareToValidator(){
128         super();
129     }
130 
131     public static final String OPERATOR_EQUALS = "eq";
132     public static final String OPERATOR_NOT_EQUALS = "ne";
133     public static final String OPERATOR_GREATER_THAN = "gt";
134     public static final String OPERATOR_LESS_THAN = "lt";
135     public static final String OPERATOR_GREATER_THAN_OR_EQUALS = "ge";
136     public static final String OPERATOR_LESS_THAN_OR_EQUALS = "le";
137 
138     public static final String OPERATOR_EQUALS_ALT = "==";
139     public static final String OPERATOR_NOT_EQUALS_ALT = "!=";
140     public static final String OPERATOR_GREATER_THAN_ALT = ">";
141     public static final String OPERATOR_LESS_THAN_ALT = "<";
142     public static final String OPERATOR_GREATER_THAN_OR_EQUALS_ALT = ">=";
143     public static final String OPERATOR_LESS_THAN_OR_EQUALS_ALT = "<=";
144 
145     public static final String OPERATOR_EQUALS_ALT2 = "=";
146 
147     protected String getOperatorForString(String operatorSpecified)
148     {
149         if (OPERATOR_EQUALS.equalsIgnoreCase(operatorSpecified))
150             return OPERATOR_EQUALS;
151         else if (OPERATOR_NOT_EQUALS.equalsIgnoreCase(operatorSpecified))
152             return OPERATOR_NOT_EQUALS;
153         else if (OPERATOR_GREATER_THAN.equalsIgnoreCase(operatorSpecified))
154             return OPERATOR_GREATER_THAN;
155         else if (OPERATOR_LESS_THAN.equalsIgnoreCase(operatorSpecified))
156             return OPERATOR_LESS_THAN;
157         else if (OPERATOR_GREATER_THAN_OR_EQUALS.equalsIgnoreCase(operatorSpecified))
158             return OPERATOR_GREATER_THAN_OR_EQUALS;
159         else if (OPERATOR_LESS_THAN_OR_EQUALS.equalsIgnoreCase(operatorSpecified))
160             return OPERATOR_LESS_THAN_OR_EQUALS;
161 
162         else if (OPERATOR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
163             return OPERATOR_EQUALS;
164         else if (OPERATOR_NOT_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
165             return OPERATOR_NOT_EQUALS;
166         else if (OPERATOR_GREATER_THAN_ALT.equalsIgnoreCase(operatorSpecified))
167             return OPERATOR_GREATER_THAN;
168         else if (OPERATOR_LESS_THAN_ALT.equalsIgnoreCase(operatorSpecified))
169             return OPERATOR_LESS_THAN;
170         else if (OPERATOR_GREATER_THAN_OR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
171             return OPERATOR_GREATER_THAN_OR_EQUALS;
172         else if (OPERATOR_LESS_THAN_OR_EQUALS_ALT.equalsIgnoreCase(operatorSpecified))
173             return OPERATOR_LESS_THAN_OR_EQUALS;
174 
175         else if (OPERATOR_EQUALS_ALT2.equalsIgnoreCase(operatorSpecified))
176             return OPERATOR_EQUALS;
177 
178         throw new IllegalStateException("Operator has unknown value of '" + operatorSpecified + "'");
179     }
180 
181     protected String nameForOperator(String operator)
182     {
183         if (OPERATOR_EQUALS == operator)
184             return "equal to";
185         else if (OPERATOR_NOT_EQUALS == operator)
186             return "inequal to";
187         else if (OPERATOR_GREATER_THAN == operator)
188             return "greater than";
189         else if (OPERATOR_LESS_THAN == operator)
190             return "less than";
191         else if (OPERATOR_GREATER_THAN_OR_EQUALS == operator)
192             return "greater than or equal to";
193         else if (OPERATOR_LESS_THAN_OR_EQUALS == operator)
194             return "less than or equal to";
195 
196         throw new IllegalStateException("Operator has unknown value of '" + operator + "'");
197     }
198 
199     protected boolean validateOperatorOnComparisonResult(String operator, int result)
200     {
201         if (OPERATOR_EQUALS == operator)
202             return result == 0;
203         else if (OPERATOR_NOT_EQUALS == operator)
204             return result != 0;
205         else if (OPERATOR_GREATER_THAN == operator)
206             return result > 0;
207         else if (OPERATOR_LESS_THAN == operator)
208             return result < 0;
209         else if (OPERATOR_GREATER_THAN_OR_EQUALS == operator)
210             return result >= 0;
211         else if (OPERATOR_LESS_THAN_OR_EQUALS == operator)
212             return result <= 0;
213 
214         throw new IllegalStateException("Operator has unknown value of '" + operator + "'");
215     }
216 
217     public void validate(
218         FacesContext facesContext,
219         UIComponent uiComponent,
220         Object value)
221         throws ValidatorException {
222 
223         if (facesContext == null) throw new NullPointerException("facesContext");
224         if (uiComponent == null) throw new NullPointerException("uiComponent");
225 
226         // Don't perform validation if the value is null
227         if (value == null)
228         {
229             return;
230         }
231 
232         String foreignComponentName = getFor();
233 
234         UIComponent foreignComponent = (UIComponent) uiComponent.getParent().findComponent(foreignComponentName);
235         if(foreignComponent == null)
236             throw new FacesException("Unable to find component '" + foreignComponentName + "' (calling findComponent on component '" + uiComponent.getId() + "')");
237 
238         if(false == foreignComponent instanceof EditableValueHolder)
239             throw new FacesException("Component '" + foreignComponent.getId() + "' does not implement EditableValueHolder");
240         EditableValueHolder foreignEditableValueHolder = (EditableValueHolder)foreignComponent;
241 
242         if (foreignEditableValueHolder.isRequired() && foreignEditableValueHolder.getValue()== null ) {
243             return;
244         }
245 
246         Object foreignValue;
247         if (foreignEditableValueHolder.isValid())
248         {
249             foreignValue = foreignEditableValueHolder.getValue();
250         }
251         else
252         {
253             try 
254             {
255                 foreignValue = getConvertedValueNonValid(facesContext, foreignComponent);
256             }
257             catch(ConverterException e)
258             {
259                 /*
260                  * If the value cannot be converted this should return,
261                  * because does not have sense compare one
262                  * foreign invalid value with other value.
263                  * this force end the validation but do not continue
264                  * with the next phases, because the converter
265                  * of the foreign component fails and show a validation error.
266                  */
267                 return;
268             }
269         }
270 
271         // Don't perform validation if the foreign value is null
272         if (null == foreignValue)
273         {
274             return;
275         }
276 
277         String operator = getOperatorForString(getOperator());
278 
279         String alternateOperatorName = getAlternateOperatorName();
280         Object[] args = {
281                 uiComponent.getId(),
282                 value.toString(),
283                 (alternateOperatorName == null) ? nameForOperator(operator) : alternateOperatorName,
284                 foreignComponent.getId(),
285                 (foreignValue == null) ? foreignComponent.getId() : foreignValue.toString()
286         };
287 
288         String message = getMessage();
289         if (null == message)  message = COMPARE_TO_MESSAGE_ID;
290 
291         Comparator comparator = createComparator();
292 
293         if (null != comparator)
294         {
295             if (false == validateOperatorOnComparisonResult(operator, comparator.compare(value, foreignValue)))
296             {
297                 throw new ValidatorException(MessageUtils.getMessage(Constants.TOMAHAWK_DEFAULT_BUNDLE, FacesMessage.SEVERITY_ERROR, message, args, facesContext));
298             }
299         }
300         else if ( (value instanceof Comparable) && (foreignValue instanceof Comparable) )
301         {
302             try
303             {
304                 if (false == validateOperatorOnComparisonResult(operator, ((Comparable)value).compareTo(foreignValue)))
305                 {
306                     throw new ValidatorException(MessageUtils.getMessage(Constants.TOMAHAWK_DEFAULT_BUNDLE, FacesMessage.SEVERITY_ERROR, message, args, facesContext));
307                 }
308             }
309             catch (RuntimeException exception)
310             {
311                 if (exception instanceof ValidatorException)
312                 {
313                     throw exception;
314                 }
315                 else
316                 {
317                     throw new ValidatorException(MessageUtils.getMessage(Constants.TOMAHAWK_DEFAULT_BUNDLE, FacesMessage.SEVERITY_ERROR, message + ": " + exception.getLocalizedMessage(), args, facesContext));
318                 }
319             }
320         }
321         else if (value instanceof Comparable)
322         {
323             throw new ClassCastException(getClassCastExceptionMessage(foreignComponent.getId(), Comparable.class, foreignValue));
324         }
325         else if (foreignValue instanceof Comparable)
326         {
327             throw new ClassCastException(getClassCastExceptionMessage(uiComponent.getId(), Comparable.class, value));
328         }
329     }
330 
331     protected String getClassCastExceptionMessage(String name, Class clazz, Object object)
332     {
333         if (null == object)
334             return name + " must be type " + clazz + " but is null";
335         else return name + " must be type " + clazz + " but is type " + object.getClass();
336     }
337 
338     protected Comparator createComparator()
339     {
340         Object comparator = getComparator();
341 
342         if (null == comparator)  return null;
343 
344         if (false == comparator instanceof Comparator)
345         {
346             throw new ClassCastException(getClassCastExceptionMessage("comparator", Comparator.class, comparator));
347         }
348 
349         return (Comparator)comparator;
350     }
351 
352     // -------------------------------------------------------- GETTER & SETTER
353 
354     /**
355      * The JSF id of the component with which to compare values.
356      * 
357      * @JSFProperty
358      * @return the foreign component_id, on which a value should be validated
359      */
360     public abstract String getFor();
361 
362     /**
363      * @param string the foreign component_id, on which a value should be validated
364      */
365     public abstract void setFor(String string);
366 
367     /**
368      * 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;=
369      * 
370      * @JSFProperty
371      * @return
372      */
373     public abstract String getOperator();
374 
375     public abstract void setOperator(String operator);
376 
377     /**
378      * Value binding for an alternate java.util.Comparator object if component 
379      * values don't implement Comparable
380      * 
381      * @JSFProperty
382      * @return
383      */
384     public abstract Object getComparator();
385 
386     public abstract void setComparator(Object comparator);
387 
388     /**
389      * custom operator name in error message (ie "after" instead of "greater than" for dates)
390      * 
391      * @JSFProperty
392      * @return
393      */
394     public abstract String getAlternateOperatorName();
395 
396     public abstract void setAlternateOperatorName(String alternateOperatorName);
397 
398     // ---------------- Borrowed to convert foreign submitted values
399 
400     protected Renderer getRenderer(FacesContext context, UIComponent foreignComponent)
401     {
402         if (context == null) throw new NullPointerException("context");
403         String rendererType = foreignComponent.getRendererType();
404         if (rendererType == null) return null;
405         String renderKitId = context.getViewRoot().getRenderKitId();
406         RenderKitFactory rkf = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
407         RenderKit renderKit = rkf.getRenderKit(context, renderKitId);
408         Renderer renderer = renderKit.getRenderer(foreignComponent.getFamily(), rendererType);
409         if (renderer == null)
410         {
411             getFacesContext().getExternalContext().log("No Renderer found for component " + foreignComponent + " (component-family=" + foreignComponent.getFamily() + ", renderer-type=" + rendererType + ")");
412         }
413         return renderer;
414     }
415 
416     protected Converter findUIOutputConverter(FacesContext facesContext, UIComponent component)
417     {
418         Converter converter = ((EditableValueHolder)component).getConverter();
419         if (converter != null) return converter;
420 
421         //Try to find out by value binding
422         ValueBinding vb = component.getValueBinding("value");
423         if (vb == null) return null;
424 
425         Class valueType = vb.getType(facesContext);
426         if (valueType == null) return null;
427 
428         if (String.class.equals(valueType)) return null;    //No converter needed for String type
429         if (Object.class.equals(valueType)) return null;    //There is no converter for Object class
430 
431         try
432         {
433             return facesContext.getApplication().createConverter(valueType);
434         }
435         catch (FacesException e)
436         {
437             getFacesContext().getExternalContext().log("No Converter for type " + valueType.getName() + " found", e);
438             return null;
439         }
440     }
441 
442 
443     // --------------------- borrowed and modified from UIInput ------------
444 
445     protected Object getConvertedValueNonValid(FacesContext facesContext, UIComponent component)
446         throws ConverterException
447     {
448         Object componentValueObject;
449         Object submittedValue = ((EditableValueHolder) component).getSubmittedValue();
450         if (submittedValue == null)
451         {
452             componentValueObject = null;
453         }
454         else
455         {
456             Renderer renderer = getRenderer(facesContext, component);
457             if (renderer != null)
458             {
459                 componentValueObject = renderer.getConvertedValue(facesContext, component, submittedValue);
460             }
461             else if (submittedValue instanceof String)
462             {
463                 Converter converter = findUIOutputConverter(facesContext, component);
464                 if (converter != null)
465                 {
466                     componentValueObject = converter.getAsObject(facesContext, component, (String)submittedValue);
467                 }
468                 else
469                 {
470                     componentValueObject = submittedValue;
471                 }
472             }else{
473                 componentValueObject = submittedValue;
474             }
475         }
476         return componentValueObject;
477     }
478 }