diff --git a/README.md b/README.md
index ec17d63d..5d93e8b7 100644
--- a/README.md
+++ b/README.md
@@ -202,6 +202,26 @@ The recommended way to use the AWS Glue Schema Registry Library for Java is to c
```
+### Jackson Support for Java 8 Date Types in JSON
+
+To support Java 8 Dates in JSON add the [Jackson JSR310 Datatype](https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310) dependency to implementing project class path.
+
+For example, in a Maven based project include the latest dependency.
+
+```xml
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.16.1
+
+```
+
+Then add the following configuration property to specify the fully qualified class path to the JavaTimeModule like so.
+
+```java
+properties.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");
+```
+
#### Producer for Kafka with PROTOBUF format
```java
diff --git a/common/src/main/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfiguration.java b/common/src/main/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfiguration.java
index ce7d870d..af582b1c 100644
--- a/common/src/main/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfiguration.java
+++ b/common/src/main/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfiguration.java
@@ -22,6 +22,7 @@
import com.amazonaws.services.schemaregistry.utils.ProtobufMessageType;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils;
@@ -58,6 +59,8 @@ public class GlueSchemaRegistryConfiguration {
private Map metadata;
private String secondaryDeserializer;
private URI proxyUrl;
+ private String javaTimeModuleClass;
+ private String objectMapperFactory = "com.amazonaws.services.schemaregistry.utils.json.DefaultObjectMapperFactory";
/**
* Name of the application using the serializer/deserializer.
@@ -104,6 +107,8 @@ private void buildSchemaRegistryConfigs(Map configs) {
validateAndSetUserAgent(configs);
validateAndSetSecondaryDeserializer(configs);
validateAndSetProxyUrl(configs);
+ validateAndSetJavaTimeModule(configs);
+ validateAndSetObjectMapperFactory(configs);
}
private void validateAndSetSecondaryDeserializer(Map configs) {
@@ -323,6 +328,29 @@ private void validateAndSetJacksonDeserializationFeatures(Map configs
}
}
+ private void validateAndSetJavaTimeModule(Map configs) {
+ if (isPresent(configs, AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE)) {
+ String moduleClassName = String.valueOf(configs.get(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE));
+ this.javaTimeModuleClass = moduleClassName;
+ }
+ }
+
+ private void validateAndSetObjectMapperFactory(Map configs) {
+ if (isPresent(configs, AWSSchemaRegistryConstants.OBJECT_MAPPER_FACTORY)) {
+ this.objectMapperFactory = String.valueOf(configs.get(AWSSchemaRegistryConstants.OBJECT_MAPPER_FACTORY));
+ }
+ }
+
+ public SimpleModule loadJavaTimeModule() {
+ try {
+ Class> moduleClass = Class.forName(this.getJavaTimeModuleClass());
+ return (SimpleModule) moduleClass.getConstructor().newInstance();
+ } catch (Exception e) {
+ String message = String.format("Invalid JavaTimeModule specified: %s", this.javaTimeModuleClass);
+ throw new AWSSchemaRegistryException(message, e);
+ }
+ }
+
private boolean isPresent(Map configs,
String key) {
if (!GlueSchemaRegistryUtils.getInstance()
diff --git a/common/src/main/java/com/amazonaws/services/schemaregistry/utils/AWSSchemaRegistryConstants.java b/common/src/main/java/com/amazonaws/services/schemaregistry/utils/AWSSchemaRegistryConstants.java
index 5ce30b06..3c63018a 100644
--- a/common/src/main/java/com/amazonaws/services/schemaregistry/utils/AWSSchemaRegistryConstants.java
+++ b/common/src/main/java/com/amazonaws/services/schemaregistry/utils/AWSSchemaRegistryConstants.java
@@ -172,6 +172,19 @@ public final class AWSSchemaRegistryConstants {
*/
public static final String USER_AGENT_APP = "userAgentApp";
+ /**
+ * Jackson Java Time Module to use for handling Java 8 Date/Time.
+ * By default, no module is registered.
+ * Ex: com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+ */
+ public static final String REGISTER_JAVA_TIME_MODULE = "registerJavaTimeModule";
+
+ /**
+ * Factory for creating custom Jackson ObjectMapper instances for use in handling JSON.
+ * Default: com.amazonaws.services.schemaregistry.utils.json.DefaultObjectMapperFactory
+ */
+ public static final String OBJECT_MAPPER_FACTORY = "objectMapperFactory";
+
/**
* Private constructor to avoid initialization of the class.
*/
diff --git a/common/src/test/java/com/amazonaws/services/schemaregistry/common/AWSSchemaRegistryClientTest.java b/common/src/test/java/com/amazonaws/services/schemaregistry/common/AWSSchemaRegistryClientTest.java
index c6a221ae..4c06c896 100644
--- a/common/src/test/java/com/amazonaws/services/schemaregistry/common/AWSSchemaRegistryClientTest.java
+++ b/common/src/test/java/com/amazonaws/services/schemaregistry/common/AWSSchemaRegistryClientTest.java
@@ -32,25 +32,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.glue.GlueClient;
-import software.amazon.awssdk.services.glue.model.CreateSchemaRequest;
-import software.amazon.awssdk.services.glue.model.CreateSchemaResponse;
-import software.amazon.awssdk.services.glue.model.DataFormat;
-import software.amazon.awssdk.services.glue.model.EntityNotFoundException;
-import software.amazon.awssdk.services.glue.model.GetSchemaByDefinitionRequest;
-import software.amazon.awssdk.services.glue.model.GetSchemaByDefinitionResponse;
-import software.amazon.awssdk.services.glue.model.GetSchemaVersionRequest;
-import software.amazon.awssdk.services.glue.model.GetSchemaVersionResponse;
-import software.amazon.awssdk.services.glue.model.GetTagsRequest;
-import software.amazon.awssdk.services.glue.model.GetTagsResponse;
-import software.amazon.awssdk.services.glue.model.MetadataKeyValuePair;
-import software.amazon.awssdk.services.glue.model.PutSchemaVersionMetadataRequest;
-import software.amazon.awssdk.services.glue.model.PutSchemaVersionMetadataResponse;
-import software.amazon.awssdk.services.glue.model.QuerySchemaVersionMetadataRequest;
-import software.amazon.awssdk.services.glue.model.QuerySchemaVersionMetadataResponse;
-import software.amazon.awssdk.services.glue.model.RegisterSchemaVersionRequest;
-import software.amazon.awssdk.services.glue.model.RegisterSchemaVersionResponse;
-import software.amazon.awssdk.services.glue.model.RegistryId;
-import software.amazon.awssdk.services.glue.model.SchemaId;
+import software.amazon.awssdk.services.glue.model.*;
import java.io.File;
import java.io.IOException;
@@ -68,10 +50,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class AWSSchemaRegistryClientTest {
@@ -693,6 +672,31 @@ public void testQuerySchemaTags_clientThrowsException_throwsException() throws N
assertEquals(expectedMsg, awsSchemaRegistryException.getMessage());
}
+ @Test
+ public void testCreateSchema_existingSchema_throwsAlreadyExistsException() throws NoSuchFieldException, IllegalAccessException {
+ String testSchemaName = "test-schema";
+ String testSchemaDefinition = "test-schema-definition";
+ awsSchemaRegistryClient = configureAWSSchemaRegistryClientWithSerdeConfig(awsSchemaRegistryClient,
+ glueSchemaRegistryConfiguration);
+
+ AWSSchemaRegistryClient awsSchemaRegistryClientSpy = spy(awsSchemaRegistryClient);
+ AlreadyExistsException ex = AlreadyExistsException.builder().message("Already exists").build();
+ when(mockGlueClient.createSchema(any(CreateSchemaRequest.class))).thenThrow(ex);
+
+ UUID expectedId = UUID.randomUUID();
+ String expectedDataFormat = DataFormat.AVRO.toString();
+ Map expectedMetadata = getMetadata();
+ doReturn(expectedId).when(awsSchemaRegistryClientSpy).registerSchemaVersion(
+ testSchemaDefinition,
+ testSchemaName,
+ expectedDataFormat,
+ expectedMetadata
+ );
+
+ UUID actualId = awsSchemaRegistryClientSpy.createSchema(testSchemaName, expectedDataFormat, testSchemaDefinition, expectedMetadata);
+ assertEquals(expectedId, actualId);
+ }
+
private Map getMetadata() {
Map metadata = new HashMap<>();
metadata.put("event-source-1", "topic1");
diff --git a/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfigurationTest.java b/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfigurationTest.java
index 249c4605..471e67a4 100644
--- a/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfigurationTest.java
+++ b/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/GlueSchemaRegistryConfigurationTest.java
@@ -18,6 +18,8 @@
import com.amazonaws.services.schemaregistry.exception.AWSSchemaRegistryException;
import com.amazonaws.services.schemaregistry.utils.AWSSchemaRegistryConstants;
import com.amazonaws.services.schemaregistry.utils.AvroRecordType;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.SerializationFeature;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -32,11 +34,7 @@
import java.util.Map;
import java.util.Properties;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for testing configuration elements.
@@ -410,4 +408,55 @@ public void testBuildConfig_invalidProxyUrl_throwsException() {
Exception exception = assertThrows(AWSSchemaRegistryException.class, () -> new GlueSchemaRegistryConfiguration(props));
assertEquals("Proxy URL property is not a valid URL: "+proxy, exception.getMessage());
}
+
+ @Test
+ public void testBuildConfig_javaTimeModuleEnabledMock_succeeds() {
+ Properties props = createTestProperties();
+ String moduleClassName = "com.amazonaws.services.schemaregistry.common.configs.TestableSimpleModule";
+ props.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, moduleClassName);
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(props);
+ assertNotNull(cfg.loadJavaTimeModule());
+ }
+
+ @Test
+ public void testBuildConfig_javaTimeModuleEnabledWithoutDependency_throwException() {
+ Properties props = createTestProperties();
+ String moduleClassName = "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule";
+ props.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, moduleClassName);
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(props);
+ Exception exception = assertThrows(AWSSchemaRegistryException.class, () -> cfg.loadJavaTimeModule());
+ String message = String.format("Invalid JavaTimeModule specified: %s", moduleClassName);
+ assertEquals(message, exception.getMessage());
+ }
+
+ @Test
+ public void testBuildConfig_specifyObjectMapperFactory_succeeds() {
+ Properties props = createTestProperties();
+ String factoryClass = "com.amazonaws.services.schemaregistry.common.configs.TestableSimpleModule"; // could be any string
+ props.put(AWSSchemaRegistryConstants.OBJECT_MAPPER_FACTORY, factoryClass);
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(props);
+ assertEquals(factoryClass, cfg.getObjectMapperFactory());
+ }
+
+ @Test
+ public void testBuildConfig_jacksonSerializationFeatures_succeeds() {
+ Properties props = createTestProperties();
+ List features = new ArrayList<>();
+ features.add(SerializationFeature.INDENT_OUTPUT.toString());
+ features.add(SerializationFeature.WRAP_EXCEPTIONS.toString());
+ props.put(AWSSchemaRegistryConstants.JACKSON_SERIALIZATION_FEATURES, features);
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(props);
+ assertEquals(cfg.getJacksonSerializationFeatures().size(), 2);
+ }
+
+ @Test
+ public void testBuildConfig_jacksonDeserializationFeatures_succeeds() {
+ Properties props = createTestProperties();
+ List features = new ArrayList<>();
+ features.add(DeserializationFeature.WRAP_EXCEPTIONS.toString());
+ features.add(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.toString());
+ props.put(AWSSchemaRegistryConstants.JACKSON_DESERIALIZATION_FEATURES, features);
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(props);
+ assertEquals(cfg.getJacksonDeserializationFeatures().size(), 2);
+ }
}
\ No newline at end of file
diff --git a/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/TestableSimpleModule.java b/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/TestableSimpleModule.java
new file mode 100644
index 00000000..fbc68016
--- /dev/null
+++ b/common/src/test/java/com/amazonaws/services/schemaregistry/common/configs/TestableSimpleModule.java
@@ -0,0 +1,6 @@
+package com.amazonaws.services.schemaregistry.common.configs;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class TestableSimpleModule extends SimpleModule {
+}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index c7e76867..87849321 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -176,6 +176,10 @@
org.hamcrest
hamcrest-all
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
javax.xml.bind
jaxb-api
diff --git a/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kafka/ObjectMapperDateTimeSupportTest.java b/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kafka/ObjectMapperDateTimeSupportTest.java
new file mode 100644
index 00000000..bf37405f
--- /dev/null
+++ b/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kafka/ObjectMapperDateTimeSupportTest.java
@@ -0,0 +1,71 @@
+package com.amazonaws.services.schemaregistry.integrationtests.kafka;
+
+import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
+import com.amazonaws.services.schemaregistry.utils.AWSSchemaRegistryConstants;
+import com.amazonaws.services.schemaregistry.utils.json.ObjectMapperUtils;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ObjectMapperDateTimeSupportTest {
+
+ @Test
+ void testCreate_useJavaTimeModule_succeeds() {
+ Map map = new HashMap<>();
+ map.put(AWSSchemaRegistryConstants.AWS_REGION, "US-West-1");
+ map.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(map);
+
+ ObjectMapper om = ObjectMapperUtils.create(cfg);
+ assertNotNull(cfg.loadJavaTimeModule());
+ assertNotNull(om);
+ }
+
+ @Test
+ void testCreate_noJavaTimeModule_failsSerializingJava8DateTime() throws JsonProcessingException {
+ Map map = new HashMap<>();
+ map.put(AWSSchemaRegistryConstants.AWS_REGION, "US-West-1");
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(map);
+
+ ObjectMapper om = ObjectMapperUtils.create(cfg);
+
+ MyTestObject myObj = new MyTestObject("obj1", LocalDateTime.now());
+
+ assertThrows(InvalidDefinitionException.class, () -> om.writeValueAsString(myObj));
+ }
+
+ @Test
+ void testCreate_withJavaTimeModule_succeedsSerializingAndDeserializingJava8DateTime() throws JsonProcessingException {
+ Map map = new HashMap<>();
+ map.put(AWSSchemaRegistryConstants.AWS_REGION, "US-West-1");
+ map.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");
+ GlueSchemaRegistryConfiguration cfg = new GlueSchemaRegistryConfiguration(map);
+
+ ObjectMapper om = ObjectMapperUtils.create(cfg);
+
+ MyTestObject expectedObj = new MyTestObject("obj1", LocalDateTime.now());
+ String jsonStr = om.writeValueAsString(expectedObj);
+
+ MyTestObject actualObj = om.readValue(jsonStr, MyTestObject.class);
+ assertNotNull(actualObj);
+ assertEquals(expectedObj.getCreated(), actualObj.getCreated());
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ static class MyTestObject {
+ String name;
+ LocalDateTime created;
+ }
+}
diff --git a/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kinesis/GlueSchemaRegistryKinesisIntegrationTest.java b/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kinesis/GlueSchemaRegistryKinesisIntegrationTest.java
index 68a03098..be77c330 100644
--- a/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kinesis/GlueSchemaRegistryKinesisIntegrationTest.java
+++ b/integration-tests/src/test/java/com/amazonaws/services/schemaregistry/integrationtests/kinesis/GlueSchemaRegistryKinesisIntegrationTest.java
@@ -496,7 +496,7 @@ private String produceRecordsWithKPL(String streamName,
byte[] serializedBytes = dataFormatSerializer.serialize(record);
putFutures.add(producer.addUserRecord(streamName, Long.toString(timestamp.toEpochMilli()), null,
- ByteBuffer.wrap(serializedBytes), gsrSchema));
+ ByteBuffer.wrap(serializedBytes), null, gsrSchema));
}
String shardId = null;
diff --git a/jsonschema-kafkaconnect-converter/src/test/java/com/amazonaws/services/schemaregistry/kafkaconnect/jsonschema/FromConnectTest.java b/jsonschema-kafkaconnect-converter/src/test/java/com/amazonaws/services/schemaregistry/kafkaconnect/jsonschema/FromConnectTest.java
index d3782e1b..959537eb 100644
--- a/jsonschema-kafkaconnect-converter/src/test/java/com/amazonaws/services/schemaregistry/kafkaconnect/jsonschema/FromConnectTest.java
+++ b/jsonschema-kafkaconnect-converter/src/test/java/com/amazonaws/services/schemaregistry/kafkaconnect/jsonschema/FromConnectTest.java
@@ -57,7 +57,7 @@
public class FromConnectTest {
private static final JsonNodeFactory JSON_NODE_FACTORY = TypeConverter.JSON_NODE_FACTORY;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
- private static final JsonValidator JSON_VALIDATOR = new JsonValidator();
+ private static final JsonValidator JSON_VALIDATOR = new JsonValidator(OBJECT_MAPPER);
private ConnectSchemaToJsonSchemaConverter connectSchemaToJsonSchemaConverter;
private ConnectValueToJsonNodeConverter connectValueToJsonNodeConverter;
diff --git a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializer.java b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializer.java
index 5c0c60a5..3a8231e4 100644
--- a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializer.java
+++ b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializer.java
@@ -20,16 +20,15 @@
import com.amazonaws.services.schemaregistry.exception.AWSSchemaRegistryException;
import com.amazonaws.services.schemaregistry.serializers.json.JsonDataWithSchema;
import com.amazonaws.services.schemaregistry.common.Schema;
+import com.amazonaws.services.schemaregistry.utils.json.ObjectMapperUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.CollectionUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -56,19 +55,7 @@ public class JsonDeserializer implements GlueSchemaRegistryDataFormatDeserialize
@Builder
public JsonDeserializer(GlueSchemaRegistryConfiguration configs) {
this.schemaRegistrySerDeConfigs = configs;
- JsonNodeFactory jsonNodeFactory = JsonNodeFactory.withExactBigDecimals(true);
- this.objectMapper = new ObjectMapper();
- this.objectMapper.setNodeFactory(jsonNodeFactory);
- if (configs != null) {
- if (!CollectionUtils.isEmpty(configs.getJacksonSerializationFeatures())) {
- configs.getJacksonSerializationFeatures()
- .forEach(this.objectMapper::enable);
- }
- if (!CollectionUtils.isEmpty(configs.getJacksonDeserializationFeatures())) {
- configs.getJacksonDeserializationFeatures()
- .forEach(this.objectMapper::enable);
- }
- }
+ this.objectMapper = ObjectMapperUtils.create(configs);
}
/**
diff --git a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonSerializer.java b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonSerializer.java
index 063bbf11..e1bccdc3 100644
--- a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonSerializer.java
+++ b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonSerializer.java
@@ -17,17 +17,16 @@
import com.amazonaws.services.schemaregistry.common.GlueSchemaRegistryDataFormatSerializer;
import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
import com.amazonaws.services.schemaregistry.exception.AWSSchemaRegistryException;
+import com.amazonaws.services.schemaregistry.utils.json.ObjectMapperUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.CollectionUtils;
import java.nio.charset.StandardCharsets;
@@ -36,7 +35,7 @@
*/
@Slf4j
public class JsonSerializer implements GlueSchemaRegistryDataFormatSerializer {
- private static final JsonValidator JSON_VALIDATOR = new JsonValidator();
+ private final JsonValidator jsonValidator;
private final JsonSchemaGenerator jsonSchemaGenerator;
private final ObjectMapper objectMapper;
@Getter
@@ -51,19 +50,8 @@ public class JsonSerializer implements GlueSchemaRegistryDataFormatSerializer {
@Builder
public JsonSerializer(GlueSchemaRegistryConfiguration configs) {
this.schemaRegistrySerDeConfigs = configs;
- JsonNodeFactory jsonNodeFactory = JsonNodeFactory.withExactBigDecimals(true);
- this.objectMapper = new ObjectMapper();
- this.objectMapper.setNodeFactory(jsonNodeFactory);
- if (configs != null) {
- if (!CollectionUtils.isEmpty(configs.getJacksonSerializationFeatures())) {
- configs.getJacksonSerializationFeatures()
- .forEach(this.objectMapper::enable);
- }
- if (!CollectionUtils.isEmpty(configs.getJacksonDeserializationFeatures())) {
- configs.getJacksonDeserializationFeatures()
- .forEach(this.objectMapper::enable);
- }
- }
+ this.objectMapper = ObjectMapperUtils.create(configs);
+ this.jsonValidator = new JsonValidator(this.objectMapper);
this.jsonSchemaGenerator = new JsonSchemaGenerator(this.objectMapper);
}
@@ -80,7 +68,7 @@ public byte[] serialize(Object data) {
final JsonNode dataNode = getDataNode(data);
final JsonNode schemaNode = getSchemaNode(data);
- JSON_VALIDATOR.validateDataWithSchema(schemaNode, dataNode);
+ jsonValidator.validateDataWithSchema(schemaNode, dataNode);
bytes = writeBytes(dataNode);
return bytes;
@@ -182,6 +170,6 @@ public void validate(String schemaDefinition, byte[] data) {
public void validate(Object jsonDataWithSchema) {
JsonNode schemaNode = getSchemaNode(jsonDataWithSchema);
JsonNode dataNode = getDataNode(jsonDataWithSchema);
- JSON_VALIDATOR.validateDataWithSchema(schemaNode, dataNode);
+ jsonValidator.validateDataWithSchema(schemaNode, dataNode);
}
}
diff --git a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidator.java b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidator.java
index 8b59c907..80dfbea9 100644
--- a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidator.java
+++ b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidator.java
@@ -30,6 +30,13 @@
* Json validator
*/
public class JsonValidator {
+
+ private final ObjectMapper mapper;
+
+ public JsonValidator(ObjectMapper mapper) {
+ this.mapper = mapper;
+ }
+
/**
* Validates data against JsonSchema
* @param schemaNode
@@ -37,7 +44,7 @@ public class JsonValidator {
*/
public void validateDataWithSchema(JsonNode schemaNode, JsonNode dataNode) {
try {
- ObjectMapper mapper = new ObjectMapper();
+// ObjectMapper mapper = new ObjectMapper();
JSONObject rawSchema = new JSONObject(mapper.writeValueAsString(schemaNode));
Schema schema = SchemaLoader.load(rawSchema, new ReferenceDisabledSchemaClient());
diff --git a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/DefaultObjectMapperFactory.java b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/DefaultObjectMapperFactory.java
new file mode 100644
index 00000000..c36b9a97
--- /dev/null
+++ b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/DefaultObjectMapperFactory.java
@@ -0,0 +1,33 @@
+package com.amazonaws.services.schemaregistry.utils.json;
+
+import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import org.apache.commons.collections4.CollectionUtils;
+
+public class DefaultObjectMapperFactory implements ObjectMapperFactory {
+
+ @Override
+ public ObjectMapper newInstance(GlueSchemaRegistryConfiguration configs) {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
+
+ if (configs != null) {
+ if (!CollectionUtils.isEmpty(configs.getJacksonSerializationFeatures())) {
+ configs.getJacksonSerializationFeatures()
+ .forEach(objectMapper::enable);
+ }
+ if (!CollectionUtils.isEmpty(configs.getJacksonDeserializationFeatures())) {
+ configs.getJacksonDeserializationFeatures()
+ .forEach(objectMapper::enable);
+ }
+ if (configs.getJavaTimeModuleClass() != null) {
+ objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ objectMapper.registerModule(configs.loadJavaTimeModule());
+ }
+ }
+
+ return objectMapper;
+ }
+}
diff --git a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperFactory.java b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperFactory.java
new file mode 100644
index 00000000..9e5b597a
--- /dev/null
+++ b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperFactory.java
@@ -0,0 +1,9 @@
+package com.amazonaws.services.schemaregistry.utils.json;
+
+import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public interface ObjectMapperFactory {
+
+ ObjectMapper newInstance(GlueSchemaRegistryConfiguration configs);
+}
diff --git a/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperUtils.java b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperUtils.java
new file mode 100644
index 00000000..2628b7d9
--- /dev/null
+++ b/serializer-deserializer/src/main/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperUtils.java
@@ -0,0 +1,20 @@
+package com.amazonaws.services.schemaregistry.utils.json;
+
+import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
+import com.amazonaws.services.schemaregistry.exception.AWSSchemaRegistryException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ObjectMapperUtils {
+
+ public static ObjectMapper create(GlueSchemaRegistryConfiguration configs) {
+ try {
+ Class> moduleClass = Class.forName(configs.getObjectMapperFactory());
+ ObjectMapperFactory factory = (ObjectMapperFactory) moduleClass.getConstructor().newInstance();
+ return factory.newInstance(configs);
+ } catch (Exception e) {
+ String message = String.format("Failed to instantiate ObjectMapperFactory: %s",
+ configs.getObjectMapperFactory());
+ throw new AWSSchemaRegistryException(message, e);
+ }
+ }
+}
diff --git a/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializerTest.java b/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializerTest.java
index 6a7d63b4..411e132b 100644
--- a/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializerTest.java
+++ b/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/deserializers/json/JsonDeserializerTest.java
@@ -14,6 +14,7 @@
*/
package com.amazonaws.services.schemaregistry.deserializers.json;
+import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
@@ -26,10 +27,10 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
public class JsonDeserializerTest {
- private JsonDeserializer jsonDeserializer = new JsonDeserializer(null);
@Test
public void testDeserialize_nullArgs_throwsException() {
+ JsonDeserializer defaultJsonDeserializer = new JsonDeserializer(new GlueSchemaRegistryConfiguration("us-west-1"));
String testSchemaDefinition = "{\"$id\":\"https://example.com/geographical-location.schema.json\","
+ "\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"title\":\"Longitude "
+ "and Latitude Values\",\"description\":\"A geographical coordinate.\","
@@ -42,8 +43,10 @@ public void testDeserialize_nullArgs_throwsException() {
Schema testSchema = new Schema(testSchemaDefinition, DataFormat.JSON.name(), "testJson");
- assertThrows(IllegalArgumentException.class, () -> jsonDeserializer.deserialize(null, testSchema));
- assertThrows(IllegalArgumentException.class, () -> jsonDeserializer.deserialize(ByteBuffer.wrap(testBytes),
+ assertThrows(IllegalArgumentException.class, () -> defaultJsonDeserializer.deserialize(null, testSchema));
+ assertThrows(IllegalArgumentException.class, () -> defaultJsonDeserializer.deserialize(ByteBuffer.wrap(testBytes),
null));
}
+
+
}
diff --git a/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidatorTest.java b/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidatorTest.java
index 4a686a9a..5774ead3 100644
--- a/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidatorTest.java
+++ b/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/serializers/json/JsonValidatorTest.java
@@ -13,8 +13,9 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
public class JsonValidatorTest {
- private JsonValidator validator = new JsonValidator();
private ObjectMapper mapper = new ObjectMapper();
+ private JsonValidator validator = new JsonValidator(mapper);
+
private String stringSchema = "{\n"
+ " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n"
+ " \"description\": \"String schema\",\n"
diff --git a/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperUtilsTest.java b/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperUtilsTest.java
new file mode 100644
index 00000000..96e47fec
--- /dev/null
+++ b/serializer-deserializer/src/test/java/com/amazonaws/services/schemaregistry/utils/json/ObjectMapperUtilsTest.java
@@ -0,0 +1,61 @@
+package com.amazonaws.services.schemaregistry.utils.json;
+
+import com.amazonaws.services.schemaregistry.common.configs.GlueSchemaRegistryConfiguration;
+import com.amazonaws.services.schemaregistry.exception.AWSSchemaRegistryException;
+import com.amazonaws.services.schemaregistry.utils.AWSSchemaRegistryConstants;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+/**
+ * Unit tests for testing object mapper
+ */
+public class ObjectMapperUtilsTest {
+
+ static GlueSchemaRegistryConfiguration testConfigs(Map map) {
+ return new GlueSchemaRegistryConfiguration(testMap(map));
+ }
+
+ static Map testMap(Map map) {
+ if (map == null) {
+ map = new HashMap<>();
+ }
+ map.putIfAbsent(AWSSchemaRegistryConstants.AWS_REGION, "US-West-1");
+ return map;
+ }
+
+ @Test
+ void testCreate_succeeds() {
+ GlueSchemaRegistryConfiguration cfg = testConfigs(null);
+
+ ObjectMapper om = ObjectMapperUtils.create(cfg);
+ assertNotNull(om);
+ }
+
+ @Test
+ void testCreate_useJavaTimeModuleWithoutDeps_throwsException() {
+ Map map = testMap(null);
+ map.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");
+ GlueSchemaRegistryConfiguration cfg = testConfigs(map);
+ assertThrows(AWSSchemaRegistryException.class, () -> ObjectMapperUtils.create(cfg));
+ }
+
+ @Test
+ void testCreate_useJavaTimeModuleWithMockTimeModule_succeeds() {
+ Map map = testMap(null);
+ map.put(AWSSchemaRegistryConstants.REGISTER_JAVA_TIME_MODULE, "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");
+
+ GlueSchemaRegistryConfiguration spy = spy(testConfigs(map));
+ doReturn(new SimpleModule()).when(spy).loadJavaTimeModule();
+
+ ObjectMapper om = ObjectMapperUtils.create(spy);
+ assertNotNull(om);
+ }
+}