Friday, May 22, 2009

GWT, Spring and JPA - not really friendly to each other

For the past several months, I have been looking for ways to integrate GWT and Spring in a way that is not as painful as it appeared to be at first.

Certainly, the new /war deployment structure of GWT 1.6 is a massive stride forward, especially for those who, like me, think that Tomcat is still the easiest way to test and deploy a web application.

Obviously, by using GWT, one no longer needs Spring MVC (it could be integrated, but, honestly the pain and suffering seem hardly worth the result) but dependency injection, at least server-side, and all the other goodies that come with Spring are a massive win-win.

In particular, I am a strong believer in the value of JPA - not least, because allows one to use ORM outside of any particular J2EE container (as it happens, I've an application currently running on a server machine, as a stand-alone Java app, that uses JPA to persist data on a MySQL database).

Unfortunately, it turns out that it's not so simple to make GWT and Spring's flavour of JPA co-operate nicely, at least not if you are developing using Eclipse, and probably not even outside of Eclipse development environment.

To understand why, one needs to know that, to work outside of a J2EE container, Spring's JPA implementation needs to 'decorate' the entity classes with Aspects that will then require one to use a specific Java Agent (see also the javadoc for java.lang.instrument):



12.6.1.3.2. General LoadTimeWeaver
For environments where class instrumentation is required but are not supported by the existing LoadTimeWeaver implementations, a JDK agent can be the only solution. For such cases, Spring provides InstrumentationLoadTimeWeaver which requires a Spring-specific (but very general) VM agent (spring-agent.jar):


<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver">
</property>
</bean>

Note that the virtual machine has to be started with the Spring agent, by supplying the following JVM options:

-javaagent:/path/to/spring-agent.jar

[from Spring Reference manual, ver. 2.06]


Now, this work absolutely fine if you are using a Spring-powered Java application, and you want (or need) to use an EntityManagerFactory that is configured as follows:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
this has the advantage that one can configure multiple data sources (eg, one for testing and one for production) pointing at different schemas, or even different instances of MySQL running on different servers:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

<bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.test.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.test.username}" />
<property name="password" value="${jdbc.test.password}" />
</bean>

(the ${jdbc.*} expressions are parsed by a PropertyPlaceholderConfigurer and substituted from a properties files specified in its location property)
This will work by running your application with a command such as:
    java -javaagent:/var/lib/spring-framework-2.0.6/dist/weavers/spring-agent.jar com.ibw.application.MyClass
