All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mats3.util.FieldBasedJacksonMapper Maven / Gradle / Ivy

Go to download

Mats^3 Utilities - notably the MatsFuturizer, which provides a bridge from synchronous processes to the highly asynchronous Mats^3 services.

There is a newer version: 0.19.22-2024-11-09
Show newest version
package io.mats3.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

/**
 * Configures a Jackson JSON field-based ObjectMapper to be used in Mats3, ensuring a common standard configuration.
 * 

* The ObjectMapper is configured be as lenient and compact as possible. The configuration is as such: *

    *
  • Only read and write fields, ignore methods and constructors (except for Records, which Jackson handles somewhat * differently).
  • *
  • Read and write any access modifier fields (private, package, public)
  • *
  • If the JSON have a value that does not map to a field in the DTO, do not fail
  • *
  • Drop nulls from JSON
  • *
  • Write times and dates using Strings of ISO-8601, e.g "1975-03-11" instead of millis-since-epoch or array-of-ints * [1975, 3, 11]
  • *
  • Handle Optional, OptionalLong, OptionalDouble
  • *
  • Handles Java Records
  • *
  • Make the security constraints when reading JSON 10x lenient, i.e. nesting level, number length and string length * to high values. (In particular the accepted string length must be very high, since the DTOs and STOs are "doubly * serialized" and will be present in the MatsTrace DTO as Strings.)
  • *
* Note: If Jackson's Blackbird Module is on the classpath, it will be used. It is a moderate performance improvement, * in extreme cases up to 20% faster on the deserialization side. The detection logic can be overridden using system * property "mats.jackson.useBlackbird", setting it to "true" or "false" before this class is loaded - if true, and the * module is not on the classpath, it will throw an exception. Add it to your project with:
* implementation "com.fasterxml.jackson.module:jackson-module-blackbird:${jacksonVersion}" */ public class FieldBasedJacksonMapper { private static final Logger log = LoggerFactory.getLogger(FieldBasedJacksonMapper.class); private static final Class __blackbirdModuleClass; private static final boolean __useBlackbird; static { // Check if 'com.fasterxml.jackson.module.blackbird.BlackbirdModule' is on the classpath, and if so, store // the class (since we need to instantiate it reflectively), and decide on the __useBlackbird flag. Class blackbirdModuleClass; try { blackbirdModuleClass = Class.forName("com.fasterxml.jackson.module.blackbird.BlackbirdModule"); } catch (ClassNotFoundException e) { blackbirdModuleClass = null; } __blackbirdModuleClass = blackbirdModuleClass; __useBlackbird = System.getProperty("mats.jackson.useBlackbird", blackbirdModuleClass != null ? "true" : "false").equalsIgnoreCase("true"); } // "Initialization-on-demand holder idiom", using a static inner class to hold the singleton instance. private static class SingletonObjectMapperHolder { private static final ObjectMapper INSTANCE = internalJacksonObjectMapper(__useBlackbird, "Creating default Mats3 singleton"); } /** * Returns the singleton ObjectMapper used by all Mats3 components. It is thread-safe, meant for sharing. You must * not further configure this ObjectMapper instance, as it is shared by all Mats3 components. * * @return the default ObjectMapper used by Mats3 components - do not mess with this! */ public static ObjectMapper getMats3DefaultJacksonObjectMapper() { return SingletonObjectMapperHolder.INSTANCE; } /** * Creates a new Jackson ObjectMapper configured exactly the same as the default ObjectMapper used by Mats3 * components. It is thread-safe, meant for sharing. Note that it is imperative that you do not create a new * ObjectMapper for each JSON serialization or deserialization, as this is an expensive operation, but worse, it * will - according to documentation for the Blackbird module - lead to a memory leak. * * @return a new Jackson ObjectMapper configured as if for Mats3. */ public static ObjectMapper createJacksonObjectMapper() { return internalJacksonObjectMapper(__useBlackbird, "Instantiating new"); } private static ObjectMapper internalJacksonObjectMapper(boolean useBlackbird, String sayWhat) { // Use StackWalker to get the caller's stack frame String callerInfo; StackWalker walker = StackWalker.getInstance(); StackWalker.StackFrame callerFrame = walker.walk(stream -> stream.skip(1) .filter(f -> !f.getClassName().contains(".FieldBasedJacksonMapper")) .findFirst().orElse(null)); if (callerFrame != null) { String callingClassName = callerFrame.getClassName(); String callingMethodName = callerFrame.getMethodName(); callerInfo = callingClassName + "." + callingMethodName; } else { callerInfo = "Unknown"; } ObjectMapper mapper; String currentJavaVersion = System.getProperty("java.version"); if (useBlackbird) { log.info(sayWhat + " Jackson JsonMapper, USING Jackson Blackbird Module! (Java: " + currentJavaVersion + ", caller: " + callerInfo + ")"); if (__blackbirdModuleClass == null) { throw new IllegalStateException("You have requested to use Jackson Blackbird Module, but it is not on" + " the classpath. Add it to your project with: " + "implementation \"com.fasterxml.jackson.module:jackson-module-blackbird:${jacksonVersion}\""); } com.fasterxml.jackson.databind.Module blackbirdModule; try { blackbirdModule = (com.fasterxml.jackson.databind.Module) __blackbirdModuleClass .getDeclaredConstructor().newInstance(); } catch (Throwable t) { throw new RuntimeException(t); } mapper = JsonMapper.builder().addModule(blackbirdModule).build(); } else { log.info(sayWhat + " Jackson JsonMapper, NOT using Jackson Blackbird Module. (Java: " + currentJavaVersion + ", caller: " + callerInfo + ")"); mapper = JsonMapper.builder().build(); } // Read and write any access modifier fields (e.g. private) mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); // Drop nulls mapper.setSerializationInclusion(Include.NON_NULL); // If props are in JSON that aren't in Java DTO, do not fail. mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Write e.g. Dates as "1975-03-11" instead of timestamp, and instead of array-of-ints [1975, 3, 11]. // Uses ISO8601 with milliseconds and timezone (if present). mapper.registerModule(new JavaTimeModule()); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); // Handle Optional, OptionalLong, OptionalDouble mapper.registerModule(new Jdk8Module()); // 10x constraints, and make max string length effectively infinite. StreamReadConstraints streamReadConstraints = StreamReadConstraints .builder() .maxNestingDepth(10000) // default 1000 .maxNumberLength(10000) // default 1000 .maxStringLength(Integer.MAX_VALUE) .build(); mapper.getFactory().setStreamReadConstraints(streamReadConstraints); return mapper; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy