JBoss 7 ed EclipseLink 2: una coppia quasi perfetta – Parte I (a.k.a. weaving statico con Ant)

Gli Application Server (AS) basati sulle specifiche Java EE ci permettono spostare una applicazione da uno all’altro senza problemi, e allo stesso tempo ci mettono in condizione di essere in grado di lavorare indistintamente con uno o l’altro AS… questo almeno in teoria! In pratica invece ci scontriamo con tutte le configurazioni specifiche di ogni AS, nonché con deployment descriptors proprietari che variano da server a server. Di solito quindi, una volta scelto l’AS su cui lavorare, è bene affidarsi a tutte le implementazioni delle specifiche che esso porta con se, per avere meno problemi possibile. Prendiamo per esempio JBoss 7, che come implementazione di JPA ha Hibernate. Finché usiamo questa implementazione della specifica JPA, funziona tutto correttamente: se però abbiamo bisogno di passare per esempio ad EclipseLink, perché magari preferiamo qualche sua caratteristica peculiare, dobbiamo armarci di pazienza e rimboccarci le maniche!

Perché EclipseLink?

Dalla versione 2.0 questa implementazione di JPA si è guadagnata il titolo di Reference Implementation (RI) della specifica: se non è un buon motivo per spingervi alla migrazione, basta dire che non soffrirete più di LazyInitializationException … può bastare? 😉

Migrare da Hibernate ad EcliseLink: sondiamo il terreno

In teoria, per cambiare implementazione JPA, basta modificare il persistence.xml in modo da definire il nuovo Persistence Provider e inserire le properties necessarie a configurarlo.
Quanto segue è vero esclusivamente per la coppia JBoss AS 7.0.2 ed EclipseLink 2.3. L’altra coppia quasi perfetta è JBoss AS 7.1 ed EclipseLink 2.4 (che affronteremo prossimamente): accoppiamenti misti non mi hanno dato buoni risultati. In questa prima parte vediamo cosa succede con la prima coppia.

Un persistence.xml valido per eseguire EclipseLink 2.3 su JBoss 7.0.2 dovrebbe essere:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
   <persistence-unit name="JBoss7EclipseLinkJPA">
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
      <jta-data-source>jdbc/MySQL</jta-data-source>
      <class>it.cosenonjaviste.jpa.entities.Order</class>
      <class>it.cosenonjaviste.jpa.entities.OrderInfo</class>
      <class>it.cosenonjaviste.jpa.entities.OrderDetail</class>
      <properties>
         <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
         <property name="eclipselink.ddl-generation" value="create-tables"/>
         <property name="eclipselink.ddl-generation.output-mode" value="database"/>
         <property name="eclipselink.target-server" value="JBoss"/>
         <property name="eclipselink.target-database" value="MySQL"/>
         <property name="eclipselink.logging.level" value="FINE"/>
      </properties>
   </persistence-unit>
</persistence>

All’inizio del persistence unit si definisce il nuovo provider di persistenza org.eclipse.persistence.jpa.PersistenceProvider che attiva EclipseLink. Vengono poi dichiarate le entità in gioco, legate come segue:

In seguito, si definiscono tutti i parametri di configurazione tra cui:

  • eclipselink.target-server: specifica l’AS su cui girerà l’applicazione.
  • eclipselink.target-database: specifica il dialetto con cui costruire le query.

In teoria dovrebbero essere sufficienti queste semplici configurazioni, ma all’avvio del server ci scontriamo subito in una NameNotFoundException perché EclipseLink sta cercando il Transaction Manager di JBoss sotto il nome JNDI java:/TransactionManager. Spulciando un po’ lo stacktrace e il sorgente di EclipseLink si scopre che è la classe JBossTransactionController che esegue il lookup JNDI con quel nome che non esiste: dalla console di JBoss 7.0.2 (e solo su questa versione!!) infatti scopriamo che il nome del Transaction Manager è java:jboss/TransactionManager. Come fare a specificare il nuovo nome? Ricordate la proprietà eclipselink.target-server? E’ il momento di sfruttarla appieno.

Personalizziamo EclipseLink

Dal momento che EclipseLink può funzionare sia con diversi DBMS che AS, è stata prevista una opportuna API (si direbbe SPI in questo caso?!) per definire gli interfacciamenti con i differenti ambienti. Come spiega il wiki di EclipseLink, è possibile per esempio modificare l’interfacciamento con l’AS implementando l’interfaccia org.eclipse.persistence.platform.ServerPlatform. Cominciamo per adesso col creare una versione del Transaction Controller ad hoc per JBoss 7.0.2 che esegua correttamente il lookup del Transaction Manager.

public class JBoss7TransactionController extends JTATransactionController {

    public static final String JNDI_TRANSACTION_MANAGER_NAME = "java:jboss/TransactionManager";

    /**
     * INTERNAL:
     * Obtain and return the JTA TransactionManager on this platform
     */
    protected TransactionManager acquireTransactionManager() throws Exception {
        return (TransactionManager)jndiLookup(JNDI_TRANSACTION_MANAGER_NAME);
    }
}

