Recently I've been playing around with some GWT ideas that really cry out for a more liberal deferred binding system. Currently, GWT imposes the restriction that deferred binding can only happen through the GWT.create() method. There's a couple of problems with this:
- Can't narrow type signature for custom library
- Can't create a method to decorate the returned result
- Can't parameterized or override the bindings at callee site
To illustrate this, I've compiled some motivation examples.
RPC requests signed by OAuth
interface MyService extends RemoteService<MyServiceAsync> { ... }
// example call
OAuth.withSignature(MyService.class).method1(arg1, arg2, asyncCallback);
public class OAuth {
@GwtCreate // method must be static, class parameter must be final
public static <S, T extends RemoteService<S>> S withSignature(final Class<T> service) {
// GWT.create with non-literal ONLY allowed
// if enclosing method is @GwtCreate and
// variable statically resolvable to literal param
S async = GWT.create(service);
((ServiceDefTarget)async).setRequestBuilderCallback(new OAuthRequestBuilderSigner());
return async;
}
}
Currently today, it would look like this:
MyServiceAsync foo = GWT.create(MyService.class);
RequestBuilder rb = foo.method1(arg1, arg2, asyncCallback);
OAuthRequestBuilderSigner.sign(rb);
rb.send();
The tight coupling at the callsite also makes it difficult to swap out implementations easy (like using AuthSub, or one of the other 4 Google Friend Connect auth techniques). An alternative is to make a separate subtype of each with a custom generator, e.g.
MyServiceOAuth extends MyService, GWT.create(MyServiceOAuth.class)
I'd argue that the above gets cumbersome with multiple services and multiple authentication types. I've taken some liberties above by adding a type parameter to RemoteService to make the return type statically resolvable, as well as allowing a global callback mechanism on ServiceDefTarget for RequestBuilder override. Note that type-safety is ensured, one can't call this method with something that is not a RemoteService, and it will be flagged at edit-time in your IDE.
An EasyMock library for Hosted/Web Mode
Subscriber mock = GMock.mock(Subscriber.class);
publisher.add(subscriber);
//...
GMock.replay(mock);
public class GMock {
// selectively override module binding rules ONLY for this method
@GwtCreate(generator=com.gmock.rebind.GMockGenerator.class)
public static <T> T mock(Class<T> toMock) {
return GWT.create(toMock);
}
}
In this method, the
mock()
method acts like GWT.create()
except that it overrides the current binding rules, forcing the specified generator.GWT Exporter
Exporter.export(Foo.class);
public class Exporter {
@GwtCreate
public static <T extends Exportable> void export(Class<T> exportable) {
ExporterImpl ximpl = GWT.create(exportable);
ximpl.export();
}
}
Note, the type safety, one can't try to Export a non-exportable class. This will be flagged at edit time in the IDE.
GIN/Guice dependency injection
Processor pimpl = Gin.inject(Processor.class, TestProcessorModule.class);
public class Gin {
@GwtCreate(generator = com.google.gin.rebind.GInjectorGenerator)
public static <T, S extends AbstractGinModule>
T inject(Class<T> interf, @GwtCreateParam Class<S> module) {
return GWT.create(interf, module);
}
}
This one is more controversial, but allows GWT.create() to be parameterized by literal metadata that is available to the generator. This is semantically equivalent to what we have today:
@GinModule(TestProcessorModule.class)
interface MyTestInjector extends GInjector {
Processor getProcessor();
}
but without the need to actually write the interface.
Combing compile-time and run-time parameters
Final example, mix-and-match both compile-time variables and run-time variables:
OAuth.withSignature(MyService.class, debugMode ? "/debugService" : "/MyService").method1(arg1, arg2, asyncCallback);
public class OAuth {
@GwtCreate // method must be static, class parameter must be final
public static <S, T extends RemoteService<S>>
S withSignature(final Class<T> service, String endPoint) {
// GWT.create with non-literal ONLY allowed if enclosing method is
// @GwtCreate and variable statically resolvable to literal param
S async = GWT.create(service);
((ServiceDefTarget)async).setRequestBuilderCallback(new OAuthRequestBuilderSigner());
((ServiceDefTarget)async).setServiceEntryPoint(endPoint);
return async;
}
}