Tuesday, March 22, 2011

Part III - Exploring the Roo-Generated GWT Application

In This Post

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.


Project Organization

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.


The 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.

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>pizzashopexample</application>
    <version>1</version>
    <sessions-enabled>true</sessions-enabled>
    <!-- Configure java.util.logging -->
    <system-properties>
        <property name="java.util.logging.config.file"
          value="WEB-INF/logging.properties"/>
        <property name="appengine.orm.disable.duplicate.emf.exception" 
          value="false"/>
    </system-properties>
</appengine-web-app>

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.

<module rename-to="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
    <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.user.theme.standard.Standard"/>
    <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
    <inherits name="com.google.gwt.user.cellview.CellView"/>
    <inherits name='com.google.gwt.logging.Logging'/>
    <inherits name="com.google.gwt.inject.Inject"/>
    <inherits name="com.google.gwt.text.Text"/>

  • Add packages to the source or public paths of our module
    <source path='client'/>
    <source path='shared'/>

    <public path="public"/>

  • Define propeties along with a set of allowed values
    <define-property name="mobile.user.agent" values="mobilesafari, none"/>

  • Set values for properties defined in our module or in inherited modules
    <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.firebugHandler" value="ENABLED"/>
    <set-property name="gwt.logging.hasWidgetsHandler" value="DISABLED"/>
    <set-property name="gwt.logging.popupHandler" value="DISABLED"/>
    <set-property name="gwt.logging.systemHandler" value="ENABLED"/>
    <set-property name="gwt.logging.simpleRemoteHandler" value="DISABLED"/>

  • Define deferred binding rules, including property providers and class generators
     <replace-with class="com.blogspot.gwtsts.pizzashop.client.scaffold.ioc.MobileInjectorWrapper">
       <when-type-is class="com.blogspot.gwtsts.pizzashop.client.scaffold.ioc.DesktopInjectorWrapper"/>
       <all>
         <when-property-is name="mobile.user.agent" value="mobilesafari"/>
       </all>
     </replace-with> 

  • 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).
    <entry-point class="com.blogspot.gwtsts.pizzashop.client.scaffold.Scaffold"/>

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. 


Startup

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.

public class Scaffold implements EntryPoint {
    final private InjectorWrapper injectorWrapper = GWT.create(DesktopInjectorWrapper.class);

    public void onModuleLoad() {
        /* Get and run platform specific app */
        injectorWrapper.getInjector().getScaffoldApp().run();
    }
}

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.

public void run() {
    /* Add handlers, setup activities */
    init();

    /* Hide the loading message */
    Element loading = Document.get().getElementById("loading");
    loading.getParentElement().removeChild(loading);

    /* And show the user the shell */
    RootLayoutPanel.get().add(shell);
}

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.

private void init() {
    GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
        public void onUncaughtException(Throwable e) {
            Window.alert("Error: " + e.getMessage());
            log.log(Level.SEVERE, e.getMessage(), e);
        }
    });

    if (LogConfiguration.loggingIsEnabled()) {
        // Add remote logging handler
        RequestFactoryLogHandler.LoggingRequestProvider provider = new RequestFactoryLogHandler.LoggingRequestProvider() {
            public LoggingRequest getLoggingRequest() {
                return requestFactory.loggingRequest();
            }
        };
        Logger.getLogger("").addHandler(
            new RequestFactoryLogHandler(provider, Level.WARNING,
            new ArrayList<string>()));
    }

    RequestEvent.register(eventBus, new RequestEvent.Handler() {
        // Only show loading status if a request isn't serviced in 250ms.
        private static final int LOADING_TIMEOUT = 250;

        public void onRequestEvent(RequestEvent requestEvent) {
            if (requestEvent.getState() == RequestEvent.State.SENT) {
                shell.getMole().showDelayed(LOADING_TIMEOUT);
            } else {
                shell.getMole().hide();
            }
        }
    });

    // AppEngine user authentication
    new GaeLoginWidgetDriver(requestFactory).setWidget(shell.loginWidget);
    new ReloadOnAuthenticationFailure().register(eventBus);

    CachingActivityMapper cached = new CachingActivityMapper(applicationMasterActivities);
    ProxyPlaceToListPlace proxyPlaceToListPlace = new ProxyPlaceToListPlace();
    ActivityMapper masterActivityMap = new FilteredActivityMapper(proxyPlaceToListPlace, cached);
    final ActivityManager masterActivityManager = new ActivityManager(masterActivityMap, eventBus);
    masterActivityManager.setDisplay(shell.getMasterPanel());

    ProxyListPlacePicker proxyListPlacePicker = new ProxyListPlacePicker(placeController, proxyPlaceToListPlace);
    HasConstrainedValue<proxylistplace> listPlacePickerView = shell.getPlacesBox();
    listPlacePickerView.setAcceptableValues(getTopPlaces());
    proxyListPlacePicker.register(eventBus, listPlacePickerView);

    final ActivityManager detailsActivityManager = new ActivityManager(applicationDetailsActivities, eventBus);

    detailsActivityManager.setDisplay(shell.getDetailsPanel());

    /* Browser history integration */
    ScaffoldPlaceHistoryMapper mapper = GWT.create(ScaffoldPlaceHistoryMapper.class);
    mapper.setFactory(placeHistoryFactory);
    PlaceHistoryHandler placeHistoryHandler = new PlaceHistoryHandler(mapper);
    if (getTopPlaces().iterator().hasNext()) {
        ProxyListPlace defaultPlace = getTopPlaces().iterator().next();
        placeHistoryHandler.register(placeController, eventBus, defaultPlace);
        placeHistoryHandler.handleCurrentHistory();
    }
}

