Pluggable modules

269 views
Skip to first unread message

manstis

unread,
Jun 9, 2011, 6:30:09 AM6/9/11
to Google Web Toolkit
Firstly, I know a lot has been said about this in the past and I have
read many posts but I did not see an immediate solution so would
appreciate the opinion of those more knowledgable than I.

I'd like to create a single GWT application WAR (let's call it
"container") that can "host" (or embed) other GWT applications (let's
call an example "editor"). All, for simplicity, would be deployed on
the same web-server.

The "container" has a client-side service API that uses GWT-RPC to
server-side services to be shared by the "container" and "editor" e.g.
"loadData(..)".

Can I use gwt-exporter to export the "service" client-side API and use
gwt-api-interop to create an equivalent cross-application Java
"service" API that "editor" can utilise? For example, the service API
has a method "addEditorToContainer(Widget)" so the "editor" can add
itself to the "container".

I've also read about EventBus becoming available outside of GWT(?); if
I've read correctly is it possible to use a single EventBus across
both applications?

I appreciate this becomes trivial if I add the "editor" into the
"container" application before compile-time; but ideally I want to
allow for third-parties to develop "editors" without having to re-
compile it into "container". In theory, all they'd need to do is
reference the gwt-api-interop generated common API.

Perhaps my questions are mis-guided; and, if anybody else has
experience and success of writing a "pluggable" infrastructure,
perhaps you'd be so kind as to share what you've learnt?

With kind regards,

Mike

lemaiol

unread,
Jun 11, 2011, 2:36:20 PM6/11/11
to Google Web Toolkit
HI Mike,

If I got it right, you want a main GWT application (own WAR) and
several other GWT applications (own WAR each) that could be loaded
altogether in the same host page (the one from the main application)
and communicate using a "dispatching" interface (maybe an event bus).
I have implemented exactly this for a customer and it is definitely
possible.

The problem is mainly how to communicate GWT classes which have been
compiled and "linked" in different applications. Also when the Java
classes are the same (shared through a common library) the GWT compile
process produces a result that is neither linked together because of
the separated compilations nor linked together at load time. JSNI
comes to the rescue allowing the embedding of native JS code that
remains the same even after the compile process (ignored by GWT
compiler).

I used JavaScriptObject classes to define the plugins common data
model and implemented a pair of JSNI classes to do the gluing. I also
used a server side mechanism to configure the plugins repository and
to generate the GWT host page dynamically. This will load the plugins
from the remote web applications and generate some required
structures.

If this is what you need, just tell me which details you are
interested in.

What you mentioned about using EventBus out of GWT would be an
improvement. Could you point me to where you read about it? Have you
got also other threads about this topic? I would like to learn about
others needs and ideas.

Hope it helps,
Berto

Daniel G

unread,
Jun 15, 2011, 5:09:18 AM6/15/11
to Google Web Toolkit
Hi Berto,
Hi Mike,

can you give me some more details about your plugin-design? What
architecture are you using for the several applications, MVC or MVP?

Regards,
Daniel

kendeng9898

unread,
Jun 15, 2011, 8:08:29 PM6/15/11
to Google Web Toolkit
Hi Berto,
Waiting for more details. give some simple sample code is
appreciate.

Since the compilation speed & easy development, I've changed to use
pyjamas project, a python verion of GWT. But I still interesting in
how to do the plugin. The hardest part is how to solved the different
compilation and linked.

Thanks,
Ken

manstis

unread,
Jun 16, 2011, 6:21:25 AM6/16/11
to Google Web Toolkit
Hi Berto,

Sorry for the delay in replying, I am travelling and not always near
an internet connection.

I would be most interested in:-

* "I used JavaScriptObject classes to define the plugins common data
model and implemented a pair of JSNI classes to do the gluing."

>> I was hoping to just use gwt-exporter to build my Javascript, and gwt-api-interop to hopefully expose the Javascript as a JAR.

* "I also used a server side mechanism to configure the plugins
repository and
to generate the GWT host page dynamically. This will load the
plugins from
the remote web applications and generate some required structures."

>> I've not given much thought to plugin registration\activation (until I know they are possible). Any thoughts are welcome.

