diff --git a/modules/benchmark/micro/src/main/java/org/elasticsearch/benchmark/micro/deps/jackson/JacksonTypesBenchmark.java b/modules/benchmark/micro/src/main/java/org/elasticsearch/benchmark/micro/deps/jackson/JacksonTypesBenchmark.java index 825f1c570ee..fae3c72fbc1 100644 --- a/modules/benchmark/micro/src/main/java/org/elasticsearch/benchmark/micro/deps/jackson/JacksonTypesBenchmark.java +++ b/modules/benchmark/micro/src/main/java/org/elasticsearch/benchmark/micro/deps/jackson/JacksonTypesBenchmark.java @@ -55,7 +55,7 @@ public class JacksonTypesBenchmark { public JacksonTypesBenchmark(String jsonString) throws IOException { Preconditions.checkNotNull(jsonString, "jsonString must have a value"); this.jsonString = jsonString; - this.objectMapper = newObjectMapper(); + this.objectMapper = defaultObjectMapper(); this.factor = 10; this.cycles = 10000; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java index 94dab972db7..e149a5bf9b6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDateFieldMapper.java @@ -83,8 +83,8 @@ public class JsonDateFieldMapper extends JsonNumberFieldMapper { float boost, boolean omitNorms, boolean omitTermFreqAndPositions, String nullValue) { super(names, precisionStep, index, store, boost, omitNorms, omitTermFreqAndPositions, - new NamedAnalyzer("_date/" + precisionStep, new NumericDateAnalyzer(precisionStep, dateTimeFormatter.formatter())), - new NamedAnalyzer("_date/max", new NumericDateAnalyzer(Integer.MAX_VALUE, dateTimeFormatter.formatter()))); + new NamedAnalyzer("_date/" + precisionStep, new NumericDateAnalyzer(precisionStep, dateTimeFormatter.parser())), + new NamedAnalyzer("_date/max", new NumericDateAnalyzer(Integer.MAX_VALUE, dateTimeFormatter.parser()))); this.dateTimeFormatter = dateTimeFormatter; this.nullValue = nullValue; } @@ -102,11 +102,11 @@ public class JsonDateFieldMapper extends JsonNumberFieldMapper { } @Override public String valueAsString(Fieldable field) { - return dateTimeFormatter.formatter().print(value(field)); + return dateTimeFormatter.printer().print(value(field)); } @Override public String indexedValue(String value) { - return NumericUtils.longToPrefixCoded(dateTimeFormatter.formatter().parseMillis(value)); + return NumericUtils.longToPrefixCoded(dateTimeFormatter.parser().parseMillis(value)); } @Override public String indexedValue(Long value) { @@ -115,15 +115,15 @@ public class JsonDateFieldMapper extends JsonNumberFieldMapper { @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) { return NumericRangeQuery.newLongRange(names.indexName(), precisionStep, - lowerTerm == null ? null : dateTimeFormatter.formatter().parseMillis(lowerTerm), - upperTerm == null ? null : dateTimeFormatter.formatter().parseMillis(upperTerm), + lowerTerm == null ? null : dateTimeFormatter.parser().parseMillis(lowerTerm), + upperTerm == null ? null : dateTimeFormatter.parser().parseMillis(upperTerm), includeLower, includeUpper); } @Override public Filter rangeFilter(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) { return NumericRangeFilter.newLongRange(names.indexName(), precisionStep, - lowerTerm == null ? null : dateTimeFormatter.formatter().parseMillis(lowerTerm), - upperTerm == null ? null : dateTimeFormatter.formatter().parseMillis(upperTerm), + lowerTerm == null ? null : dateTimeFormatter.parser().parseMillis(lowerTerm), + upperTerm == null ? null : dateTimeFormatter.parser().parseMillis(upperTerm), includeLower, includeUpper); } @@ -137,7 +137,7 @@ public class JsonDateFieldMapper extends JsonNumberFieldMapper { if (dateAsString == null) { return null; } - long value = dateTimeFormatter.formatter().parseMillis(dateAsString); + long value = dateTimeFormatter.parser().parseMillis(dateAsString); Field field = null; if (stored()) { field = new Field(names.indexName(), Numbers.longToBytes(value), store); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonObjectMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonObjectMapper.java index 5379e4be97c..8e762e9fadb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonObjectMapper.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonObjectMapper.java @@ -310,7 +310,7 @@ public class JsonObjectMapper implements JsonMapper { boolean isDate = false; for (FormatDateTimeFormatter dateTimeFormatter : dateTimeFormatters) { try { - dateTimeFormatter.formatter().parseMillis(jsonContext.jp().getText()); + dateTimeFormatter.parser().parseMillis(jsonContext.jp().getText()); mapper = dateField(currentFieldName).dateTimeFormatter(dateTimeFormatter).build(builderContext); isDate = true; break; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java index 3c73294df65..44b78fcaaf2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java @@ -51,7 +51,7 @@ public class RestMainAction extends BaseRestHandler { JsonNode rootNode; int quotesSize; try { - rootNode = Jackson.newObjectMapper().readValue(Classes.getDefaultClassLoader().getResourceAsStream("org/elasticsearch/rest/action/main/quotes.json"), JsonNode.class); + rootNode = Jackson.defaultObjectMapper().readValue(Classes.getDefaultClassLoader().getResourceAsStream("org/elasticsearch/rest/action/main/quotes.json"), JsonNode.class); ArrayNode arrayNode = (ArrayNode) rootNode.get("quotes"); quotesSize = Iterators.size(arrayNode.getElements()); } catch (Exception e) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/FormatDateTimeFormatter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/FormatDateTimeFormatter.java index 2ebb4c19c4f..cb364f2a0c2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/FormatDateTimeFormatter.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/FormatDateTimeFormatter.java @@ -33,18 +33,29 @@ public class FormatDateTimeFormatter { private final String format; - private final DateTimeFormatter formatter; + private final DateTimeFormatter parser; - public FormatDateTimeFormatter(String format, DateTimeFormatter formatter) { + private final DateTimeFormatter printer; + + public FormatDateTimeFormatter(String format, DateTimeFormatter parser) { + this(format, parser, parser); + } + + public FormatDateTimeFormatter(String format, DateTimeFormatter parser, DateTimeFormatter printer) { this.format = format; - this.formatter = formatter; + this.parser = parser; + this.printer = printer; } public String format() { return format; } - public DateTimeFormatter formatter() { - return formatter; + public DateTimeFormatter parser() { + return parser; + } + + public DateTimeFormatter printer() { + return this.printer; } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/Joda.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/Joda.java index 9e28bb4139e..fd0709fc812 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/Joda.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/joda/Joda.java @@ -73,7 +73,10 @@ public class Joda { } else if ("dateHourMinuteSecondMillis".equals(input)) { formatter = ISODateTimeFormat.dateHourMinuteSecondMillis(); } else if ("dateOptionalTime".equals(input)) { - formatter = ISODateTimeFormat.dateOptionalTimeParser(); + // in this case, we have a separate parser and printer since the dataOptionalTimeParser can't print + return new FormatDateTimeFormatter(input, + ISODateTimeFormat.dateOptionalTimeParser().withZone(DateTimeZone.UTC), + ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC)); } else if ("dateTime".equals(input)) { formatter = ISODateTimeFormat.dateTime(); } else if ("dateTimeNoMillis".equals(input)) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/Jackson.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/Jackson.java index b26249deed6..9a404af3134 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/Jackson.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/Jackson.java @@ -19,10 +19,21 @@ package org.elasticsearch.util.json; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.JsonGenerator; -import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.*; +import org.codehaus.jackson.map.DeserializationContext; import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializerProvider; +import org.codehaus.jackson.map.deser.CustomDeserializerFactory; +import org.codehaus.jackson.map.deser.StdDeserializer; +import org.codehaus.jackson.map.deser.StdDeserializerProvider; +import org.codehaus.jackson.map.ser.CustomSerializerFactory; +import org.codehaus.jackson.map.ser.SerializerBase; +import org.elasticsearch.util.joda.FormatDateTimeFormatter; +import org.elasticsearch.util.joda.Joda; +import org.joda.time.DateTime; + +import java.io.IOException; +import java.util.Date; /** * A set of helper methods for Jackson. @@ -33,14 +44,21 @@ public final class Jackson { private static final JsonFactory defaultJsonFactory; + private static final ObjectMapper defaultObjectMapper; + static { defaultJsonFactory = newJsonFactory(); + defaultObjectMapper = newObjectMapper(); } public static JsonFactory defaultJsonFactory() { return defaultJsonFactory; } + public static ObjectMapper defaultObjectMapper() { + return defaultObjectMapper; + } + public static JsonFactory newJsonFactory() { JsonFactory jsonFactory = new JsonFactory(); jsonFactory.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); @@ -52,10 +70,82 @@ public final class Jackson { ObjectMapper mapper = new ObjectMapper(); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, true); + + CustomSerializerFactory serializerFactory = new CustomSerializerFactory(); + serializerFactory.addSpecificMapping(Date.class, new DateSerializer()); + serializerFactory.addSpecificMapping(DateTime.class, new DateTimeSerializer()); + mapper.setSerializerFactory(serializerFactory); + + CustomDeserializerFactory deserializerFactory = new CustomDeserializerFactory(); + deserializerFactory.addSpecificMapping(Date.class, new DateDeserializer()); + deserializerFactory.addSpecificMapping(DateTime.class, new DateTimeDeserializer()); + mapper.setDeserializerProvider(new StdDeserializerProvider(deserializerFactory)); + return mapper; } private Jackson() { } + + public static class DateDeserializer extends StdDeserializer { + + private final FormatDateTimeFormatter formatter = Joda.forPattern("dateTime"); + + public DateDeserializer() { + super(Date.class); + } + + @Override public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonToken t = jp.getCurrentToken(); + if (t == JsonToken.VALUE_STRING) { + return new Date(formatter.parser().parseMillis(jp.getText())); + } + throw ctxt.mappingException(getValueClass()); + } + } + + public final static class DateSerializer extends SerializerBase { + + private final FormatDateTimeFormatter formatter = Joda.forPattern("dateTime"); + + @Override public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { + jgen.writeString(formatter.parser().print(value.getTime())); + } + + @Override public JsonNode getSchema(SerializerProvider provider, java.lang.reflect.Type typeHint) { + return createSchemaNode("string", true); + } + } + + public static class DateTimeDeserializer extends StdDeserializer { + + private final FormatDateTimeFormatter formatter = Joda.forPattern("dateTime"); + + public DateTimeDeserializer() { + super(DateTime.class); + } + + @Override public DateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonToken t = jp.getCurrentToken(); + if (t == JsonToken.VALUE_STRING) { + return formatter.parser().parseDateTime(jp.getText()); + } + throw ctxt.mappingException(getValueClass()); + } + } + + + public final static class DateTimeSerializer extends SerializerBase { + + private final FormatDateTimeFormatter formatter = Joda.forPattern("dateTime"); + + @Override public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { + jgen.writeString(formatter.printer().print(value)); + } + + @Override public JsonNode getSchema(SerializerProvider provider, java.lang.reflect.Type typeHint) { + return createSchemaNode("string", true); + } + } } diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/util/json/JsonBuilderTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/util/json/JsonBuilderTests.java index 999194efae4..478e74f87dc 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/util/json/JsonBuilderTests.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/util/json/JsonBuilderTests.java @@ -22,17 +22,24 @@ package org.elasticsearch.util.json; import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.JsonNode; +import org.elasticsearch.util.MapBuilder; import org.elasticsearch.util.io.FastByteArrayInputStream; import org.elasticsearch.util.io.FastByteArrayOutputStream; import org.elasticsearch.util.io.FastCharArrayWriter; +import org.joda.time.DateTime; import org.testng.annotations.Test; +import java.util.Date; +import java.util.Map; + +import static org.elasticsearch.util.json.Jackson.*; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; /** - * @author kimchy (Shay Banon) + * @author kimchy (shay.banon) */ +@Test public class JsonBuilderTests { @Test public void verifyReuseJsonGenerator() throws Exception { @@ -83,4 +90,14 @@ public class JsonBuilderTests { JsonNode node = Jackson.newObjectMapper().readValue(new FastByteArrayInputStream(data), JsonNode.class); assertThat(node.get("source").get("test").getTextValue(), equalTo("value")); } + + @Test public void testDatesObjectMapper() throws Exception { + Date date = new Date(); + DateTime dateTime = new DateTime(); + Map data = MapBuilder.newMapBuilder() + .put("date", date) + .put("dateTime", dateTime) + .map(); + System.out.println("Data: " + defaultObjectMapper().writeValueAsString(data)); + } }