I spent the last three weeks in a repetition course of the Swiss army - far away from any Java code. Naturally my fingers started to itch while reading the announcement of Google that their App Engine now supports Java! So I grabbed my MacBook to enjoy the sun, Java coding and Eastern holidays.
As I usually write web applications with JBoss Seam, I decided to give the framework a try in the Google cloud - preparing for a bumpy road as Seam founds on standards which are mostly listed as either not or not known to be working. The following article describes the (bad) tweaks I had to do to get something basic running - I guess if you start doing serious development, you might hit more walls.
First, install the Eclipse Plugin for App Engine and create a new project. I'll base my descriptions on this initial setup. Switch on sessions in
appengine-web.xml:<sessions-enabled>true</sessions-enabled>
Setting up JSF 1.2
Download the latest Mojarra 1.2 release [1.2_12] and put it in the
WEB-INF/lib directory. You can configure the Faces servlet in web.xml as usual<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.seam</url-pattern>
</servlet-mapping>
Starting Jetty with only this will fail due to some incompatibilities of Jetty and Mojarra. As with Seam we will need JBoss EL anyway, we can configure Mojarra to use the JBoss EL
ExpressionFactory. Add the JBoss EL JAR to WEB-INF/lib and the following XML to your web.xml:<context-param>
<param-name>com.sun.faces.expressionFactory</param-name>
<param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
</context-param>
Now it's already patch time. Jetty in the Google environment seems to have a bug in its Servlet API implementation, missing the
ServletContext.getContextPath() method (new in version 2.5). Also, Mojarra tries to be clever about initialization work and uses Threads in the ConfigManager - the App Engine SecurityManager will let the whole thing blow up. Something similar happens in the JBoss ReferenceCache class. All patched classes can be found here. Drop the Java code in your source folder, Jettys classloader will pick it up.This will at least make the whole thing start up. I also added facelets (JAR file, view handler in
faces-config.xml and view suffix in web.xml).Unfortunately, using RichFaces components is an absolute no go on App Engine. RichFaces is full of references to AWT or JAI classes, which Google blocks completely. If anybody wants to try ICEFaces - good luck!
Adding Seam
Now it's time to add all the Seam stuff. Quite a couple of JARs to put into the application. I basically used Seam 2.1.1.GA and libraries from JBoss 4.2.3.GA. The complete list of what should go into
WEB-INF/lib is shown in the screenshot below.
JTA is not even on the list of APIs Google gives a recommendation, so let's fall back on Java SE behavior. Configure
components.xml with:<transaction:entity-transaction entity-manager="#{entityManager}"/>Although we cannot use Hibernate as persistence provider, Seam has a couple of dependencies to it (e.g. when using Hibernate Validators). As soon as we have it in the classpath, Seam activates its
HibernatePersistenceProvider component. This will behave pretty bad, and for simplicity we just override this component:@Name("org.jboss.seam.persistence.persistenceProvider")
@Scope(ScopeType.STATELESS)
@BypassInterceptors
@Install(precedence = Install.APPLICATION,
classDependencies={"org.hibernate.Session", "javax.persistence.EntityManager"})
public class OverrideHibernatePersistenceProvider extends PersistenceProvider {
}Now also add a persistence provider as described in the Google docs and disable DataNucleus checking for multiple
PersistenceContextFactory instantiations with this property in appengine-web.xml.<property name="appengine.orm.disable.duplicate.emf.exception" value="true"/>
As before, also Seam needs a couple of patches. Main reasons for those are references to
javax.naming.NamingException (which is not white listed, credits to Toby for the hint) and session/conversation components not implementing Serializable correctly. The last point is probably something not hitting Seam or your application the last time.Identity
As a next step I tried to add some users to the application. Seam's Identity module builds around the
javax.security.auth.Subject and Principal classes. Even though those classes are white listed, the SecurityManager blocks any attempt to add a Principal to a Subject. Well, how useful is that... As a quick fallback I integrated the Google Accounts API:@Name("org.jboss.seam.security.identity")
@Scope(SESSION)
@Install(precedence = Install.APPLICATION)
@BypassInterceptors
@Startup
public class AppEngineIdentity extends Identity {
private static final long serialVersionUID = -9111123179634646677L;
public static final String ROLE_USER = "user";
public static final String ROLE_ADMIN = "admin";
private transient UserService userService;
@Create
@Override
public void create() {
userService = UserServiceFactory.getUserService();
}
@Override
public boolean isLoggedIn() {
return getUserService().isUserLoggedIn();
}
@Override
public Principal getPrincipal() {
if (isLoggedIn())
return new SimplePrincipal(getUserService().getCurrentUser().getNickname());
return null;
}
@Override
public void checkRole(String role) {
if (!isLoggedIn())
throw new NotLoggedInException();
if ((ROLE_ADMIN.equals(role) && !getUserService().isUserAdmin()) || !ROLE_USER.equals(role))
throw new AuthorizationException(String.format(
"Authorization check failed for role [%s]", role));
}
@Override
public boolean hasRole(String role) {
if (!isLoggedIn())
return false;
return ((ROLE_ADMIN.equals(role) && getUserService().isUserAdmin()) || ROLE_USER.equals(role));
}
@Override
public String getUsername() {
if (isLoggedIn())
return getUserService().getCurrentUser().getNickname();
return null;
}
public String createLoginURL(String destination) {
return getUserService().createLoginURL(destination);
}
public String createLogoutURL(String destination) {
return getUserService().createLogoutURL(destination);
}
public User getUser() {
if (isLoggedIn())
return getUserService().getCurrentUser();
return null;
}
private UserService getUserService() {
if (userService == null)
userService = UserServiceFactory.getUserService();
return userService;
}
}Both
create... methods can be used in the UI for generating login/logout URLs. Destination defines the URL the user gets redirected after successful login/logout. Also make sure that the identity configuration is removed from components.xmlWrap up
Running this setup should give you a base for a very simple Seam app like in the screenshot below.

This first step has not been doing any persistence work, and my first tries with DataNucleus were not as straight forward as I had expected from a JPA implementation. Hope Google will catch up here with something more mature. Also, even the simple setup required a couple of nasty tweaks on the frameworks. Another big hurdle here are the runtime differences from production to local environment. For some serious work on App Engine, it's so far more recommendable to look into GWT.
Anyway, if you found this useful - looking forward to hear from your next steps in the cloud.
