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.trinidad.validator;
20  
21  import java.util.Date;
22  
23  import javax.faces.application.FacesMessage;
24  import javax.faces.component.StateHolder;
25  import javax.faces.component.UIComponent;
26  import javax.faces.component.ValueHolder;
27  import javax.faces.context.FacesContext;
28  import javax.faces.convert.Converter;
29  import javax.faces.convert.DateTimeConverter;
30  import javax.faces.el.ValueBinding;
31  import javax.faces.validator.Validator;
32  import javax.faces.validator.ValidatorException;
33  
34  import org.apache.myfaces.trinidad.bean.FacesBean;
35  import org.apache.myfaces.trinidad.bean.PropertyKey;
36  import org.apache.myfaces.trinidad.util.ComponentUtils;
37  import org.apache.myfaces.trinidad.util.MessageFactory;
38  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
39  
40  
41  /**
42   * <p><strong>DateTimeRangeValidator</strong> is a {@link Validator} that checks
43   * the value of the corresponding component against specified minimum and
44   * maximum dates.  The following algorithm is implemented:</p>
45   * <ul>
46   * <li>If the passed value is <code>null</code>, exit immediately.</li>
47   * <li>If both a <code>maximum</code> and <code>minimum</code> property
48   *     has been configured on this {@link Validator}, check the component
49   *     value against both limits.  If the component value is not within
50   *     this specified range, throw a {@link ValidatorException} containing a
51   *     {@link Validator#NOT_IN_RANGE_MESSAGE_ID} message.</li>
52   * <li>If a <code>maximum</code> property has been configured on this
53   *     {@link Validator}, check the component value against
54   *     this limit.  If the component value is greater than the
55   *     specified maximum, throw a {@link ValidatorException} containing a
56   *     MAXIMUM_MESSAGE_ID message.</li>
57   * <li>If a <code>minimum</code> property has been configured on this
58   *     {@link Validator}, check the component value against
59   *     this limit.  If the component value is less than the
60   *     specified minimum, throw a {@link ValidatorException} containing a
61   *     MINIMUM_MESSAGE_ID message.</li>
62   * </ul>
63   * <p>The detail part of faces message which arise during validation can be
64   * customised by overriding the message associated with each message id by calling
65   * appropriate setter methods.</p>
66   * <p>The methods used for customizing the detail message associated with each id
67   * is given below:</p>
68   * <ul>
69   * <li>{@link #MAXIMUM_MESSAGE_ID} - {@link #setMessageDetailMaximum(String)}</li>
70   * <li>{@link #MINIMUM_MESSAGE_ID} - {@link #setMessageDetailMinimum(String)}</li>
71   * <li>{@link #NOT_IN_RANGE_MESSAGE_ID} - {@link #setMessageDetailNotInRange(String)} - </li></ul>
72   *  Then this message will be used to construct faces message
73   *  when validation fails based on the above-mentioned algorithm
74  
75   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/validator/DateTimeRangeValidator.java#0 $) $Date: 10-nov-2005.19:08:33 $
76   */
77  // TODO The error message date/time reads
78  // "Date cannot be before Mon Feb 16 16:11:13 PST 2004", but the
79  // date should probably be in the format of the converter....
80  public class DateTimeRangeValidator implements Validator, StateHolder {
81  
82  
83    public static final String VALIDATOR_ID = "org.apache.myfaces.trinidad.DateTimeRange";
84  
85  
86    /**
87     * <p>The message identifier of the {@link javax.faces.application.FacesMessage}
88     * to be created if the maximum value check fails.  The message format
89     * string for this message may optionally include <code>{0}</code>,
90     * <code>{1}</code> and <code>{3}</code> placeholders,
91     * which will be replaced by user input, component label and configured
92     * maximum value.</p>
93     */
94    public static final String MAXIMUM_MESSAGE_ID =
95        "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.MAXIMUM";
96  
97    /**
98     * <p>The message identifier of the {@link javax.faces.application.FacesMessage}
99     * to be created if the minimum value check fails.  The message format
100    * string for this message may optionally include <code>{0}</code>,
101    * <code>{1}</code> and <code>{2}</code> placeholders, which will be replaced
102    * by user input, component label and configured minimum value.</p>
103    */
104   public static final String MINIMUM_MESSAGE_ID =
105       "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.MINIMUM";
106 
107 
108   /**
109    * <p>The message identifier of the {@link javax.faces.application.FacesMessage}
110    * to be created if the maximum or minimum value check fails, and both
111    * the maximum and minimum values for this validator have been set.
112    * The message format string for this message may optionally include
113    * <code>{0}</code>, <code>{1}</code>, <code>{2}</code> and <code>{3}</code>
114    * placeholders, which will be replaced by user input, component label,
115    * configured minimum value and configured maximum value.</p>
116    */
117   public static final String NOT_IN_RANGE_MESSAGE_ID =
118       "org.apache.myfaces.trinidad.validator.DateTimeRangeValidator.NOT_IN_RANGE";
119 
120 
121   /**
122    * Construct a {@link Validator} with no preconfigured limits.
123    */
124   public DateTimeRangeValidator()
125   {
126     super();
127   }
128 
129   /**
130    * Construct a {@link Validator} with the specified preconfigured
131    * limit.
132    *
133    * @param maximum Maximum value to allow
134    */
135   public DateTimeRangeValidator(Date maximum)
136   {
137     super();
138     setMaximum(maximum);
139   }
140 
141   /**
142    * Construct a {@link Validator} with the specified preconfigured
143    * limits.
144    *
145    * @param maximum Maximum value to allow
146    * @param minimum Minimum value to allow
147    *
148    */
149   public DateTimeRangeValidator(Date maximum, Date minimum)
150   {
151     super();
152     setMaximum(maximum);
153     setMinimum(minimum);
154   }
155 
156   /**
157    * Return the maximum value to be enforced by this {@link
158    * Validator} or null if it has not been
159    * set.
160    */
161   public Date getMaximum()
162   {
163     Object maxDate = _facesBean.getProperty(_MAXIMUM_KEY);
164     return ComponentUtils.resolveDate(maxDate);
165   }
166 
167   /**
168    * Set the maximum value to be enforced by this {@link Validator}.
169    *
170    * @param maximum The new maximum value
171    *
172    */
173   public void setMaximum(Date maximum)
174   {
175     _facesBean.setProperty(_MAXIMUM_KEY, maximum);
176   }
177 
178 
179   /**
180    * Return the minimum value to be enforced by this {@link
181    * Validator}, or null if it has not been
182    * set.
183    */
184   public Date getMinimum()
185   {
186     Object minDate = _facesBean.getProperty(_MINIMUM_KEY);
187     return ComponentUtils.resolveDate(minDate);
188   }
189 
190   /**
191    * Set the minimum value to be enforced by this {@link Validator}.
192    *
193    * @param minimum The new minimum value
194    *
195    */
196   public void setMinimum(Date minimum)
197   {
198     _facesBean.setProperty(_MINIMUM_KEY, minimum);
199   }
200 
201   /**
202    * <p>Custom error message to be used, for creating detail part of the
203    * {@link FacesMessage}, when input value exceeds the maximum value set.</p>
204    * Overrides detail message identified by message id {@link #MAXIMUM_MESSAGE_ID}
205    * @param maximumMessageDetail Custom error message.
206    */
207   public void setMessageDetailMaximum(String maximumMessageDetail)
208   {
209     _facesBean.setProperty(_MAXIMUM_MESSAGE_DETAIL_KEY, maximumMessageDetail);
210   }
211 
212   /**
213    *  <p>Return custom detail error message that was set for creating {@link FacesMessage},
214    *  for cases where input value exceeds the <code>maximum</code> value set.</p>
215    * @return Custom error message.
216    * @see #setMessageDetailMaximum(String)
217    */
218   public String getMessageDetailMaximum()
219   {
220     Object maxMsgDet = _facesBean.getProperty(_MAXIMUM_MESSAGE_DETAIL_KEY);
221     return ComponentUtils.resolveString(maxMsgDet);
222   }
223 
224   /**
225    * <p>Custom error message to be used, for creating detail part of the
226    * {@link FacesMessage}, when input value is less the set
227    * <code>minimum</code> value.</p>
228    * Overrides detail message identified by message id {@link #MINIMUM_MESSAGE_ID}
229    * @param minimumMessageDetail Custom error message.
230    */
231   public void setMessageDetailMinimum(String minimumMessageDetail)
232   {
233     _facesBean.setProperty(_MINIMUM_MESSAGE_DETAIL_KEY, minimumMessageDetail);
234   }
235 
236   /**
237    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
238    * for cases where, input value is less than the <code>minimum</code> value set.</p>
239    * @return Custom error message.
240    * @see #setMessageDetailMinimum(String)
241    */
242   public String getMessageDetailMinimum()
243   {
244     Object minMsgDet = _facesBean.getProperty(_MINIMUM_MESSAGE_DETAIL_KEY);
245     return ComponentUtils.resolveString(minMsgDet);
246   }
247 
248   /**
249    * <p>Custom error message to be used, for creating detail part of the
250    * {@link FacesMessage}, when input value is not with in the range,
251    * when <code>minimum</code> and <code>maximum</code> is set.</p>
252    * Overrides detail message identified by message id {@link #NOT_IN_RANGE_MESSAGE_ID}
253    * @param notInRangeMessageDetail Custom error message.
254    */
255   public void setMessageDetailNotInRange(String notInRangeMessageDetail)
256   {
257     _facesBean.setProperty(_NOT_IN_RANGE_MESSAGE_DETAIL_KEY, notInRangeMessageDetail);
258   }
259 
260   /**
261    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
262    * for cases where, input value exceeds the <code>maximum</code> value and is
263    * less than the <code>minimum</code> value set.</p>
264    * @return Custom error message.
265    * @see #setMessageDetailNotInRange(String)
266    */
267   public String getMessageDetailNotInRange()
268   {
269     Object notInRngMsg = _facesBean.getProperty(_NOT_IN_RANGE_MESSAGE_DETAIL_KEY);
270     return ComponentUtils.resolveString(notInRngMsg);
271   }
272 
273   /**
274    * <p>Custom hint maximum message.</p>
275    * Overrides default hint message
276    * @param hintMaximum Custom hint message.
277    */
278   public void setHintMaximum(String hintMaximum)
279   {
280     _facesBean.setProperty(_HINT_MAXIMUM_KEY, hintMaximum);
281   }
282 
283   /**
284    * <p>Return custom hint maximum message.</p>
285    * @return Custom hint message.
286    * @see  #setHintMaximum(String)
287    */
288   public String getHintMaximum()
289   {
290     Object obj = _facesBean.getProperty(_HINT_MAXIMUM_KEY);
291     return ComponentUtils.resolveString(obj);
292   }
293 
294   /**
295    * <p>Custom hint minimum message.</p>
296    * Overrides default hint message
297    * @param hintMinimum Custom hint message.
298    */
299   public void setHintMinimum(String hintMinimum)
300   {
301     _facesBean.setProperty(_HINT_MINIMUM_KEY, hintMinimum);
302   }
303 
304   /**
305    * <p>Return custom hint minimum message.</p>
306    * @return Custom hint message.
307    * @see  #setHintMinimum(String)
308    */
309   public String getHintMinimum()
310   {
311     Object obj = _facesBean.getProperty(_HINT_MINIMUM_KEY);
312     return ComponentUtils.resolveString(obj);
313   }
314 
315   /**
316    * <p>Custom hint notInRange message.</p>
317    * Overrides default hint message
318    * @param hintNotInRange Custom hint message.
319    */
320   public void setHintNotInRange(String hintNotInRange)
321   {
322     _facesBean.setProperty(_HINT_NOT_IN_RANGE, hintNotInRange);
323   }
324 
325   /**
326    * <p>Return custom hint notInRange message.</p>
327    * @return Custom hint message.
328    * @see  #setHintNotInRange(String)
329    */
330   public String getHintNotInRange()
331   {
332     Object obj = _facesBean.getProperty(_HINT_NOT_IN_RANGE);
333     return ComponentUtils.resolveString(obj);
334   }
335 
336   /**
337    * @exception IllegalArgumentException if <code>value</code> is not of type
338    * {@link java.util.Date}
339    */
340   public void validate(
341     FacesContext context,
342     UIComponent  component,
343     Object       value) throws ValidatorException
344   {
345     if ((context == null) || (component == null))
346     {
347       throw new NullPointerException(_LOG.getMessage(
348         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
349     }
350 
351     if (value != null)
352     {
353 
354       Date converted = _getDateValue(value);
355 
356       // even after this values can change. But this should be very remote.
357       Date max = getMaximum();
358       Date min = getMinimum();
359       if (max != null && (converted.after(max)))
360       {
361         if (min != null)
362         {
363            throw new ValidatorException
364                       (_getNotInRangeMessage(context, component, value, min, max));
365         }
366         else
367         {
368            throw new ValidatorException
369                       (_getMaximumMessage(context, component, value, max));
370         }
371       }
372 
373       if (min != null && (converted.before(min)))
374       {
375         if (max != null)
376         {
377           throw new ValidatorException
378                       (_getNotInRangeMessage(context, component, value, min, max));
379         }
380         else
381         {
382           FacesMessage msg = _getMinimumMessage(context, component, value, min);
383           throw new ValidatorException(msg);
384         }
385       }
386     }
387   }
388 
389 
390 
391   //  StateHolder Methods
392 
393   public Object saveState(FacesContext context)
394   {
395     return _facesBean.saveState(context);
396   }
397 
398 
399   public void restoreState(FacesContext context, Object state)
400   {
401     _facesBean.restoreState(context, state);
402   }
403 
404   /**
405    * <p>Set the {@link ValueBinding} used to calculate the value for the
406    * specified attribute if any.</p>
407    *
408    * @param name Name of the attribute for which to set a {@link ValueBinding}
409    * @param binding The {@link ValueBinding} to set, or <code>null</code>
410    *  to remove any currently set {@link ValueBinding}
411    *
412    * @exception NullPointerException if <code>name</code>
413    *  is <code>null</code>
414    * @exception IllegalArgumentException if <code>name</code> is not a valid
415    *            attribute of this validator
416    */
417   public void setValueBinding(String name, ValueBinding binding)
418   {
419     ValidatorUtils.setValueBinding(_facesBean, name, binding) ;
420   }
421 
422   /**
423    * <p>Return the {@link ValueBinding} used to calculate the value for the
424    * specified attribute name, if any.</p>
425    *
426    * @param name Name of the attribute or property for which to retrieve a
427    *  {@link ValueBinding}
428    *
429    * @exception NullPointerException if <code>name</code>
430    *  is <code>null</code>
431    * @exception IllegalArgumentException if <code>name</code> is not a valid
432    * attribute of this validator
433    */
434   public ValueBinding getValueBinding(String name)
435   {
436     return ValidatorUtils.getValueBinding(_facesBean, name);
437   }
438 
439   @Override
440   public boolean equals(Object o)
441   {
442     if ( o instanceof DateTimeRangeValidator)
443     {
444       DateTimeRangeValidator that = (DateTimeRangeValidator)o;
445 
446       if ( _transientValue == that._transientValue &&
447            (ValidatorUtils.equals(getMinimum(), that.getMinimum())) &&
448            (ValidatorUtils.equals(getMaximum(), that.getMaximum())) &&
449            (ValidatorUtils.equals(getMessageDetailMaximum(),
450                                    that.getMessageDetailMaximum())) &&
451            (ValidatorUtils.equals(getMessageDetailMinimum(),
452                                    that.getMessageDetailMinimum())) &&
453            (ValidatorUtils.equals(getMessageDetailNotInRange(),
454                                    that.getMessageDetailNotInRange()))
455           )
456       {
457         return true;
458       }
459     }
460     return false;
461   }
462 
463   @Override
464   public int hashCode()
465   {
466     int result = 17;
467     Object max = getMaximum();
468     Object min = getMinimum();
469     Object maxMsgDet        =  getMessageDetailMaximum();
470     Object minMsgDet        =  getMessageDetailMinimum();
471     Object notInRangeMsgDet =  getMessageDetailNotInRange();
472 
473     result = 37 * result + ( max == null ? 0 : max.hashCode());
474     result = 37 * result + ( min == null ? 0 : min.hashCode());
475     result = 37 * result + ( _transientValue ? 0 : 1);
476     result = 37 * result + ( maxMsgDet == null ? 0: maxMsgDet.hashCode());
477     result = 37 * result + ( minMsgDet == null ? 0: minMsgDet.hashCode());
478     result = 37 * result + ( notInRangeMsgDet == null ? 0: notInRangeMsgDet.hashCode());
479     return result;
480   }
481 
482   public boolean isTransient()
483   {
484     return (_transientValue);
485   }
486 
487 
488   public void setTransient(boolean transientValue)
489   {
490     _transientValue = transientValue;
491   }
492 
493   private static Date _getDateValue(
494     Object value) throws IllegalArgumentException
495   {
496     if (value instanceof Date)
497     {
498       return ( (Date)value );
499     }
500 
501     throw new IllegalArgumentException(_LOG.getMessage(
502       "VALUE_IS_NOT_DATE_TYPE"));
503   }
504 
505   private FacesMessage _getNotInRangeMessage(
506     FacesContext context,
507     UIComponent component,
508     Object value,
509     Object min,
510     Object max)
511   { 
512     Converter converter = _getConverter(context, component);
513 
514     Object cValue = _getConvertedValue(context, component, converter, value);
515     Object cMin   = _getConvertedValue(context, component, converter, min);
516     Object cMax   = _getConvertedValue(context, component, converter, max);
517 
518     Object msg   = _getRawNotInRangeMessageDetail();
519     Object label = ValidatorUtils.getComponentLabel(component);
520 
521     Object[] params = {label, cValue, cMin, cMax};
522 
523     return MessageFactory.getMessage(context, NOT_IN_RANGE_MESSAGE_ID,
524                                       msg, params, component);
525   }
526 
527 
528   
529   private Object _getRawNotInRangeMessageDetail()
530   {
531     return _facesBean.getRawProperty(_NOT_IN_RANGE_MESSAGE_DETAIL_KEY);
532   }
533 
534 
535   private FacesMessage _getMaximumMessage(
536     FacesContext context,
537     UIComponent component,
538     Object value,
539     Object max)
540   {
541     Converter converter = _getConverter(context, component);
542 
543     Object cValue = _getConvertedValue(context, component, converter, value);
544     Object cMax   = _getConvertedValue(context, component, converter, max);
545 
546     Object msg   = _getRawMaximumMessageDetail();
547     Object label = ValidatorUtils.getComponentLabel(component);
548 
549     Object[] params = {label, cValue, cMax};
550 
551     return MessageFactory.getMessage(context,
552                                      MAXIMUM_MESSAGE_ID,
553                                      msg,
554                                      params,
555                                      component);
556   }
557 
558   private Object _getRawMaximumMessageDetail()
559   {
560     return _facesBean.getRawProperty(_MAXIMUM_MESSAGE_DETAIL_KEY);
561   }
562 
563   private FacesMessage _getMinimumMessage(
564     FacesContext context,
565     UIComponent component,
566     Object value,
567     Object min)
568   {
569     Converter converter = _getConverter(context, component);
570 
571     Object cValue = _getConvertedValue(context, component, converter, value);
572     Object cMin   = _getConvertedValue(context, component, converter, min);
573 
574 
575     Object msg      = _getRawMinimumMessageDetail();
576     Object label    = ValidatorUtils.getComponentLabel(component);
577 
578     Object[] params = {label, cValue, cMin};
579 
580     return MessageFactory.getMessage(context, MINIMUM_MESSAGE_ID,
581                                      msg, params, component);
582   }
583 
584   private Object _getRawMinimumMessageDetail()
585   {
586     return _facesBean.getRawProperty(_MINIMUM_MESSAGE_DETAIL_KEY);
587   }
588 
589   private Converter _getConverter(
590     FacesContext context,
591     UIComponent component)
592   {
593     Converter converter = null;
594     if (component instanceof ValueHolder)
595     {
596       converter = ((ValueHolder) component).getConverter();
597     }
598 
599     if (converter == null)
600     {
601       // Use the DateTimeConverter's CONVERTER_ID, not Date.class,
602       // because there is in fact not usually a converter registered
603       // at Date.class
604       converter = context.getApplication().createConverter(
605                       DateTimeConverter.CONVERTER_ID);
606     }
607 
608     assert(converter != null);
609 
610     return converter;
611   }
612 
613 
614   private Object _getConvertedValue(
615     FacesContext context,
616     UIComponent  component,
617     Converter    converter,
618     Object       value)
619   {
620     return converter.getAsString(context, component, value);
621   }
622 
623   private static final FacesBean.Type _TYPE = new FacesBean.Type();
624 
625   private static final PropertyKey _MINIMUM_KEY =
626     _TYPE.registerKey("minimum", Date.class);
627 
628   private static final PropertyKey _MAXIMUM_KEY =
629     _TYPE.registerKey("maximum", Date.class );
630 
631   private static final PropertyKey _MAXIMUM_MESSAGE_DETAIL_KEY =
632     _TYPE.registerKey("messageDetailMaximum", String.class);
633 
634   private static final PropertyKey _MINIMUM_MESSAGE_DETAIL_KEY =
635     _TYPE.registerKey("messageDetailMinimum", String.class);
636 
637   private static final PropertyKey _NOT_IN_RANGE_MESSAGE_DETAIL_KEY =
638     _TYPE.registerKey("messageDetailNotInRange", String.class);
639 
640   private static final PropertyKey  _HINT_MAXIMUM_KEY =
641     _TYPE.registerKey("hintMaximum", String.class);
642 
643   private static final PropertyKey  _HINT_MINIMUM_KEY =
644     _TYPE.registerKey("hintMinimum", String.class);
645 
646   private static final PropertyKey  _HINT_NOT_IN_RANGE =
647     _TYPE.registerKey("hintNotInRange", String.class);
648 
649   private FacesBean _facesBean = ValidatorUtils.getFacesBean(_TYPE);
650 
651   private boolean _transientValue = false;
652 
653   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
654     DateTimeRangeValidator.class);
655 }