Orika Implementación de la Transformación - Uso Directo

El orika La biblioteca -transformation-impl proporciona un extension a la Orika mapping biblioteca, destinada a simplificar el proceso de creación de transformaciones. Introduce OrikaCustomiser y TransformationService como las dos principales interfaces. El OrikaCustomiser proporciona una interfaz simple para customizing el subyacente Orika mapping fábrica. Cada TransformationService la instancia será customized con un conjunto de mappers y enriquecedores que apoyarán todas las transformaciones necesarias en un contexto dado.

Orika Uso de Transformación Legado

El orika-el plugin de generación de transformación puede ser utilizado para generar una *TransformationFactory.

Esta Fábrica puede ser utilizada para crear una instancia configurada de un TransformationService, como se muestra a continuación:

TransformationService transformationService
                = new ExampleTransformationFactory(enrichmentContext).transformationService();

Una vez que se crea el servicio, puede llamar a los siguientes métodos para aplicar mappings o enriquecimientos.

  Destination destination = transformationService.map(sourceObject, Destination.class);

  transformationService.enrich(source, myObject);

Para utilizar esta biblioteca, añada la siguiente dependencia:

  1. pom.xml

<dependency>
    <groupId>com.iconsolutions.ipf.core.mapper</groupId>
    <artifactId>orika-transformation-impl</artifactId>
</dependency>

Orika Uso Extendido de la Transformación

El OrikaTransformationServiceFactory utiliza SpEL y Orika para proporcionar Transformaciones. Cualquier diferencia La migración a esta opción puede deberse a diferencias en SpEL.

Además, la biblioteca ahora permite la creación de transformaciones condicionales, que anteriormente se gestionaban a través de una combinación de Java código y configuraciones de transformación. Este enfoque anterior era difícil de gestionar y añadía complejidad innecesaria.

Esta última versión de esta biblioteca amplía el uso del Lenguaje de Expresión de Spring con el fin de introducir condicionales. transformaciones.

Más detalles sobre SpEL aquí.

Esta biblioteca introduce dos nuevas implementaciones de OrikaCustomisers utilizado internamente:

  1. OrikaEnrichmentsCustomiser -Proporciona la capacidad de enriquecer una entidad a través de la configuración

  2. OrikaMappingCustomiser- Proporciona la capacidad de mapear una entidad de un tipo a otro a través de la configuración.

La otra diferencia clave es el uso de OrikaTransformationServiceFactory para construir TransformationService directamente desde la configuración, en lugar de a través de code generation.

Guía paso a paso

Para poder utilizar orika-transformación-impl, se requieren los siguientes pasos:

  1. Configure la última orika-dependencia-impl de transformación.

  2. Autoconexión OrikaTransformationServiceFactory en una clase de configuración de Spring.

  3. Cree archivos .conf apropiados para proporcionar enriquecimiento o mapping transformaciones de tipo.

  4. Invocar el OrikaTransformationServiceFactory.transformationService método de fábrica (EnrichmentContext context, String.. resource).

Paso 1. Configure la última orika-dependencia de -transformación-

  1. pom.xml

<dependency>
    <groupId>com.iconsolutions.ipf.core.mapper</groupId>
    <artifactId>orika-transformation-impl</artifactId>
</dependency>

Paso 2. Autoconexión OrikaTransformationServiceFactory en una clase de configuración de Spring.

El OrikaTransformationServiceFactory se proporciona por el OrikaTransformationServiceFactoryConfig clase, que se carga automáticamente en el Spring Application Context.

@Configuration
public class MyMappingTransformationServiceConfig {

    @Bean
    public TransformationService myTransformationService(OrikaTransformationServiceFactory orikaTransformationServiceFactory) {

        //logic to create TransformationService.
    }
}

Paso 3. Cree archivos *.conf apropiados para proporcionar enriquecimiento o mapping transformaciones de tipo.

Asegúrese de que todos los archivos.conf se encuentren en src/main/resources.

Mapping Ejemplo

El ejemplo a continuación muestra cómo condicional mapping está configurado. Tenga en cuenta que la expresión está en formato SpEL.

source-class: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestSourceObject
destination-class: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestTargetObject
implicit-mapping: false
bidirectional-mapping: true