Now, to make Spring work in my GWT-based web app, I have (tentatively, I'm looking for a smarter / more general approach -- suggestions warmly welcome!) resorted to the oldest trick in the book: a Singleton, called at initialization by my servlets (either GWT RPC RemoteService implementations, or POS - Plain Old Servlets):

public class QuoteServiceImpl extends RemoteServiceServlet implements QuoteService {
private static final int MIN_SYM_LEN = 3;

private StockServiceConnector connector;
private StocksApplicationContext context = StocksApplicationContext.getInstance();

// rest of servlet code follows here....
}

where the StocksApplicationContext Singleton takes care of all the plumbing necessary to get Spring's DI framework started:
// Copyright Infinite Bandwidth ltd (c) 2009. All rights reserved.
// Created 28 Apr 2009, by marco
package com.ibw.stocks.server;

import java.util.logging.Logger;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* Singleton class that acts as the main application context for the server-side
* classes: it will initialise the Spring ApplicationContext, and generate the beans.
*
* This will also act as factory class (typically, just a thin wrapper around the Spring
* bean factories) for the main application server component(s): the business layer, DAOs, etc.
*
* To use, just invoke the {@link #getInstance() static getInstance} method, and
* invoke the desired methods on the returned instance. A further convenience method (
* {@link #getBean(String) getBean}) is provided, as a thin wrapper around Spring's
* {@link ApplicationContext#getBean(String) ApplicationContext's getBean} method.
*
* <h4>All rights reserved Infinite Bandwidth ltd (c) 2009</h4>
* @author Marco Massenzio
* @version 1.0
*/
public class StocksApplicationContext {
private static final String BEAN_DEFS_XML = "com/ibw/stocks/beans.xml";

private static Logger log = Logger.getLogger("com.ibw.stocks");
private ApplicationContext ctx;
private static StocksApplicationContext instance;

private StocksApplicationContext() {
log.info("Creating a StocksApplicationContext singleton. Bean definitions from " + BEAN_DEFS_XML);
ctx = new ClassPathXmlApplicationContext(BEAN_DEFS_XML);
}

public static StocksApplicationContext getInstance() {
if (instance == null) {
instance = new StocksApplicationContext();
log.info("A StocksApplicationContext factory has been created");
}
return instance;
}

public Object getBean(String beanName) {
return ctx.getBean(beanName);
}
}
Back to JPA, I thought that, having done all the above, I was pretty much done: unfortunately, when you launch your GWT application (either in Hosted Mode, or from within a browser) you get an exception, and it's clear that the javaagent option has not been picked up by either Jetty (currently, the embedded server in GWT's hosted mode).

The only solution I have found so far, is to use a much simpler EntityManagerFactory (*) setup (in /com/ibw/stocks/beans.xml):
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="StocksPU">
</bean>
that refers back to a Persistence Unit (in META-INFO/persistence.xml):
<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0" >
<persistence-unit name="StocksPU" type="RESOURCE_LOCAL">
<class>com.ibw.stocks.model.Currency</class>
<class>com.ibw.stocks.model.Index</class>
<class>com.ibw.stocks.model.Stock</class>
<class>com.ibw.stocks.model.Quote</class>
<properties>
<property name="toplink.jdbc.driver"
value="com.mysql.jdbc.Driver" />
<property name="toplink.jdbc.url"
value="jdbc:mysql://my.server.com:3306/myschema" />
<property name="toplink.jdbc.user" value="auser">
<property name="toplink.jdbc.password" value="asifimtellingyou">
</properties>
</persistence-unit>
</persistence>

This works, but leaves me unhappy, as I now need to configure my data persistence configuration in an XML file (persistence.xml) instead of using Spring's beans properties (and the ability to inject values programmatically): I plan to explore next the possibility of using multiple Persistence Units, and use those to configure multiple configurations (eg, development, stating and production).


(*)UPDATE: although in the original post, this may be overlooked: this is, in fact, what tripped Yaakov (see comments below) and caused the server to complain about
"No persistence unit with name 'xxxx' found."
It took us an entertaining googlechat session when I felt compelled to explain to him things such as where to put WEB-INF, and then he eventually figured out what the problem was by himself, tracking down an old post of mine to GWT Group.

Why was that "entertaining"? well, this is the very same Yaakov Chaikin of Core Servlets and JavaServer Pages ... I just had no idea at the time!
Blogged with the Flock Browser

10 comments:

  1. You can try maybe to do compile time weaving.

    ReplyDelete
  2. I had a similar problem. My application client is deployed through web start. Guess what doesn´t work in Web Start? Java Agent.

    So, as sasajovancic said, i had to do compile time weaving, there is no other way. You can do it by some different methods.

    Since you are using eclipse, i think it´s quite easier. I had to do using ant, on netbeans.

    You can find some examples for eclipse, maven, etc on google.

    if you want my ant script, send me an email.

    ReplyDelete
  3. I will definitely look into compile-time weaving, sounds like the way to go.

    However, howzito, if you feel like sharing your solution (ant script) that would be awesome: probably best as an 'update' to the blog entry (full credit to you) or, if you do have your own blog, by all means, feel free to post the URL here!

    Thanks for the suggestions!

    ReplyDelete
  4. Do you mind posting your entire (at least as far as the configuration of all the plumbing is concerned) spring configuration file?

    For example, you leave out how you configured transaction manager, etc...

    I have my persistence defined in a different Eclipse project (module) which my GWT project depends on... However, I keep getting "No persistence unit with name 'xxxx' found...

    This works just fine when I deploy the app to Glassfish.

    Thanks!

    ReplyDelete
  5. The persistence unit itself is defined in a persistence.xml file apart, that MUST reside inside a META-INF folder just under your top-level directory (essentially, in the same folder as WEB-INF).

    Here it's what it looks like:

    <?xml version="1.0" encoding="UTF-8"?>

    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
    version="1.0" >
    <persistence-unit name="StocksPU" transaction-type="RESOURCE_LOCAL">
    <class>com.ibw.stocks.model.Currency</class>
    <class>com.ibw.stocks.model.Index</class> <class>com.ibw.stocks.model.Stock</class>
    <class>com.ibw.stocks.model.Quote</class>

    <!-- Change the following properties to reflect Production DB values -->
    <properties>
    <property name="toplink.jdbc.driver"
    value="com.mysql.jdbc.Driver" />
    <property name="toplink.jdbc.url"
    value="jdbc:mysql://MyServer:3306/dbname" />
    <property name="toplink.jdbc.user" value="auser" />
    <property name="toplink.jdbc.password" value="asif" />
    </properties>
    </persistence-unit>
    </persistence>

    Not sure how you deploy that to GlassFish, but with Tomcat is as simple as placing the file directly inside the war/META-INF folder in your project's structure.

    As far as the Transaction Manager is concerned, here is what I have in beans.xml:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    <property name="dataSource" ref="yourDataSource" />
    </bean>

    Hope this helps!

    ReplyDelete
  6. so, if you defined your datasource in persistence.xml, what are you referencing in your transaction manager bean definition when you say:
    ... ref="datasource"?

    Did you say that you removed the definition from your spring config file and stuck into persistence.xml?

    Thanks,
    Yaakov.

    ReplyDelete
  7. No, the only thing defined in persistence.xml is what is shown above in <persistence> (sorry about the formatting, Blogger does not allow -easily- to format code in comments).

    The TransactionManager bean is still defined in the beans.xml Spring definition file (I have placed that in the classpath, as I'm loading it from there, but you can use whatever Spring loading mechanism you prefer).

    In my case, I am guessing the TM is not using the datasource property, as it's currently 'pointed to' the "testDataSource" (shown above in the blog post) which is not being used.

    (as it happens, I was adding a note to this effect earlier, then decided against that to avoid confusing the matter... it only goes to show :-)

    Have you checked out Spring's manual: from recollection, it shows several alternative ways to have the javaagent loaded up when deployed to other 'containers' (such as Tomcat) so maybe there's something there too.

    ReplyDelete
  8. Are you using Eclipse GWT 1.7?

    I basically followed everything you did and it is still complaining "No persistence unit with name '...' found.

    Do you have all of your persistence code in the same Eclipse module as the rest of your GWT app?

    ReplyDelete
  9. You mentioned earlier that "in Glassfish it all works" -- so where is it that this is not working?
    I'm assuming here that it may be in "hosted mode"[*] launched from within Eclipse?

    In that case, then, I must presume you have "the other project" set up as a WTP project, and it is "Run as... > Run on Server" and you have configured your own (eg) Tomcat instance?

    In this case, you need to run the GWT app with -noserver, and point the hosted mode browser to the port your server is running on (eg: http://localhost:8080/MyProject/myproject.html or whatever the URL is).

    In which case then, you must deploy persistence.xml on the server project, in META-INF, just alongside WEB-INF (where your classes/ and all the lib/ - JARs - reside).

    However, this means that you will have to configure GWT to output the compiled javascript in the correct folder for your server to find (and if you do make a change in the source, for it to be reflected in the served code, you will need to re-run a compilation pass).

    As it happens, I had a similar setup originally, but moved away from it as it was such a pain, and hosted mode didn't really deliver the advantages (quick incremental recompilation on source code changes and ability to do step debug).

    Hope this helps: if you have gmail/gchat, please contact me directly and I can try and help you sort this out (I do know how frustrationg it is when a program or other "cannot find" something that's just sitting there under its bloody nose :-)

    [*] in GWT 2.0 'hosted mode' is going away, replaced by OOPHM, which is actually going to be renamed 'development mode' (or 'dev mode' for simplicity).

    ReplyDelete
  10. First, let me thank Marco for his great post. As is clear from his update to the post, all my troubles stemmed from the fact that I missed the switch in class names for the EntityManagerFactory... So to make it painfully obviously for the rest:
    you need to switch the entityManagerFactory class from LocalContainerEntityManagerFactoryBean to LocalEntityManagerFactoryBean.

    Don't you just LOVE those little details sending you on a wild goose chase?!

    Anyway, my thanks (AGAIN!) goes to Marco for helping me figure it out... His old post, which I found googling this issue, made it painfully obvious what I was missing: "I decided to just address the issue at the root and do
    away with the LocalContainerEntityManagerFactoryBean.
    I'm using instead a (much more limited)
    org.springframework.orm.jpa.LocalEntityManagerFactoryBean.
    "

    Marco's original post concentrated on a solution to enable GWT, Spring, and JPA to play together with the same module (Eclipse project). In my project, this is not really an option. I have a multi-module (maven2 term) project and I most certainly need to pack my persistence module separately from the web module. If not for just pure clean separation then for the fact that I have other (unrelated to web) modules that use my persistence module.

    So, what is the problem then? Well, in short, for whatever reason, Eclipse GWT plugin which controls my Web project refuses to place my Persistence project correctly on the classpath. This is the origin of my "No persistence unit with name 'xxx' found." This error message occurs because META-INF/persistence.xml is NOT on the classpath, even though my Web project has a dependency on my Persistence project. Weird.

    At this point, let me remind the reader what my original objective was. Without all this entity factory bean class name switching, I could already configure my Web app such that when I build (I do this with maven2), it creates a nice xxx.war file which deploys correctly to the server (I am using Glassfish 2.1). What I was trying to accomplish, however, is what attracts most people GWT in the first place: the ability to change code in Eclipse and without having to rebuild/redeploy my entire Web project to the server, click Refresh in the Hosted Browser and see the change immediately. This is where all these tweaks to the Spring configuration become important.

    So, here is what I did to accomplish this:
    1) Manually (for now) copy toplink-essentials.jar into my Web project's war/WEB-INF/lib directory.
    2) In my Persistence module, change the META-INF/persistence.xml file to reflect the toplink.xxx properties Marco mentioned above in his post.
    3) Build your Persistence module.
    4) Manually (for now) copy the built by maven Persistence-Blah-xxx.jar into my Web project's war/WEB-INF/lib directory. (You can copy this JAR file either from the target directory of that module or from the local repository... or, if you are not using maven, just build it by hand, ant, or however you arrive at your-persistence-module.jar file and then copy it as I explained).
    5) Note that now, you can go to your Spring config file and remove (or make irrelevant) the dataSource configuration. In other words, you can take out the dataSource configuration alltogether. Its now being configured by your persistence.xml. AND your transaction manager configuration is quite happy to be without the dataSource as well:
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    Ta-da! You should now be able to run your Web module as a GWT Web application which will bring up the hosted browser and you can code to your heart's content! :-)

    ReplyDelete