UPDATE: Line 108 has to be added manually until a fix for ROO-2269 has been provided.

Lines 75-80 set up the mechanism to display any uncaught exceptions in a dialog box and add them to client-side logs.  The block of code that adds a remote logging handler in lines 82-92 will only get translated into JavaScript if logging has been enabled in our module definition file.  You can read more about client-side logging with GWT at this link.  Lines 94-105 deal with the "loading" message displayed at startup and line 109 arranges our Web application to be reloaded in case of an authentication failure.

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.


UiBinder

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.


View

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.

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
      xmlns:g='urn:import:com.google.gwt.user.client.ui'
      xmlns:s='urn:import:com.blogspot.gwtsts.pizzashop.client.scaffold.ui'>

  <ui:image field='gwtLogo' src="../style/images/gwtLogo.png"/>
  <ui:image field='rooLogo' src="../style/images/rooLogo.png"/>
  <ui:style>
    ...
  </ui:style>

  <g:DockLayoutPanel unit='EM'>
    <g:north size='6'>
      <g:HTMLPanel styleName='{style.centered}'>
        <div class='{style.banner}'>
          <div class='{style.error}' ui:field='error'></div>
          <span class='{style.title}'>
            <h2>Data Browser</h2>
          </span>
          <s:LoginWidget styleName='{style.login}' ui:field="loginWidget"/>
        </div>
      </g:HTMLPanel>
    </g:north>
    <g:south size='2'>
      <g:HTML>
        <div class='{style.logos}'>
          <span>Powered by:</span>
          <a href='http://code.google.com/webtoolkit/'>
            <div class='{style.gwtLogo}'></div>
          </a>
          <a href='http://www.springsource.org/roo/'>
            <div class='{style.rooLogo}'></div>
          </a>
        </div>
      </g:HTML>
    </g:south>
    <g:center>
      <g:FlowPanel styleName='{style.content} {style.centered}'>
        <g:SimplePanel styleName='{style.entities}'>
          <g:ValuePicker styleName="{style.entitiesList}" width='100%' pageSize='100' ui:field='placesBox'/>
        </g:SimplePanel>
        <g:FlowPanel>
          <g:NotificationMole animationDuration='0' message='loading...' ui:field='mole'></g:NotificationMole>
          <g:SimplePanel styleName="{style.entityDetails}" ui:field='master'></g:SimplePanel>
        </g:FlowPanel>
        <g:SimplePanel styleName="{style.entityDetails}" ui:field='details'></g:SimplePanel>
      </g:FlowPanel>
    </g:center>
  </g:DockLayoutPanel>
</ui:UiBinder>

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.


Editor Framework

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.


Security

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

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
Built to complement the service-oriented GWT RPC and not replace it, the data-oritented RequestFactory is GWT's new mechanism that provides more efficient client/server data transfer.  It allows us to define data-centric Entity classes on the server side and to define business logic with EntityProxy on the client side.


Entity

Entity s are server-side Data Access Objects (DAO) in our RequestFactory mechanism and should be placed in a package that will not be converted to JavaScript by GWT.  In our application, ~.server is such a package since we have not declared it as a source path in our module definition file.

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:

@RooJavaBean
@RooToString
@RooEntity
public class Base {
    @NotNull
    @Size(min = 2)
    private String name;
}

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.


EntityProxy

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:

package com.blogspot.gwtsts.pizzashop.client.managed.request;

import ...

@RooGwtMirroredFrom("com.blogspot.gwtsts.pizzashop.server.domain.Base")
@ProxyForName("com.blogspot.gwtsts.pizzashop.server.domain.Base")
public interface BaseProxy extends EntityProxy {
    abstract Long getId();
    abstract void setId(Long id);
    abstract Integer getVersion();
    abstract void setVersion(Integer version);
    abstract String getName();
    abstract void setName(String name);
}

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.


Activity Pattern

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.


Activity

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.


Place

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.equals(Object) and 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.


PlaceController

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.


ActivityManager

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:
  1. User navigates from one Place to another
  2. mayStop() method of the current Activity is called
  3. mayStop() asks for confirmation from the User before navigating via PlaceChangeRequestEvent
  4. If the confirmation is obtained, then a PlaceChangeEvent is fired and the current Activity is stopped with a call to its onStop() method
  5. The ActivityMapper determines the next Activity depending on the current place and passes it to the ActivityManager
  6. The next Activity's start() method is invoked by the ActivityManager that is responsible for the current display region (AcceptsOneWidget)
  7. 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.


ActivityMapper

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.


ActivityWrapper

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.



IntroPart IPart IIPart IIIPart IVPart V



10 comments:

  1. Just a quick note of thanks for posting this series. They've proven very interesting and I've been enjoying following them.

    ReplyDelete
  2. You are welcome. I am glad that you've enjoyed them.

    ReplyDelete
  3. Hi I just played couple of days with Roo and it looks good
    Since it generated the Code I wanted to customize the application by creating my own page
    and adding some methods
    If you have any samples please can you send it to me
    Right now I an just doing trial and error method
    Thanks
    Kantha

    ReplyDelete
  4. Kantha, Ebrahim,
    I am planning on publishing the next part by Thursday the latest. In the meanwhile, you could run your projects in debug mode and go through the code step by step. It helps a great deal in understanding the inner workings of the generated app.
    Please also notice the UPDATE note that I have placed above. Without this fix, your login functionality will not work properly.

    ReplyDelete
  5. Do you recommand use of spring roo + gwt setup for application which do not have the typical spring roo scaffold design.

    E.g. I want to have a mainnavigation and subnavigation and one Contentarea, where I display either the cellTable or the details of a record.

    Is this easy to accomplish with the roo (and gwt setup) generated code.
    Do you have any advice how to achieve the design mentioned above or how to change roo generated code (best practise).

    I am totally lost in customizing the roo generated code, I am very thankful for any kind of help.

    Thanks, Alex

    ReplyDelete
  6. The biggest advantage of using Roo to generate your GWT application is that it sets up your app to handle requests efficiently and be highly maintainable with the RequestFactory and the MVP pattern. With your infrastructure set up, you can then concentrate on customizing your view. To customize the generated scaffold view, you can start by modifying ScaffoldDesktopShell layout to suit your design. You might want to read Part IV as it makes minor modifications to ScaffoldDesktopShell.

    ReplyDelete
  7. Hello, your article is really Good! I'm still not completely sure of using Spring Roo in my projects but something that I found useful in the Roo Spring+GWT implementation is that gives me a complete example of the best practices that I should follow to create a GWT web application correctly wired with spring.

    I'm a newbie in the three frameworks so maybe this is a silly question but after inspecting the ScaffoldWrapper Code I found that the ScaffoldApplication is injected using GIN.

    The question is, if spring have its own way to inject dependencies why use a third party library. Is used because some sort of incompatibility between spring and GWT? Could you explain a little deeper the ScaffoldModule class?

    ReplyDelete
  8. I think I got my answer reading the GIN documentation. It say that you must use GIN in the client package of a GWT app because for example GUICE can't be compiled to javascript mainly because this IoC framework rely on reflection that is not supported by GWT at this point. I suppose that this is the same reason that spring IoC can't be used in GWT client package. Am I correct?

    Anyway, could you provide a deeper explanation of client.scaffold.ioc package?

    ReplyDelete
  9. Hi Ricardo,

    Thanks, I am glad you liked the article.

    Yes, exactly. Guice and Spring IoC cannot be used because the GWT compiler does not support reflection.

    I will add to part 3, to explain it in further detail.

    ReplyDelete