mappings = [
  {
    source: age
    destination: myage
  },
  {
    source: name
    destination: myname
    conditions: {
      a-to-b: [
        "a.name == 'name-123'" (1)
      ]
    }
  },
  {
    source: address
    destination: myaddress
    conditions: {
      a-to-b: [
        "a.age >= 10" (2)
      ]
    }
  }
]
1 Mapa name to myname solo si name is name-123
2 Mapa address to myaddress solo si age es mayor o igual a 10

Ejemplo de Enriquecimiento

El ejemplo a continuación muestra cómo enriquecer valores en una instancia de un JavaBean.

enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject

enrichments: [
  {
    destination: "myStringProperty",
    enrichment-type: value,
    value: "updatedText"
  },
  {
    destination: "myIntegerProperty",
    enrichment-type: value,
    value: 500,
    enrichment-field-conditions:[
      "myIntegerProperty == 100",
    ]
  }
]

Paso 4. Invoque el OrikaTransformationServiceFactory.transformationService método de fábrica (EnrichmentContext context, String.. resource)

El OrikaTransformationServiceFactory.transformationService El método (EnrichmentContext context, String.. resources) se utiliza para instanciar nuevos TransformationService instancias. Requiere un EnrichmentContext y múltiples archivos de transformación *.conf como argumentos.

@Configuration
public class MyMappingTransformationServiceConfig {

    /**
     * construct the TransformationService
     */
    @Bean
    public TransformationService myTransformationService(OrikaTransformationServiceFactory orikaTransformationServiceFactory,
                                                                    EnrichmentContext enrichmentContext) {

        return orikaTransformationServiceFactory.transformationService(enrichmentContext,
                "mapping-example.conf",
                "enrichment-example.conf"
        );
    }

    /**
     * construct an EnrichmentContext to be used by each TransformationService.
     */
    @Bean
    public EnrichmentContext enrichmentContext(Config config) {
        return DefaultEnrichmentContext.builder()
                .withClock(Clock.systemDefaultZone())
                .withConfigProvider(() -> config)
                .build();
    }
}

Detalles de Configuración de Transformación

Mapping Ejemplos de Configuración

