diff --git a/pom.xml b/pom.xml index 52ee17e..839fe0e 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,130 @@ ${java.version} + + + + + com.fasterxml.jackson + jackson-bom + 2.15.2 + pom + import + + + org.springframework.boot + spring-boot-dependencies + pom + import + 3.1.0 + + + + + org.slf4j + slf4j-api + 2.0.7 + + + ch.qos.logback + logback-classic + 1.4.8 + + + ch.qos.logback.contrib + logback-jackson + 0.1.5 + + + ch.qos.logback.contrib + logback-json-classic + 0.1.5 + + + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.openapitools + jackson-databind-nullable + 0.2.6 + + + + + org.assertj + assertj-core + 3.24.2 + test + + + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + ch.qos.logback.contrib + logback-jackson + + + ch.qos.logback.contrib + logback-json-classic + + + + + jakarta.validation + jakarta.validation-api + + + org.openapitools + jackson-databind-nullable + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.assertj + assertj-core + test + + + @@ -36,6 +160,26 @@ true + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.12 + + + + build-info + repackage + + + + + diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/Application.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/Application.java new file mode 100644 index 0000000..4f30228 --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/Application.java @@ -0,0 +1,13 @@ +package tk.dmanstrator.sb3_serialization_test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(final String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/entity/CustomObject.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/entity/CustomObject.java new file mode 100644 index 0000000..ec4d503 --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/entity/CustomObject.java @@ -0,0 +1,6 @@ +package tk.dmanstrator.sb3_serialization_test.entity; + +import java.io.Serializable; +import java.util.List; + +public record CustomObject(String hello, List world) implements Serializable {} diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporter.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporter.java new file mode 100644 index 0000000..cb9fceb --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporter.java @@ -0,0 +1,22 @@ +package tk.dmanstrator.sb3_serialization_test.exporter; + +import tk.dmanstrator.sb3_serialization_test.entity.CustomObject; +import tk.dmanstrator.sb3_serialization_test.serialization.SerializationHandlerFactory; + +import java.io.IOException; + +/** + * Class responsible for exporting entities. + */ +public class EntityExporter { + + private EntityExporter() { + // hide default constructor + } + + public static String exportCustomObject(final CustomObject customObject) throws IOException { + return SerializationHandlerFactory.getDefaultHandlerWithPrettyPrint() + .writeObjectAsString(customObject); + } + +} diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandler.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandler.java new file mode 100644 index 0000000..de70cf1 --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandler.java @@ -0,0 +1,22 @@ +package tk.dmanstrator.sb3_serialization_test.serialization; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; + +public interface SerializationHandler { + + boolean isBinary(); + + String getFileNameExtension(); + + void writeObject(final T object, final OutputStream os) throws IOException; + + String writeObjectAsString(final T object) throws IOException; + + T readObject(final Class clazz, final InputStream is) throws IOException; + + T readObjectFromString(final Class clazz, final String input) throws IOException; + +} diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandlerFactory.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandlerFactory.java new file mode 100644 index 0000000..29d113a --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/SerializationHandlerFactory.java @@ -0,0 +1,20 @@ +package tk.dmanstrator.sb3_serialization_test.serialization; + +import tk.dmanstrator.sb3_serialization_test.serialization.impl.JsonSerializationHandler; + +public final class SerializationHandlerFactory { + + private static final SerializationHandler DEFAULT_HANDLER = new JsonSerializationHandler(false); + private static final SerializationHandler DEFAULT_HANDLER_WITH_PRETTY_PRINT = new JsonSerializationHandler(true); + + private SerializationHandlerFactory() {} + + public static SerializationHandler getDefaultHandler() { + return DEFAULT_HANDLER; + } + + public static SerializationHandler getDefaultHandlerWithPrettyPrint() { + return DEFAULT_HANDLER_WITH_PRETTY_PRINT; + } + +} diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/AbstractJsonSerializationHandler.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/AbstractJsonSerializationHandler.java new file mode 100644 index 0000000..9f516b9 --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/AbstractJsonSerializationHandler.java @@ -0,0 +1,101 @@ +package tk.dmanstrator.sb3_serialization_test.serialization.impl; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import tk.dmanstrator.sb3_serialization_test.serialization.SerializationHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class AbstractJsonSerializationHandler implements SerializationHandler { + + protected static final ThreadLocal OBJECT_MAPPER_THREAD_LOCAL = + ThreadLocal.withInitial(AbstractJsonSerializationHandler::newObjectMapper); + + private final Map, ObjectReader> readers = new ConcurrentHashMap<>(); + private final Map, ObjectWriter> writers = new ConcurrentHashMap<>(); + + private final boolean doPrettyPrint; + + protected AbstractJsonSerializationHandler(final boolean doPrettyPrint) { + this.doPrettyPrint = doPrettyPrint; + } + + @Override + public void writeObject(final T object, final OutputStream os) throws IOException { + ObjectWriter writer = getWriter(object.getClass()); + + if (doPrettyPrint) { + writer = writer.withDefaultPrettyPrinter(); + } + + writer.writeValue(os, object); + } + + @Override + public String writeObjectAsString(final T object) throws IOException { + ObjectWriter writer = getWriter(object.getClass()); + + if (doPrettyPrint) { + writer = writer.withDefaultPrettyPrinter(); + } + + return writer.writeValueAsString(object); + } + + @Override + public T readObject(final Class clazz, final InputStream is) throws IOException { + return getReader(clazz).readValue(is); + } + + @Override + public T readObjectFromString(final Class clazz, final String input) + throws IOException { + return getReader(clazz).readValue(input); + } + + private static ObjectMapper newObjectMapper() { + final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator + .builder() + .allowIfBaseType(List.class) + .allowIfBaseType(Map.class) + .allowIfBaseType(Set.class) + .allowIfSubType(List.class) + .allowIfSubType(Map.class) + .build(); + return JsonMapper.builder() + .enable(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES) + .activateDefaultTyping(ptv) + .addModule(new JavaTimeModule()) + .addModule(new Jdk8Module()) + .visibility(PropertyAccessor.ALL, Visibility.NONE) + .visibility(PropertyAccessor.FIELD, Visibility.ANY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .serializationInclusion(Include.NON_NULL) + .build(); + } + + protected abstract ObjectMapper createObjectMapper(); + + private ObjectReader getReader(final Class clazz) { + return readers.computeIfAbsent(clazz, c -> OBJECT_MAPPER_THREAD_LOCAL.get().readerFor(c)); + } + + private ObjectWriter getWriter(final Class clazz) { + return writers.computeIfAbsent(clazz, c -> OBJECT_MAPPER_THREAD_LOCAL.get().writerFor(c)); + } + +} diff --git a/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/JsonSerializationHandler.java b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/JsonSerializationHandler.java new file mode 100644 index 0000000..cd749e2 --- /dev/null +++ b/src/main/java/tk/dmanstrator/sb3_serialization_test/serialization/impl/JsonSerializationHandler.java @@ -0,0 +1,42 @@ +package tk.dmanstrator.sb3_serialization_test.serialization.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JsonSerializationHandler extends AbstractJsonSerializationHandler { + + /** + * Creates a new JSON Serializer. Won't apply pretty-printing when writing objects. + */ + public JsonSerializationHandler() { + this(false); + } + + /** + * Creates a new JSON Serializer. + * + * @param doPrettyPrint Flag to tell whether pretty-printing should be performed when writing objects or not. + */ + public JsonSerializationHandler(final boolean doPrettyPrint) { + super(doPrettyPrint); + } + + @Override + public boolean isBinary() { + return false; + } + + @Override + public String getFileNameExtension() { + return ".json"; + } + + public static ObjectMapper getStandardObjectMapper() { + return OBJECT_MAPPER_THREAD_LOCAL.get(); + } + + @Override + protected ObjectMapper createObjectMapper() { + return new ObjectMapper(); + } + +} diff --git a/src/test/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporterTest.java b/src/test/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporterTest.java new file mode 100644 index 0000000..fccee60 --- /dev/null +++ b/src/test/java/tk/dmanstrator/sb3_serialization_test/exporter/EntityExporterTest.java @@ -0,0 +1,23 @@ +package tk.dmanstrator.sb3_serialization_test.exporter; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import tk.dmanstrator.sb3_serialization_test.entity.CustomObject; + +import java.io.IOException; +import java.util.Arrays; + +class EntityExporterTest { + + private static final String EMPTY_JSON = "{ }"; + + @Test + void testExportCustomObject() throws IOException { + final CustomObject customObject = new CustomObject("hello", Arrays.asList("wor", "ld")); + final String json = EntityExporter.exportCustomObject(customObject); + Assertions.assertThat(json) + .isNotEmpty() + .isNotEqualTo(EMPTY_JSON); + } + +} \ No newline at end of file