We are going to take a look at what Roo has generated for us. The generated project's structure and organization will be our first focal point. Further into the post, we will delve into the architecture of our GWT application and discover its inner workings. I will sporadically place links to further reading material that provide useful information on the given subject.
The STS Project Explorer gives us a tidied-up hierarchical view of all resources in our project. Each resource has a context menu that allows a range of operations depending on the type of resource.
The Project Explorer provides easy access to the most important resources in our project. Most of the time, we will be working with the following resources:
- The Deployment Descriptor listing provides links to elements within our web.xml file for our Pizza Shop project. The web.xml file is located in the %PROJECTROOT%\src\main\webapp\WEB-INF directory.
- The Java Resources hierarchy contains all Java source code, properties files, GWT module and persistence configuration, and some image files, which GWT will bundle up into a single image to enhance the performance of our Web application.
- Web Resources hierarchy contains HTML host pages and Google App Engine (GAE) configuration.
I will not go into the deployment descriptor as Google has prepared detailed documentation on the subject, including information on elements that are supported by GAE and a list of elements that are not. It is a highly-recommended read for anyone who wishes to find out more on the subject.
The Roo-generated appengine-web.xml is our GAE configuration file and defines the name of our GAE application, its version, property file location for GAE logging, and a switch to enable duplicate EntityManagerFactory creation as shown below. It is located inside the %PROJECTROOT%\src\main\webapp\WEB-INF directory.
The logging.properties file allows the configuration of logging levels for the various loggers present in our application. Once server-side logging has been enabled and application deployed, application logs can be viewed from GAE application management console.
The Roo-generated module definition file is located in the %PROJECTROOT%\src\main\java\%ROOTPACKAGE% directory.
Our module definition file is named ApplicationScaffold.gwt.xml and the root package for our Pizza Shop project is com.blogspot.gwtsts.pizzashop. Inside the module definition file, we can see that our module has been given the name applicationScaffold.
Upon further examination of ApplicationScaffold.gwt.xml we can see that module definition files allow us to:
- Declare other modules that we wish to inherit
- Add packages to the source or public paths of our module
- Define propeties along with a set of allowed values
- Set values for properties defined in our module or in inherited modules
- Define deferred binding rules, including property providers and class generators
- Define the entry point class for our module. More than one entry-point declaration can be made for each module, which will result in successive calls to the onModuleLoad() implementation for each EntryPoint class (including EntryPoints inherited from other modules).
Also notice that the value of mobile.user.agent property is automatically read from the user's browser, which will allow us to display a customized view for mobile devices when such a device is used to access our application.
Now that we have covered most important points on the configuration side, lets look at the source code.
Model View Presenter Pattern
Our Roo-generated Web application uses the Model View Presenter (MVP) pattern, which separates functionality into the model, view, and presenter components. The model is the data to be manipulated, the view takes care of interaction with the browser, and the presenter includes the rendering logic that formats data for display as well as the UI event handling that modifies application state in response to events generated by the view.
Advantages to using the MVP pattern include increased maintainability of medium to large Web applications and simplified and more efficient testing. In a GWT application, the MVP pattern decouples UI widgets and presentation logic, allowing minimized use of the costly GWTTestCase, which relies on the presence of a browser.
The onModuleLoad() method of our EntryPoint class is called when users launch the Pizza Shop Web application. As previously mentioned, our EntryPoint class is declared in our module definition file (ApplicationScaffold.gwt.xml). The last line of our module definition file states that our EntryPoint class is com.blogspot.gwtsts.pizzashop.client.scaffold.Scaffold.
The Scaffold class creates an instance of DesktopInjectorWrapper by default. This class will be replaced by MobileInjectorWrapper if user's browser type is mobilesafari as configured in our module definition file. By default, we end up calling ScaffoldDesktopApp's run() method.
All event handlers are registered to the EventBus with the call to ScaffoldDesktopApp's init() method, lines 62 and 63 remove the "loading" message from display, and line 66 attaches our view to the root panel in the HTML document.
Lets take a more detailed look at the init() method.
UPDATE: Line 108 has to be added manually until a fix for ROO-2269 has been provided.
The section from line 111 to line 134 makes up the most important part of our initialization. This is where the our Presenter is set up. Notice that we are creating two display regions; the master panel that displays a list of our domain object types and the details panel that displays a list of domain objects of the selected type. I will comment more about this block of code in the Activity Pattern section.
The call to handleCurrentHistory() method of the PlaceHistoryHandler on line 133 triggers a PlaceChangeEvent that causes the default Activity to be start()ed.
After completion of the init() method, our Web application is running and ready to respond to user events.
The UiBinder framework allows us to build our applications as HTML pages with GWT widgets sprinkled throughout them. It is not a renderer, which means that it contains no logic such as loops, conditionals, or if statements in its markup. UiBinder allows us to lay out our widgets, but it is still up to the widgets themselves to convert rows of data into rows of HTML. Read more here.
The ~.client.scaffold.ScaffoldDesktopShell makes up the view of the Roo-generated scaffold application. It is made up of ScaffoldDesktopShell.ui.xml and ScaffoldDesktopShell.java bound together by the GWT UiBinder. The following is the contents of the HTML-like ScaffoldDesktopShell.ui.xml file.
UiBinder allows us to import Widget s defined in other packages in our class path. Each imported package is assigned a letter, which is placed in front of Widget s used in the view to declare the package that the Widget resides in. For example, the UiBinder searches the com.google.gwt.user.client.ui package when it encounters a <g:DockLayoutPanel> tag.
The <g:north> element contains the title bar of our application including a LoginWidget that displays the username and a logout link. The <g:south> element contains logos for GWT and Roo. The <g:center> element contains a panel where our Entity s are listed and a panel where details of the selected Entity are displayed.
Notice that we can use regular HTML tags in UiBinder files as long as they are placed inside a <g:HTML> tag.
You might be wondering how the generated application reads and writes entity objects from and to the view. It is all hidden away inside the GWT Editor Framework, which provides the functionality implicitly, using the marker interface pattern.
The login screen that we see after launching our application is a mock App Engine login screen used to simulate GAE login functionality in development mode. There is no view associated with this screen within our project.
I will be discussing application security and security configuration in detail in my next post.
The RequestFactory and JPA provide an easy way to build data-oriented CRUD applications and together make up the data-access layer of our Roo-generated application.
The following diagram depicts how the RequestFactory works with other components of our application to provide data synchronization. This diagram is a slightly-modified version of the one used in the Architecting GWT Apps presentation at Google I/O 2010.
The program flow for creating a new PizzaOrder in our application would be as follows:
- RequestContext's create() method is used to create an instance of PizzaOrderProxy
- A new instance of PizzaOrderRequest is created
- PizzaOrderProxy interface's accessor methods are used to modify the record
- The persist() method is called on PizzaOrderRequest using() our PizzaOrderProxy instance
- PizzaOrderRequest is fire()d off with a callback object that handles the case of success
- Callback object is notified of success
- Display is updated
In compliance with our directive, Roo has placed all our Entity classes inside the ~.server.domain package. The following is our Roo-generated Base class:
Notice that our Base Entity class contains only field declarations and annotations. @NotNull annotation declares that this field cannot be null and @Size (min = 2) declares that the value of this field must contain 2 characters or more. Accessor methods for our Base Entity are located in Base_Roo_JavaBean.aj AspectJ file, entity methods in Base_Roo_Entity.aj, and toString() method in Base_Roo_ToString.aj.
AspectJ files are maintained by Spring Roo and will be updated automatically in response to any changes we make to the Entity class.
Objects that implement the EntityProxy interface are Data Transfer Objects (DTO) and client-side representations of corresponding Entity objects. The exclusive use of EntityProxy interface on the client side relieves us from the requirement of writing GWT-compatible code in our Entity implementations.
The following is our BaseProxy interface:
The BaseProxy interface, along with others that extend EntityProxy interface, enables RequestFactory to determine fields that have changed and send only these changes to the server. This functionality allows for more efficient client-server communication in GWT applications that use the RequestFactory. Implementations of BaseProxy interface methods have been generated in the previously-mentioned Base_Roo_JavaBean.aj and Base_Roo_Entity.aj AspectJ files.
In the application that Roo has generated for us, the Activity is the implementation of the Presenter component and IsWidget is the implementation of the View component of our MVP pattern. The following diagram, also taken from Ray Ryan's Architecting GWT Apps presentation, shows the life cycle of an Activity.
Notice how the ActivityManager responds to PlaceChangeEvents by swapping Activity s and as the next Activity is start()ed it communicates with the server to load the data necessary to initialize its view.
An Activity is completely isolated from the view and contains no widgets or UI code. This isolation greatly simplifies the testing of logic contained within an Activity. All that is needed to test an Activity is to tie it up to a mock ActivityManager and a mock RequestFactory. An Activity is started with a call to its start() method, which takes a display region (AcceptsOneWidget) and an EventBus as arguments. The initialization performed by the start() method can be run asynchronously. In this case, the display region is passed the View object in the form of an object that implements IsWidget when the initialization is complete.
A Place is a bookmarkable state for a Web application. An Activity needs a corresponding Place in order to be accessible via a URL. A Place extends com.google.gwt.app.place.Place and has an associated PlaceTokenizer that serializes the Place's state to a URL token. By default, the URL consists of the Place's simple class name (ie. ProxyPlace) followed by a colon (:) and the string returned by the associated PlaceTokenizer's getToken() method. The PlaceTokenizer can be declared as a static class inside the corresponding Place, but it is not mandatory to have a PlaceTokenizer for each Place. Many Places within a Web application may not save their state to the URL and could just extend a BasicPlace, which comes with a PlaceTokenizer declaration that returns a null token.
As many Place subclasses as needed can be defined in a Web application, which will be instantiated anew each time users navigate within an application. The Place class doesn't define any methods, but subclasses are expected to correctly implement
Object.hashCode(). Subclasses of Place can also be used to carry data from one Activity to the next.
See ProxyPlace class inside the com.blogspot.gwtsts.pizzashop.client.scaffold.place package for an example of a Place in Roo-generated scaffold application.
The PlaceController manages the current Place and navigation between Places in a Web application and makes the back-button and bookmarks work as users would expect.
Since we are managing an application's state with the PlaceController, there should not be more than one PlaceController instance per application.
It is the PlaceController 's goTo() method that fires PlaceChangeEvents on the EventBus, which in turn notifies registered components about these events.
Each display region has its own ActivityManager, which responds to User action by stopping one Activity and starting another. To put it another way, the ActivityManager swaps Activity s in its display region in response to PlaceChangeEvents. As the next Activity is initialized with its start() method, the previous Activity is still displayed and accessible by the User. Developers have the choice, at this point, of disabling the view, displayed by the stopped Activity, or to allow it to trigger navigation.
The following is application flow from the ActivityManager perspective:
- User navigates from one Place to another
- mayStop() method of the current Activity is called
- mayStop() asks for confirmation from the User before navigating via PlaceChangeRequestEvent
- If the confirmation is obtained, then a PlaceChangeEvent is fired and the current Activity is stopped with a call to its onStop() method
- The ActivityMapper determines the next Activity depending on the current place and passes it to the ActivityManager
- The next Activity's start() method is invoked by the ActivityManager that is responsible for the current display region (AcceptsOneWidget)
- When the next Activity is fully initialized, it asks to be displayed
Since we have two display regions in our application, two ActivityManagers have been created for us; the masterActivityManager and detailsActivityManager (see lines 114 and 122 shown in the Startup section). Each ActivityManager is initialized with a display region to manage at module startup.
Note that the initialization of an Activity can be asynchronous, which would enable the User to change its mind before the Activity is fully initialized and navigate to another Place. Instead of onStop() method of the current Activity, the onCancel() method would be called in this case, which doesn't require a prior call to mayStop() as the Activity isn't fully started.
Another issue to note is that there can be several Activity s running concurrently in different display regions. Because of this, it is possible that multiple mayStop() requests are launched at the same time. Not to worry though, as these won't interfere with each other.
ActivityMappers are associated with a display region and direct the ActivityManager to the appropriate Activity for a given Place.
For an example of an ActivityMapper generated for our Pizza Shop application, see com.blogspot.gwtsts.pizzashop.client.managed.activity.BaseActivitiesMapper.
ActivityWrappers provide a layer of abstraction between the Presenter and the View; they establish the connection between the Presenter's representation of the View and the View's implementation.
Notes On Managed Source Code
All managed source code, such as classes under the ~.client.managed package, are managed by Roo and will be automatically updated when changes are made to any Entity class. You will need to have Roo up and running inside STS for the automatic updates to take place. Files under the ~.client.managed package should not be modified manually, except for those inside ~.client.managed.ui package ending with .ui.xml which are UiBinder files and can be changed to rearrange the view.
Notes On Application Performance
The costliest of operations on the client side are those that manipulate the DOM and should be avoided where possible to increase application performance.
Our Roo-generated application creates views as singletons to avoid repeating the costly process of view creation. On the other hand, Presenters, which are light-weight POJOs, are recreated rather than being cleaned up each time they're recycled. This is implemented by listening to User events through the registration of a presenter delegate to the view. view.setDelegate(this) is called from an Activity's start() method and view.setDelegate(null) is called from its onStop() method.
In The Next Post
We are going to make our first attempt at customizing the Roo-generated project by modifying the way GAE authentication currently works. After our changes, users with GAE administrator privileges and non-admin users will see different content.
|Intro||Part I||Part II||Part III||Part IV||Part V|
Also check out The Unofficial Google Web Toolkit Blog