Ejemplo Java Código

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TestSourceObject {

    String name;
    String address;
    Integer age;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TestTargetObject {
    String myname;
    String myaddress;
    Integer myage;
}

Mapping Configurar con condiciones por individuo mapping

Puede incluir un nuevo elemento opcional llamado "conditions" en cada mapping. Este elemento le permite controlar si un mapping se aplica en función de una expresión de condición específica. Dentro del elemento de condiciones, puede especificar condiciones para un *único*mapping dirección ya sea a-a o b-a.

Las condiciones para cada dirección deben definirse por separado.mappings según el siguiente ejemplo. Al utilizar spel,bidirectional-mapping debe ser verdadero y las condiciones deben ser definidas por medios si es aplicable.

El ejemplo a continuación demuestra dos separados mappings basado en condiciones específicas. Estos mappings tiene los siguientes efectos:

  1. Mapeo de source.name a destination.myname cuando source.name == 'name-123'.

  2. Mapeo de source.address a destination.myaddress cuando source.age >= 10.

source-class: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestSourceObject
destination-class: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestTargetObject
implicit-mapping: false
bidirectional-mapping: true
// fields below are no longer used and no longer need to be specified when using `orika-transformation-service-impl`
target-class-name: TestSourceObjectMapper
target-package: com.ipf.payments.common.execution.mapper
mappings = [
  {
    source: name
    destination: myname
    conditions: {
      a-to-b: [
        "a.name == 'name-123'"
      ]
    }
  },
  {
    source: name
    destination: myname
    conditions: {
      b-to-a: [
        "b.myname == 'name-123'"
      ]
    }
  },
  {
    source: address
    destination: myaddress
    conditions: {
      a-to-b: [
        "a.age >= 10"
      ]
    }
  }
]

Ejemplos de Configuración de Enriquecimiento

Ejemplo Java Código

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TestEnrichmentObject {

    String myStringProperty;
    Integer myIntegerProperty;
}

Configuración de enriquecimiento con condiciones por enriquecimiento individual

Puede incluir un nuevo atributo opcional llamado "enrichment-field-conditions" en cada enriquecimiento. Este atributo proporciona la capacidad de controlar si se aplica un enriquecimiento en función de una expresión de condición específica.

El ejemplo a continuación demuestra dos enriquecimientos separados basados en condiciones específicas. Estos enriquecimientos tienen los siguientes efectos:

  1. Enriqueciendo target.myIntegerProperty a 500 cuando source.myIntegerProperty == 100

  2. Enriqueciendo target.myStringProperty a 'updatedText' cuando source.myStringProperty == 'matchingText'

enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject
// target-package is no longer used and does not need to be specified when using `orika-transformation-service-impl`
target-package: com.ipf.payments.common.execution.enrichment.orika.testmodel
enrichments: [
  {
    destination: "myIntegerProperty",
    enrichment-type: value,
    value: 500,
    enrichment-field-conditions:[
      "myIntegerProperty == 100",
    ]
  },
  {
    destination: "myStringProperty",
    enrichment-type: value,
    value: "updatedText",
    enrichment-field-conditions:[
      "myStringProperty == 'matchingText'"
    ]
  }
]

Configuración de enriquecimiento con condiciones por grupo de enriquecimientos

Puede incluir un nuevo atributo opcional llamado "enrichment-conditions" en cada archivo de enriquecimiento. Este atributo le permite controlar si las mejoras en la lista se aplican en función de una expresión de condición específica.

El ejemplo a continuación aplica todos los enriquecimientos solo cuando target.myIntegerProperty es igual a 100 Y source.myStringProperty es igual a 'matchingText.'.

enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject

enrichment-conditions:[ "myIntegerProperty == 100", "myStringProperty == 'matchingText'" ] enriquecimientos: [ { destino: "myStringProperty", tipo-de-enriquecimiento: valor, valor: "textoActualizado" } ]

Pruebas de Transformaciones

Ejemplo Java Código

@SpringBootTest(classes = {
        //core transformation config
        OrikaTransformationServiceFactoryConfig.class,

        //Config object required
        MyMappingTransformationServiceITest. DefaultAppConfig.class,

        //include specific mapping spring config
        MyMappingTransformationServiceConfig.class
}
)
class MyMappingTransformationServiceITest {

    @Autowired
    private TransformationService myTransformationService;

    @TestConfiguration
    public static class DefaultAppConfig{

        /**
         * Config bean is required for EnrichmentContext.
         */
        @Bean
        public Config config() {
            return ConfigFactory.load("ipf");
        }
    }

    @Test
    void mapSourceToTarget() {

        TestSourceObject sourceObject = new TestSourceObject("name-123", "address", "postcode", 10);

        TestTargetObject targetObject = myTransformationService.map(sourceObject, TestTargetObject.class);

        assertThat(targetObject).isEqualTo(TestTargetObject.builder().myname("name-123").myaddress("address").build());
    }
}

Extensiones de Biblioteca

Registro de Estrategias de Enriquecimiento

Interfaz de Estrategia de Enriquecimiento

public interface OrikaEnrichmentStrategy {
    void applyEnrichment(EnrichmentContext enrichmentContext, Object target, OrikaEnrichment orikaEnrichment);
}

Defina Nueva Estrategia de Enriquecimiento

@RequiredArgsConstructor
public class HelloWorldEnrichmentStrategy implements OrikaEnrichmentStrategy {

    private final OrikaExpressionEvaluator orikaExpressionEvaluator;

    @SneakyThrows
    @Override
    public void applyEnrichment(EnrichmentContext enrichmentContext, Object target, OrikaEnrichment orikaEnrichment) {
        orikaExpressionEvaluator.setValue(target, orikaEnrichment.getFieldExpression(), "Hello World");
    }
}

Defina OrikaEnrichmentType Valor

public enum OrikaEnrichmentType {

    VALUE("value", OrikaPropertyName. VALUE),
    PROVIDED("provided", PATH),
    CONFIG_VALUE("config-value", PATH),
    FROM_INSTANT("from-instant", FORMAT),
    RANDOM_ALPHA_NUMERIC("randomAlphaNumeric"),
    ENRICHMENT_FUNCTION("enrichment-function"),
    CURRENT_INSTANT("current-instant"),
    CURRENT_LOCAL_DATE("current-local-date"),
    CURRENT_OFFSET_DATETIME("current-offset-datetime"),
    CURRENT_LOCAL_DATE_TIME("current-local-datetime"),
    CURRENT_ZONED_DATE_TIME("current-zoned-datetime"),
    CURRENT_ISO_DATE_TIME("current-iso-datetime"),

    //new value added
    HELLO_WORLD("hello-world");

    private final String configPropertyName;
    private final Set<OrikaPropertyName> requiredProperties;

    OrikaEnrichmentType(String configPropertyName, OrikaPropertyName.. requiredProperties) {
        this.configPropertyName = configPropertyName;
        this.requiredProperties = Set.of(requiredProperties);
    }
}

Defina el Mapa de Estrategias de Enriquecimiento e incluya nuevas Bean

@Bean
public Map<OrikaEnrichmentType, OrikaEnrichmentStrategy> orikaEnrichmentStrategies(@Qualifier("orikaExpressionEvaluator") OrikaExpressionEvaluator orikaExpressionEvaluator){

    return ImmutableMap.<OrikaEnrichmentType, OrikaEnrichmentStrategy>builder()
            .put(VALUE, new ValueEnrichmentStrategy(orikaExpressionEvaluator))
            .put(CONFIG_VALUE, new ConfigValueEnrichmentStrategy(orikaExpressionEvaluator))
            .put(ENRICHMENT_FUNCTION, new EnrichmentContextFunctionEnrichmentStrategy(orikaExpressionEvaluator))
            .put(PROVIDED, new ProvidedEnrichmentStrategy(orikaExpressionEvaluator))
            .put(FROM_INSTANT, new FromInstantEnrichmentStrategy(orikaExpressionEvaluator))
            .put(RANDOM_ALPHA_NUMERIC, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::randomAlphaNumeric))
            .put(CURRENT_INSTANT, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::currentInstant))
            .put(CURRENT_LOCAL_DATE, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::currentLocalDate))
            .put(CURRENT_OFFSET_DATETIME, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::currentOffsetDateTime))
            .put(CURRENT_LOCAL_DATE_TIME, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::currentLocalDateTime))
            .put(CURRENT_ZONED_DATE_TIME, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::currentZonedDateTime))
            .put(CURRENT_ISO_DATE_TIME, new FunctionEnrichmentStrategy(orikaExpressionEvaluator, EnrichmentContext::currentIsoDateTime))

            //new hello world strategy added
            .put(HELLO_WORLD, new HelloWorldEnrichmentStrategy())
            .build();
}

