GAE (Google App Engine) is the cloud computing platform from Google that provides the infrastructure for deploying your web applications. In this recipe, we'll look at how Roo simplifies developing an application for GAE. We'll also see how a Roo-scaffolded GWT application is created and deployed on GAE. In the Deploying Spring Web MVC applications on GAE recipe, we'll see a Spring Web MVC application that can be deployed on GAE.
If you only want to run the GWT application locally using App Engine SDK for Java, then you don't need to sign up with Google App Engine and create an application identifier. If you want to deploy the application on GAE, follow the steps mentioned here to create an application identifier for your application.
myappid
, then after deploying the application on GAE you can access it by going to http://myappid.appspot.com.Now, we are all set to create our GWT application, which we want to deploy to GAE.
Create a sub-directory ch06-gae-gwt
inside the C:
oo-cookbook
directory and start the Roo shell from C:
oo-cookbookch06-gae-gwt
.
To create a Roo-scaffolded GWT application and deploy it on GAE, follow the steps given here:
flightapp-gae-gwt
project using project
command:... roo> project --topLevelPackage sample.roo.flightapp --java 6 --projectName flightapp-gae-gwt
persistence
setup
command to setup DataNucleus
as persistence provider and set GOOLE_APP_ENGINE
as the database. The applicationId
argument is optional and if you only want to test the application locally, then you don't need to specify it.... roo> persistence setup --provider DATANUCLEUS --database GOOGLE_APP_ENGINE --applicationId <your application identifier> Created SRC_MAIN_WEBAPPWEB-INFappengine-web.xml Created SRC_MAIN_WEBAPPWEB-INFlogging.properties Updated SRC_MAIN_RESOURCESlog4j.properties Updated ROOTpom.xml [Added property 'gae.home' with value '${user.home}/.m2/repository/com/google/appengine/appengine-java-sdk/1.4.0/appengine-java-sdk-1.4.0'] Updated ROOTpom.xml [Added dependencies com.google.appengine.orm:datanucleus-appengine:1.0.7.final..] Updated ROOTpom.xml [Added plugin maven-gae-plugin] Updated ROOTpom.xml [Added plugin maven-datanucleus-plugin]
For brevity, the given output only shows GAE-specific actions that are performed by Roo.
FlightDescription
JPA entity and add fields to it, as shown here:... roo> entity --class ~.domain.FlightDescription --identifierType java.lang.Long --testAutomatically ... roo> field string --fieldName origin --notNull ... roo> field string --fieldName destination --notNull ... roo> field number --type java.lang.Float --fieldName price --notNull
gwt
setup
command:... roo> gwt setup
flightapp-gae-gwt
into Eclipse IDE , execute the perform
eclipse
command:... roo> perform eclipse
gae:run
goal of Maven GAE Plugin to run the flightapp-gae-gwt
application locally on the Google App Engine development web server that comes bundled with App Engine SDK for Java, as shown here:C:
oo-cookbookch06-gae-gwt> mvn gae:run
pom.xml
file of the flightapp-gae-gwt
project when we executed the persistence
setup
command. A successful start of development server will show the following message: The
server
is
running
at
http://localhost:8080/
http://localhost:8080
to access the GWT flightapp-gae-gwt
web application, which allows you to perform CRUD operations on the FlightDescription
JPA entity, as shown here:flightapp-gae-gwt
application to GAE by executing the gae:deploy
goal of Maven GAE Plugin, as shown in the following command. If you had not created application identifier and specified it as the value of the applicationId
argument of the persistence
setup
command, then this step will fail.C: oo-cookbookch06-gae-gwt> mvn gae:deploy Beginning server interaction for <your-application-identifier>... ... Email: <email-id>@gmail.com Password for <email-id>@gmail.com: ...
flightapp-gae-gwt
application is successfully deployed on GAE, you can access it via the following URL:http://<your-application-identifier>.appspot.com
As the flightapp-gae-gwt
application is a secured application, you'll be required to log in using your Google Accounts or OpenID credentials.
The persistence
setup
command determines that the target deployment environment is Google App Engine if the value of database
argument is GOOGLE_APP_ENGINE
. If the value of the database
argument is GOOGLE_APP_ENGINE
, then it becomes mandatory to specify DATANUCLEUS
as the value of the provider
argument.
You might be wondering why it's mandatory to specify the persistence provider as DataNucleus
and GOOGLE_APP_ENGINE
as the database. Well, Google App Engine uses a proprietary schema-less object datastore, BigTable, for persisting application data. Java applications can access the BigTable datastore using JPA or JDO via DataNucleus
App
Engine
plugin
(this is not a Maven plugin but a DataNucleus plugin). DataNucleus is a separate product that allows access to datastores (which includes RDBMS, Excel, XML, LDAP, and so on) using JDO and JPA APIs. Also, the Datanucleus App Engine plugin is developed and maintained by Google and is specifically meant for use with GAE. So, you can say that by using DataNucleus, developers can use JDO or JPA APIs in their applications for accessing or persisting data, irrespective of the datastore(s) used by the application. This could be particularly useful in case your application makes use of distinct types of data sources.
In response to persistence
setup
, Roo performs the following actions:
appengine-web.xml
file in the WEB-INF
directoryDataNucleus App Engine plugin in pom.xml
Maven GAE plugin
in pom.xml
Maven
DataNucleus
plugin
in pom.xml
logging.properties
configuration for Java logging APIpersistence.xml
file in the META-INF
directory, which provides persistence provider (DataNucleus in our case) informationapplicationContext.xml
file in META-INF/spring
directory, which contains transaction manager and JPA EntityManagerFactory
definitionsLet's now look at appengine-web.xml
file and the plugins configured by Roo.
The appengine-web.xml
is a configuration file specific to GAE, which specifies application identifier, version of the application, static and resource files in the application, system properties, and so on. The following listing shows the content of appengine-web.xml
generated by Roo for the flightapp-gae-gwt
project:
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> <application>myappid</application> <version>1</version> <sessions-enabled>true</sessions-enabled> <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/> <property name="appengine.orm.disable.duplicate.emf.exception" value="false"/> </system-properties> </appengine-web-app>
<appengine-web-app>
is the root element of appengine-web.xml
. <application>
element specifies the application identifier, the value of which comes from the applicationId
argument of persistence
setup
command.
<version>
element specifies the version identifier of the application code that you are deploying on GAE. The application version identifier is particularly useful when you want to test your deployed application on GAE before making it the default version, which is accessible to the users. Let's say, you have version 1 of Flight Booking application already deployed on GAE. Now, you make some changes to your application code in order to fix bugs or add/modify application features. To test your modified application on GAE, change the <version>
element to a different value, let's say 2, and deploy the application on GAE using gae:deploy
goal (more on this later) of maven GAE plugin. GAE uses the value of <version>
element to determine if the existing application code needs to be replaced by the newly deployed application code or to create a new version of the application code. As the version of newly deployed Flight Booking application code is 2 and the existing application code had version 1, GAE creates a new version of the application, which you can access by going to the following URL: http://<app-version>.latest.<application-id>.appspot.com
. Assuming that the application identifier of the Flight Booking application is myappid
and the version deployed is 2, the URL becomes http://2.latest.myappid.appspot.com.
The <session-enabled>
element
enables GAE's session persistence feature, that is, session data is persisted into App Engine's datastore. So, if you set session data in your web application using setAttribute
method of HttpSession
, then it is stored in App Engine's datastore. As the session objects are persisted, the objects that you set in the session must implement the java.io.Serializable
interface.
The <system-properties>
element
defines the system properties available to the application. App Engine supports application logging via Logging API of Java (refer to the java.util.logging
package).
The logging configuration is read from the file, which is specified as the value of java.util.logging.config.file
system property. In the appengine-web.xml
file of the flightapp-gae-gwt
project, the <property>
sub-element of the <system-properties>
element specifies that the value of the java.util.logging.config.file
system property is WEB-INF/logging.properties
. Similarly, appengine.orm.disable.duplicate.emf.exception
system property with value true
instructs App Engine not to raise exceptions when the application attempts to create multiple javax.persistence.EntityManagerFactory
instances for a persistence unit. By default, App Engine expects that only a single instance of EntityManagerFactory
exists per persistence unit, and an attempt to create a duplicate EntityManagerFactory
instance results in exception.
The Maven GAE plugin simplifies developing Java applications for App Engine by providing goals, which help with downloading and unzipping App Engine SDK, starting and stopping App Engine development server, deploying application to App Engine, retrieving application logs from App Engine, and so on.
The following listing shows Maven GAE plugin specific configuration as defined in pom.xml
of the flightapp-gae-gwt
project:
<project ...> ... <properties> ... <gae.home> ${user.home}/.m2/repository/com/google/appengine/ appengine-java-sdk/1.4.0/appengine-java-sdk-1.4.0 </gae.home> </properties> ... <plugin> <groupId>net.kindleit</groupId> <artifactId>maven-gae-plugin</artifactId> <version>0.5.7</version> <configuration> <unpackVersion>1.4.0</unpackVersion> </configuration> <executions> <execution> <phase>validate</phase> <goals> <goal>unpack</goal> </goals> </execution> </executions> </plugin> </project>
The gae.home
property specifies the location of the unpacked version of App Engine SDK.
The sub-element <unpackVersion
>
of plugin <configuration>
specifies the version of the plugin to unpack. The <execution>
element
specifies that the gae:unpack
goal of the Maven GAE plugin is executed in the validate
build lifecycle phase. The validate
build lifecycle phase is the one in which Maven validates that the project is correct and all the required information to make the build is available. The gae:unpack
goal unpacks the GAE SDK to the location specified by the gae.home
property.
Spring Roo 1.1.3 generates pom.xml
, which makes project dependent on GAE SDK 1.4.0 and Maven GAE plugin 0.5.7, as shown in the listing we just saw. At the time of writing this book, the current version of GAE SDK is 1.5.1 and that of Maven GAE plugin is 0.8.4. To change the version of GAE SDK, modify the gae.home
property. And, to change the version of Maven GAE plugin, simply modify the value of the <version>
sub-element of the <plugin>
element, which configures Maven GAE plugin. If you are using Spring Roo 1.1.5, then the project already uses GAE SDK 1.5.1 and Maven GAE plugin 0.8.4.
The following table specifies some of the goals defined by the Maven GAE plugin:
Goal |
Description |
---|---|
|
Runs the project locally on the GAE development web server |
|
Uploads the application to the GAE server |
|
Retrieves application logs from the GAE server |
|
Shows the plugin and GAE SDK versions |
Let's now look at the Maven DataNucleus plugin and the role it plays in the GAE application.
To make a class persistent, DataNucleus expects that the class must implement the PersistenceCapable
interface of JDO. Why are we talking about JDO now? Well, it's because DataNucleus support for JPA is built on top of JDO. This means that even if you have annotated your domain classes with the @Entity
JPA annotation, DataNucleus can't persist them. To free developers from implementing the PersistenceCapable
interface in their domain classes, DataNucleus provides an enhancer, which works on the compiled domain classes and implements PersistenceCapable
interface via bytecode enhancement. The Maven DataNucleus plugin provides a datanucleus:enhance
goal, which enhances JPA classes annotated with the @Entity
annotation. The following code shows this:
<plugin> <groupId>org.datanucleus</groupId> <artifactId>maven-datanucleus-plugin</artifactId> <version>1.1.4</version> <configuration> <mappingIncludes>**/*.class</mappingIncludes> <enhancerName>ASM</enhancerName> <api>JPA</api> <mappingExcludes>**/GaeAuthFilter.class</mappingExcludes> </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>enhance</goal> </goals> </execution> </executions> </plugin>
In the plugin configuration, the <execution>
element specifies that datanucleus:enhance
goal is executed in the compile
build lifecycle phase. So, when Java source files are compiled, the Maven DataNucleus plugin enhances the compiled JPA domain classes. The <mappingIncludes>
element specifies the classes that should be included for enhancement. The <mappingExcludes>
specifies the classes that should not be considered for enhancement.
The <api>
element specifies whether the enhancement is for JPA or JDO. As we are using JPA in the flightapp-gae-gwt
project, the value of the <api>
element is jpa
. The <enhancerName>
element specifies ASM
as the value, which basically refers to the ASM framework (http://asm.ow2.org/) used by DataNucleus for enhancing the bytecode.
Let's now look at the FlightDescription
entity that was generated by Roo:
As GAE datastore is not a relational database, you'll find that some of the concepts that apply while using JPA with relational databases will not apply when using JPA with GAE datastore.
The following code shows the FlightDescription
JPA entity generated by Roo.
@RooJavaBean @RooToString @RooEntity public class FlightDescription { @NotNull private String origin; @NotNull private String destination; @NotNull private Float price; }
The given code shows that we are not using @Column
and @Table
JPA annotations to identify the table into which the entity instances are saved and the table column to which a persistent entity field maps to. As the GAE datastore is schema-less, you don't need to specify the table or column information. You can still use the JSR 303 annotations, such as @NotNull
in this code, for validating your domain objects.
The following code shows the FlightDescription_Roo_Entity.aj
AspectJ ITD file:
privileged aspect FlightDescription_Roo_Entity { declare @type: FlightDescription: @Entity; @PersistenceContext transient EntityManager FlightDescription.entityManager; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long FlightDescription.id; @Version @Column(name = "version") private Integer FlightDescription.version; ... }
It is interesting to note that the primary key generation strategy is specified as GenerationType.IDENTITY
. In GAE, this means that the identifier value is not assigned to the FlightDescription
entity until the associated transaction completes or you explicitly call the flush
method of EntityManager
.
Let's now look at how the flightapp-gae-gwt
application ensures that only authenticated users can access it.
As with any other web application, web request security constraints for the flightapp-gae-gwt
application are specified in the web.xml
file of the application. The following listing shows the <security-constraint>
element of the web.xml
file of flightapp-gae-gwt
application:
<security-constraint> <display-name>...</display-name> <web-resource-collection> <web-resource-name>...</web-resource-name> <url-pattern>*.html</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint>
The <url-pattern>
specifies that any URL that matches the *.html
pattern is secured and would require authentication. In the case of the flightapp-gae-gwt
application, the home page of the application is index.html
, which is secured according to the URL pattern specified by the <url-pattern>
element. As the entry into the flightapp-gae-gwt
application is restricted, users need to authenticate using their Google Accounts credentials before accessing the application. The <role-name>
element specifies *
as the value, which means that any authenticated user can access the application. If you want your application on GAE to be accessible to anonymous users also, then remove the <security-constraint>
element from the web.xml
file.
If you remove the <security-constraint>
element from web.xml
of the flightapp-gae-gwt
project and upload the application to GAE servers, you'll find that an attempt to access the flightapp-gae-gwt
application still asks for authentication. The reason behind this behavior is that the sample.roo.flightapp.server.gae.GaeAuthFilter
servlet filter configured in the Roo-generated web.xml
file. GaeAuthFilter
is a Roo-generated servlet filter, which checks if the user is logged in or not. If the user is not logged in, then it redirects the user to the Google Accounts sign in page. The following code listing from GaeAuthFilter.java
shows the GaeAuthFilter
class:
import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; public class GaeAuthFilter implements Filter { ... public void doFilter(...) ... { UserService userService = UserServiceFactory.getUserService(); ... if (!userService.isUserLoggedIn()) { String requestUrl = request.getHeader("requestUrl"); if (requestUrl == null) { requestUrl = request.getRequestURI(); } response.setHeader("login", userService.createLoginURL(requestUrl)); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } ... }
In the given code, UserServiceFactory
is a GAE-specific class whose getUserService
method returns an instance of UserService
. The UserService
interface defines methods to create login and logout URLs, get details of the currently signed in user, check if the user is logged in, and so on. In this code, GaeAuthFilter
checks if the user is logged in by calling the isUserLoggedIn()
method. If the user is not logged in, GaeAuthFilter
makes use of the createLoginURL(..)
method of UserService
to create a login URL and redirects the user to it.
Another interesting point to notice about GaeAuthFilter
is its mapping. The following listing shows the mapping of GWT's RequestFactoryServlet
and GaeAuthFilter
in the web.xml
file:
<filter-mapping> <filter-name>GaeAuthFilter</filter-name> <url-pattern>/gwtRequest/*</url-pattern> </filter-mapping> <servlet-mapping> <servlet-name>requestFactory</servlet-name> <url-pattern>/gwtRequest</url-pattern> </servlet-mapping>
The <url-pattern>
elements of GaeAuthFilter
and GWT's RequestFactoryServlet
show that a web request sent to RequestFactoryServlet
is intercepted by GaeAuthFilter
. This ensures that if the session expires, the application user is redirected to the Google Accounts sign-in page.
UserService
provides a getCurrentUser
method that returns a User
object if the user is logged in. The User
object contains user id, nickname, and e-mail information of the authenticated user. If your application requires capturing more information about the user, such as their preferences, address, and so on, then you need to save such information as part of your application data.
By default, the only role defined by App Engine is admin
, which you can specify as the value of the <role-name>
element. The admin
role is assigned to users that are application administrators, that is, users that you add using the Admin Console
of GAE. Admin Console gives you complete control over your deployed application on GAE. It allows you to administer your datastore, test different versions of your application, create application, and so on. You can access Admin Console by going to the following URL: http://appengine.google.com. You can use the isUserAdmin
method of UserService
to determine if the logged in user belongs to admin
role or not.
Even though GAE supports only admin
role, you can still incorporate role-based security in your App Engine applications by introducing application-specific roles. You can save application-specific role information as part of application data. For instance, you can use
Spring Security framework with your GWT or Spring Web MVC application to implement web request security and method-level security based on the roles assigned to users.
3.136.17.12