For more complex indexes, you will have to build them manually in the index configuration file, as shown in Listing 7-2.. Sample transaction import javax.jdo.Transaction; public void cr
Trang 1Building Indexes
At runtime, if App Engine executes a query with no corresponding index, it will fail miserably By default, App Engine builds a number of simple indexes for you For more complex indexes, you will have to build them manually in the index
configuration file, as shown in Listing 7-2
Listing 7-2 Sample datastore-index.xml file
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
xmlns="http://appengine.google.com/ns/datastore-indexes/1.0"
autoGenerate="true">
<datastore-index kind="Contact" ancestor="false">
<property name="countryName" direction="asc" />
</datastore-index>
</datastore-indexes>
Indexes are built automatically by App Engine for queries that contain:
• Single property inequality filters
• Only one property sort order (ascending or descending) and no filters
• Inequality or range filters on keys and equality filters on properties
• Only ancestor and equality filters
You must specify in the index configuration file any queries containing:
• Multiple sort orders
• Inequality and ancestor filters
• A sort order on multiple keys in descending order
• One or more inequality filters on a property and one or more equality
filters over the properties
Creating Indexes In Development Mode
During development, App Engines tries to create your indexes for you in the
configuration file If the development web server encounters a query that does not have
a corresponding index, it will try to create an index for you automatically If your unit tests call every possible query for your application, the generated configuration file will
Trang 2you think your tests call all possible queries but your application still fails at runtime,
you’ll have to edit the datastore-index.xml file and add these indexes manually
Using Transactions
At a high level, the App Engine datastore supports transactions like most relational
databases A transaction consists of one or more database operations that either
succeed or fail in entirety If a transaction succeeds, then all operations are
committed to the datastore However, if one of the operations fails, then all
operations are rolled back to their original state An example method using
transactions is shown in Listing 7-3
Listing 7-3 Sample transaction
import javax.jdo.Transaction;
public void createContact(Contact contact, String accountId) {
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
// start the transaction tx.begin();
pm.makePersistent(contact);
// fetch the parent account Account account = pm.getObjectById(Account.class, accountId);
account.incrementContacts(1);
pm.makePersistent(account);
// commit if no errors tx.commit();
// roll back the transactions in case of an error
tx.rollback();
} }
}
Trang 3All entities in the datastore belong to an entity group Entities in the same group are stored in the same part of Google’s distributed network Better distribution across database nodes improves performance when creating and updating data When creating a new entity, you can assign an existing entity as its parent so that the new entity becomes part of that entity group If you do not specify a parent for an entity, it
is considered a root entity
The datastore places restrictions on what operations can be performed inside a single transaction:
• Your application can perform a query inside a transaction but only
if the query includes an ancestor filter to retrieve all descendants of the specific entity
• A transaction must operate only on entities in the same entity
group
• If your transaction fails, your application must try again
programmatically JDO will not attempt to retry the transaction automatically, like most systems with optimistic concurrency
• A transaction can only update an entity once
Finishing Up Your Application
Now that you have a good understanding of the App Engine datastore and how to use JDO to interact with it, you can finish up the application You’ll need to tie various parts of your application into the datastore using GWT RPC to create a fully
functioning application, following these steps:
• Populate your Projects picklist with values
• Populate your Milestones picklist with values based on the selected
project
• Implement your Save handler to persist your timecard entries to
the datastore
• Display the current user’s timecard entries from the datastore
Making Remote Procedure Calls with GWT RPC
Similar to your authentication service, your data service will use GTW RPC to
Trang 4is invoked by your client to fetch and save timecard entries and related project
information You will need to implement the following components to round out
your application:
1 A server-side service containing the methods that your client will
invoke
2 The client-side code that will invoke the service
3 A serializable POJO containing your actual timecard data that is
passed between your server and client
Figure 7-1 Your GWT RPC components model
TimeEntryData POJO
Your client and server will need a POJO to pass data back and forth The POJO in Listing 7-4 will be a single timecard entry that will be persisted to the datastore
When using GWT RPC, the class, parameters, and return types must be
serializable so that the object can be moved from layer to layer
Trang 5Listing 7-4 The TimeEntryData POJO
package com.appirio.timeentry.client;
import java.io.Serializable;
import java.util.Date;
public class TimeEntryData implements Serializable { private String project;
private String milestone;
private Boolean billable;
private Date date;
private double hours;
public String getProject() {
}
public void setProject(String project) {
}
public String getMilestone() {
}
public void setMilestone(String milestone) {
}
public Boolean getBillable() {
}
public void setBillable(Boolean billable) {
}
public Date getDate() {
Trang 6public void setDate(Date date) {
}
public double getHours() {
}
public void setHours(double hours) {
}
}
■Note GWT serialization is a little different from the Java Serializable interface Check out the GWT
Developer’s Guide for details on the differences and reasons behind them
TimeEntryEntity JDO Class
Your TimeEntryData POJO is transferred across the wire to your server and is
deserialized automatically For flexibility, you’re going to create the JDO class in
Listing 7-5 for persisting your instances to the datastore
Listing 7-5 The code for your JDO class, TimeEntryEntity.java
package com.appirio.timeentry.server;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import java.util.Date;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class TimeEntryEntity {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Trang 7private Long id;
@Persistent
private String email;
@Persistent
private String project;
@Persistent
private String milestone;
@Persistent
private Boolean billable;
@Persistent
private Date date;
@Persistent
private double hours;
public Long getId() {
}
public void setId(Long id) {
}
public String getEmail() {
}
public void setEmail(String email) {
}
public String getProject() {
}
public void setProject(String project) {
}
public String getMilestone() {
}
Trang 8public void setMilestone(String milestone) {
}
public Boolean getBillable() {
}
public void setBillable(Boolean billable) {
}
public Date getDate() {
}
public void setDate(Date date) {
}
public double getHours() {
}
public void setHours(double hours) {
}
}
NotLoggedIn Exception
When interacting with the datastore, your service needs to ensure that the user is
logged in to the application with her Google account If the user has not logged in or her session has expired, you need to handle this by throwing the
NotLoggedInException shown in Listing 7-6
Listing 7-6 The code for the NotLoggedInException
package com.appirio.timeentry.client;
import java.io.Serializable;
Trang 9public class NotLoggedInException extends Exception implements Serializable {
public NotLoggedInException() {
super();
}
public NotLoggedInException(String message) {
super(message);
}
}
Creating Your Data Service
In order to create your data service for your server, you need to define both a service interface and the actual service For your service interface you need to define the interface extending the GWT RemoteService interface,GWT RemoteService
interface, as shown in Listing 7-7 Your service will consist of the following methods that will be called from your client:
1 getProjects: Returns an Array of Strings for the Project picklist
2 getMilestones: Accepts a project name and returns an Array of
Strings for the Milestones picklist
3 addEntries: Accepts a Vector of TimeEntryData objects and returns
a String with the results of the datastore commit
4 getEntries: Returns a Vector of TimeEntryData objects containing
the current timecard entries for the current user
Listing 7-7 Your data service extending the GWT RemoteService
package com.appirio.timeentry.client;
import java.util.Vector;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.appirio.timeentry.client.TimeEntryData;
@RemoteServiceRelativePath("data")
Trang 10String[] getProjects();
String[] getMilestones(String project);
String addEntries(Vector<TimeEntryData> entries) throws
NotLoggedInException;
Vector<TimeEntryData> getEntries() throws NotLoggedInException;
}
■Note Notice the @RemoteServiceRelativePath annotation You’ll define this path in the deployment
descriptor based on the relative path of the base URL
The guts of your service reside in the DataServiceImpl class shown in Listing 7-8 The methods defined in your interface are implemented in addition to a number of helper methods This class extends GWT RemoteServiceServlet and does the heavy lifting of serializing responses and deserializing requests for you Since the servlet runs as Java bytecode instead of JavaScript on the client, you are not hamstrung by the
functionality of the browser
Listing 7-8 The entire listing for DataServiceImpl.java
package com.appirio.timeentry.server;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.logging.Level;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.JDOHelper;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.appirio.timeentry.client.NotLoggedInException;
import com.appirio.timeentry.client.DataService;
import com.appirio.timeentry.client.TimeEntryData;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;