Ejemplo de uso de configuración

enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject

enriquecimientos: [ { destino: "myStringProperty", tipo-de-enriquecimiento: hello-world } ]

Registrándose Custom Convertidores de Primavera

El Lenguaje de Expresión de Spring (SpEL) proporciona la capacidad de especificar custom Convertidores para mapping entre dos tipos de objetos diferentes. Esto es particularmente útil cuando el objeto objetivo es de un tipo diferente y los nombres de las propiedades también difieren.

Ver Detalles:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/converter/Converter.html

El ejemplo a continuación demuestra la inyección del DynamicConversionService y el registro de cualquier requisito necesario.custom convertidores. Estos convertidores se aplican durante las transformaciones cuando una conversión predeterminada no es aplicable.

public class SupplementaryDataCompanyConverter implements Converter<SupplementaryDataCompany, SupplementaryData1> {

    public static final String SUPPLEMENTARY_DATA_NAME_2 = "supl2";

    @Override
    public SupplementaryData1 convert(SupplementaryDataCompany supplementaryDataCompany) {
        SupplementaryData1 supplementaryData1 = new SupplementaryData1();
        supplementaryData1.setPlcAndNm(supplementaryDataCompany.getSplmtryDataNm());
        supplementaryData1.setEnvlp(SupplementaryDataEnvelope1.builder().any(supplementaryDataCompany.getEnvlp().getAny()).build());
        if (SUPPLEMENTARY_DATA_NAME_2.equals(supplementaryData1.getPlcAndNm())) {
            if (((Document) supplementaryData1.getEnvlp().getAny()).getBkNtry() == null) {
                ((Document) supplementaryData1.getEnvlp().getAny()).setBkNtry(Document. BkNtry.builder().build());
            }
            if (((Document) supplementaryData1.getEnvlp().getAny()).getSrcInf() == null) {
                ((Document) supplementaryData1.getEnvlp().getAny()).setSrcInf(Document. SrcInf.builder().build());
            }
        }
        return supplementaryData1;
    }

}

