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  
20  package org.apache.myfaces.tobago.convert;
21  
22  import org.apache.myfaces.tobago.internal.util.StringUtils;
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  import javax.faces.component.UIComponent;
27  import javax.faces.component.UIInput;
28  import javax.faces.context.FacesContext;
29  import javax.faces.convert.ConverterException;
30  import java.lang.invoke.MethodHandles;
31  import java.text.ParseException;
32  import java.text.SimpleDateFormat;
33  import java.time.LocalDate;
34  import java.time.LocalDateTime;
35  import java.time.LocalTime;
36  import java.time.OffsetDateTime;
37  import java.time.OffsetTime;
38  import java.time.ZonedDateTime;
39  import java.time.format.DateTimeFormatter;
40  import java.time.temporal.TemporalAccessor;
41  import java.util.Calendar;
42  import java.util.Locale;
43  import java.util.TimeZone;
44  
45  import static org.apache.myfaces.tobago.convert.DateTimeConverter.CONVERTER_ID;
46  
47  @org.apache.myfaces.tobago.apt.annotation.Converter(id = CONVERTER_ID)
48  public class DateTimeConverter extends javax.faces.convert.DateTimeConverter {
49  
50    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
51  
52    public static final String CONVERTER_ID = "org.apache.myfaces.tobago.DateTime";
53  
54    private static final String TYPE_DATE = "date";
55    private static final String TYPE_TIME = "time";
56    private static final String TYPE_BOTH = "both";
57    private static final String TYPE_CALENDAR = "calendar";
58    private static final String TYPE_LOCAL_DATE = "localDate";
59    private static final String TYPE_LOCAL_TIME = "localTime";
60    private static final String TYPE_LOCAL_DATE_TIME = "localDateTime";
61    private static final String TYPE_OFFSET_TIME = "offsetTime";
62    private static final String TYPE_OFFSET_DATE_TIME = "offsetDateTime";
63    private static final String TYPE_ZONED_DATE_TIME = "zonedDateTime";
64  
65    @Override
66    public Object getAsObject(FacesContext facesContext, UIComponent component, String string) throws ConverterException {
67      if (StringUtils.isBlank(string)) {
68        return null;
69      } else {
70        final String type = getType();
71        if (TYPE_DATE.equals(type) || TYPE_TIME.equals(type) || TYPE_BOTH.equals(type)) {
72          return super.getAsObject(facesContext, component, string);
73        } else if (TYPE_CALENDAR.equals(type)) {
74          final Locale locale = getLocale();
75          final String pattern = getPattern();
76          final TimeZone timeZone = getTimeZone();
77          final Calendar calendar;
78          if (component instanceof UIInput && ((UIInput) component).getValue() != null) {
79            calendar = (Calendar) ((UIInput) component).getValue();
80          } else {
81            if (timeZone != null && locale != null) {
82              calendar = Calendar.getInstance(timeZone, locale);
83            } else if (locale != null) {
84              calendar = Calendar.getInstance(locale);
85            } else if (timeZone != null) {
86              calendar = Calendar.getInstance(timeZone);
87            } else {
88              calendar = Calendar.getInstance();
89            }
90          }
91  
92          SimpleDateFormat sdf = locale != null ? new SimpleDateFormat(pattern, locale) : new SimpleDateFormat(pattern);
93          try {
94            calendar.setTime(sdf.parse(string));
95          } catch (ParseException e) {
96            throw new ConverterException("string='" + string + "'", e);
97          }
98  
99          return calendar;
100       } else if (TYPE_LOCAL_DATE.equals(type)) {
101         logMandatoryPatterns("yMd");
102         return getDateTimeFormatter().parse(string, LocalDate::from);
103       } else if (TYPE_LOCAL_TIME.equals(type)) {
104         logMandatoryPatterns("H");
105         return getDateTimeFormatter().parse(string, LocalTime::from);
106       } else if (TYPE_LOCAL_DATE_TIME.equals(type)) {
107         logMandatoryPatterns("yMdH");
108         return getDateTimeFormatter().parse(string, LocalDateTime::from);
109       } else if (TYPE_OFFSET_TIME.equals(type)) {
110         logMandatoryPatterns("HZ");
111         return getDateTimeFormatter().parse(string, OffsetTime::from);
112       } else if (TYPE_OFFSET_DATE_TIME.equals(type)) {
113         logMandatoryPatterns("yMdHZ");
114         return getDateTimeFormatter().parse(string, OffsetDateTime::from);
115       } else if (TYPE_ZONED_DATE_TIME.equals(type)) {
116         logMandatoryPatterns("yMdHZ");
117         return getDateTimeFormatter().parse(string, ZonedDateTime::from);
118       } else {
119         throw new ConverterException("invalid type '" + type + "'");
120       }
121     }
122   }
123 
124   @Override
125   public String getAsString(FacesContext facesContext, UIComponent component, Object object) throws ConverterException {
126     if (object == null) {
127       return null;
128     } else {
129       final String type = getType();
130       if (TYPE_DATE.equals(type) || TYPE_TIME.equals(type) || TYPE_BOTH.equals(type)) {
131         return super.getAsString(facesContext, component, object);
132       } else if (TYPE_CALENDAR.equals(type)) {
133         Calendar calendar = (Calendar) object;
134         final Locale locale = getLocale();
135         final String pattern = getPattern();
136 
137         SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale);
138         return sdf.format(calendar.getTime());
139       } else if (TYPE_LOCAL_DATE.equals(type)
140           || TYPE_LOCAL_TIME.equals(type)
141           || TYPE_LOCAL_DATE_TIME.equals(type)
142           || TYPE_OFFSET_TIME.equals(type)
143           || TYPE_OFFSET_DATE_TIME.equals(type)
144           || TYPE_ZONED_DATE_TIME.equals(type)) {
145         return getDateTimeFormatter().format((TemporalAccessor) object);
146       } else {
147         throw new ConverterException("invalid type '" + type + "'");
148       }
149     }
150   }
151 
152   private void logMandatoryPatterns(String mandatoryChars) {
153     logMandatoryPattern(mandatoryChars, "y", "year");
154     logMandatoryPattern(mandatoryChars, "M", "month");
155     logMandatoryPattern(mandatoryChars, "d", "day");
156     logMandatoryPattern(mandatoryChars, "H", "hour");
157     logMandatoryPattern(mandatoryChars, "Z", "offset");
158   }
159 
160   private void logMandatoryPattern(String mandatoryChars, String c, String name) {
161     final String pattern = getPattern();
162     if (pattern != null && mandatoryChars.contains(c) && !pattern.contains(c)) {
163       LOG.error("No char for " + name + " ('" + c + "') in pattern: " + pattern);
164     }
165   }
166 
167   private DateTimeFormatter getDateTimeFormatter() {
168     final String pattern = getPattern();
169     if (!StringUtils.isBlank(pattern)) {
170       final Locale locale = getLocale();
171       return locale != null ? DateTimeFormatter.ofPattern(pattern, locale) : DateTimeFormatter.ofPattern(pattern);
172     } else {
173       throw new ConverterException("no pattern set");
174     }
175   }
176 }