Uso en tiempo de ejecución
La biblioteca orika-transformation-impl proporciona una extensión a la Orika mapping biblioteca, destinada a simplificar el proceso de creación de transformaciones. Se 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 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 SpEL diferencias. |
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 versión de la biblioteca amplía el uso de la Spring Lenguaje de expresión para introducir condiciones transformaciones.
Más detalles sobre SpEL aquí.
Esta biblioteca introduce dos nuevas implementaciones de OrikaCustomisers utilizado internamente:
-
OrikaEnrichmentsCustomiser -Proporciona la capacidad de enriquecer una entidad a través de la configuración
-
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 utilizar orika-transformation-impl, se requieren los siguientes pasos:
-
Configure la última dependencia orika-transformation-impl.
-
Autoconexión OrikaTransformationServiceFactory en un Spring Clase Config.
-
Cree archivos .conf apropiados para proporcionar enriquecimiento o mapping transformaciones de tipo.
-
Invocar el OrikaTransformationServiceFactory.transformationService(EnrichmentContext contexto, String.. recurso) método de fábrica.
Paso 1. Configure la última dependencia orika-transformation-impl
-
pom.xml
<dependency>
<groupId>com.iconsolutions.ipf.core.mapper</groupId>
<artifactId>orika-transformation-impl</artifactId>
</dependency>
Paso 2. Autoconexión OrikaTransformationServiceFactory en un Spring Clase de configuración.
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 los 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.
Ejemplo de Mapeo
El ejemplo a continuación muestra cómo condicional mapping está configurado. Tenga en cuenta que la expresión está en SpEL formato.
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(EnrichmentContext contexto, String.. recurso) método de fábrica
El OrikaTransformationServiceFactory.transformationService(EnrichmentContext El método context, String.. resources) se utiliza para instanciar nuevos TransformationService instancias. Se 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
Ejemplos de Configuración de Mapeo
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;
}
Configuración de mapeo 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:
-
Mapeo de source.name a destination.myname cuando source.name == 'name-123'.
-
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 tres enriquecimientos separados basados en condiciones específicas. Estos enriquecimientos tienen los siguientes efectos:
-
Enriqueciendo target.myIntegerProperty a 500 cuando source.myIntegerProperty== 100'
-
Enriqueciendo target.myStringProperty a updatedText' cuando 'source.myStringProperty== 'matchingText'
-
Enriqueciendo target.myBooleanProperty a verdadero cuando la propiedad de configuración de la aplicación my.test.property tiene un valor de foo
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'"
]
},
{
destination: "myBooleanProperty",
enrichment-type: value,
value: "true",
enrichment-field-conditions:[
"#config().getString('my.test.property') == 'foo'"
]
}
]
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 igual a 100 Y source.myStringProperty igual a ''matchingText'.
enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject
enrichment-conditions:[ " myIntegerProperty== 100", " myStringProperty== 'matchingText "" ] enriquecimientos: [ { destination: " myStringProperty ", tipo-de-enriquecimiento: valor, value: " updatedText " } ]
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 el nuevo 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();
}
Registro de Personalizado Spring Convertidores
El Spring Lenguaje de Expresión (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 de la DynamicConversionService y registrando cualquier requerido 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());
}
}
Funciones de Expresión Personalizadas
Aplicando Funciones de Expresión Personalizadas
An EvaluationContext se proporciona a la interna SpelExpressionParser durante las transformaciones. Esta biblioteca proporciona un customized versión de la StandardEvaluationContext donde se registran las 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 Nuevas Funciones de Expresión Personalizadas
Si usted requiere nuevas funciones de utilidad, el método más sencillo es inyectar el StandardEvaluationContext y invoque el registerFunction método. 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 el nuevo randomUUID() función en un enriquecimiento.
enrichment-target: com.ipf.payments.common.execution.enrichment.orika.testmodel. TestEnrichmentObject
enriquecimientos: [ { destination: " 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'"
]
}
}
]