@Configuration
public class MyMappingTransformationServiceConfig {

    @Autowired
    void initTypeConverters(DynamicConversionService conversionService) {
        //register any custom converters
        conversionService.addConverter(new SupplementaryDataCompanyConverter());
    }
}

Custom Funciones de Expresión

Aplicando Custom Funciones de Expresión

Se proporciona un EvaluationContext al SpelExpressionParser interno durante las transformaciones. Esta biblioteca proporciona un customized versión del StandardEvaluationContext donde se registran funciones de utilidad para su uso dentro de una configuración de transformación.

public class OrikaEvaluationContext extends StandardEvaluationContext {

    @SneakyThrows
    public OrikaEvaluationContext() {
        this.registerFunction("isBlank", StringUtils.class.getMethod("isBlank", CharSequence.class));
        this.registerFunction("isNotBlank", StringUtils.class.getMethod("isNotBlank", CharSequence.class));
        this.registerFunction("isNull", Objects.class.getMethod("isNull", Object.class));
        this.registerFunction("isNotNull", Objects.class.getMethod("nonNull", Object.class));
    }
}

El ejemplo a continuación muestra cómo puede aplicar el nuevo custom funciones a sus condiciones.

source-class: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestSourceObject
destination-class: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestTargetObject
implicit-mapping: false
bidirectional-mapping: true
 mappings= [
 {
 fuente: nombre
 destino: myname
 conditions: {
 a-a-b: [
 "#isNotNull(a.name)"
 ]
 }
 },
 {
 dirección
 destino: myaddress
 conditions: {
 a-a-b: [
 "#isNotBlank(a.myaddress)"
 ]
 }
 }
]

Registrar nuevo Custom Funciones de Expresión

Si requiere nuevas funciones utilitarias, el método más sencillo es inyectar el StandardEvaluationContext e invocar el método registerFunction. Un ejemplo de esto se demuestra a continuación:

@SpringBootTest(classes = {
        //core transformation config
        OrikaTransformationServiceFactoryConfig.class,

        //Config object required
        MyMappingTransformationServiceITest. DefaultAppConfig.class,

        //include specific mapping spring config
        MyMappingTransformationServiceConfig.class
}
)
class MyMappingTransformationServiceITest {

    @Autowired
    private TransformationService myTransformationService;

    @SneakyThrows
    @Autowired
    void initFunctions(StandardEvaluationContext evaluationContext) {

        //register a new function to produce a random UUID
        evaluationContext.registerFunction("randomUUID", UUID.class.getMethod("randomUUID"));
    }
}

El ejemplo a continuación muestra cómo puede aplicar la nueva función randomUUID() en un enriquecimiento.

enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject

enriquecimientos: [ { destino: "myStringProperty", tipo-de-enriquecimiento: función-de-enriquecimiento, función: "#randomUUID()" } ]

Aplicando información contextual adicional

El ThreadLocalContext puede ser utilizado para establecer algún contexto antes de realizar una transformación. Este contexto se pone a disposición del mapping o configuración de enriquecimiento y, por lo tanto, puede ser muy útil para tener condiciones basadas en datos separados del objeto de transformación o enriquecimiento en sí mismo.

El uso típico podría verse así:

  try {
        ThreadLocalContext.setContext(new MyContext());
        TargetObject mapped = transformationService.map(sourceObject, TargetObject.class);
  }
  finally {
        ThreadLocalContext.clearContext();
  }

El contexto está disponible utilizando #context(), como se muestra en el mapping ejemplo de configuración a continuación:

 mappings = [
     {
        source: name
        destination: myname
        conditions: {
            a-to-b: [
             "#context().stringProperty == 'context-value'"
            ]
        }
     }

]