1. Data Mapping com Groovy – Part 2
Apresentando GroovyMap
Existem 3 cenários de transformação de dados que comumente encontramos:
xml para xml - Lê um xml de entrada e mapeia para uma estrutura XML diferente.
xml para mapear - Lê um xml de entrada e mapeia para um java.util.Map. Mapas e listas são
estruturas de dados muito comuns em aplicações de mula, sendo usados para ambos os
formatos de dados JSON e quando utilizando o transporte de banco de dados.
mapa para xml - Lê uma resposta adaptador na forma de um java.util.Map, itera sobre a
enumeração de chaves e mapas que a um xml de resposta.
Notamos essas transformações muitas vezes envolvem um monte de código de canalização:
Aceitar uma matriz de entradas de mapeamento e uma série de objetos "ajudante" (por
exemplo, tabelas de pesquisa, conexões DB)
Inicializar um objeto construtor Groovy para ajudar a construir a saída de mapeamento.
Realizar o mapeamento de dados usando o objeto de resultado Builder.
'Serialise' o resultado de uma determinada maneira (string, árvore DOM, POJOs etc)
Para produzir rapidamente out código de mapeamento, faz sentido para mover este código
clichê em um script comum que os desenvolvedores iria importar. Este script fornece uma
base envolvendo os "construtores" que são utilizados em todos os nossos código de
mapeamento e a abordagem de serialização.
Aqui está o que um peso leve pode parecer:
package au.com.sixtree.esb.mapper
import au.com.sixtree.esb.mapper.GroovyMap;
import au.com.sixtree.esb.mapper.DOMBuilder;
import groovy.json.JsonBuilder
import groovy.xml.MarkupBuilder
import groovy.xml.NamespaceBuilder;
import groovy.xml.XmlUtil
/**
2. * Data Mapping utility that lets developers map between data structures and formats using
Groovy Builder syntax.
*/
class GroovyMap {
private Object[] inputs
private BuilderSupport builder
private Object[] helpers
private Closure mapping
private Closure outputSerialiser
private GroovyMap(Object[] inputs, BuilderSupport builder, Object[] helpers,
Closure mapping, Closure outputSerialiser) {
this.inputs = inputs
this.builder = builder
this.helpers = helpers
this.mapping = mapping
this.mapping.delegate = this
this.outputSerialiser = outputSerialiser
this.outputSerialiser.delegate = this
}
public Object map() {
return outputSerialiser.call(mapping.call())
}
public static GroovyMap toXmlDom(Object[] inputs, Object[] helpers, Closure
mapping) {
def domBuilder = DOMBuilder.newInstance()
return new GroovyMap(inputs,
NamespaceBuilder.newInstance(domBuilder), helpers, mapping, { it.getOwnerDocument()
})
}
public static GroovyMap toXmlDom(Object input, Closure mapping) {
return toXmlDom([input] as Object[], null, mapping)
}
}
Você pode ver o construtor completo para o GroovyMap é bastante envolvido. Para tornar a
vida mais fácil para desenvolvedores, nós fornecemos métodos de fábrica estáticos para criar
GroovyMaps pré-construídos para cenários comuns (por exemplo, transformando a DOM
XML). O método final é um método de conveniência para o cenário de mapeamento mais
simples, mais comum: um objeto único de entrada para uma única saída sem ajudantes.
3. Agora vamos dar uma olhada em alguns exemplos dos cenários de mapeamento de dados
comuns:
XML para XML
//import the GroovyMap
import au.com.sixtree.esb.mapper.GroovyMap
//We have a customized version of DOMCategory helper class, this provides us with xpath
like features to access elements
//The default version involves a slightly different syntax.
import au.com.sixtree.java.esb.mapper.DOMCategory
//GroovyMap's toXMLDom() method takes input as the root/documentElement of the XML
tree, type @Element
//very similar to accessing the root element in Java - payload.getDocumentElement()
return GroovyMap.toXmlDom(payload.documentElement) {
//inputs is a list of input arguments
def _in = inputs[0]
//Groovy provides a neat feature called use(TYPE){} closure using which you can
retrieve xml elements using xpath style syntax
use(DOMCategory) {
/* map the root element with a namespace
The builder object is part of GroovyMap (of type NamespaceBuilder), which
has a method called declareNamespace(namespace_map)
*/
builder.declareNamespace('en':'http://sixtree.com.au/system/inboundadapter/v1/xsd' ,
'xsi':'http://www.w3.org/2001/XMLSchema-instance')
//Using the same builder object, construct the root element
<en:getbookInventoryResponse>
builder.'en:getbookInventoryResponse' {
/*
* The below construct is trying to represent a <book> with its
Inventory Status across various locations
* One book can be stocked at various locations
* */
book{
//If condition to check if the input xml contains an element
called <identifier>
if(_in.book[0].identifier)
//This creates an identifier tag
<identifier>12345</identifier>
//You can also use the element(value) style to achieve a
similar result
//eg. identifier(_in.book[0].identifier)
4. identifier _in.book[0].identifier
if(_in.book[0].name)
name _in.book[0].name
/*Since one book can have multiple inventory locations and
details, iterate over the inbound xml's inventoryDetails element list
Note the "inventoryDetails_tmp" identifier, its important
you dont use same identifiers for input and output variables
Here inventoryDetails is present in both input and output
xml, so while mapping to an output xml, ensure you are using a
different id for the variable (eg. inventoryDetails_tmp is
used to iterate over the inbound inventoryDetails)
You wont notice this ambiguity until runtime.
*/
for(inventoryDetails_tmp in _in.book[0].inventoryDetails)
inventoryDetails{
status inventoryDetails_tmp.status
location{
city inventoryDetails_tmp.location.city
state inventoryDetails_tmp.location.state
}
inventoryCount
inventoryDetails_tmp.inventoryCount
}
}
}
}
}.map()
//Calling the map, serializes (or marshals) the string to an xml
Listas de Java / Mapas para XML
Este é um requisito comum quando se trabalha com o transporte de mula JDBC (por
exemplo).
//import GroovyMap to get a handle to the transformation method - toXmlDom()
import au.com.sixtree.esb.mapper.GroovyMap
//DOMCategory to get us xpath style operations eg.getNode, getAttributes while traversing
through the xml
5. import au.com.sixtree.java.esb.mapper.DOMCategory
import groovy.xml.XmlUtil;
//import static codeset cross-referencing helper class, see below for lookups
import au.com.sixtree.java.esb.mapper.ValueCrossReferenceHelper;
return GroovyMap.toXmlDom(payload) {
/*inputs is the args array, where the 0th element is the payload (passed in the line
above)*/
def _in = inputs[0]
//use(Type){} - Construct to access xml elements
use(DOMCategory) {
/* add namespace to the output xml
* use builder object (@BuilderSupport) to create xml elements
* builder object (type NamespaceBuilder) has the
declareNamespace(Comma_Seperated_Map), use that as a setter for namespaces
*/
builder.declareNamespace('xsi':'http://www.w3.org/2001/XMLSchema-
instance' , 'ca':'http://sixtree.com.au/system/inboundadapter/v1/xsd')
//create root element and create its closure
builder.'ca:getBookInventoryResponse' {
// nested mapping for book with identifier
book {
name(_in[0].NAME)
identifier(_in[0].BOOK_ID)
//repeating field. Iterate over the inbound Map's keys enumeration
for(inventoryDetailsResponse in _in) {
inventoryDetails {
//xref lookup using a custom Java method
status(au.com.sixtree.esb.common.crossreferenceclient.ValueCrossReferenceHelper.
lookup("Book Inventory Status", "Outbound_System_Name",
inventoryDetailsResponse.INVENTORY_STATUS, "Inbound_System_Name"))
location {
city(inventoryDetailsResponse.CITY)
state(inventoryDetailsResponse.STATE)
}
6. inventoryCount(inventoryDetailsResponse.INVENTORY_COUNT)
}
}
}
}
}
}.map()//Serialize the entire closure to create an xml. Check the GroovyMap.map() method
for more details
XML para Java Lists / Maps
// no need to use builder here, just raw Groovy map syntax
//get DOMCategory to get access to GPATH style operations like getNode getNodeAt etc.
import au.com.sixtree.java.esb.mapper.DOMCategory;
//declare a java.util.Map to hold results
def result = [:]
//get input root element
def _in = payload.documentElement
use(DOMCategory) {
/*using DOMCategory get access to various xml elements, this is like calling
*_in.bookId.text() is equivalent to
responseDoc.getDocumentElement().getElementsByTagName("book").item(0).getFirstChild
().getTextContent());
*result.bookId is equivalent to result.put("bookId" , "123456") in Java
*/
result.bookId = _in.bookId.text()
//Using ?: Java operator to create an if else condition
/*
* Set results("inventoryStatus") value only if the inbound element has a non-empty
value
* One of the setter calls a custom XREF method
(ValueCrossReferenceHelper.lookup(String args...)) to retrieve the corresponding system
value
* */
(_in.inventoryStatus.text() != null &&
_in.inventoryStatus.text()!="")?(result.inventoryStatus =
au.com.sixtree.esb.common.crossreferenceclient.ValueCrossReferenceHelper.lookup("Book
7. Inventory Status", "Outbound_System_Name", _in.inventoryStatus.text() ,
"Inbound_System_Name")): (result.inventoryStatus = "");
}
Return result
Neste caso, não é necessária a classe auxiliar GroovyMap, porque as estruturas de dados /
Mapa Lista Groovy são bastante fáceis de criar, sem um construtor
Outras classes auxiliares
Você notará no código acima que usamos nossas próprias versões do padrão 'DOMBuilder' e
classes "DOMCategory '. Fizemos algumas pequenas correções para as classes GDK para
tornar o nosso código de mapeamento se comportar da maneira que queríamos:
A classe DOMBuilder remendado constrói um objeto org.w3.dom documento completo com
o elemento mais alto criado pelo construtor inserido como o elemento do documento.
A classe DOMCategory remendado tenta converter cada valor expressão para uma cadeia se
ele tem uma representação de cadeia sensível (isto é, não é uma lista de elementos). Este é
replicar o comportamento útil de XPath e significa que não tem que adicionar .text () ou
.toString () ao final de cada declaração de mapeamento.
Trabalhando com Groovy no Eclipse / Mule Estúdio
Desde Mule Studio é baseado na plataforma Eclipse, você pode instalar um plugin do
Eclipse para obter excelente editor e suporte a depuração para escrever mapeamentos Groovy
sem sair do seu IDE.
Para adicionar suporte Groovy para o seu IDE baseado em Eclipse, instalar o plugin
GRECLIPSE:
Ajuda → Instalar Novo Software → No "trabalhar com" campo add text
nome - site de atualização Groovy-Eclipse
url - http://dist.springsource.org/release/GRECLIPSE/e4.2/
Adicione o suporte ao editor Groovy e Groovy 1.8.6 tempos de execução.
Reinicie quando solicitado.
Uma vez que este está instalado, clique direito sobre o Projeto → Configurar → Converter
para Projeto Groovy. Isso deve adicionar as Groovy DSL bibliotecas de apoio ao classpath.
Ele também adiciona Bibliotecas Groovy para o classpath. No entanto, se você já
8. acrescentou que em sua pom, se livrar dele usando o "Configure Build Path ..." link no
eclipse.