Google Application Engine (GAE)
GAE supports
Java
applications.
Google provides a great infrastructure for web applications. It takes care of many web
application developer's headaches: hardware, operating system support, backups, scaling,
security, mail etc. To run under GAE your application has to comply to GAE rules.
The biggest difference is GAE datastore - it is not a relational database - instead they use
something called
BigTable.
To simplify development,
Google has adopted
DataNucleus
Access Platform
to provide
JPA 1.0 support.
Because GAE datastore is not a relational database
some JPA features
are not supported by this JPA implementation.
Setting up Smart GWT application for GAE
Under the
/WEB-INF
directory you have to create a file named
appengine-web.xml
which will hold Google's specific settings.
One important thing to note: static and dynamic contents will be served from different servers.
There are
special sections
in
appengine-web.xml
specifying static and dynamic resources. All resources are duplicated if not specified.
A single GAE application is limited to 3000 files. A typical Smart GWT application consists of
many
resources and it exceeds the limit when duplicated (even with a single theme).
To run a Smart GWT application we have to split resources. Here is an example
configuration:
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>GAE_APPLICATION_NAME</application>
<version>1</version>
<static-files>
<include path="/index.jsp"/>
<include path="/[MODULE_NAME]/**"/>
<exclude path="/[MODULE_NAME]/**.xml"/>
<exclude path="/[MODULE_NAME]/**.xsl"/>
<exclude path="/[MODULE_NAME]/**.wsdl"/>
</static-files>
<resource-files>
<include path="/[PATH_TO_DATA_SOURCE_FILES]/**"/>
<include path="/[MODULE_NAME]/**.xml"/>
<include path="/[MODULE_NAME]/**.xsl"/>
<include path="/[MODULE_NAME]/**.wsdl"/>
</resource-files>
</appengine-web-app>
To interact with DataSources an additional servlet mapping has to be added to
web.xml
:
<servlet-mapping>
<servlet-name>IDACall</servlet-name>
<url-pattern>/[MODULE_NAME]/sc/IDACall</url-pattern>
</servlet-mapping>
Setting up DataSources
GAE supports only four types as primary keys:
java.lang.Long
java.lang.String
java.lang.String
with additional annotations
com.google.appengine.api.datastore.Key
not supported by Smart GWT
Primary key can not be altered after entity is saved.
Entities with primary keys
Long
or
String
can not participate in
transactions and can not be used in relations.
Here is an example how to declare primary key of type
String
with additional
annotations:
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.datanucleus.jpa.annotations.Extension;
@Entity
public class Bar
implements Serializable
{
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Extension (vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String id;
}
DataSource creation is similar to
standard JPA
with one
difference: property
serverConstructor
should be set to
com.isomorphic.jpa.GAEJPADataSource
.
Because of
GAE queries limitations
this DataSource implementation supports only single inequality criteria in filter.
Only
TextMatchStyle.STARTS_WITH
filtering mode is supported for text fields.
All queries are case sensitive because GAE does not support
upper()/lower()
functions in criterias.
TextMatchStyle.EXACT
is used for all number fields.
com.isomorphic.jpa.EMFProviderLMT
or
com.isomorphic.jpa.EMFProviderNoTransactions
should be used as
transaction providers (depending whether you use transactions or not).
To participate in a transaction, entities have
to belong to the
same group.
Note: entities of different type can not participate in a transaction even if these
entities have GAE specific primary key (you can not even fetch (SELECT) entities belonging
to different groups).
Relationships
Entities are grouped by establishing owned relationships (where dependent entities are
instantiated automatically by the JPA provider) between them. Entities in groups can form a
chain of the following sort::
ClassA has reference to ClassB,
ClassB has reference to ClassC
But it is impossible to have an entity referencing two other entities:
ClassD has reference to ClassE,
ClassD has reference to ClassF
There are no foreign keys - the actual reference is encoded into the primary key of child
entity.
GAE datastore does not support many-to-many relationships.
Unidirectional one-to-many relationships work only if the parent has a declaration of
List<ChildEntityClass>
.
Unidirectional relationships do not work if only the child entity has reference to the
parent.
Bidirectional relationship example:
@Entity
public class Country
implements Serializable
{
@Id
@Column (nullable = false)
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Extension (vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String countryId;
@OneToMany
private List cities;
//....
}
@Entity
public class City
implements Serializable
{
@Id
@Column (nullable = false)
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Extension (vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String cityId;
// This is fake column - it is calculated by provider and is not saved.
// Actual reference to parent entity is encoded in primary key.
@Column (nullable = false)
@Extension (vendorName = "datanucleus", key = "gae.parent-pk", value = "true")
private String countryId;
@ManyToOne (fetch=FetchType.LAZY)
private Country country;
//....
}
Note: GAE does not support
FetchType.EAGER
.
With
Unowned Relationships
(when you save parent's primary key as simple child's property) you can model unsupported
relationships. But this approach has drawbacks:
- related entities are not instantiated automatically
- transactions can not be used
- you have to manually keep track of changes in case of bidirectional relationship