Deploying a Spring Web MVC application on GAE

As of Spring Roo 1.1.5, the Roo-generated Spring Web MVC application doesn't work on GAE. The reason for this is related to mismatch in the JSTL version used by GAE and by the Roo-generated Spring MVC application. Also, Roo-generated JPA entities support only unowned relationships (refer http://code.google.com/appengine/docs/java/datastore/jdo/relationships.html to learn about owned and unowned relationships). In this recipe, we'll look at a Spring 3.0 Web MVC application (which uses JSTL tags that work on GAE) consisting of FlightDescription and Flight JPA entities and demonstrates how to create a unidirectional owned one-to-many relationship between JPA entities. The FlightDescription entity is on the one side of one-to-many relationship.

Note

If you are using Spring Roo 1.2.x, then the Roo-generated Spring Web MVC application can be deployed successfully on GAE.

Getting ready

If you only want to run the Spring Web MVC 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, then follow the steps described in the previous recipe and ensure that you modify the application identifier in WEB-INF/appengine-web.xml file. It is recommended that you deploy the application on GAE servers, as we'll also discuss some of the features offered by Admin Console.

Extract the ch06-gae-spring-mvc.zip file that accompanies this book to the C: oo-cookbook directory. Extracting the ZIP file will create a directory named ch06-gae-spring-mvc, which contains the flightapp-gae-spring-mvc Eclipse project. The flightapp-gae-spring-mvc project makes use of App Engine SDK version 1.4.2 and Maven GAE Plugin 0.8.2.

How to do it...

To deploy a Spring Web MVC application on GAE follow the steps given here:

  1. Open the command prompt and go to C: oo-cookbookch06-gae-spring-mvc directory. Execute mvn gae:run command to deploy the flightapp-gae-spring-mvc project on App Engine development web server:
    C:
    oo-cookbookch06-gae-spring-mvc> mvn gae:run
    
  2. Once the development web server starts successfully, open web browser and go to the following URL: http://localhost:8080. If you see the following page, then it means that your flightapp-gae-spring-mvc project is successfully deployed:
    How to do it...
  3. The given screenshot shows the home page of our Flight Booking application, which currently allows managing Flight and FlightDescription objects. Selecting the Manage Flight Descriptions hyperlink allows users to create and view FlightDescription instances. Also, it allows users to child Flight instances associated with a FlightDescription instance. The Manage Flights option allows users to create and delete Flight instances.
  4. Select Manage Flight Descriptions option to view FlightDescription instances in the application, as shown here:
    How to do it...
  5. As we have not yet created any FlightDescription instances, the table shows No record found message.
  6. Select the Create Flight Description option to view the form for creating a new FlightDescription instance, as shown here:
    How to do it...
  7. Enter the FlightDescription details as shown in the given screenshot and click the Create button. The newly created FlightDescription instance is displayed in the list of FlightDescription instances, as shown here:
    How to do it...
  8. The View button corresponding to a FlightDescription record shows the FlightDescription details along with the Flight details, which are associated with the FlightDescription instance. The Delete button deletes the FlightDescription instance along with the associated Flight instances.
  9. Click the Home link to go back to the home page of the application and select the Manage Flights link to view the Flight instances, as shown here:
    How to do it...
  10. Select the Create Flight link to view the form for creating Flight instances, as shown here:
    How to do it...
  11. Enter FLT-100 as the value of Flight name field and select NYC – DELHI for only 1200 USD as the value of Flight Description field. Click Create button to create Flight instance (with FLT-100 as the flight name), which is a child of FlightDescription instance (origin: NYC, destination: DELHI, price: 1200). Similarly, create flights with names FLT-200, FLT-300, and FLT-400. Now, if you go to the FlightDescription listing page and click the View button, then you'll see the newly created Flight instances as a child of the NYC-DELHI-1200 FlightDescription instance, as shown here:
    How to do it...
  12. The given screenshot shows the Flight instances that are associated with FlightDescription instance.
  13. If you now delete the FlightDescription instance by selecting the Delete button (as shown in the next screenshot) corresponding to the NYC-DELHI-1200 FlightDescription instance, then it'll also delete associated Flight instances.
    How to do it...

How it works...

GAE datastore is a non-relational database in which relationships between entities are modeled as either owned or unowned. An owned relationship is based on the concept of parent-child relationship, where the child entity instances cannot exist without a parent. In an unowned relationship, entity instances can exist irrespective of their relationship with other entities.

An owned relationship can be best visualized as a tree structure in which the root of the tree is an entity instance, which is the ultimate parent for all the entities. The following figure shows an owned relationship example in which FlightDescription is the root entity with Flight entity instances as its child entities, and Booking entity instances are the child of the Flight entity.

How it works...

In the given figure, FlightDescription (NYC-DELHI-1200) is at the root of the tree. The Flight instances (FLT-100, FLT-200, FLT-300, FLT-400) are children of the FlightDescription (NYC-DELHI-1200) instance. The Booking (ashish, greg) instances are children of the Flight (FLT-400) instance. If such a parent-child relationship is defined in the entities stored in the App Engine datastore, then these entities are referred to as part of the same entity group. You can think of an entity group as a tree with a root entity at the top and this root entity doesn't have any parents. In this figure, FlightDescription (NYC-DELHI-1200) represents the root entity. The other important point about owned relationships is that except the root entity, all other entities in the entity group cannot be created without a parent. For example, Flight instance cannot exist without a FlightDescription instance and Booking instance cannot exist without a Flight instance.

In GAE, creating an owned relationship puts a restriction on the type of primary key that you can define for the child entity. The reason for this is that the child entity instance needs to know its parent in the entity group apart from its own entity ID. The type of primary key of the child entity in an owned relationship is either com.google.appengine.api.datastore.Key or encoded String form of com.google.appengine.api.datastore.Key. GAE provides a KeyFactory class (discussed later in this recipe), which you can use to create a Key instance or to convert a Key instance value to String (referred to as encoded form of the Key) and vice versa.

In an unowned relationship, each entity instance exists independently of each other. For example, the relationship between Student and Course entities is an example of an unowned relationship. A Student entity can exist without a Course and a Course entity can exist without a Student.

In our Flight Booking application, the relationship between FlightDescription and Flight entities is an example of an owned relationship, where Flight entity instances cannot exist without a FlightDescription instance. Let's now see how an owned one-to-many relationship has been created between the FlightDescription and Flight entities in the Flight Booking application.

Owned relationship

The following code listing shows some of the important methods and attributes of the FlightDescription entity:

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@Entity
public class FlightDescription {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Basic
   private Key flightDescriptionId;
    
   @Basic
   @NotNull
   private String origin;
   ...

   @OneToMany
   private List<Flight> flights;
   
   ...
   @Transactional
   public void persist() {
     if (this.entityManager == null) 
        this.entityManager = entityManager();
     this.entityManager.persist(this);
   }

   @Transactional
   public static FlightDescription 
        findFlightDescription(String key) {
     return 
     (FlightDescription)entityManager().createQuery("select o 
        from FlightDescription o where o.flightDescriptionId = 
         :id").setParameter("id", 
               KeyFactory.stringToKey(key)).getSingleResult();
   }
   ...
   public String getFlightDescriptionKeyAsString() {
      return KeyFactory.keyToString(flightDescriptionId);
   }
   ...
}

The given code shows that FlightDescription entity's primary key is of type com.google.appengine.api.datastore.Key. The Key not only holds the entity's primary key, but also holds information about the entity group to which the entity instance belongs. As the FlightDescription represents a root entity (that is, without a parent), the Key field will not contain entity group information. The @OneToMany annotated flights field defines an owned relationship between the FlightDescription and Flight entities.

If it was an unowned relationship the flights field would have taken the following form:

private List<Key> flights;

Here, the flights field refers to the primary keys of Flight entity instances. Also, the field is not annotated with the @OneToMany annotation.

The findFlightDescription method of the FlightDescription entity takes encoded String value of Key to find the matching FlightDescription. The KeyFactory class provides keyToString and stringToKey to convert Key into its encoded String form and vice versa. This is particularly useful when you are creating relationships between entities via the user interface of the web application. The query to fetch the FlightDescription object makes use of the KeyFactory class to obtain Key from its String representation and use it as part of the query, as shown here again:

entityManager().createQuery("select o 
        from FlightDescription o where o.flightDescriptionId = 
         :id").setParameter("id", 
               KeyFactory.stringToKey(key)).getSingleResult();

The persist method of FlightDescription shows that you don't need to set the value of the primary key. It is set by the App Engine.

The getFlightDescriptionKeyAsString method of the FlightDescription entity makes use of KeyFactory class to return the primary key of the entity as an encoded String value.

Let's now look at how Flight entity is modeled.

The following code listing shows noteworthy attributes and methods of the Flight entity:

@Entity
public class Flight {
    
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic
  private Key flightId;
    
  @Basic
  @NotNull
  private String flightName;

  private transient String encodedFlightDescriptionId;

  @Transactional
  public void persist() {
    if (this.entityManager == null) 
      this.entityManager = entityManager();
    Key parentKey = KeyFactory.stringToKey(
          encodedFlightDescriptionId);
    
    Key flightKey = KeyFactory.createKey(parentKey, 
          Flight.class.getSimpleName(), flightName);

    setFlightId(flightKey);
    this.entityManager.persist(this);
  }
    
  public String getEncodedFlightDescriptionId() {
    return encodedFlightDescriptionId;
  }

  public String getFlightKeyAsString() {
    return KeyFactory.keyToString(flightId);
  }
}

In the given code, flightId represents the primary key of the Flight entity. The persist method of the Flight entity makes use of the createKey method of the KeyFactory class to create the primary key. The createKey(parent, kind, name) method accepts primary key information of the parent entity (FlightDescription is the parent in the case of Flight entity), the kind (which is similar to the concept of tables in relational databases), and the name of the key that uniquely identifies the entity within the kind. In the case of the Flight entity's primary key, the kind refers to the simple name of the entity class: Flight.class.getSimpleName() and the name of the key is the flightName property of the Flight entity. So, if you re-create a Flight entity with flightName as FLT-100, then it will overwrite the existing Flight (FLT-100) entity instance. GAE datastore doesn't support a composite primary key, but you can still achieve it by concatenating multiple entity field values in creating the name of the key.

The encodedFlightDescriptionId is the String form of the FlightDescription primary key who is the parent of Flight entity instance. The value of encodedFlightDescriptionId is set when the user selects the FlightDescription from the user interface for creating the Flight instance, as shown here:

Owned relationship

The <option> element corresponding to the FlightDescription (NYC-DELHI-1200) entity instance is rendered as shown here:

<option value="<string value of Key>">NYC-DELHI for only 1200 USD</option>

The value attribute specifies the String form of the FlightDescription primary key (which is of type Key). So, when the user clicks the Create button, the String value of Key is bound to the encodedFlightDescriptionId field of the Flight instance.

The flightapp-gae-spring-mvc project contains the KeyEditor property editor class that uses KeyFactory to perform conversion from Key to String format and vice versa. The property editor has been used instead of a Spring ConversionService implementation because the ConversionService implementation doesn't currently work with the <options> tag of the Spring form tag library.

Let's now look at how we can manage persisted data in GAE datastore using Admin Console.

Managing persisted data using Admin Console

You can view, edit, and delete data persisted by your application in GAE datastore by using Admin Console. Go to http://appengine.google.com and sign in to view the applications that you have created, as shown here:

Managing persisted data using Admin Console

The given screenshot shows the two applications that I have created in GAE. The roo-cookbook-spring-mvc application corresponds to the application that we saw in this recipe. Selecting the application will show the Dashboard for the application where you can view different statistics related to your application. We are only interested in viewing the data that we saved in the datastore, so select the Datastore Viewer option from the Dashboard. The Datastore Viewer shows the entities that you saved in the datastore, as shown in the following screenshot:

Managing persisted data using Admin Console

The given screenshot shows the data for the FlightDescription kind that we created. You can use the Query tab to query the datastore using GQL. You can also delete or edit the entity instances. If you change kind to Flight, then the Admin Console will show the Flight entities that you have created, as shown here:

Managing persisted data using Admin Console

The given screenshot shows that the name set for the primary key of the Flight instance is displayed under the ID/Name column.

There's more...

If you are designing your entities for App Engine, it is important to note that within a transaction you can only operate on entities which belong to the same entity group. So, if you have Student and Course entities, which are in different entity groups, then you can't create or update them in a single transaction.

As of Spring Roo 1.1.3, you can only create unowned relationship between entities. For instance, if you execute the field reference or field set command to add a relationship field, then the *_Roo_JavaBean.aj AspectJ ITD file corresponding to the JPA entity removes the @OneToMany or @OneToOne or @ManyToOne or @ManyToMany annotation from the corresponding field in the JPA entity Java source file, resulting in an unowned relationship. The following code fragment shows how the AspectJ ITD file removes the relationship annotation from Java source file:

privileged aspect FlightDescription_Roo_JavaBean {

  declare @field: * FlightDescription.flights: -@OneToMany;
  ...
}

The given code shows that if the FlightDescription and Flight entities were created using Spring Roo, then the @OneToMany annotation on the flights (which refers to a collection of Flight entities) field of FlightDescription will be removed by the FlightDescription_Roo_JavaBean.aj AspectJ ITD file. The minus sign (-) indicates that the @OneToMany annotation will be removed from the FlightDescription.java file.

See also

  • Refer to the Deploying a GWT application on GAE recipe, to see an example of GWT application for App Engine
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.171.153