This thread:
https://groups.google.com/group/google-web-toolkit-contributors/browse_thread/thread/6a50ab53b29d93db/d5adfec31a925ce1?hl=en&lnk=gst&q=eventbus#d5adfec31a925ce1
led me to believe EventBus was going to become available outside of
GWT however I see the commits have subsequently been rolled back :(

With kind regards,

Mike

lemaiol

unread,
Jun 17, 2011, 6:50:12 AM6/17/11
to Google Web Toolkit
Yes, I think those changes in the event bus (also in the packages) is
to share the code in Android.

> This thread:https://groups.google.com/group/google-web-toolkit-contributors/brows...

lemaiol

unread,
Jun 17, 2011, 7:20:36 AM6/17/11
to Google Web Toolkit
I followed the basic ideas of application's linking and loading:
- The GWT host page is the loader. So you have to know how to
discover the modules to be loaded.
+ KIS: have a class on the server side that know modules' ids,
names and URL's and generate the GWT host page using a JSP.
+ Best practice: map the default GWT page's name to the JSP within
your web.xml. From outside nobody will know that the page is not
static.
+ The resulting page has as many script tags to load the
*.nocache.js files as modules should be loaded.
- Once that all the applications (independent WARs) have been loaded,
they all live in the same application space: the browser window. But
they have each an own GWT application space. The JS engine is common
to all and native JS (not generated from GWT) is more pervasive and
does not get affected by GWT compilation + linking.
- Implement a communication mechanism based on JS (ex: message
dispatch facility)
+ Create an structure in the GWT page where the plugins can
register themselves. An JSON object will do the trick: "var pluggins =
{{name: 'plugin1', callback: ''}, {name: 'plugin1', callback:
''}, ...};" Because it is embedded in the page, it is already there
(static) when all applications are loaded. No application is assured
to be loaded and initialized first!
+ Create GWT wrapper classes inheriting from "JavaScriptObject" to
access this structure (ex: "register" / "attach", "sendMessage",
"receive"). Define communication and plugin interfaces and pack all in
a jar with sources and <module>.gwt.xml = GWT module.
+ Use JSNI for the methods implementation and calling back into
GWT. Use these classes in each plugin application. The GWT compiler
with compile and link the GWT code but the JSNI code remains as it is.
- Because at some time, the individual plugins have to be shown in
the web page, create an static HTML structure with explicit HTML IDs.
One or more of them will be populated by the plugins once at a time.
It is important that it is static because, once again, it belongs to
the broswer application space (window) and not to a concrete GWT
application space. Said in another way, none of you applications
should generate the DOM elements for other plugins neither from code
nor with UiBinder.
- One plugin is the main plugin and he is the first to show up + he
normally dispatches to other plugins or offers the dispatching
mechanism.
- Your application needs a common message model for the messages.
Should inherit from JavaScriptObject and be created with the class
factory methods and the populated.

Which snippets do you want?

Berto

manstis

unread,
Jun 21, 2011, 7:32:27 AM6/21/11
to Google Web Toolkit
Hi,

Thanks for your invaluable help.

I'm mashing together a simple example for me to learn from that has a
single statically defined plug-in.

I am growing to believe I need to deploy each plug-in as a separate
WAR on a web-server. Is this true, or have I missed something?

I am also using gwt-exporter to make the JSNI messaging mechanism;
i.e. I just write an "Exportable" class and use the resulting JS API
from my plugins. I am hoping to use gwt-api-interop to remove the need
to use the JS API.

I assume the JS "bridge" between GWT applications handles the
messaging between them (i.e. plugins register callbacks to handle
messages sent from the "host"?)

If I understand correctly, adding additional script tags to my
"loader" page and having each plug-in as a separate WAR I avoid the
need to inherit in my gwt.xml file?

An example of your use of JavaScriptObject in your messaging sub-
system would be interesting....

Thanks again.

Mike

lemaiol

unread,
Jun 21, 2011, 2:32:20 PM6/21/11
to Google Web Toolkit
Hi Mike,

> I am growing to believe I need to deploy each plug-in as a separate
> WAR on a web-server. Is this true, or have I missed something?

In the case of my architecture, it is even a requirement that every
plugin will be deployed as an own WAR file. It allows different teams
working in parallel in completely independent applications (plugins)
which have a common "message data model". When inside the plugins
container, they receive messages with this "common data model". When
out of the plugins container, the show a simplified UI to let the
developer pass a "message" generated by hand and therefore test the
application.

> I am also using gwt-exporter to make the JSNI messaging mechanism;
> i.e. I just write an "Exportable" class and use the resulting JS API
> from my plugins. I am hoping to use gwt-api-interop to remove the need
> to use the JS API.

I have seen gwt-exporter but do not know gwt-api-interop.

> I assume the JS "bridge" between GWT applications handles the
> messaging between them (i.e. plugins register callbacks to handle
> messages sent from the "host"?)

Yes.

> If I understand correctly, adding additional script tags to my
> "loader" page and having each plug-in as a separate WAR I avoid the
> need to inherit in my gwt.xml file?

My common model and plugin base classes live in a common GWT module
that each plugin inherits.

> An example of your use of JavaScriptObject in your messaging sub-
> system would be interesting....

// Application specific common model
public final class CustomerInfo extends ModuleInfo {
protected CustomerInfo() {
}

public native String getId() /*-{
return this.id;
}-*/;

public native void setId(String id) /*-{
this.id = id;
}-*/;
}

// Base class for common models - no functionality as of now
public class ModuleInfo extends JavaScriptObject {
protected ModuleInfo() {
}
}

// To be implemented by every module
public interface IModule<T extends ModuleInfo> {
public String getName();
public void dispatch(String containerId, T moduleInfo);
}

// To be implemented by the main module or classes interested in
module lifecycle events
public interface IModuleAware<T extends ModuleInfo> {
public void onModuleAttached(Module<T> module);
}

// Durable callback implementation - Because it uses JSNI, can be
inherited from GWT modules but it is interoperable. Avoids using gwt-
exporter
public final class ModuleListener<T extends ModuleInfo> extends
JavaScriptObject {
protected ModuleListener() {
}

public void onModuleAttached(Module<T> module) {
invokeCallback(getCallback(), module);
}

private native void invokeCallback(JavaScriptObject callback,
Module<T> module) /*-{
this.callback(module);
}-*/;

private native JavaScriptObject getCallback() /*-{
return this.callback;
}-*/;

public native void setCallback(JavaScriptObject callback) /*-{
this.callback = callback;
}-*/;
}

// Support for common modules functionality (static methods)
public abstract class ModulesSupport {
public static <T extends ModuleInfo> boolean
registerAsListener(IModuleAware<T> instance) {
ModulesConfig<T> modulesConfig = getModulesConfig();
boolean result = null != modulesConfig;
if (result) {
modulesConfig.registerListener(createListener(instance));
}
return result;
}

public static <T extends ModuleInfo> boolean
registerAsModule(IModule<T> module) {
ModulesConfig modulesConfig = ModulesConfig.get();
boolean result = null != modulesConfig;
if (result) {
modulesConfig.attach(module.getName(),
createDispatchCallback(module));
}
return result;
}

public static <T extends ModuleInfo> ModulesConfig<T>
getModulesConfig() {
return ModulesConfig.get();
}

private static <T extends ModuleInfo> ModuleListener<T>
createListener(IModuleAware<T> instance) {
ModuleListener<T> result =
JavaScriptObject.createObject().cast();
result.setCallback(createListenerCallback(instance));
return result;
}

private static native JavaScriptObject
createListenerCallback(IModuleAware instance) /*-{
return function(module) {

instance.@org.myexample.modules.client.IModuleAware::onModuleAttached(Lorg/
myexample/modules/client/Module;)(module);
};
}-*/;

private static native JavaScriptObject
createDispatchCallback(IModule instance) /*-{
return function(containerId, moduleInfo) {

instance.@org.myexample.modules.client.IModule::dispatch(Ljava/lang/
String;Lorg/myexample/modules/client/ModuleInfo;)(containerId,
moduleInfo);
};
}-*/;
}

Hope it helps,
Berto

manstis

unread,
Jun 22, 2011, 8:10:28 AM6/22/11
to Google Web Toolkit
Great, lots to play with :)

