From b688919010a9147376dc435f742812afd5b46885 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 19 Jul 2017 16:37:22 -0700 Subject: [PATCH] Fix #1685 --- .../jackson/databind/ObjectMapper.java | 4 +- .../jackson/databind/SerializerProvider.java | 2 +- .../databind/ser/std/CalendarSerializer.java | 15 ++------ .../databind/ser/std/DateSerializer.java | 13 ++----- .../ser/std/DateTimeSerializerBase.java | 38 +++++++++++++++++++ .../databind/ser/std/SqlDateSerializer.java | 31 +++++---------- 6 files changed, 58 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 63a39718bc..b34337e799 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -3840,7 +3840,7 @@ public void acceptJsonFormatVisitor(Class type, JsonFormatVisitorWrapper visi { acceptJsonFormatVisitor(_typeFactory.constructType(type), visitor); } - + /** * Method for visiting type hierarchy for given type, using specified visitor. * Visitation uses Serializer hierarchy and related properties @@ -3861,7 +3861,7 @@ public void acceptJsonFormatVisitor(JavaType type, JsonFormatVisitorWrapper visi } _serializerProvider(getSerializationConfig()).acceptJsonFormatVisitor(type, visitor); } - + /* /********************************************************** /* Internal methods for serialization, overridable diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java index 4b4fbba8cc..fa299b49dd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java @@ -1422,7 +1422,7 @@ protected final DateFormat _dateFormat() if (_dateFormat != null) { return _dateFormat; } - /* At this point, all timezone configuration should have occured, with respect + /* At this point, all timezone configuration should have occurred, with respect * to default dateformat configuration. But we still better clone * an instance as formatters are stateful, not thread-safe. */ diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java index ff93ba3100..3140352be3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CalendarSerializer.java @@ -37,19 +37,12 @@ protected long _timestamp(Calendar value) { } @Override - public void serialize(Calendar value, JsonGenerator jgen, SerializerProvider provider) throws IOException + public void serialize(Calendar value, JsonGenerator g, SerializerProvider provider) throws IOException { if (_asTimestamp(provider)) { - jgen.writeNumber(_timestamp(value)); - } else if (_customFormat != null) { - // 21-Feb-2011, tatu: not optimal, but better than alternatives: - synchronized (_customFormat) { - // _customformat cannot parse Calendar, so Date should be passed - jgen.writeString(_customFormat.format(value.getTime())); - } - } else { - provider.defaultSerializeDateValue(value.getTime(), jgen); + g.writeNumber(_timestamp(value)); + return; } + _serializeAsString(value.getTime(), g, provider); } - } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java index 3138ae7042..028d7d662e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateSerializer.java @@ -42,17 +42,12 @@ protected long _timestamp(Date value) { } @Override - public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException + public void serialize(Date value, JsonGenerator g, SerializerProvider provider) throws IOException { if (_asTimestamp(provider)) { - gen.writeNumber(_timestamp(value)); - } else if (_customFormat != null) { - // 21-Feb-2011, tatu: not optimal, but better than alternatives: - synchronized (_customFormat) { - gen.writeString(_customFormat.format(value)); - } - } else { - provider.defaultSerializeDateValue(value, gen); + g.writeNumber(_timestamp(value)); + return; } + _serializeAsString(value, g, provider); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java index f830ea85ee..5b5d5d09f1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/DateTimeSerializerBase.java @@ -4,8 +4,10 @@ import java.lang.reflect.Type; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Locale; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.JsonFormat; @@ -35,12 +37,23 @@ public abstract class DateTimeSerializerBase */ protected final DateFormat _customFormat; + /** + * If {@link #_customFormat} is used, we will try to reuse instances in simplest + * possible form; thread-safe, but without overhead of ThreadLocal + * (not from code, but wrt retaining of possibly large number of format instances + * over all threads, properties with custom formats). + * + * @since 2.9 + */ + protected final AtomicReference _reusedCustomFormat; + protected DateTimeSerializerBase(Class type, Boolean useTimestamp, DateFormat customFormat) { super(type); _useTimestamp = useTimestamp; _customFormat = customFormat; + _reusedCustomFormat = (customFormat == null) ? null : new AtomicReference(); } public abstract DateTimeSerializerBase withFormat(Boolean timestamp, DateFormat customFormat); @@ -189,4 +202,29 @@ protected void _acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaTy visitStringFormat(visitor, typeHint, JsonValueFormat.DATE_TIME); } } + + /** + * @since 2.9 + */ + protected void _serializeAsString(Date value, JsonGenerator g, SerializerProvider provider) throws IOException + { + if (_customFormat == null) { + provider.defaultSerializeDateValue(value, g); + return; + } + + // 19-Jul-2017, tatu: Here we will try a simple but (hopefully) effective mechanism for + // reusing formatter instance. This is our second attempt, after initially trying simple + // synchronization (which turned out to be bottleneck for some users in production...). + // While `ThreadLocal` could alternatively be used, it is likely that it would lead to + // higher memory footprint, but without much upside -- if we can not reuse, we'll just + // clone(), which has some overhead but not drastic one. + + DateFormat f = _reusedCustomFormat.getAndSet(null); + if (f == null) { + f = (DateFormat) _customFormat.clone(); + } + g.writeString(f.format(value)); + _reusedCustomFormat.compareAndSet(null, f); + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java index c14d14d84d..54326eed17 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/SqlDateSerializer.java @@ -40,34 +40,21 @@ protected long _timestamp(java.sql.Date value) { } @Override - public void serialize(java.sql.Date value, JsonGenerator gen, SerializerProvider provider) + public void serialize(java.sql.Date value, JsonGenerator g, SerializerProvider provider) throws IOException { if (_asTimestamp(provider)) { - gen.writeNumber(_timestamp(value)); - } else if (_customFormat != null) { - // 11-Oct-2016, tatu: As per [databind#219], same as with `java.util.Date` - synchronized (_customFormat) { - gen.writeString(_customFormat.format(value)); - } - } else { + g.writeNumber(_timestamp(value)); + return; + } + // Alas, can't just call `_serializeAsString()`.... + if (_customFormat == null) { // 11-Oct-2016, tatu: For backwards-compatibility purposes, we shall just use // the awful standard JDK serialization via `sqlDate.toString()`... this // is problematic in multiple ways (including using arbitrary timezone...) - gen.writeString(value.toString()); + g.writeString(value.toString()); + return; } - } - - @Override - public JsonNode getSchema(SerializerProvider provider, Type typeHint) - { - //todo: (ryan) add a format for the date in the schema? - return createSchemaNode("string", true); - } - - @Override - public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException - { - _acceptJsonFormatVisitor(visitor, typeHint, _useTimestamp); + _serializeAsString(value, g, provider); } }