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:
-
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:
-
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 poder utilizar orika-transformación-impl, se requieren los siguientes pasos:
-
Configure la última orika-dependencia-impl de transformación.
-
Autoconexión OrikaTransformationServiceFactory en una clase de configuración de Spring.
-
Cree archivos .conf apropiados para proporcionar enriquecimiento o mapping transformaciones de tipo.
-
Invocar el OrikaTransformationServiceFactory.transformationService método de fábrica (EnrichmentContext context, String.. resource).
Paso 1. Configure la última orika-dependencia de -transformación-
-
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:
-
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 dos 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'
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();
}
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'"
]
}
}
]