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 javax.faces.convert;
20  
21  import java.text.DateFormat;
22  import java.text.ParseException;
23  import java.text.SimpleDateFormat;
24  import java.time.LocalDate;
25  import java.time.LocalDateTime;
26  import java.time.LocalTime;
27  import java.time.OffsetDateTime;
28  import java.time.OffsetTime;
29  import java.time.ZonedDateTime;
30  import java.time.format.DateTimeFormatter;
31  import java.time.format.FormatStyle;
32  import java.time.temporal.TemporalAccessor;
33  import java.time.temporal.TemporalQuery;
34  import java.util.Date;
35  import java.util.Locale;
36  import java.util.TimeZone;
37  
38  import javax.faces.component.PartialStateHolder;
39  import javax.faces.component.UIComponent;
40  import javax.faces.context.FacesContext;
41  
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFConverter;
43  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFJspProperty;
44  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
45  
46  /**
47   * This tag associates a date time converter with the nearest parent UIComponent.
48   * 
49   * Unless otherwise specified, all attributes accept static values or EL expressions.
50   * 
51   * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
52   */
53  @JSFConverter(
54      name="f:convertDateTime",
55      bodyContent="empty",
56      tagClass="org.apache.myfaces.taglib.core.ConvertDateTimeTag")
57  @JSFJspProperty(
58      name="binding", 
59      returnType = "javax.faces.convert.DateTimeConverter",
60      longDesc = "A ValueExpression that evaluates to a DateTimeConverter.")
61  public class DateTimeConverter
62          implements Converter, PartialStateHolder
63  {
64  
65      // API field
66      public static final String CONVERTER_ID = "javax.faces.DateTime";
67      public static final String DATE_ID = "javax.faces.converter.DateTimeConverter.DATE";
68      public static final String DATETIME_ID = "javax.faces.converter.DateTimeConverter.DATETIME";
69      public static final String STRING_ID = "javax.faces.converter.STRING";
70      public static final String TIME_ID = "javax.faces.converter.DateTimeConverter.TIME";
71  
72      // internal constants
73      private static final String TYPE_DATE = "date";
74      private static final String TYPE_TIME = "time";
75      private static final String TYPE_BOTH = "both";
76      private static final String TYPE_LOCAL_DATE = "localDate";
77      private static final String TYPE_LOCAL_TIME = "localTime";
78      private static final String TYPE_LOCAL_DATE_TIME = "localDateTime";
79      private static final String TYPE_OFFSET_TIME = "offsetTime";
80      private static final String TYPE_OFFSET_DATE_TIME = "offsetDateTime";
81      private static final String TYPE_ZONED_DATE_TIME = "zonedDateTime";
82              
83      private static final String STYLE_DEFAULT = "default";
84      private static final String STYLE_MEDIUM = "medium";
85      private static final String STYLE_SHORT = "short";
86      private static final String STYLE_LONG = "long";
87      private static final String STYLE_FULL = "full";
88      private static final TimeZone TIMEZONE_DEFAULT = TimeZone.getTimeZone("GMT");
89  
90      private String _dateStyle;
91      private Locale _locale;
92      private String _pattern;
93      private String _timeStyle;
94      private TimeZone _timeZone;
95      private String _type;
96      private boolean _transient;
97  
98      // CONSTRUCTORS
99      public DateTimeConverter()
100     {
101     }
102 
103     // METHODS
104     public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
105     {
106         if (facesContext == null)
107         {
108             throw new NullPointerException("facesContext");
109         }
110         if (uiComponent == null)
111         {
112             throw new NullPointerException("uiComponent");
113         }
114 
115         if (value != null)
116         {
117             value = value.trim();
118             if (value.length() > 0)
119             {
120                 if (isJava8DateTimeFormatter())
121                 {
122                     DateTimeFormatter format = getDateTimeFormatter();
123                     try
124                     {
125                         TemporalQuery tq = getTemporalQuery();
126                         if (tq != null)
127                         {
128                             return format.parse(value, tq);
129                         }
130                         else
131                         {
132                             return format.parse(value);
133                         }
134                     }
135                     catch (Exception e)
136                     {
137                         String type = getType();
138                         TemporalAccessor currentDate;
139                         if (TYPE_LOCAL_DATE.equals(type) || TYPE_LOCAL_DATE_TIME.equals(type) 
140                                 || TYPE_LOCAL_TIME.equals(type))
141                         {
142                             currentDate = LocalDateTime.now();
143                         }
144                         else if (TYPE_OFFSET_TIME.equals(type) || TYPE_OFFSET_DATE_TIME.equals(type))
145                         {
146                             currentDate = OffsetDateTime.now();
147                         }
148                         else
149                         {
150                             currentDate = ZonedDateTime.now();
151                         }
152                         Object[] args = new Object[]{value,
153                                 format.format(currentDate),_MessageUtils.getLabel(facesContext, uiComponent)};
154 
155                         if(type.equals(TYPE_LOCAL_DATE))
156                         {
157                             throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, DATE_ID, args));
158                         }
159                         else if (type.equals(TYPE_LOCAL_TIME) || type.equals(TYPE_OFFSET_TIME))
160                         {
161                             throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, TIME_ID, args));
162                         }
163                         else if (type.equals(TYPE_LOCAL_DATE_TIME) || type.equals(TYPE_OFFSET_DATE_TIME) 
164                                 || type.equals(TYPE_ZONED_DATE_TIME))
165                         {
166                             throw new ConverterException(
167                                     _MessageUtils.getErrorMessage(facesContext, DATETIME_ID, args));
168                         }
169                         else
170                         {
171                             throw new ConverterException("invalid type '" + _type + "'");
172                         }
173                     }
174                 }
175                 else
176                 {
177                     DateFormat format = getDateFormat();
178                     TimeZone tz = getTimeZone();
179                     if( tz != null )
180                     {
181                         format.setTimeZone(tz);
182                     }
183                     try
184                     {
185                         return format.parse(value);
186                     }
187                     catch (ParseException e)
188                     {
189                         String type = getType();
190                         Object[] args = new Object[]{value,
191                                 format.format(new Date()),_MessageUtils.getLabel(facesContext, uiComponent)};
192 
193                         if(type.equals(TYPE_DATE))
194                         {
195                             throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, DATE_ID, args));
196                         }
197                         else if (type.equals(TYPE_TIME))
198                         {
199                             throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, TIME_ID, args));
200                         }
201                         else if (type.equals(TYPE_BOTH))
202                         {
203                             throw new ConverterException(
204                                     _MessageUtils.getErrorMessage(facesContext, DATETIME_ID, args));
205                         }
206                         else
207                         {
208                             throw new ConverterException("invalid type '" + _type + "'");
209                         }
210                     }
211                 }
212             }
213         }
214         return null;
215     }
216 
217     public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value)
218     {
219         if (facesContext == null)
220         {
221             throw new NullPointerException("facesContext");
222         }
223         if (uiComponent == null)
224         {
225             throw new NullPointerException("uiComponent");
226         }
227 
228         if (value == null)
229         {
230             return "";
231         }
232         if (value instanceof String)
233         {
234             return (String)value;
235         }
236 
237         if (isJava8DateTimeFormatter())
238         {
239             DateTimeFormatter format = getDateTimeFormatter();
240             
241             if (value instanceof TemporalAccessor)
242             {
243                 try
244                 {
245                     return format.format((TemporalAccessor) value);
246                 }
247                 catch (Exception e)
248                 {
249                     throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, STRING_ID,
250                             new Object[]{value,_MessageUtils.getLabel(facesContext, uiComponent)}),e);
251                 }
252             }
253             return null;
254         }
255         else
256         {        
257             DateFormat format = getDateFormat();
258             TimeZone tz = getTimeZone(); 
259             if (tz != null)
260             {
261                 format.setTimeZone(tz);
262             }
263             try
264             {
265                 return format.format(value);
266             }
267             catch (Exception e)
268             {
269                 throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, STRING_ID,
270                         new Object[]{value,_MessageUtils.getLabel(facesContext, uiComponent)}),e);
271             }
272         }
273     }
274 
275     private DateFormat getDateFormat()
276     {
277         String type = getType();
278         DateFormat format;
279         if (_pattern != null)
280         {
281             try 
282             {
283                 format = new SimpleDateFormat(_pattern, getLocale());
284             } 
285                 catch (IllegalArgumentException iae)
286             {
287                 throw new ConverterException("Invalid pattern", iae);    
288             }
289         }
290         else if (type.equals(TYPE_DATE))
291         {
292             format = DateFormat.getDateInstance(calcStyle(getDateStyle()), getLocale());
293         }
294         else if (type.equals(TYPE_TIME))
295         {
296             format = DateFormat.getTimeInstance(calcStyle(getTimeStyle()), getLocale());
297         }
298         else if (type.equals(TYPE_BOTH))
299         {
300             format = DateFormat.getDateTimeInstance(calcStyle(getDateStyle()),
301                                                     calcStyle(getTimeStyle()),
302                                                     getLocale());
303         }
304         else
305         {
306             throw new ConverterException("invalid type '" + _type + "'");
307         }
308         
309         // format cannot be lenient (JSR-127)
310         format.setLenient(false);
311         return format;
312     }
313     
314     private DateTimeFormatter getDateTimeFormatter()
315     {
316         DateTimeFormatter formatter = null;
317         String type = getType();
318         String pattern = getPattern();
319         if (pattern != null && pattern.length() > 0)
320         {
321             Locale locale = getLocale();
322             if (locale == null)
323             {
324                 formatter = DateTimeFormatter.ofPattern(pattern);
325             }
326             else
327             {
328                 formatter = DateTimeFormatter.ofPattern(pattern, locale);
329             }
330         }
331         else
332         {
333             if (TYPE_LOCAL_DATE.equals(type))
334             {
335                 formatter = DateTimeFormatter.ofLocalizedDate(calcFormatStyle(getDateStyle()));
336             }
337             else if (TYPE_LOCAL_DATE_TIME.equals(type) )
338             {
339                 String timeStyle = getTimeStyle();
340                 if (timeStyle != null && timeStyle.length() > 0)
341                 {
342                     formatter = DateTimeFormatter.ofLocalizedDateTime(
343                             calcFormatStyle(getDateStyle()), calcFormatStyle(timeStyle));
344                 }
345                 else
346                 {
347                     formatter = DateTimeFormatter.ofLocalizedDateTime(
348                             calcFormatStyle(getDateStyle()));
349                 }
350             }
351             else if (TYPE_LOCAL_TIME.equals(type) )
352             {
353                 formatter = DateTimeFormatter.ofLocalizedTime(calcFormatStyle(getTimeStyle()));
354             }
355             else if (TYPE_OFFSET_TIME.equals(type))
356             {
357                 formatter = DateTimeFormatter.ISO_OFFSET_TIME;
358             }
359             else if (TYPE_OFFSET_DATE_TIME.equals(type))
360             {
361                 formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
362             }
363             else if (TYPE_ZONED_DATE_TIME.equals(type))
364             {
365                 formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
366             }
367             
368             Locale locale = getLocale();
369             if (locale != null)
370             {
371                 formatter = formatter.withLocale(locale);
372             }
373         }
374         return formatter;
375     }
376     
377     /**
378      * According to java8 api, parse() also receives a TemporalQuery parameter that works as a qualifier to decide
379      * how to parse and return the right type of value.
380      * 
381      * @return 
382      */
383     private TemporalQuery getTemporalQuery()
384     {
385         String type = getType();
386         if (TYPE_LOCAL_DATE.equals(type))
387         {
388             return LocalDate::from;
389         }
390         else if (TYPE_LOCAL_DATE_TIME.equals(type) )
391         {
392             return LocalDateTime::from;            
393         }
394         else if (TYPE_LOCAL_TIME.equals(type) )
395         {
396             return LocalTime::from;
397         }
398         else if (TYPE_OFFSET_TIME.equals(type))
399         {
400             return OffsetTime::from;
401         }
402         else if (TYPE_OFFSET_DATE_TIME.equals(type))
403         {
404             return OffsetDateTime::from;
405         }
406         else if (TYPE_ZONED_DATE_TIME.equals(type))
407         {
408             return ZonedDateTime::from;
409         }
410         return null;
411     }
412     
413     private FormatStyle calcFormatStyle(String name)
414     {
415         if (name.equals(STYLE_DEFAULT))
416         {
417             return FormatStyle.MEDIUM;
418         }
419         if (name.equals(STYLE_MEDIUM))
420         {
421             return FormatStyle.MEDIUM;
422         }
423         if (name.equals(STYLE_SHORT))
424         {
425             return FormatStyle.SHORT;
426         }
427         if (name.equals(STYLE_LONG))
428         {
429             return FormatStyle.LONG;
430         }
431         if (name.equals(STYLE_FULL))
432         {
433             return FormatStyle.FULL;
434         }
435         
436         throw new ConverterException("invalid style '" + name + "'");
437     }
438 
439     private int calcStyle(String name)
440     {
441         if (name.equals(STYLE_DEFAULT))
442         {
443             return DateFormat.DEFAULT;
444         }
445         if (name.equals(STYLE_MEDIUM))
446         {
447             return DateFormat.MEDIUM;
448         }
449         if (name.equals(STYLE_SHORT))
450         {
451             return DateFormat.SHORT;
452         }
453         if (name.equals(STYLE_LONG))
454         {
455             return DateFormat.LONG;
456         }
457         if (name.equals(STYLE_FULL))
458         {
459             return DateFormat.FULL;
460         }
461 
462         throw new ConverterException("invalid style '" + name + "'");
463     }
464     
465     private boolean isJava8DateTimeFormatter()
466     {
467         String type = getType();
468         if (type != null)
469         {
470             return  TYPE_LOCAL_DATE.equals(type) ||
471                     TYPE_LOCAL_TIME.equals(type) ||
472                     TYPE_LOCAL_DATE_TIME.equals(type) ||
473                     TYPE_OFFSET_TIME.equals(type) ||
474                     TYPE_OFFSET_DATE_TIME.equals(type) ||
475                     TYPE_ZONED_DATE_TIME.equals(type);
476         }
477         else
478         {
479             return false;
480         }
481     }
482 
483     // STATE SAVE/RESTORE
484     public void restoreState(FacesContext facesContext, Object state)
485     {
486         if (state != null)
487         {
488             Object[] values = (Object[])state;
489             _dateStyle = (String)values[0];
490             _locale = (Locale)values[1];
491             _pattern = (String)values[2];
492             _timeStyle = (String)values[3];
493             _timeZone = (TimeZone)values[4];
494             _type = (String)values[5];
495         }
496     }
497 
498     public Object saveState(FacesContext facesContext)
499     {
500         if (!initialStateMarked())
501         {
502             Object[] values = new Object[6];
503             values[0] = _dateStyle;
504             values[1] = _locale;
505             values[2] = _pattern;
506             values[3] = _timeStyle;
507             values[4] = _timeZone;
508             values[5] = _type;
509             return values;
510         }
511         return null;
512     }
513 
514     // GETTER & SETTER
515     
516     /**
517      * The style of the date.  Values include: default, short, medium, 
518      * long, and full.
519      * 
520      */
521     @JSFProperty
522     public String getDateStyle()
523     {
524         return _dateStyle != null ? _dateStyle : STYLE_DEFAULT;
525     }
526 
527     public void setDateStyle(String dateStyle)
528     {
529         //TODO: validate timeStyle
530         _dateStyle = dateStyle;
531         clearInitialState();
532     }
533 
534     /**
535      * The name of the locale to be used, instead of the default.
536      * 
537      */
538     @JSFProperty
539     public Locale getLocale()
540     {
541         if (_locale != null)
542         {
543             return _locale;
544         }
545         FacesContext context = FacesContext.getCurrentInstance();
546         return context.getViewRoot().getLocale();
547     }
548 
549     public void setLocale(Locale locale)
550     {
551         _locale = locale;
552         clearInitialState();
553     }
554 
555     /**
556      * A custom Date formatting pattern, in the format used by java.text.SimpleDateFormat.
557      * 
558      */
559     @JSFProperty
560     public String getPattern()
561     {
562         return _pattern;
563     }
564 
565     public void setPattern(String pattern)
566     {
567         _pattern = pattern;
568         clearInitialState();
569     }
570 
571     /**
572      * The style of the time.  Values include:  default, short, medium, long, 
573      * and full.
574      * 
575      */
576     @JSFProperty
577     public String getTimeStyle()
578     {
579         return _timeStyle != null ? _timeStyle : STYLE_DEFAULT;
580     }
581 
582     public void setTimeStyle(String timeStyle)
583     {
584         //TODO: validate timeStyle
585         _timeStyle = timeStyle;
586         clearInitialState();
587     }
588 
589     /**
590      * The time zone to use instead of GMT (the default timezone). When
591      * this value is a value-binding to a TimeZone instance, that
592      * timezone is used. Otherwise this value is treated as a String
593      * containing a timezone id, ie as the ID parameter of method
594      * java.util.TimeZone.getTimeZone(String).
595      * 
596      */
597     @JSFProperty
598     public TimeZone getTimeZone()
599     {
600         return _timeZone != null ? _timeZone : TIMEZONE_DEFAULT;
601     }
602 
603     public void setTimeZone(TimeZone timeZone)
604     {
605         _timeZone = timeZone;
606         clearInitialState();
607     }
608 
609     public boolean isTransient()
610     {
611         return _transient;
612     }
613 
614     public void setTransient(boolean aTransient)
615     {
616         _transient = aTransient;
617     }
618 
619     /**
620      * Specifies whether the date, time, or both should be 
621      * parsed/formatted.  Values include:  date, time, and both.
622      * Default based on setting of timeStyle and dateStyle.
623      * 
624      */
625     @JSFProperty
626     public String getType()
627     {
628         return _type != null ? _type : TYPE_DATE;
629     }
630 
631     public void setType(String type)
632     {
633         //TODO: validate type
634         _type = type;
635         clearInitialState();
636     }
637     
638     private boolean _initialStateMarked = false;
639 
640     public void clearInitialState()
641     {
642         _initialStateMarked = false;
643     }
644 
645     public boolean initialStateMarked()
646     {
647         return _initialStateMarked;
648     }
649 
650     public void markInitialState()
651     {
652         _initialStateMarked = true;
653     }
654 }