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