Since the start of Google’s App Engine for Java, I have tried to create a small but representative project and to deploy it on Google’s infrastructure. I have learned much, mainly by making mistakes. Interestingly enough, there are much that I achieved I didn’t hope I would. On the contrary, the Devil is in the details, as they say, and I found some problems I couldn’t imagine there: for some, there are other solutions, for some, there aren’t.

Eclipse

Eclipse is my first and foremost IDE. I tried NetBeans but, though I am NetBeans certified, it was too strange for me to work with. Anyway, Google provides an Eclipse plugin for GAE. When I tried to install it with the update site, I had a not so nice error popup:

Cannot complete the install because one or more required items could not be found.
Software being installed: com.google.gdt.eclipse.suite.e34.feature.feature.group 1.0.0.v200904062334
Missing requirement: com.google.gdt.eclipse.suite.e34.feature.feature.group 1.0.0.v200904062334 requires \
    'org.eclipse.platform.feature.group [3.4.0,3.5.0)' but it could not be found

Google’s plugin only works with version 3.4 (Ganymede) and I have version 3.5 (Galileo). So long for Eclipse, yet it seems Google plans to release a Galileo compatible version no later than Galileo own’s release (thanks Rajeev).

Maven

Like every not-so-young Java developer, I was proficient in Ant once. But I was bored to script the same tasks for every project, so I let Maven seduce me. It seems people at Google are still using only Ant (there’s no reference to Maven in GAE documentation), so you have to do the following if you want to use Maven:

  • Reference DataNucleus repository. Here’s the url : http://www.datanucleus.org/downloads/maven2,
  • Add dependencies to : ** org.datanucleus:datanucleus-core:1.1.0:jar ** org.datanucleus:datanucleus-jpa:1.1.0:jar, ** org.datanucleus:datanucleus-enhancer:1.1.0:jar,
  • datanucleus-core has a dependency on javax.jta:transaction-api:1.1:jar. Since its POM references a local repository (!), this artifact won’t be found. You have to install manually JTA 1.1 under this name,
  • Install manually and add dependencies to : ** org.datanucleus:datanucleus-appengine:1.0.0.final:jar, ** com.google.appengine:appengine-api:1.0:jar.

Spring

Spring works like a charm in GAE. GAE’s documentation says that reflection is near fully supported, so this shouldn’t come as a surprise. I didn’t use AOP though.

Commons-logging

Commons-logging is a dependency of many frameworks, Spring included. On the local server, everything runs fine. In the cloud, Google App Engine infrastructure replaces the commons-logging-1.1.1.jar with a JAR of its own that has a different package structure. In effect, that means you get funny NoClassDefFoundError on org.apache.commons.logging.LogFactory even though you included the JAR as a dependency. The solution is to still include the classes, but to give the JAR another name.

Since I use Maven, I removed the commons-logging dependency from the WAR with the exclusion tag for Spring and MyFaces artifact. Then, I added a dependency on commons-logging:commons-logging-api:1.1:jar with the runtime scope. This jar won’t be replaced.

JSP

JSP work fine in pages. Yet, XML format JSP doesn’t seem to get translated. You need to create “classic” JSP.

Still, you may run into some funny exception. It will look like this:

java.lang.ClassCastException: SomeException cannot be cast to javax.servlet.ServletException
    at org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:754)
    at org.apache.jsp.some_jsp._jspService(some_jsp.java:some_line)

It seems ther’s a bug in the PageContextImpl class where any RuntimeException will be cast to ServletException and thus end up creating another exception. It is very hindering since the ClassCastException’s stack trace will hide the first one’s stack trace. In order to debug, I advise you to use the following code snippet in the incriminated JSP:

<% try { %>
... your JSP body...
<% } catch (RuntimeException e) {
    Logger log = Logger.getLogger(getClass().getName());
    StringWriter sw = new StringWriter();
    e.printStackTrace(new PrintWriter(sw));
    log.severe(sw.toString());
    throw e;
} %>

EL