Una volta definito, creiamo anche un nuovo ServerPlatform per JBoss 7, che usa il nostro TransactionController come segue:

public class JBoss7Platform extends JBossPlatform {

   public JBoss7Platform(DatabaseSession newDatabaseSession) {
      super(newDatabaseSession);
   }

   @Override
   public Class<?> getExternalTransactionControllerClass() {
      if (externalTransactionControllerClass == null) {
         externalTransactionControllerClass = JBoss7TransactionController.class;
      }
      return externalTransactionControllerClass;
   }
}

Invece che reimplementare tutta l’interfaccia, modifichiamo solo il metodo che ci interessa in modo che venga usato il Transaction Controller che ci interessa. A questo punto modifichiamo il valore di eclipselink.target-server nel persistence.xml come segue:

<property name="eclipselink.target-server" value="it.cosenonjaviste.jpa.utils.JBoss7Platform"/>

avviamo JBoss e godiamoci la nostra applicazione con EclipseLink!!

Non è tutto oro quel che luccica

A prima vista sembra filare tutto liscio, ma se diamo un’occhiata alla console notiamo con dispiacere un messaggio di questo tipo:

The temporary classLoader for PersistenceLoadProcessor [JBoss7EclipseLinkJPA] is not available. Switching classLoader to [ModuleClassLoader for Module "deployment.JBoss7TestEAR.ear:main" from Service Module Loader]. Weaving has been disabled for this session. EclipseLink may be unable to get a spec mandated temporary class loader from the server, you may be able to use static weaving as an optional workaround.

In poche parole, EclipseLink si è avviato senza weaving… che significa? Provate a dichiarare un FetchType.LAZY su una qualsiasi relazione @OneToOne (per esempio tra Order e OrderInfo) e noterete tristemente che verrà ignorata 🙁 Che fare? Cercando un po’ in rete sembra che tutto derivi da un bug non ancora chiuso EJBTHREE-572 legato al ClassLoader. Tornando al sorgente di EclipseLink si scopre che il warning famoso viene creato da un metodo della classe JBossPlatform.. proprio quella che abbiamo esteso!! Dando un’occhiata a come sono state implementate le altre TargetPlatform, possiamo aggiungere un metodo al nostro JBoss7Platform:

@Override
public JPAClassLoaderHolder getNewTempClassLoader(PersistenceUnitInfo puInfo) {
   return new JPAClassLoaderHolder(puInfo.getNewTempClassLoader(), true);
}

purtroppo con il solo risultato di far sparire il warning dalla console (anche se passare il ClassLoader corretto non guasta mai…). Non ci resta che ripiegare al piano B: ovvero il weaving statico!

JBoss, EclipseLink e Weaving Statico: ménage à trois

Fortunatamente configurare il weaving statico è estremamente semplice e, data la scarsa mutabilità di un progetto JPA, non è che crei grossi disagi lanciare uno script di ANT se si modifica qualche entità. Creiamo quindi il classico build.xml nella root del progetto JPA:

<?xml version="1.0" encoding="UTF-8"?>
<project name="JBoss7EclipseLinkJPA">
   <target name="run-weaver">
      <taskdef name="weave" classname="org.eclipse.persistence.tools.weaving.jpa.StaticWeaveAntTask">
         <classpath>
            <pathelement path="lib/eclipselink.jar" />
            <pathelement path="lib/javax.persistence_2.0.3.jar" />
            <pathelement path="lib/persistence_2_0.xsd" />
         </classpath>
      </taskdef>

      <!-- process the weaving function, persistenceInfo references persistence.xml -->
      <weave source="build/classes" 
            target="build_woven/JBoss7EclipseLinkJPAWoven.jar" 
            loglevel="FINEST">
      </weave>
   </target>
</project>

Sarà necessario quindi avere gli archivi eclipselink.jar, javax.persistence_2.0.3.jar (presenti nel pacchetto di download di EclipseLink) e lo schema “persistence 2.0” nella cartella lib del progetto JPA per definire il nuovo task weave. Il target del nuovo task sarà un nuovo file jar che provvederemo ad aggiungere all’EAR al posto del progetto JPA. La prima volta lanciamo l’ANT da dentro Eclipse cliccando con il tasto destro sul file -> Run As -> Ant Build… e aggiungiamo i jar della lib anche al classpath:

Classpath di ANT

Al termine, un bel refresh del progetto e vedremo apparire il nuovo jar nella cartella build_woven. Andiamo quindi nel Deployment Assembly dell’EAR e sostituiamo il progetto JPA con il jar appena creato!

Conclusioni

Alla fine, per usare JBoss con EclipseLink pare che sia davvero necessario ricorrere al weaving statico, in modo che il lazy loading si comporti come dovrebbe in una piattaforma Java EE. Alla fine non è una grossa seccatura, basta solo ricordarsi di rilanciare l’ANT quando viene modificata qualche entità! Se qualcuno trovasse il modo di far funzionare il weaving dinamico fatemi sapere!!

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+