Serializando e Deserializando Classes de Entidade do Hibernate/JPA para XML com XStream

Em um projeto que trabalhei tivemos a necessidade de representar em XML a nossa principal entidade de banco de dados, com [quase] todas as suas relações diretas e indiretas (uma grande árvore). A opção que se mostrou mais fácil foi o uso da biblioteca XStream. No entanto, esbarramos em alguns problemas uma vez que o Hibernate trabalha com Proxies e implementações próprias das Collections do Java, afim de promover a abordagem Lazy para a obtenção de entidades relacionadas. Nesse post demonstro como resolvemos esse problema.

Se estiver usando maven, adicione ao pom.xml da sua aplicação a dependência xstream-hibernate:

<dependency>
	<groupId>com.thoughtworks.xstream</groupId>
	<artifactId>xstream-hibernate</artifactId>
	<version>1.4.7</version>
</dependency>	

É muito importante que você crie e configure uma única instância de XStream que deverá ser usada tanto na serializaçaõ quanto na deserialização, para que tudo funcione. Configure-a da seguinte forma:

	
import org.hibernate.collection.internal.PersistentBag;
import org.hibernate.collection.internal.PersistentList;
import org.hibernate.collection.internal.PersistentMap;
import org.hibernate.collection.internal.PersistentSet;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentCollectionConverter;
import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentMapConverter;
import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentSortedMapConverter;
import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentSortedSetConverter;
import com.thoughtworks.xstream.hibernate.converter.HibernateProxyConverter;
import com.thoughtworks.xstream.hibernate.mapper.HibernateMapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;

public class MeuXStream {

    public static XStream xs;

    static {

        xs = new XStream() {
	    @Override
	    protected MapperWrapper wrapMapper(final MapperWrapper next) {
	        return new HibernateMapper(next);
	    }
        };

        xs.registerConverter(new HibernateProxyConverter());
        xs.registerConverter(new HibernatePersistentCollectionConverter(xs.getMapper()));
        xs.registerConverter(new HibernatePersistentMapConverter(xs.getMapper()));
        xs.registerConverter(new HibernatePersistentSortedMapConverter(xs.getMapper()));
        xs.registerConverter(new HibernatePersistentSortedSetConverter(xs.getMapper()));

        xs.alias("Bag", PersistentBag.class);
        xs.alias("Map", PersistentMap.class);
        xs.alias("List", PersistentList.class);
        xs.alias("Set", PersistentSet.class);

        xs.autodetectAnnotations(true);
    }
    ...
}

A maior parte das linhas acimas é para tratar a conversão de coleções para XML. Suas Classes de Entidades podem conter coleções, e estas vão estar com os tipos abstratos das Collections (Ex: Set, List, Map). Em tempo de execução, o Hibernate instanciará essas propriedades com suas próprias implementações dessas Collections (ex: org.hibernate.collection.PersistentList para o caso de List). No entanto você não vai querer que no seu XML final conste detalhes dessas coleções específicas do hibernate. Para isso, defina o HibernateMapper como o MapperWrapper do seu XStream. Além disso, registre os conversores do Hibernate fornecidos pela biblioteca xstream-hibernate. Por fim, é interessante definir alias para as 4 coleções do hibernate, pois, mesmo depois de convertidas, o XML final utiliza no atributo class o nome da classe do Hibernate, o que é irrelevante, mas fica poluído e verboso.

Caso você tenha mapeado alguma herança, certifique-se também de colocar a anotação @Proxy com lazy=false na super-classe. Isso evita que o Hibernate utilize Proxies nas instâncias dessa classe, o que leva ao XStream a serializar essas Proxies como se fossem instancias apenas da super-classe, gerando conflitos quando ele esbarra com uma propriedade da sub-classe e causando erros de de conversão (ConversionException). Lembrando que mesmo que você coloque essa anotação, isso não impedirá que carregamento das propriedades do tipo anotado seja Lazy, pois ainda há a configuração de cada propriedade nas próprias entidades (FetchType), apenas evitará a confusão com Proxies da super-classe.

	
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Proxy(lazy = false)
public class MinhaSuperClasse implements Serializable {
    ...
}

Provavelmente você não desejará representar toda sua cadeia de classes no XML, podando alguns pontos. Para isso, anote as propriedades que não desejar serializar com a anotação @XStreamOmitField. Para que essas anotações sejam reconhecidas, foi preciso aquele ultimo comando do bloco estático de inicialização do XStream.

Por fim, para serializar, o mais importante é carregar a sua entidade logo antes de transformar em XML, pois dessa forma, com a sessão ainda aberta, o próprio XStream irá percorrer todos os getters das propriedades, forçando o carregamento de tudo que foi configurado com a abordagem Lazy.

	
...
MinhaEntidade entidade = minhaEntidadeDAO.carregarPeloId(id);
String xml = xs.toXML(entidade)
...

Para deserializar, obtenha de volta o mesmo XStream usado na serialização e faça:

	
...
String xml = lerXmlDeAlgumLugar();
MinhaEntidade entidade = (MinhaEntidade) xs.fromXML(xml);
...

Atente para a limitação de que o XML gerado em um momento só poderá ser deserializado em um outro momento se a mesma estrutura de classes e propriedades se mantiver. Qualquer mudança de nome de classe ou de propriedade, ou mesmo alteração de pacote, resultará em um erro de conversão. Para contornar esse problema pode-se definir alias para todas as propriedades e classes com as anotações @XStreamAlias e @XStreamAliasType, ou fazendo chamadas à xs.alias(…) ou xs.aliasType(…). Pode-se ainda criar Converters customizados para definir como serializar/deserializar uma classe específica, ou no caso de um comportamento geral para todas a classes, o ideal é escrever um Mapper customizado. Detalhes sobre esse processo de customização você encontra nessa ajuda do XStream.

Forçando o uso de Hibernate 3.x com JPA no Jboss AS 7 / EAP 6

O Jboss AS 7 já provê a biblioteca do Hibernate 4 para os projetos, não sendo necessário incluí-la na lib de seu WAR. No entanto, pode acontecer de você querer usar uma versão mais antiga do Hibernate, como por exemplo a versão 3. Além de incluí-la como lib, você precisa avisar para o Jboss para deixar de prover o Hibernate dele, no caso a versão 4. Para tanto, alguns passos sesão necessários.

Você precisa adicionar ao WAR final o arquivo META-INF/jboss-deploy-structure.xml com o segunte conteúdo:

<jboss-deployment-structure>
	<deployment>
		<exclusions>
			<module name="org.hibernate" />
		</exclusions>		
	</deployment>
</jboss-deployment-structure>

Você também vai precisar adicionar uma propriedade a mais na sua Persistence Unit declarada no persistence.xml:

...
	<property name="jboss.as.jpa.providerModule" value="hibernate3-bundled" />
...

E por fim, certifique-se de fornecer o JAR do hibernate ao compilar o pacote final. Para quem usa Maven, basta adicionar o seguinte:

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>3.6.10-Final</version>
</dependency>

Não se esqueça de verificar a árvore de dependências do Maven para saber se alguma dependência indireta está requerendo alguma biblioteca específica do Hiberante também na versão 4. Se houver, o downgrade pode não ser possível ou pelo menos será não-trivial. Boa sorte!