EL syntax in JSF tags (#) run fine. For EL syntax in JSTL taglibs - core for example ($), it didn’t work at first. Thanks to Jim, I know now you have to tell the App Engine not to ignore it with the code <%@ page isELIgnored="false" %> on each page you use EL with JSTL. IMHO, it could be a default behaviour, like in Tomcat, for example.

JSF

Apache MyFaces v1.1 works nicely. I had a problem with version 1.2: apparently, there was an API mismatch somewhere. So I’m stuck with the older version for now.

JPA

GAE’s persistence API of choice is JDO. Come on, guys, JDO in 2009? Still, GAE accepts JPA as an alternate persistence API, so you can use it, with caution. In both cases, GAE uses the DataNucleus framework (first time I heard of it - it seems related to JDO JPOX team). This framework, like any JDO framework enhances your entity classes with custom byte-code: this takes place in the build process after compilation. What’s very surprising with DataNucleus is that even if you choose JPA as your persistence API, you have to enhance your entity classes.

Since the enhancement process is dependent on some artifacts, check whether these artifacts are referenced as dependencies (see above for list). To bind the enhancement to the compile phase, use the following snippet in your POM (thanks Andy):

<build>
...
    <plugin>
        <groupId>org.datanucleus</groupId>
        <artifactId>maven-datanucleus-plugin</artifactId>
        <version>1.1.0</version>
        <configuration>
            <mappingIncludes>net/frankel/hr/cra/model/*.class</mappingIncludes>
            <verbose>true</verbose>
            <api>JPA</api>
        </configuration>
        <executions>
            <execution>
                <phase>compile</phase>
                <goals>
                    <goal>enhance</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
...
</build>

Using Datanucleus presents another limitation in that if you query using JPA (or JDO), you have to explicitly call the size() method on the resulting List. Otherwise, your JSP will throw a not-so-nice NucleusUserException labeled “Object Manager has been closed”. There’s an issue raised on the datanucleus-appengine integration project.

Local AppEngine server

Local server runs fine. I can’t help wondering why the translation / compilation process takes som much time (measured in minutes) though. Once it’s done, page rendering is very swift, since I think the server optimizes caching.

Deploying the application

The initial appcfg.cmd launches a Java class. I don’t know why but the application just ignores the JAVA_HOME environment variable and keeps checking for the java.home system property. If the latter is not found, it tries to use C:\Program Files\Java\jre6\bin and C:\Program Files\Java\bin looking for javac. Then it fails if not found… I had to modify the appcfg.cmd to put this command line:

@java -Djava.home="%JAVA_HOME%" -cp "%~dp0\..\lib\appengine-tools-api.jar" com.google.appengine.tools.admin.AppCfg %*

Anyway, it avails to nothing because I get this following SSL exception:

Unable to upload:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
  No trusted certificate found
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(Unknown Source)
    at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source)
    at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source)
    at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(Unknown Source)
    at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(Unknown Source)
    at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Unknown Source)
    at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(Unknown Source)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(Unknown Source)
    at com.google.appengine.tools.admin.ServerConnection.connect(ServerConnection.java:295)
    at com.google.appengine.tools.admin.ServerConnection.getAuthToken(ServerConnection.java:215)
    at com.google.appengine.tools.admin.ServerConnection.authenticate(ServerConnection.java:189)
    at com.google.appengine.tools.admin.ServerConnection.send(ServerConnection.java:116)
    at com.google.appengine.tools.admin.ServerConnection.post(ServerConnection.java:66)
    at com.google.appengine.tools.admin.AppVersionUpload.send(AppVersionUpload.java:345)
    at com.google.appengine.tools.admin.AppVersionUpload.beginTransaction(AppVersionUpload.java:159)
    at com.google.appengine.tools.admin.AppVersionUpload.doUpload(AppVersionUpload.java:68)
    at com.google.appengine.tools.admin.AppAdminImpl.update(AppAdminImpl.java:41)
    at com.google.appengine.tools.admin.AppCfg$UpdateAction.execute(AppCfg.java:469)
    at com.google.appengine.tools.admin.AppCfg.(AppCfg.java:114)
    at com.google.appengine.tools.admin.AppCfg.main(AppCfg.java:59)
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
    at sun.security.validator.SimpleValidator.buildTrustedChain(Unknown Source)
    at sun.security.validator.SimpleValidator.engineValidate(Unknown Source)
    at sun.security.validator.Validator.validate(Unknown Source)
    at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(Unknown Source)
    at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
    at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
    ... 24 more

The solution is to use the JRE bundled in your JDK. The command line thus becomes:

>@java -Djava.home="%JAVA_HOME%/jre" -cp "%~dp0\..\lib\appengine-tools-api.jar" com.google.appengine.tools.admin.AppCfg %*

Now everything should run perfectly from here. Have fun!

To go further: