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

com.twitter.finatra.jackson.modules.ScalaObjectMapperModule.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finatra.jackson.modules

import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair
import com.fasterxml.jackson.databind.{
  Module => JacksonModule,
  ObjectMapper => JacksonObjectMapper,
  _
}
import com.fasterxml.jackson.datatype.joda.JodaModule
import com.google.inject.{Injector, Provides}
import com.twitter.finatra.jackson.caseclass.{DefaultAnnotationIntrospector, GuiceInjectableValues}
import com.twitter.finatra.jackson.serde.BaseSerdeModule
import com.twitter.finatra.json.annotations.{CamelCaseMapper, SnakeCaseMapper}
import com.twitter.inject.TwitterModule
import com.twitter.util.jackson.{JacksonScalaObjectMapperType, ScalaObjectMapper}
import com.twitter.util.validation.ScalaValidator
import javax.annotation.Nullable
import javax.inject.Singleton

object ScalaObjectMapperModule extends ScalaObjectMapperModule {
  // java-friendly access to singleton
  def get(): this.type = this
}

/**
 * [[TwitterModule]] to configure Jackson object mappers. Extend this module to override defaults
 * or provide additional configuration to the bound [[ScalaObjectMapper]] instances.
 *
 * Example:
 *
 * {{{
 *    import com.fasterxml.jackson.databind.{
 *      DeserializationFeature,
 *      Module,
 *      ObjectMapper,
 *      PropertyNamingStrategy
 *    }
 *    import com.twitter.finatra.jackson.modules.ScalaObjectMapperModule
 *
 *    object MyCustomObjectMapperModule extends ScalaObjectMapperModule {
 *
 *      override val propertyNamingStrategy: PropertyNamingStrategy =
 *        new PropertyNamingStrategy.KebabCaseStrategy
 *
 *      override val additionalJacksonModules: Seq[Module] =
 *        Seq(MySimpleJacksonModule)
 *
 *      override def additionalMapperConfiguration(mapper: ObjectMapper): Unit = {
 *        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
 *       }
 *     }
 * }}}
 */
class ScalaObjectMapperModule extends TwitterModule {

  /* Note: this is a stateful Module */
  private[this] val builder = ScalaObjectMapper.builder

  override protected def configure(): Unit = {
    bindOption[ScalaValidator]
  }

  /* Public */

  /** Return a [[JacksonScalaObjectMapperType]] configured from this [[ScalaObjectMapperModule]]. */
  def jacksonScalaObjectMapper: JacksonScalaObjectMapperType =
    withBuilder.objectMapper.underlying

  /** Return a [[ScalaObjectMapper]] configured from this [[ScalaObjectMapperModule]]. */
  def objectMapper: ScalaObjectMapper = withBuilder.objectMapper

  /**
   * Return a [[JacksonScalaObjectMapperType]] configured from this [[ScalaObjectMapperModule]]
   * using the given (nullable) [[Injector]].
   *
   * @param injector a configured (nullable) [[Injector]].
   */
  def jacksonScalaObjectMapper(@Nullable injector: Injector): JacksonScalaObjectMapperType = {
    val withGuiceInjectableValues = configureGuiceInjectableValues(injector)
    withBuilder
      .withAdditionalMapperConfigurationFn(withGuiceInjectableValues)
      .objectMapper
      .underlying
  }

  /**
   * Return a [[ScalaObjectMapper]] configured from this [[ScalaObjectMapperModule]]
   * using the given (nullable) [[Injector]].
   *
   * @param injector a configured (nullable) [[Injector]].
   */
  def objectMapper(@Nullable injector: Injector): ScalaObjectMapper = {
    val withGuiceInjectableValues = configureGuiceInjectableValues(injector)
    withBuilder
      .withAdditionalMapperConfigurationFn(withGuiceInjectableValues)
      .objectMapper
  }

  /**
   * Return a [[ScalaObjectMapper]] configured from this [[ScalaObjectMapperModule]] explicitly
   * configured with [[PropertyNamingStrategy.LOWER_CAMEL_CASE]] as a `PropertyNamingStrategy`.
   */
  final def camelCaseObjectMapper: ScalaObjectMapper =
    ScalaObjectMapper.camelCaseObjectMapper(this.jacksonScalaObjectMapper(null))

  /**
   * Return a [[ScalaObjectMapper]] configured from this [[ScalaObjectMapperModule]] explicitly
   * configured with [[PropertyNamingStrategy.SNAKE_CASE]] as a `PropertyNamingStrategy`.
   */
  final def snakeCaseObjectMapper: ScalaObjectMapper =
    ScalaObjectMapper.snakeCaseObjectMapper(this.jacksonScalaObjectMapper(null))

  /* Protected -- users are expected to customize behavior by overriding these methods and members */

  /**
   * @see [[ScalaObjectMapper.DefaultSerializationInclude]]
   * @see [[ScalaObjectMapper.Builder.withSerializationInclude]]
   */
  protected def serializationInclusion: Include = ScalaObjectMapper.DefaultSerializationInclude

  /**
   * @see [[ScalaObjectMapper.DefaultSerializationConfig]]
   * @see [[ScalaObjectMapper.Builder.withSerializationInclude]]
   */
  protected def serializationConfig: Map[SerializationFeature, Boolean] =
    ScalaObjectMapper.DefaultSerializationConfig

  /**
   * @see [[ScalaObjectMapper.DefaultDeserializationConfig]]
   * @see [[ScalaObjectMapper.Builder.withDeserializationConfig]]
   */
  protected def deserializationConfig: Map[DeserializationFeature, Boolean] =
    ScalaObjectMapper.DefaultDeserializationConfig