Before I look into writing my own JSNI bridge, I've still been trying
to use gwt-exporter, but.....

I've three GWT applications:-
(1) Plugin (inherits "Common")
(2) Backend (inherits "Common")
(3) Common

"Plugin"
--------
Contains:-

...
echoButton.addClickHandler(new ClickHandler() {

public void onClick(ClickEvent event) {
//Debugging statements shown --> shows 'callback' is not null
PluginCallback<String> callback = getPluginCallback();
Window.alert("Plugin.onModuleLoad.onClick.callback is " +
(callback != null ? "not " : "") + "null");

echo(text.getText(), callback);
}

});
...

private native void echo(String text, PluginCallback<String>
callback) /*-{
//Debugging statements shown --> shows 'callback' is not null
alert("Plugin.echo.callback is " + (callback != null ? "not " :
"") + "null");

$wnd.com.anstis.pluginserver.CommonService.echo(text, callback);
}-*/;


"Backend"
--------
Contains:-

public void onModuleLoad() {
//Use gwt-exporter to generate the JSNI for CommonService.class
GWT.create(CommonService.class);
}

CommonService is:-

@Export
public class CommonService implements Exportable {

private static final EchoServiceAsync echoService =
GWT.create(EchoService.class);

public static void echo(String input, PluginCallback<String>
callback) {
//Debugging statements shown --> shows 'callback' is null
Window.alert("CommonService.echo.callback is " + (callback !=
null ? "not " : "") + "null");
try {

//The guts of the method, invoke a GWT-RPC call --> RTE, as
'callback' is null
echoService.echo(input, callback);

} catch (Exception e) {
e.fillInStackTrace();
e.printStackTrace(System.out);
Window.alert(e.getMessage());
}
}

}


