In this article I describe a way to "fake" or "stub" your RPC calls in GWT's hosted mode. It allows you to deploy to web mode without having to change any code, change any configurations, and without any stub or development-only code being deployed. It's very clean, and will work with any web application setup (for example, Spring is optional).
This article assumes that you have a good grasp of the following GWT fundamentals:
This technique allows you to not only create wireframes, something GWT is well-suited for anyway, but allows you to do so on top of servlet (as well as UI) prototypes or tracer bullets. This can be a good stone soup with which to introduce GWT gradually into your organization.
The other unique advantage of this technique is that it allows Java web programmers to specialize on programming the front end, without having to first learn a system's entire stack and its related technologies. This is something new in Java. Currently, adding a single HTML field can involve a breath-taking number of fields and
technologies: the schema and sql for the corresponding table column,
Hibernate, Spring, service and mapping layers, Struts or Webwork or
some other framework and its tags, JSP, JSTL, Javascript libraries like
Prototype, and so on. For this reasons alone, it can take a very long time before a junior Java programmer can program the UI, and front-end programming will always be slow. With this stubbing technique and GWT, all a front-end programmer essentially needs to know anymore is Java, HTML, CSS, and Javascript, and coding it can be remarkably swift. At the same time, the front-end is tightly coupled to the Java middleware. The front-end and back-end programmers' code cannot diverge wildly from each other: shared Java interfaces ensure there will be compiler errors if they do. It is the best of both worlds: front-end programming unencumbered by heavy back-end baggage, yet always in synch with it.
The setup has two steps. In step 1, you set up GWT to call one
RPC URL for hosted mode, and another for web mode. In step 2,
you stub your GWT service and configure GWT to call the stub only in
hosted mode.
In GWT, Java services on the server-side receive an Ajax/RPC call from the client. These "services" are simply servlets which extend RemoteServiceServlet. RemoteServiceServlet itself is just an extension of javax's
Servlet class. Hosted mode launches a little Tomcat server to run these servlets in. In a normal Tomcat server, you have to edit web.xml to tell Tomcat which
URLs to route to which Servlets. In hosted mode though, there is no web.xml to edit; instead, you use your GWT module's *.gwt.xml file. (For web mode, which runs in a real Tomcat or other web server, you must associate URLs with servlets in the normal way.)
NOTE: This <servlet> tag has no meaning in web mode. It is completely ignored when you deploy.<module>
<source path ="client"/>
<public path ="public"/>
<entry-point class="com.foo.gwt.myexample.client.MyExample"/>
<servlet path ="/gumby"
class ="com.foo.gwt.myexample.server.MyExampleServiceImpl"/>
</module>
We've just set up your project to use a certain servlet in hosted mode only, if a certain URL is used. But how do we tell the GWT client code to call this URL in hosted mode?
// (1) Create the client proxy. Note that although you are creating the
// service interface proper, you cast the result to the asynchronous
// version of
// the interface. The cast is always safe because the generated proxy
// implements the asynchronous interface automatically.
//
MyEmailServiceAsync emailService = (MyEmailServiceAsync) GWT.create(MyEmailService.class);
// (2) Specify the URL at which our service implementation is running.
// Note that the target URL must reside on the same domain and port from
// which the host page was served.
//
ServiceDefTarget endpoint = (ServiceDefTarget) emailService;
String moduleRelativeURL = GWT.getModuleBaseURL() + "email";
endpoint.setServiceEntryPoint(moduleRelativeURL);
// (3) Create an asynchronous callback to handle the result.
//
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
// do some UI stuff to show success
}
public void onFailure(Throwable caught) {
// do some UI stuff to show failure
}
};
// (4) Make the call. Control flow will continue immediately and later
// 'callback' will be invoked when the RPC completes.
//
emailService.emptyMyInbox(fUsername, fPassword, callback);
// (2) Specify the URL at which our service implementation is running.In Figure 1, we configured the server-side to detect any URL containing "/gumby". The line in Figure 3 moduleRelativeURL = GWT.getModuleBaseURL() + "gumby"; adds "/gumby" to the base URL. Note that
// Note that the target URL must reside on the same domain and port from
// which the host page was served.
//
ServiceDefTarget endpoint = (ServiceDefTarget) emailService;
String moduleRelativeURL = null;
if(GWT.isScript()) // hosted mode
moduleRelativeURL = GWT.getModuleBaseURL() + "gumby";
else // web mode
moduleRelativeURL = GWT.getModuleBaseURL() + "email";
endpoint.setServiceEntryPoint(moduleRelativeURL);
GWT.getModuleBaseURL() is guaranteed to add a forward slash when necessary (that's what its Javadoc says), so "/gumby" or "gumby" will both work here.
public class MyExampleServiceImpl extends RemoteServiceServlet implements MyExampleService
{
protected InfoSvc infoSvc;
public MyExampleServiceImpl() {}
public MyExampleServiceImpl( InfoSvc infoSvc )
{
this.infoSvc = infoSvc;
}
public String getSomeData(String someData) throws SerializableException
{
return invoSvc.infoData(someData);
}
}
public class MyExampleServiceImplStub extends MyExampleServiceImpl
{
protected InfoSvc infoSvc;
/** GWT's Tomcat will call this constructor on the first RPC call. */
public MyExampleServiceImpl()
{
super();
infoSvc = new InfoSvc(...) // feed InfoSvc's constructor
// whatever it needs to stay happy:
// we only need to stub it!
{
public String infoData(String data)
{
return "Some stub data.";
}
}; // end InfoSvc stub
} // end constructor
} // end MyExampleServiceImplStub
<module>
<source path="client"/>
<public path="public"/>
<entry-point class="com.foo.gwt.myexample.client.MyEntryPoint"/>
<servlet path="/gumby" class="com.foo.gwt.myexample.server.MyExampleServiceImplStub"/>
</module>
This approach is very clean. The only development-only code consists of the
<servlet> tag in our *.gwt.xml file, the Stub class, and the
GWT.isScript() conditional in your client code. You don't have to touch any of
this to deploy to web mode, or your production environment. The
<servlet> tag is for hosted mode only anyway. The Stub class -- or
classes, because you'll find you want to write more than one for different
kinds of stub data -- can simply be excluded from deployment by editing your
deployment scripts to ignore all files in the /server directory which end in
"Stub". In fact, a good place to keep them is in your unit test directory, which is obviously never deployed.
