1 package com.fasterxml.jackson.databind.ser.std;
2 
3 import java.io.IOException;
4 import java.lang.reflect.Type;
5 import java.text.DateFormat;
6 import java.text.SimpleDateFormat;
7 import java.util.Date;
8 import java.util.Locale;
9 import java.util.TimeZone;
10 import java.util.concurrent.atomic.AtomicReference;
11 
12 import com.fasterxml.jackson.annotation.JsonFormat;
13 
14 import com.fasterxml.jackson.core.JsonGenerator;
15 import com.fasterxml.jackson.core.JsonParser;
16 
17 import com.fasterxml.jackson.databind.*;
18 import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
19 import com.fasterxml.jackson.databind.ser.ContextualSerializer;
20 import com.fasterxml.jackson.databind.util.StdDateFormat;
21 
22 @SuppressWarnings("serial")
23 public abstract class DateTimeSerializerBase<T>
24     extends StdScalarSerializer<T>
25     implements ContextualSerializer
26 {
27     /**
28      * Flag that indicates that serialization must be done as the
29      * Java timestamp, regardless of other settings.
30      */
31     protected final Boolean _useTimestamp;
32 
33     /**
34      * Specific format to use, if not default format: non null value
35      * also indicates that serialization is to be done as JSON String,
36      * not numeric timestamp, unless {@link #_useTimestamp} is true.
37      */
38     protected final DateFormat _customFormat;
39 
40     /**
41      * If {@link #_customFormat} is used, we will try to reuse instances in simplest
42      * possible form; thread-safe, but without overhead of <code>ThreadLocal</code>
43      * (not from code, but wrt retaining of possibly large number of format instances
44      * over all threads, properties with custom formats).
45      *
46      * @since 2.9
47      */
48     protected final AtomicReference<DateFormat> _reusedCustomFormat;
49 
DateTimeSerializerBase(Class<T> type, Boolean useTimestamp, DateFormat customFormat)50     protected DateTimeSerializerBase(Class<T> type,
51             Boolean useTimestamp, DateFormat customFormat)
52     {
53         super(type);
54         _useTimestamp = useTimestamp;
55         _customFormat = customFormat;
56         _reusedCustomFormat = (customFormat == null) ? null : new AtomicReference<DateFormat>();
57     }
58 
withFormat(Boolean timestamp, DateFormat customFormat)59     public abstract DateTimeSerializerBase<T> withFormat(Boolean timestamp, DateFormat customFormat);
60 
61     @Override
createContextual(SerializerProvider serializers, BeanProperty property)62     public JsonSerializer<?> createContextual(SerializerProvider serializers,
63             BeanProperty property) throws JsonMappingException
64     {
65         // Note! Should not skip if `property` null since that'd skip check
66         // for config overrides, in case of root value
67         JsonFormat.Value format = findFormatOverrides(serializers, property, handledType());
68         if (format == null) {
69             return this;
70         }
71         // Simple case first: serialize as numeric timestamp?
72         JsonFormat.Shape shape = format.getShape();
73         if (shape.isNumeric()) {
74             return withFormat(Boolean.TRUE, null);
75         }
76 
77         // 08-Jun-2017, tatu: With [databind#1648], this gets bit tricky..
78         // First: custom pattern will override things
79         if (format.hasPattern()) {
80             final Locale loc = format.hasLocale()
81                             ? format.getLocale()
82                             : serializers.getLocale();
83             SimpleDateFormat df = new SimpleDateFormat(format.getPattern(), loc);
84             TimeZone tz = format.hasTimeZone() ? format.getTimeZone()
85                     : serializers.getTimeZone();
86             df.setTimeZone(tz);
87             return withFormat(Boolean.FALSE, df);
88         }
89 
90         // Otherwise, need one of these changes:
91         final boolean hasLocale = format.hasLocale();
92         final boolean hasTZ = format.hasTimeZone();
93         final boolean asString = (shape == JsonFormat.Shape.STRING);
94 
95         if (!hasLocale && !hasTZ && !asString) {
96             return this;
97         }
98 
99         DateFormat df0 = serializers.getConfig().getDateFormat();
100         // Jackson's own `StdDateFormat` is quite easy to deal with...
101         if (df0 instanceof StdDateFormat) {
102             StdDateFormat std = (StdDateFormat) df0;
103             if (format.hasLocale()) {
104                 std = std.withLocale(format.getLocale());
105             }
106             if (format.hasTimeZone()) {
107                 std = std.withTimeZone(format.getTimeZone());
108             }
109             return withFormat(Boolean.FALSE, std);
110         }
111 
112         // 08-Jun-2017, tatu: Unfortunately there's no generally usable
113         //    mechanism for changing `DateFormat` instances (or even clone()ing)
114         //    So: require it be `SimpleDateFormat`; can't config other types
115         if (!(df0 instanceof SimpleDateFormat)) {
116             serializers.reportBadDefinition(handledType(), String.format(
117 "Configured `DateFormat` (%s) not a `SimpleDateFormat`; cannot configure `Locale` or `TimeZone`",
118 df0.getClass().getName()));
119         }
120         SimpleDateFormat df = (SimpleDateFormat) df0;
121         if (hasLocale) {
122             // Ugh. No way to change `Locale`, create copy; must re-crete completely:
123             df = new SimpleDateFormat(df.toPattern(), format.getLocale());
124         } else {
125             df = (SimpleDateFormat) df.clone();
126         }
127         TimeZone newTz = format.getTimeZone();
128         boolean changeTZ = (newTz != null) && !newTz.equals(df.getTimeZone());
129         if (changeTZ) {
130             df.setTimeZone(newTz);
131         }
132         return withFormat(Boolean.FALSE, df);
133     }
134 
135     /*
136     /**********************************************************
137     /* Accessors
138     /**********************************************************
139      */
140 
141     @Override
isEmpty(SerializerProvider serializers, T value)142     public boolean isEmpty(SerializerProvider serializers, T value) {
143         // 09-Mar-2017, tatu: as per [databind#1550] timestamp 0 is NOT "empty"; but
144         //   with versions up to 2.8.x this was the case. Fixed for 2.9.
145 //        return _timestamp(value) == 0L;
146         return false;
147     }
148 
_timestamp(T value)149     protected abstract long _timestamp(T value);
150 
151     @Override
getSchema(SerializerProvider serializers, Type typeHint)152     public JsonNode getSchema(SerializerProvider serializers, Type typeHint) {
153         //todo: (ryan) add a format for the date in the schema?
154         return createSchemaNode(_asTimestamp(serializers) ? "number" : "string", true);
155     }
156 
157     @Override
acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)158     public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException
159     {
160         _acceptJsonFormatVisitor(visitor, typeHint, _asTimestamp(visitor.getProvider()));
161     }
162 
163     /*
164     /**********************************************************
165     /* Actual serialization
166     /**********************************************************
167      */
168 
169     @Override
serialize(T value, JsonGenerator gen, SerializerProvider serializers)170     public abstract void serialize(T value, JsonGenerator gen, SerializerProvider serializers)
171         throws IOException;
172 
173     /*
174     /**********************************************************
175     /* Helper methods
176     /**********************************************************
177      */
178 
_asTimestamp(SerializerProvider serializers)179     protected boolean _asTimestamp(SerializerProvider serializers)
180     {
181         if (_useTimestamp != null) {
182             return _useTimestamp.booleanValue();
183         }
184         if (_customFormat == null) {
185             if (serializers != null) {
186                 return serializers.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
187             }
188             // 12-Jun-2014, tatu: Is it legal not to have provider? Was NPE:ing earlier so leave a check
189             throw new IllegalArgumentException("Null SerializerProvider passed for "+handledType().getName());
190         }
191         return false;
192     }
193 
_acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint, boolean asNumber)194     protected void _acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint,
195 		boolean asNumber) throws JsonMappingException
196     {
197         if (asNumber) {
198             visitIntFormat(visitor, typeHint,
199                     JsonParser.NumberType.LONG, JsonValueFormat.UTC_MILLISEC);
200         } else {
201             visitStringFormat(visitor, typeHint, JsonValueFormat.DATE_TIME);
202         }
203     }
204 
205     /**
206      * @since 2.9
207      */
_serializeAsString(Date value, JsonGenerator g, SerializerProvider provider)208     protected void _serializeAsString(Date value, JsonGenerator g, SerializerProvider provider) throws IOException
209     {
210         if (_customFormat == null) {
211             provider.defaultSerializeDateValue(value, g);
212             return;
213         }
214 
215         // 19-Jul-2017, tatu: Here we will try a simple but (hopefully) effective mechanism for
216         //    reusing formatter instance. This is our second attempt, after initially trying simple
217         //    synchronization (which turned out to be bottleneck for some users in production...).
218         //    While `ThreadLocal` could alternatively be used, it is likely that it would lead to
219         //    higher memory footprint, but without much upside -- if we can not reuse, we'll just
220         //    clone(), which has some overhead but not drastic one.
221 
222         DateFormat f = _reusedCustomFormat.getAndSet(null);
223         if (f == null) {
224             f = (DateFormat) _customFormat.clone();
225         }
226         g.writeString(f.format(value));
227         _reusedCustomFormat.compareAndSet(null, f);
228     }
229 }
230