JSF in the cloud : Finding the PaaS - Part 1 : Google App Engine
Short intro
Google App Engine is google PaaS offer. It is a fully managed PaaS. It promises automatic load balancing and handling. Given the identity of the provider, I tend to think it works.
To use Google App Engine, you must have a Google Account. Then, you can access some free offers. Currently, this incudes USD 300 in credit to spend on all Cloud Platform products over 60 days.
The simpliest way to go IMO is to follow the official "Getting started".
The only official technical requirements are Maven 3.1 and Java 7 (which are both pretty much standard).
Creating a project
Your new project will be created in two steps :
- create a project in the appengine console ;
- use a maven archetype to create your project skeleton.
For maven users, this is just straightforward.
I created and lptestgcloud2 app in my free account then generated the app skeleton with :
mvn archetype:generate -Dappengine-version=1.9.15 -Dapplication-id=lptestgcloud2 -Dfilter=com.google.appengine.archetypes:
and chosing the first proposal :
com.google.appengine.archetypes:appengine-skeleton-archetype
Full log :
Choose archetype: 1: remote -> com.google.appengine.archetypes:appengine-skeleton-archetype (A skeleton application with Google App Engine) 2: remote -> com.google.appengine.archetypes:endpoints-skeleton-archetype (A skeleton project using Cloud Endpoints with Google App Engine Java) 3: remote -> com.google.appengine.archetypes:guestbook-archetype (A guestbook application with Google App Engine) 4: remote -> com.google.appengine.archetypes:hello-endpoints-archetype (A simple starter application using Cloud Endpoints with Google App Engine Java) 5: remote -> com.google.appengine.archetypes:skeleton-archetype (-) Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1 Choose com.google.appengine.archetypes:appengine-skeleton-archetype version: 1: 1.8.7 2: 2.0.0-1.9.10 Choose a number: 2: Define value for property 'groupId': : fr.penet Define value for property 'artifactId': : testgcloud2 Define value for property 'version': 1.0-SNAPSHOT: : Define value for property 'package': fr.penet: : [INFO] Using property: appengine-version = 1.9.15 [INFO] Using property: application-id = lptestgcloud2 Confirm properties configuration: groupId: fr.penet artifactId: testgcloud2 version: 1.0-SNAPSHOT package: fr.penet appengine-version: 1.9.15 application-id: lptestgcloud2
The following source tree is generated :
── eclipse-launch-profiles │ ├── DevAppServer.launch │ └── UpdateApplication.launch ├── nbactions.xml ├── pom.xml ├── README.md └── src ├── main │ ├── java │ └── webapp │ └── WEB-INF │ ├── appengine-web.xml │ ├── logging.properties │ └── web.xml └── test
eclipse-launch-profiles files are used by eclipse to provide shortcuts to debug and publication of the app. As I prefer netbeans to eclipse, the nbactions.xml file is more interesting to me. It contains custom actions that will be proposed for the project under this IDE.
netbeans is not officially supported, but, in addition to the nbactions.xml file, one can use the gaelyk plugin. Its latest version, for Netbeans 7.4, is compatible with Netbeans 8. With this plugin, you can test, debug, profile and upload your webapp.
The original pom.xml is quite short :
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <groupId>fr.penet</groupId> <artifactId>testgcloud2</artifactId> <properties> <appengine.app.version>1</appengine.app.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <prerequisites> <maven>3.1.0</maven> </prerequisites> <dependencies> <!-- Compile/runtime dependencies --> <dependency> <groupId>com.google.appengine</groupId> <artifactId>appengine-api-1.0-sdk</artifactId> <version>1.9.15</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Test Dependencies --> <dependency> <groupId>com.google.appengine</groupId> <artifactId>appengine-testing</artifactId> <version>1.9.15</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.appengine</groupId> <artifactId>appengine-api-stubs</artifactId> <version>1.9.15</version> <scope>test</scope> </dependency> </dependencies> <build> <!-- for hot reload of the web application--> <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>versions-maven-plugin</artifactId> <version>2.1</version> <executions> <execution> <phase>compile</phase> <goals> <goal>display-dependency-updates</goal> <goal>display-plugin-updates</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <version>3.1</version> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <archiveClasses>true</archiveClasses> <webResources> <!-- in order to interpolate version from pom into appengine-web.xml --> <resource> <directory>${basedir}/src/main/webapp/WEB-INF</directory> <filtering>true</filtering> <targetPath>WEB-INF</targetPath> </resource> </webResources> </configuration> </plugin> <plugin> <groupId>com.google.appengine</groupId> <artifactId>appengine-maven-plugin</artifactId> <version>1.9.15</version> <configuration> <enableJarClasses>false</enableJarClasses> <!-- Comment in the below snippet to bind to all IPs instead of just localhost --> <!-- address>0.0.0.0</address> <port>8080</port --> <!-- Comment in the below snippet to enable local debugging with a remove debugger like those included with Eclipse or IntelliJ --> <!-- jvmFlags> <jvmFlag>-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n</jvmFlag> </jvmFlags --> </configuration> </plugin> </plugins> </build> </project>
Dependencies on com.google.appengine:appengine-api-1.0-sdk:1.9.15, javax.servlet:servlet-api:2.5 and jstl:jstl:1.2 , a few plugins configuration and that's all.
Adding the stack components
First thing is to add dependencies to MyFaces 2.2.6, Primefaces 5.1, Deltaspike 1.1 and OpenWebBeans 1.2.7-SNAPSHOT.
<!-- JSF --> <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-api</artifactId> <version>2.2.6</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-impl</artifactId> <version>2.2.6</version> <scope>runtime</scope> </dependency> <!-- primefaces --> <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>5.1</version> <type>jar</type> </dependency> <!-- Specifications --> <!-- JSR-330 --> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-atinject_1.0_spec</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <!-- JSR-299 --> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jcdi_1.0_spec</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-interceptor_1.1_spec</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-validation_1.0_spec</artifactId> <version>1.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-servlet_3.0_spec</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-el_2.2_spec</artifactId> <version>1.0.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jsp_2.1_spec</artifactId> <version>1.0.1</version> </dependency> <!-- OpenWebBeans --> <dependency> <groupId>org.apache.openwebbeans</groupId> <artifactId>openwebbeans-impl</artifactId> <version>1.2.7-SNAPSHOT</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.openwebbeans</groupId> <artifactId>openwebbeans-jsf</artifactId> <version>1.2.7-SNAPSHOT</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.openwebbeans</groupId> <artifactId>openwebbeans-web</artifactId> <version>1.2.7-SNAPSHOT</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.openwebbeans</groupId> <artifactId>openwebbeans-spi</artifactId> <version>1.2.7-SNAPSHOT</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.openwebbeans</groupId> <artifactId>openwebbeans-el22</artifactId> <version>1.2.7-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.xbean</groupId> <artifactId>xbean-asm4-shaded</artifactId> <version>3.15</version> </dependency> <dependency> <groupId>org.apache.deltaspike.core</groupId> <artifactId>deltaspike-core-api</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.apache.deltaspike.core</groupId> <artifactId>deltaspike-core-impl</artifactId> <version>1.1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-jsf-module-api</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-jsf-module-impl</artifactId> <version>1.1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-security-module-api</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-security-module-impl</artifactId> <version>1.1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-bean-validation-module-api</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.apache.deltaspike.modules</groupId> <artifactId>deltaspike-bean-validation-module-impl</artifactId> <version>1.1.0</version> <scope>runtime</scope> </dependency>
I use Primefaces 5.1 there only because it is the latest community version available. It is however crucial to use the version I indicate or later for the other packages :
- MyFaces 2.2.6 corrects MYFACES-3923 . As Google App Engine is "only" servlet spec 2.5, it is required. GAE being servlet 2.5 rather than 3.0 is not big deal.I can see no other feature than jsf file upload that will not work, and other solutions are available.
- OpenWebBeans 1.2.6 does not work because it uses sun.misc.Unsafe. Google App Engine uses a somewhat strict whitelist mechanism, which prevents access to this class. In 1.2.7-SNAPSHOT, Mark Struberg commited a patch that falls back to java.lang.Class#newInstance in this case. I am not sure that this solution is the best one. I proposed a patch to 1.5.0-SNAPSHOT that leverages google reflection API.
We also have to modify the appengine-web.xml file as explained in this useful blog on JSF Corner. So, our appengine-web.xml file is now :
<?xml version="1.0" encoding="utf-8"?> <appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> <application>lptestgcloud2</application> <version>${appengine.app.version}</version> <threadsafe>true</threadsafe> <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/> </system-properties> <sessions-enabled>true</sessions-enabled> <threadsafe>true</threadsafe> <static-files> <exclude path="/**.xhtml" /> </static-files> </appengine-web-app>
As explained in JSF Corner, we have to provide our own EL 2.2 implementation. After toying a bit, I found not better solution than JSF Corner' one, so let's also add a dependency on org.jboss.el:jboss-el:1.0_02.CR6
<dependency> <groupId>org.jboss.el</groupId> <artifactId>jboss-el</artifactId> <version>1.0_02.CR6</version> </dependency>
and the JBoss repository :
<repositories> <repository> <id>JBoss</id> <url>https://repository.jboss.org/nexus/content/repositories/releases/</url> </repository> </repositories>
I also use lombok and commons-lang3 in almost all my projects :
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.14.4</version> <scope>provided</scope> </dependency>
In web.xml, we have to add a context-param to use this EL implementation :
<context-param> <param-name>org.apache.myfaces.EXPRESSION_FACTORY</param-name> <param-value>org.jboss.el.ExpressionFactoryImpl</param-value> </context-param>
A minimal web.xml file looks like :
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Test application</display-name> <welcome-file-list> <welcome-file>accueil.xhtml</welcome-file> </welcome-file-list> <context-param> <param-name>org.apache.myfaces.EXPRESSION_FACTORY</param-name> <param-value>org.jboss.el.ExpressionFactoryImpl</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.EL_RESOLVER_COMPARATOR</param-name> <param-value>org.apache.myfaces.el.unified.OpenWebBeansELResolverComparator</param-value> </context-param> <listener> <listener-class> org.apache.webbeans.servlet.WebBeansConfigurationListener </listener-class> </listener> <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>*.xhtml</url-pattern> </servlet-mapping> </web-app>
Finally, let's add empty beans.xml and and faces-config.xml files in WEB-INF :
beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd" />
faces-config.xml
<?xml version="1.0" encoding="UTF-8"?> <faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2" />
If we start it right now with :
mvn clean install appengine:devserver
it almost works. Almost because there are still a few JNDI related errors during DeltaSpike initialization. GAE does not support JNDI. So, until we can cleanly disable it, we have to shadow the BeanManagerProvider and JNDIUtils classes.
In our BeanManagerProvider version, we shall comment the line
result = resolveBeanManagerViaJndi();
and in JNDIUtils we shall comment the static initialization block, where an attempt to access InitialContext is performer, throwing an exception :
static { try { initialContext = new InitialContext(); } catch (Exception e) { throw new ExceptionInInitializerError(e); } }
...and then, it finally works as expected.
Source
The source of this (very) simple example is downloadable there.
Conclusion
A few hacks are required to run this PrimeFaces/MyFaces/DeltaSpike/OpenWebbeans stack on Google App Engine. Some workarounds, like using java.lang.Class#newInstance instead of sun.misc.Unsafe can have a performance hit or even raise issues (what happens when you use a proxy for a singleton ???). However, it is still quite acceptable and usable for who wants to leverage all the features of GAE without giving up the power of JSF 2.2 and CDI extensions such as DeltaSpike.
As a tomcat user, I have to learn again how to perform access control and such things, but Google seems to offer rich, yet specific API.
It seems that you can even have Google App Engine on your own private cloud, thanks to a collaboration between Google and Red Hat that led to the project CapeDwarf