  /**
   * @see [[ScalaObjectMapper.DefaultPropertyNamingStrategy]]
   * @see [[ScalaObjectMapper.Builder.withPropertyNamingStrategy]]
   */
  protected def propertyNamingStrategy: PropertyNamingStrategy =
    ScalaObjectMapper.DefaultPropertyNamingStrategy

  /**
   * @see [[ScalaObjectMapper.DefaultNumbersAsStrings]]
   * @see [[ScalaObjectMapper.Builder.withNumbersAsStrings]]
   * @see [[com.fasterxml.jackson.core.json.JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS]]
   */
  protected def numbersAsStrings: Boolean = ScalaObjectMapper.DefaultNumbersAsStrings

  /**
   * @see [[ScalaObjectMapper.DefaultJacksonModules]]
   * @see [[ScalaObjectMapper.Builder.withDefaultJacksonModules]]
   */
  protected def defaultJacksonModules: Seq[JacksonModule] =
    ScalaObjectMapper.DefaultJacksonModules ++ Seq(new JodaModule, BaseSerdeModule)

  /**
   * @see [[ScalaObjectMapper.DefaultAdditionalJacksonModules]]
   * @see [[ScalaObjectMapper.Builder.withAdditionalJacksonModules]]
   */
  protected def additionalJacksonModules: Seq[JacksonModule] =
    ScalaObjectMapper.DefaultAdditionalJacksonModules

  /**
   * @see [[ScalaObjectMapper.Builder.withAdditionalMapperConfigurationFn]]
   * @param mapper the underlying [[JacksonObjectMapper]] to configure
   * @note caution -- it is expected that this method mutates the given mapper reference.
   */
  protected def additionalMapperConfiguration(mapper: JacksonObjectMapper): Unit = {}

  /**
   * @see [[ScalaObjectMapper.DefaultValidation]]
   * @see [[ScalaObjectMapper.Builder.withNoValidation]]
   */
  protected def validation: Boolean = ScalaObjectMapper.DefaultValidation

  /** Convenience method for copying a [[JacksonScalaObjectMapperType]]  */
  protected final def copy(
    objectMapper: JacksonScalaObjectMapperType
  ): JacksonScalaObjectMapperType =
    ObjectMapperCopier.copy(objectMapper)

  // allows for overriding the injectable types via the provides method for injection
  protected[modules] def provideScalaObjectMapper(
    injector: Injector,
    validator: Option[ScalaValidator]
  ): ScalaObjectMapper =
    provideConfiguredObjectMapperBuilder(injector, validator).objectMapper

  /* Private */

  private[modules] final def provideConfiguredObjectMapperBuilder(
    injector: Injector,
    validator: Option[ScalaValidator]
  ): ScalaObjectMapper.Builder = {
    val withGuiceInjectableValues = configureGuiceInjectableValues(injector)

    val builderWithValidator = validator match {
      case Some(v) => withBuilder.withValidator(v)
      case _ => withBuilder
    }

    builderWithValidator
      .withAdditionalMapperConfigurationFn(withGuiceInjectableValues)
  }

  private[modules] final def withBuilder: ScalaObjectMapper.Builder = {
    val base = builder
      .withPropertyNamingStrategy(this.propertyNamingStrategy)
      .withNumbersAsStrings(this.numbersAsStrings)
      .withSerializationInclude(this.serializationInclusion)
      .withSerializationConfig(this.serializationConfig)
      .withDeserializationConfig(this.deserializationConfig)
      .withDefaultJacksonModules(this.defaultJacksonModules)
      .withAdditionalJacksonModules(this.additionalJacksonModules)
      .withAdditionalMapperConfigurationFn(this.additionalMapperConfiguration)
    if (validation) base else base.withNoValidation
  }

  private[modules] final def configureGuiceInjectableValues(
    injector: Injector
  ): JacksonObjectMapper => Unit = { mapper =>
    val defaultAnnotationIntrospector: DefaultAnnotationIntrospector =
      new DefaultAnnotationIntrospector
    mapper.setInjectableValues(new GuiceInjectableValues(injector))
    mapper.setAnnotationIntrospectors(
      new AnnotationIntrospectorPair(
        defaultAnnotationIntrospector,
        mapper.getSerializationConfig.getAnnotationIntrospector
      ),
      new AnnotationIntrospectorPair(
        defaultAnnotationIntrospector,
        mapper.getDeserializationConfig.getAnnotationIntrospector
      )
    )
  }

  /* NOTE: these methods are private as they are only meant to be called by the
     injector for providing these types. */

  // We explicitly add the `validator: Option[ScalaValidator]` as a parameter
  // to force the injector to resolve this type instead of attempting to read it from
  // the passed injector which may not be completely initialized here.
  @Singleton
  @Provides
  @SnakeCaseMapper
  private final def provideSnakeCaseObjectMapper(
    injector: Injector,
    validator: Option[ScalaValidator]
  ): ScalaObjectMapper =
    ScalaObjectMapper.snakeCaseObjectMapper(
      provideScalaObjectMapper(injector, validator).underlying
    )

  @Singleton
  @Provides
  @CamelCaseMapper
  private final def provideCamelCaseObjectMapper(
    injector: Injector,
    validator: Option[ScalaValidator]
  ): ScalaObjectMapper =
    ScalaObjectMapper.camelCaseObjectMapper(
      provideScalaObjectMapper(injector, validator).underlying
    )

  @Singleton
  @Provides
  private final def provideObjectMapper(
    injector: Injector,
    validator: Option[ScalaValidator]
  ): ScalaObjectMapper =
    provideScalaObjectMapper(injector, validator)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy