In this recipe, we'll look at the gwt
setup
command, which scaffolds GWT artifacts from JPA entities.
Create a new directory (C:
oo-cookbookch05-gwt
) in your system. Copy the ch05_gwt_app.roo
script that accompanies this book to the ch05-gwt
directory. Start the Roo shell from the ch05-gwt
directory and execute the ch05_gwt_app.roo
script using the script
command. Executing the ch05_gwt_app.roo
script does the following:
flightapp-gwt
Eclipse projectFlight
and FlightDescription
JPA entities and defines a many-to-one relationship between Flight
and FlightDescription
entitiesIf you are using a different database than MySQL or your connection settings are different from what is specified in the script, then modify the script accordingly.
Install the Google Plugin for Eclipse IDE (http://code.google.com/eclipse/); it simplifies developing GWT applications using Eclipse IDE.
Follow these steps to scaffold GWT applications:
gwt
setup
command, as shown here:..roo>gwt setup .... Created SRC_MAIN_WEBAPPWEB-INFspring Created SRC_MAIN_WEBAPPWEB-INFspringwebmvc-config.xml Created SRC_MAIN_WEBAPPWEB-INFweb.xml Updated ROOTpom.xml[...] Created SRC_MAIN_JAVAsample ooflightappclient Created SRC_MAIN_JAVAsample ooflightappApplicationScaffold.gwt.xml Created SRC_MAIN_JAVAsample ooflightappclientmanaged equest Created SRC_MAIN_JAVAsample ooflightappclientscaffold equest Created SRC_MAIN_WEBAPPindex.html Created SRC_MAIN_WEBAPPApplicationScaffold.html ...
Note that only partial output has been shown above for brevity.
gwt
setup
command creates GAE-specific (Google App Engine) Java files that you must remove from the generated source. To do so, remove the following folders from the flightapp-gwt
project before going to the next step:src/main/java/sample/roo/flightapp/server/gae src/main/java/sample/roo/flightapp/shared/gae src/main/java/sample/roo/flightapp/client/scaffold/gae
If you are using Spring Roo 1.1.5, GAE-specific Java files are not generated.
perform
eclipse
command to update the .classpath
file of the flightapp-gwt
Eclipse project and to convert the nature of the project to gwt
:..roo>perform eclipse
flightapp-gwt
project into your Eclipse IDE. Add the Google Web Toolkit library to the build path (Project properties | Java Build Path | Add Library) of the flightapp-gwt
project, so that the project doesn't show any compilation errors in Eclipse IDE.gwt:run
goal of the GWT maven plugin, as shown here (alternatively, you may go to the next step):..recipe>mvn clean compile gwt:run
Executing the gwt:run
goal opens the GWT Development Mode window, as shown here:
Click the Launch Default Browser button in the GWT Development Mode window to launch the flightapp-gwt
application. If not already installed, you'll be prompted to install the Google Web Toolkit Developer Plugin for your browser, which is required when you are running a GWT application in development mode.
As shown in the screenshot, set the WAR directory value to src/main/webapp
—Maven's standard WAR directory that contains the ApplicationScaffold.html
host page. Make sure that the Launch and deploy from this directory option is unchecked.
ApplicationScaffold.html
(or index.html
) page in the HTML Page Selection dialog, as shown in the following figure:The HTML page that you select on this screen represents a host HTML page that is responsible for loading the GWT application. Both ApplicationScaffold.html
and index.html
files are located in the src/main/webapp
directory of the flightapp-gwt
GWT application. index.html
is a simple HTML page that simply loads the ApplicationScaffold.html
page—the host HTML page of flightapp-gwt
GWT application.
When you are running the GWT application for the first time, you will be asked to select the location of the WAR directory of the flightapp-gwt
project, which is target/flightapp-gwt-0.1.0.BUILD-SNAPSHOT
.
flightapp-gwt
application is successfully deployed, you'll see the home page of the application, as shown here:You can now use the Flights and FlightDescriptions menu options to perform CRUD
operations on Flight
and FlightDescription
JPA entities.
The gwt
setup
command is processed by the GWT add-on of Spring Roo.
You might be wondering, why the Roo-generated GWT user interface doesn't show a link corresponding to the findFlightDescriptionsByDestinationAndOrigin
finder method in the FlightDescription
JPA entity? As of Spring Roo 1.1.5, the GWT add-on doesn't add finder functionality to the scaffolded GWT application.
The gwt
setup
command does the heavy lifting of scaffolding GWT Activities, Places, Proxies, and Views for performing CRUD operations on JPA entities. Let's first take a look at the Roo-generated GWT module descriptor file, ApplicationScaffold.gwt.xml
, which describes a GWT module.
Roo creates ApplicationScaffold.gwt.xml
in the root package, sample.roo.flightapp
, of the flightapp-gwt
project. It defines module dependencies, source paths, properties, deferred binding configurations, and module entry points. Let's look at some of the important elements defined in ApplicationScaffold.gwt.xml
.
By default, the name of the GWT module is derived from the location of the module descriptor. As Roo creates the ApplicationScaffold.gwt.xml
file in the sample.roo.flightapp
package, the name of the module is sample.roo.flightapp.ApplicationScaffold
. The ApplicationScaffold.gwt.xml
file renames the module to applicationScaffold
using the rename-to
attribute of the <module>
element, as shown here:
<module rename-to="applicationScaffold">
The GWT compiler generates JavaScript code in the directory identified by the module name; therefore, the code for our applicationScaffold
module is generated in the applicationScaffold
sub-directory of the generated WAR file.
The <inherits>
element of the module descriptor specifies modules on which the module is dependent upon. For instance, applicationScaffold
is dependent on User
, Logging
, Activity
, Places
, and so on, built-in modules of GWT, as shown here:
<inherits name='com.google.gwt.activity.Activity'/> <inherits name='com.google.gwt.place.Place'/> <inherits name="com.google.gwt.user.User"/> <inherits name='com.google.gwt.logging.Logging'/>
The <source>
element
of the module descriptor specifies package, including its sub-packages (relative to the classpath location of ApplicationScaffold.gwt.xml
file), which contain Java classes that GWT compiler needs to translate into JavaScript, as shown here for applicationScaffold
module:
<source path='client'/> <source path='shared'/>
The given <source>
element instructs the GWT compiler to translate Java classes contained in sample.roo.flightapp.client
and sample.roo.flightapp.shared
packages, and their sub-packages.
The <public>
element of module descriptor specifies packages (and their sub-packages) that contain publicly accessible resources, like images and CSS files, as shown here:
<public path="public"/>
As with the <source>
elements, the <public>
element specifies the location of packages relative to the classpath location of the module descriptor file.
The ApplicationScaffold.gwt.xml
file configures logging for the module, as shown here:
<set-property name="gwt.logging.enabled" value="TRUE"/> <set-property name="gwt.logging.logLevel" value="INFO"/> <set-property name="gwt.logging.consoleHandler" value="ENABLED"/> <set-property name="gwt.logging.developmentModeHandler" value="ENABLED"/> <set-property name="gwt.logging.simpleRemoteHandler" value="DISABLED"/> ...
In this code, gwt.logging.enabled
property enables logging for the applicationScaffold
module, gwt.logging.logLevel
property sets the logging level to INFO
, gwt.logging.consoleHandler
property enables logger output to appear in the IDE console, gwt.logging.developmentModeHandler
property enables logger output to appear in the 'Development Mode' console of the IDE and gwt.logging.simpleRemoteHandler
property disables remote logging of log messages. Later in this recipe, we'll see that gwt.logging.simpleRemoteHandler
property is set to ENABLED
to enable logging messages on the server-side.
Roo-generated GWT applications by default provide support for the mobile Safari browser. So, if you are developing a GWT application, it'll work seamlessly on mobile phones that use the mobile Safari browser. If the application is accessed using the mobile Safari browser, then the GWT application will create a web UI suitable for display in mobile devices. To support both desktop and mobile Safari browsers, the applicationScaffold
module makes use of the deferred binding feature of the GWT compiler.
To use the deferred binding feature, the <define-property>
element
of module descriptor is used to define a new property named mobile.user.agent
, as shown here:
<define-property name="mobile.user.agent" values="mobilesafari, none"/>
The values
attribute specifies a comma-separated list of values that the mobile.user.agent
property can accept.
To set the mobile.user.agent
property value, the module descriptor makes use of the <property-provider>
element, as shown here:
<property-provider name="mobile.user.agent"> <![CDATA[ var ua = navigator.userAgent.toLowerCase(); ... ]]>
The CDATA
section contains the JavaScript that is used to obtain the value of the mobile.user.agent
from the user-agent information sent by the web browser to the server hosting the GWT application.
Now, the most important part: the deferred binding rule is defined using a replacement technique in the applicationScaffold
module descriptor file, as shown here:
<replace-with class="...client.scaffold.ioc.MobileInjectorWrapper"> <when-type-is class="...client.scaffold.ioc.DesktopInjectorWrapper"/> <all> <when-property-is name="mobile.user.agent" value="mobilesafari"/> </all> </replace-with>
This configuration instructs the GWT compiler to replace the code of DesktopInjectorrapper
with MobileInjectorWrapper
(while generating JavaScript for the applicationScaffold
module) if the value of mobile.user.agent
property is mobilesafari
. This is possible because both DesktopInjectorWrapper
and MobileInjectorWrapper
implement the same interface, InjectorWrapper
. When the GWT compiler executes, it uses deferred binding rules (defined in the module descriptor file) to generate separate flightapp-gwt
application's JavaScript code for mobile Safari and desktop browsers. This ensures that the desktop and mobile browsers download JavaScript code meant specifically for that browser type. For instance, the mobile Safari browser will not download JavaScript code that is specific to the desktop web browser and vice versa. The classes that need to create an instance of DesktopInjectorWrapper
or MobileInjectorWrapper
make use of the create
static method of the com.google.gwt.core.client.GWT
class to instruct the GWT compiler to instantiate the DesktopInjectorWrapper
or MobileInjectorWrapper
instance using deferred binding.
The code generated for mobile Safari browser follows a similar design approach as the code for the desktop browser; therefore, in this book we'll limit the discussion of Roo-generated code specific to desktop browser.
A module descriptor also describes entry points
into the GWT application using <entry-point>
element, as shown here for applicationScaffold
module:
<entry-point class="sample.roo.flightapp.client.scaffold.Scaffold"/>
The above code suggests that Scaffold
class represents the entry point for the applicationScaffold
module. Scaffold
class implements com.google.gwt.core.client.EntryPoint
interface of GWT—a mandatory requirement for entry point classes.
As mentioned earlier, the Scaffold
class of the flightapp-gwt
application represents an entry point into the applicationScaffold
module. The Scaffold
class implements GWT's EntryPoint
interface and implements its onModuleLoad
method to bootstrap the flightapp-gwt
application, as shown here:
Scaffold.java package sample.roo.flightapp.client.scaffold; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; public class Scaffold implements EntryPoint { final private InjectorWrapper injectorWrapper = GWT.create(DesktopInjectorWrapper.class); public void onModuleLoad() { injectorWrapper.getInjector().getScaffoldApp().run(); } }
The first thing to notice in this code is the use of the create
method of the com.google.gwt.core.client.GWT
class to create the DesktopInjectorWrapper
instance. As the DesktopInjectorWrapper
implementation needs to be replaced by MobileInjectorWrapper
for the mobile Safari browser, DesktopInjectorWrapper
is created using the create
method of the GWT
class.
The onModuleLoad
method is like Java's main
method, and is responsible for initializing the flightapp-gwt
application. In the Scaffold
class, the onModuleLoad
method is responsible for creating the application's web UI, registering event handlers with EventBus
, and so on. The DesktopInjectorWrapper
and MobileInjectorWrapper
classes represent a wrapper around GIN's Ginjector
implementation.
GIN
is a dependency injection framework that uses Google
Guice
framework to support dependency injection in GWT applications. In GWT applications, references to objects that are needed throughout the application can be created using GIN or a factory. The Roo-generated flightapp-gwt
GWT application makes use of GIN to create EventBus
, ApplicationRequestFactory
, and PlaceController
—objects that are used across the flightapp-gwt
application. The following table describes the importance of these objects in the flightapp-gwt
application:
Object |
Description |
---|---|
The | |
A Roo-generated interface that extends GWT's | |
A GWT |
Now that we know what objects are used across the flightapp-gwt
application, let's look at how GIN creates these objects and how these objects are injected into objects that depend on them.
To understand how
Ginjector
is used in flightapp-gwt
, let's look at the following class diagram:
In this class diagram, the Ginjector
interface is part of GIN API. The ScaffoldInjector
and DesktopInjector
are generated by Roo. The following are the important points to notice in the above class diagram:
ScaffoldInjector
extends the Ginjector
interface and defines a single method getScaffoldApp
, which returns an instance of type ScaffoldApp
.ScaffoldApp
is a Roo-generated class that defines the contract for initializing the GWT application for both the desktop and mobile browser.DesktopInjector
extends ScaffoldInjector
and overrides the getScaffoldApp
method to return ScaffoldDestopApp
. This change in return type is perfectly legal because ScaffoldDesktopApp
is a subclass of the ScaffoldApp
class.ScaffoldDesktopApp
is responsible for creating the web UI, tailored for the desktop web browser, and performing all the initialization work before the application is put into service. Similarly, ScaffoldMobileApp
is responsible for creating the web UI for the mobile browser.GIN's Ginjector
interface is at the heart of the GIN framework and is responsible for performing dependency injection. To use Ginjector
, a GWT application must do the following:
Ginjector
—this is the DesktopInjector
interface in the case of flightapp-gwt
. The GWT compiler is responsible for providing the implementation of this interface.Ginjector
creates the top-level object by injecting dependencies of the lower-level objects based on the bindings configured by the Ginjector
. In the case of the flightapp-gwt
application, ScaffoldDesktopApp
and ScaffoldMobileApp
represent top-level objects.GinModule
or AbstractGinModule
(both are part of GIN API) class that defines bindings for the dependencies. The ScaffoldModule
class in flightapp-gwt
defines bindings for EventBus
, PlaceController
, and ApplicationRequestFactory
.Ginjector
to perform dependency injection with the @Inject
annotation (part of Guice API). In the case of flightapp-gwt
, ApplicationDetailsActivites
, ApplicationMasterActivities
, and so on, make use of the @Inject
annotation to let Ginjector
inject dependencies.Let's now look at the code created by Roo corresponding to each of the activities described previously.
The following code shows the DesktopInjector
interface:
DesktopInjector.java package sample.roo.flightapp.client.scaffold.ioc; .. import sample.roo.flightapp.client.scaffold.ScaffoldDesktopApp; import com.google.gwt.inject.client.GinModules; @GinModules(value = {ScaffoldModule.class}) public interface DesktopInjector extends ScaffoldInjector { ScaffoldDesktopApp getScaffoldApp(); }
DesktopInjector
extends the ScaffoldInjector
interface (which in turn extends GIN's Ginjector
interface) and defines a single method—getScaffoldApp
, which returns the ScaffoldDesktopApp
object. So, the responsibility of Ginjector
implementation generated by the GWT compiler is to return an instance of ScaffoldDesktopApp
with all its dependencies injected with appropriate implementations.
The @GinModules
annotation specifies the class (which implements the GinModule
interface or extends the AbstractGinModule
abstract class) responsible for defining dependencies and their providers. The following code shows the ScaffoldModule
class, which binds EventBus
, PlaceController
, and ApplicationRequestFactory
dependencies to their respective providers:
ScaffoldModule.java package sample.roo.flightapp.client.scaffold.ioc; import com.google.gwt.event.shared.EventBus; import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.inject.client.AbstractGinModule; import com.google.gwt.place.shared.PlaceController; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; public class ScaffoldModule extends AbstractGinModule { @Override protected void configure() { bind(EventBus.class). to(SimpleEventBus.class).in(Singleton.class); bind(ApplicationRequestFactory.class). toProvider(RequestFactoryProvider.class). in(Singleton.class); bind(PlaceController.class). toProvider(PlaceControllerProvider.class). in(Singleton.class); } static class PlaceControllerProvider implements Provider<PlaceController> { private final PlaceController placeController; @Inject public PlaceControllerProvider(EventBus eventBus) { this.placeController = new PlaceController(eventBus); } public PlaceController get() { return placeController; } } static class RequestFactoryProvider implements Provider<ApplicationRequestFactory> { .. } }
The Roo-generated ScaffoldModule
class extends AbstractGinModule
and overrides the configure
method to associate dependencies with their providers. The bind
method of AbstractGinModule
binds a dependency to its provider. The in(Singleton.class)
instructs that only a single instance of EventBus
, PlaceController
, and ApplicationRequestFactory
are created for the application. The PlaceControllerProvider
and RequestFactoryProvider
static inner classes represent provider for the PlaceController
and RequestFactory
instances, respectively. SimpleEventBus
(part of GWT API) is an implementation of EventBus
.
An important point to notice in this code is the use of @
Inject
annotation by PlaceControllerProvider
for instructing Ginjector
to inject an implementation of EventBus
. So, if you are creating a hierarchy of objects, then define the method in Ginjector
, which returns the top-level object because the lower-level objects in the hierarchy can make use of Ginjector
's dependency injection feature using the @Inject
annotation.
The following figure summarizes how the Scaffold
class makes use of the GIN framework to bootstrap the application:
The Scaffold
class' onModuleLoad
method invokes the getInjector
method of DesktopInjectWrapper
to access Ginjector
implementation, followed by a call to getScaffoldApp
to obtain an instance of ScaffoldDesptopApp
. The following sequence diagram further clarifies the sequence of method invocations between classes:
In the above sequence diagram, the call to the run
method of DesktopScaffoldApp
results in initialization of the web UI.
Now that we know how the flightapp-gwt
application makes use of GIN and initializes itself, we'll look at GWT's EntityProxy
and RequestFactory
interfaces and how scaffolded code makes use of them.
An entity proxy represents a client-side (the JavaScript-side) object that mimics an entity on the server-side, which is a JPA entity in the case of the flightapp-gwt
GWT application. Entity proxies act as means to transfer data between client and the server. In GWT, a client-side object that acts as an entity proxy extends GWT's EntityProxy
interface and defines abstract
getter and setter methods for the fields defined in the corresponding server-side entity. In flightapp-gwt
, we have Flight
and FlightDescription
JPA entities; therefore, Roo generates corresponding entity proxies FlightProxy
and FlightDescriptionProxy
, respectively. By default, Roo creates abstract
getter and setter methods in entity proxy for all the attributes defined in the corresponding JPA entity class. The following code shows the FlightProxy
entity proxy:
FlightProxy.java package sample.roo.flightapp.client.managed.request; import com.google.gwt.requestfactory.shared.EntityProxy; import com.google.gwt.requestfactory.shared.ProxyForName; import org.springframework.roo.addon.gwt.RooGwtMirroredFrom; @RooGwtMirroredFrom("sample.roo.flightapp.domain.Flight") @ProxyForName("sample.roo.flightapp.domain.Flight") public interface FlightProxy extends EntityProxy { abstract Long getId(); abstract Integer getVersion(); abstract Date getDepartureDate(); abstract Date getArrivalDate(); abstract FlightDescriptionProxy getFlightDescription(); abstract void setId(Long id); ... }
Roo's @RooGwtMirroredFrom
annotation specifies the fully-qualified class name of the JPA entity for which the FlightProxy
entity proxy was created.
It is the @RooGwtMirroredFrom
annotation that keeps the entity proxy in-sync with the corresponding JPA entity.
The GWT's @ProxyForName
annotation specifies the server-side entity represented by the entity proxy. Notice that in the above code the getFlightDescription
method is defined to return FlightDescriptionProxy
because it represents the entity proxy corresponding to the FlightDescription
JPA entity.
To invoke server-side services, the GWT application's client-side code needs to have client-side stubs for remote services. The client-side code makes use of service stubs to invoke remote services. A service stub is defined by an interface that extends GWT's RequestContext
interface and defines methods with a signature similar to that of the corresponding remote service methods. We'll shortly see the difference between methods defined in the remote service class and the client-side service stub.
By default, Roo generates rich entities and the resulting application doesn't have a service layer; therefore, in the case of the Roo-generated GWT application a service stub defines methods corresponding to the JPA entity class. The following code shows the Roo-generated FlightRequest
service stub corresponding to the Flight
JPA entity in flightapp-gwt
application:
FlightRequest.java package sample.roo.flightapp.client.managed.request; import com.google.gwt.requestfactory.shared.InstanceRequest; import com.google.gwt.requestfactory.shared.Request; import com.google.gwt.requestfactory.shared.RequestContext; import com.google.gwt.requestfactory.shared.ServiceName; import org.springframework.roo.addon.gwt.RooGwtMirroredFrom; @RooGwtMirroredFrom("sample.roo.flightapp.domain.Flight") @ServiceName("sample.roo.flightapp.domain.Flight") public interface FlightRequest extends RequestContext { abstract Request<java.lang.Long> countFlights(); abstract Request<java.util.List<...FlightProxy>> findAllFlights(); .... abstract InstanceRequest<...FlightProxy, java.lang.Void> remove(); abstract InstanceRequest<...FlightProxy, java.lang.Void> persist(); }
As this code shows, a FlightRequest
service stub extends GWT's RequestContext
interface. GWT's @ServiceName
annotation specifies the full-qualified name of the server-side service class corresponding to the client-side service stub, which is the Flight
JPA entity in the case of the FlightRequest
stub. The above code shows that the FlightRequest
service stub defines methods that return the following:
Request<T>
– where T
represents the actual return type of the corresponding method on the server-side class. For instance, countFlights
method returns java.long.Long
in Flight
JPA entity, and so does the countFlights
method in FlightRequest
stub. If a method on the server-side service class returns an entity, then the client-side service stub returns the corresponding entity proxy. For instance, findAllFlights
method in Flight
JPA entity returns a java.util.List<Flight>
, so the findAllFlights
method in FlightRequest
stub returns java.util.List<FlightProxy>
. It is important to note that the methods corresponding to static methods of the server-side service return Request<T>
type in the client-side service stub.InstanceRequest<P,T>
– where P
represents the entity type on which the corresponding server-side service method acts and T
represents the actual return type of the method. For instance, persist
method of Flight
JPA entity acts on Flight
entity instance and returns void
; therefore, the return type of the corresponding method in FlightRequest
stub is InstanceRequest<FlightProxy, java.lang.Void>
. Note that the only stub methods corresponding to instance methods on the server-side service return InstanceRequest
.It is Roo's @RooGwtMirroredFrom
annotation that keeps the client-side stub in-sync with the corresponding JPA entity.
Now, let's look at how RequestFactory
helps with communication between client-side and server-side code.
RequestFactory
acts as a communication bridge between the entity proxy and the corresponding entity on the server-side. RequestFactory
manages entity proxies and is responsible for copying server-side entity attribute values to corresponding entity proxy and vice versa. In your GWT application, you are required to define an interface that extends RequestFactory
interface and provide methods that return client-side stubs for server-side services. Roo creates an ApplicationRequestFactory
class (we discussed earlier that this was created using GIN), which is shown here:
package sample.roo.flightapp.client.managed.request; import sample.roo.flightapp.shared.scaffold.ScaffoldRequestFactory; public interface ApplicationRequestFactory extends ScaffoldRequestFactory { FlightRequest flightRequest(); FlightDescriptionRequest flightDescriptionRequest(); }
The following class diagram shows the inheritance hierarchy of the ApplicationRequestFactory
class:
The above figure shows that ApplicationRequestFactory
extends the Roo-generated ScaffoldRequestFactory
interface, which in turn extends GWT's RequestFactory
interface. The hierarchy is created such that if you want to define your custom client-side service stub methods, then you can add them to the ScaffoldRequestFactory
.
ApplicationRequestFactory
is managed by Roo; therefore, you should not modify it manually to add custom client-side service stub methods. Instead, add them to ScaffoldRequestFactory
.
The interaction between entity proxy and server-side entity is achieved by configuring GWT's RequestFactoryServlet
in the web.xml
file of the GWT web application.
You don't need to configure RequestFactoryServlet
in the web.xml
file of the flightapp-gwt
project because when you execute the gwt setup
command, Roo configures GWT's RequestFactoryServlet
in the web.xml
file.
The following code shows the configuration of RequestFactoryServlet
in the web.xml
file of the flightapp-gwt
application:
web.xml
<servlet>
<servlet-name>requestFactory</servlet-name>
<servlet-class>
com.google.gwt.requestfactory.server.RequestFactoryServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>requestFactory</servlet-name>
<url-pattern>/gwtRequest</url-pattern>
</servlet-mapping>
This configuration shows that by default Roo maps the RequestFactoryServlet
to /gwtRequest
URL, which you can change. If you change the URL mapping of RequestFactoryServlet
, then you also need to change how ApplicationRequestFactory
is created by ScaffoldModule
—the Roo-generated GIN module we discussed earlier.
RequestFactory
requires that the server-side entity must define a no-argument constructor, getId
, getVersion
, and find<Entity>
methods. The getVersion
should return the version, getId
should return the unique ID associated with entity instance, and the find<Entity>
method which accepts entity ID and returns the corresponding entity instance. So, if you create entities using the Roo entity
command, then make sure that you don't set the name of the identifier field or version field to anything other than id
and version
, respectively. When you execute the Roo gwt setup
command, and Roo finds that the names of ID and version fields is different from id
and version
, then it doesn't scaffold the GWT application.
Let's now look at some of the Roo-generated GWT activities and places.
Roo-generated GWT code consists of many base classes and interfaces that attempt to provide a consistent approach to performing CRUD operations on JPA entities. In this section, we'll look at some of the important classes and interfaces and concepts which will give you a starting point to understand the Roo-generated GWT code.
The Roo-generated GWT web UI consists of a Master display region and a Detail display region. The Master display region shows the list of entities that can be managed from the web UI. The widget that shows Flight and FlightDescriptions in a list represents the Master display region. The Detail display region shows activities that can be performed on each of the entities displayed in the Master display region. The region that shows the list of Flight
or FlightDescription
entity instances in the system, the form to create a new entity instance, the form to edit an entity instance, and so on, represents the Detail display region.
The following figure shows the Master display region and Detail display region in the flightapp-gwt
application:
Let's now look at some of the examples of activities, places and views, in the Roo-generated flightapp-gwt
application.
Activities are responsible for driving views and handling events generated by user interaction in a display region. It is created by implementing GWT's Activity
interface or by extending GWT's AbstractActivity
abstract class. The following diagram shows some of the activities that were generated by Roo for flightapp-gwt
application:
This diagram shows that FlightDescriptionDetailsActivity
and FlightDetailsActivity
inherit from the AbstractActivity
abstract class, and FlightDescriptionListActivity
and FlightListActivity
classes implement the Activity
interface. <entity-name>DetailsActivity
activities drive views and manage user interactions when an existing entity instance's details are displayed in the Detail display region. <entity-name>ListActivity
activities drive views and manage user interactions when the list of entity instances are displayed in the Detail display region.
Places are locations within the display region that can be translated into a URL. An Activity is mapped to a place (changeable into a URL), which makes Activities
accessible via URL. A place is created by extending GWT's Place
abstract class. The place implementation class also defines how the place instance can be translated into a URL. ProxyPlace
and ProxyListPlace
classes generated by Roo in flightapp-gwt
are examples of places in GWT. The following figure shows the attributes defined by the ProxyPlace
and ProxyListPlace
classes:
The ProxyPlace
corresponds to a place in the 'detail' display region and ProxyListPlace
corresponds to a place in the Master display region. When a ProxyPlace
instance is created, it knows the EntityProxy
for which the place instance is being created (identified by proxyClass
attribute), the operation to be performed (identified by operation
attribute) on the EntityProxy
, and the unique identifier (identified by proxyId
attribute of type EntityProxyId
) of the EntityProxy
. Similarly, when ProxyListPlace
is created, it knows about the EntityProxy
for which the place instance is being created (identified by the proxyType
attribute).
Each display region is associated with an ActivityMapper
, which maps each Place
in the display region to an Activity
. It is created by implementing GWT's ActivityMapper
interface. ActivityMapper
defines a single method getActivity(Place place)
, which returns an activity corresponding to a place. In the flightapp-gwt
application, ApplicationMasterActivities
is an ActivityMapper
for the Master display region and ApplicationDetailsActivities
is an ActivityMapper
for the Detail display region. The following class diagram shows the ActivityMapper
s created by Roo in the flightapp-gwt
project:
This figure shows that Roo creates FlightActivitiesMapper
and FlightDescriptionActivitiesMapper
classes corresponding to the Flight
and FlightDescription
JPA entities. Even though these activity mappers don't implement GWT's ActivityMapper
interface, they act as activity mappers in the flightapp-gwt
application. These activity mappers return Activity
instances specific to the EntityProxy
. The getActivity
method of an <entity-name>ActivitiesMapper
class accepts a ProxyPlace
(which represents a place in the Detail display region) argument and returns an Activity
for that place. The ApplicationDetailsActivities
activity mapper (which applies to the Detail display region of the web UI) is responsible for creating the FlightActivitiesMapper
and FlightDescriptionActivities
instances depending upon the JPA entity on which the user actions are to be performed. The ApplicationMasterActivities
returns either the FlightListActivity
or FlightDescriptionListActivity
instance, depending upon the JPA entity selected from the Master display region.
An ActivityManager
is associated with a display region and starts and stops an activity when a user navigates from one place to another. It is created by creating an instance of GWT's ActivityManager
class by passing an ActivityMapper
instance and an EventBus
instance. In flightapp-gwt
, ActivityManager
is created for both 'master' and Detail display region when the run
method of ScaffoldDesktopApp
is executed, as shown in the following sequence diagram:
Roo creates an abstract generic class, ApplicationEntityTypesProcessor<T>
, for dealing with different entity proxies in the scaffolded GWT application. ApplicationEntityTypesProcessor<T>
is implemented by classes that perform a functionality based on entity proxy type. The following code listing shows the ApplicationEntityTypesProcessor<T>
class of the flightapp-gwt
project:
ApplicationEntityTypesProcessor.java package sample.roo.flightapp.client.managed.request; public abstract class ApplicationEntityTypesProcessor<T> { private final T defaultValue; private T result; private static void process( ApplicationEntityTypesProcessor<?> processor, Class<?> clazz) { if (FlightProxy.class.equals(clazz)) { processor.handleFlight((FlightProxy) null); return; } if (FlightDescriptionProxy.class.equals(clazz)) { processor.handleFlightDescription( (FlightDescriptionProxy) null); return; } processor.handleNonProxy(null); } .... public abstract void handleFlight(FlightProxy proxy); public abstract void handleFlightDescription(FlightDescriptionProxy proxy); public T process(Class<?> clazz) { setResult(defaultValue); ApplicationEntityTypesProcessor.process(this, clazz); return result; } .... }
ApplicationEntityTypesProcessor<T>
class represents a generic class. The handleFlight
and handleFlightDescription
methods are defined as abstract
methods; therefore, subclasses need to provide implementation of these methods.
You'll find an abstract handleXXX
method defined for each entity proxy in the ApplicationEntityTypesProcessor<T>
class.
The result
attribute identifies the result to return when the public process
method of the ApplicationEntityTypesProcessor<T>
class is invoked. Note that the return type of the result
attribute is a generic type—determined by the generic type associated with the class. The public process
method accepts the class or object of the entity proxy as the argument and internally invokes the private process
method, which in turn calls the handleFlight
or handleFlightDescription
method, depending upon the entity proxy object or class.
So, what is the handleXXX
method expected to do in the implementation class? It simply sets a return value, which is returned when the public process
method is called. Let's see this in the context of an example.
We mentioned earlier that the ApplicationDetailsActivities
class represents an ActivityMapper
for the Detail display region of the Roo-generated GWT application. The ApplicationDetailsActivity
returns a FlightActivityMapper
(specific to the FlightProxy
entity proxy) or FlightDescriptionActivityMapper
instance (specific to the FlightDescriptionProxy
entity proxy) by implementing the ApplicationEntityTypesProcessor<T>
class.
ApplicationDetailsActivities
extends the ApplicationDetailsActivities_Roo_Gwt
class, which actually implements the ActivityMapper
GWT interface.
The following code shows how ApplicationDetailsActivities
makes use of the ApplicationEntityTypesProcessor<T>
class:
ApplicationDetailsActivities_Roo_Gwt.java public Activity getActivity(Place place) { .... final ProxyPlace proxyPlace = (ProxyPlace) place; return new ApplicationEntityTypesProcessor<Activity>() { @Override public void handleFlight(FlightProxy proxy) { setResult(new FlightActivitiesMapper(requests, placeController).getActivity(proxyPlace)); } @Override public void handleFlightDescription(FlightDescriptionProxy proxy) { setResult(new FlightDescriptionActivitiesMapper(requests, placeController).getActivity(proxyPlace)); } }.process(proxyPlace.getProxyClass()); }
In this code, the following are the important points to note:
ApplicationEntityTypesProcessor<T>
is associated with Activity
type.handleFlight
method sets the return value to the Activity
returned from FlightActivityMapper
.handleFlightDescription
method sets the return value to the Activity
returned from FlightDescriptionActivityMapper
.process
method of ApplicationEntityTypesProcessor<T>
is invoked at the end to obtain the return value set by the handleFlight
or handleFlightDescription
method.This code showed that ApplicationEntityTypesProcessor<T>
generic class is associated with an Activity
type and is used to retrieve Activity
specific to FlightProxy
or FlightDescriptionProxy
. Similarly, Roo-generated GWT code makes use of ApplicationEntityTypesProcessor<T>
class to perform other entity proxy-specific processing, like rendering the list of entities in the Master display region.
Let's now look at:
flightapp-gwt
applicationIn the Development Mode, the GWT application is not compiled into JavaScript. You can compile the GWT application into JavaScript and run it using an embedded Jetty container by executing mvn
jetty:run-exploded
command, as shown here:
C:
oo-cookbookch05-gwt> mvn jetty:run-exploded
You can now access the flightapp-gwt
application by entering the following URL: http://localhost:8080/flightapp-gwt/index.html
If you have compiled and deployed the flightapp-gwt
application using the mvn jetty:run-exploded
command, then access the mobile version of the flightapp-gwt
application using the following URL:
http://localhost:8080/flightapp-gwt/index.html&m=true
If you are running the flightapp-gwt
application in Development Mode, then use the following URL:
http://127.0.0.1:8888/ApplicationScaffold.html?gwt.codesvr=127.0.0.1:9997&m=true
In either case, you'll see the mobile version of the flightapp-gwt
application, as shown here:
In this screenshot, the Flights and FlightDescriptions options are clickable, and by selecting them you can get started with performing CRUD operations on JPA entity instances.
If you add, modify, or delete any field from a JPA entity in the Roo-scaffolded GWT application, then Roo makes the necessary changes to GWT artifacts accordingly. To see Roo's round-tripping support for the scaffolded GWT application, start the Roo shell from the root directory of the flightapp-gwt
project and add an aircraftModel
field to the FlightDescription
entity using the field
command or by editing the FlightDescription.java
file directly from your IDE. In response to the addition of the aircraftModel
attribute, the Roo shell shows the following actions taken by Roo:
Updated ...FlightDescriptionProxy.java Updated ...FlightDescriptionListView_Roo_Gwt.java Updated ...FlightDescriptionDetailsView_Roo_Gwt.java Updated ...FlightDescriptionDetailsView.ui.xml Updated ...FlightDescriptionEditView_Roo_Gwt.java Updated ...FlightDescriptionEditView.ui.xml ...
The output shows that Roo updates the GWT entity proxy, FlightDescriptionProxy
, and other GWT artifacts to reflect the modification to the FlightDescription
JPA entity. The other important thing to notice is that most of the modifications are limited to *_Roo_Gwt.java
files—files that are managed by Roo. So, if you make changes to files that don't follow the naming convention *_Roo_Gwt.java
, then such changes will be preserved by Roo (except in the case that you are modifying Java files in the *.client.managed.*
package). In the Roo-scaffolded GWT application, *_Roo_Gwt.java
files
are equivalent to *_Roo_*.aj
AspectJ ITD files, that is, Roo attempts to minimize
the impact on the scaffolded GWT code by only modifying *_Roo_Gwt.java
files.
The GWT logging framework emulates Java Logging API, making it possible to log messages from the Java code that resides in the sample.roo.client
package and its sub-packages. As we mentioned earlier, Java classes contained inside the sample.roo.client
package and its sub-packages are translated into JavaScript by the GWT compiler. The remote logging capability in GWT enables client code to send log messages to the server-side logging infrastructure. To configure remote logging, set the value of the gwt.logging.simpleRemoteHandler
property to ENABLED
in the ApplicationScaffold.gwt.xml
file:
<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED"/>
The above configuration enables remote logging of messages. GWT provides a RemoteLoggingServiceImpl
servlet, which acts as a handler for logging messages received from the client-side. You'll need to configure RemoteLoggingServiceImpl
servlet in your web.xml
file, as shown here:
<servlet> <servlet-name>remoteLogger</servlet-name> <servlet-class> com.google.gwt.logging.server.RemoteLoggingServiceImpl </servlet-class> </servlet> <servlet-mapping> <servlet-name>remoteLogger</servlet-name> <url-pattern> /applicationScaffold/remote_logging </url-pattern> </servlet-mapping>
The important point to note is that the RemoteLoggingServiceImpl
servlet should be mapped to the /<module_name>/remote_logging
URL.
As the method names of the Java class in the client-side are obfuscated when the GWT compiler converts them into JavaScript, you need to resymbolize or deobfuscate them by setting the following properties in the ApplicationScaffold.gwt.xml
file:
<set-property name="compiler.emulatedStack" value="true" /> <set-configuration-property name="compiler.emulatedStack.recordLineNumbers" value="true" /> <set-configuration-property name="compiler.emulatedStack.recordFileNames" value="true" />
Also, you'll need to create a symbol maps directory using the –extra
option of GWT compiler and place it inside a directory accessible to the server-side code, like the WEB-INF/classes
directory of the generated WAR file.
18.117.187.62