"Common"
--------
Simply defines PluginCallback, as follows:-

public class PluginCallback<T> implements AsyncCallback<T> {
public void onFailure(Throwable caught) {}
public void onSuccess(T result) {}
}


My problem is that the PluginCallback instance is proven to be non-
null in "Plugin" (both Java and JSNI methods), but is reported as null
in "Backends" CommonService.

Somewhere it is getting munged, but I only pass a Java Object from
"Plugin" to JSNI (echo) to Java (CommonService) so don't understand
why it's being nulled?!?!

Any ideas (I'll post as a new question if you don't see anything
wrong, so it gets seen by a new audience as opposed to those just
following this thread).

Thanks,

Mike
> instan...@org.myexample.modules.client.IModuleAware::onModuleAttached(Lorg/
> myexample/modules/client/Module;)(module);
>         };
>     }-*/;
>
>     private static native JavaScriptObject
> createDispatchCallback(IModule instance) /*-{
>         return function(containerId, moduleInfo) {
>
> instan...@org.myexample.modules.client.IModule::dispatch(Ljava/lang/

lemaiol

unread,
Jul 3, 2011, 5:14:16 AM7/3/11
to Google Web Toolkit
Hi Mike,

How are the modules loaded and how are they invoked?

Berto

manstis

unread,
Jul 4, 2011, 4:27:39 AM7/4/11
to Google Web Toolkit
Hi Berto,

I've stuck everything on github; git://github.com/manstis/gwt-plugins.git

plugin - the "plugin"
pluginserver - the "framework"
plugincommon contains my common model

There's only about 10 files in all; so it's not so ominous.

I thought this would be easier than trying to explain directly - I'm
not expecting you to fix anything, it's just so you can see what I
have and give some suggestions.

As it stands, I can pass the "common" model class to the "framework",
but all attributes are undefined:
http://stackoverflow.com/questions/6535303/gwt-gwt-exporter-passing-objects

If you decided to try and run it; plugin and pluginserver WARs can be
deployed to Tomcat (I use 6.0) and select the URL for the server
portion.

Cheers,

Mike

lemaiol

unread,
Jul 5, 2011, 1:14:01 PM7/5/11
to Google Web Toolkit
I do not know really well gwt-exporter, but having a look at your
question in SO, I have the feeling that you need somehow to pass "real
JS objects" as parameters. I would try inheriting from
JavaScritpObject:

public class Person extends JavaScriptObject {
protected Person() {
// GWT JSO compliance requirement
}

// Use this factory method to create the entities
public static Person create(String name, int age) {
Person result = JSObject.create().cast(); // Maybe the name of
the class "JSObject" used here is wrong (not compiled code!)
result.setName(name);
result.setAge(age);
}

public native String getName() /*-{
return this.name; // the use of "this" is here required!!
}-*/;

public native void setName(String name) /*-{
this.name = name;
}-*/;
...
}

Hope it helps!
Berto

On Jul 4, 10:27 am, manstis <michael.ans...@gmail.com> wrote:
> Hi Berto,
>
> I've stuck everything on github; git://github.com/manstis/gwt-plugins.git
>
> plugin - the "plugin"
> pluginserver - the "framework"
> plugincommon contains my common model
>
> There's only about 10 files in all; so it's not so ominous.
>
> I thought this would be easier than trying to explain directly - I'm
> not expecting you to fix anything, it's just so you can see what I
> have and give some suggestions.
>
> As it stands, I can pass the "common" model class to the "framework",
> but all attributes are undefined:http://stackoverflow.com/questions/6535303/gwt-gwt-exporter-passing-o...
>
> If you decided to try and run it; plugin and pluginserver WARs can be
> deployed to Tomcat (I use 6.0) and select the URL for the server
> portion.
>
> Cheers,
>
> Mike
>
> ...
>
> read more »

manstis

unread,
Jul 8, 2011, 3:50:51 PM7/8/11
to Google Web Toolkit
Great, success. Thanks.

So I conclude it is impossible to pass a Java object from one GWT
application client space, through JSNI to another GWT application
client space even with the XS linker option.

It only appears possible using JavaScriptObject subclasses, i.e.
JScript overlays.

I might need to bother you again, but I have lots to keep trying
now :)

Thanks again.

Mike
> ...
>
> read more »

manstis

unread,
Jul 8, 2011, 4:44:48 PM7/8/11
to Google Web Toolkit
Should anybody else search for something similar and stumbles across
this thread there is a working example at git://github.com/manstis/gwt-plugins.git
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages