Cotrix, the RIA I’m working on, is packaged differently depending on the target environment. For each environment we want some features enabled or not, both client and server side. From the client POV I’ve to enable or disable some GWT components, I will call those components plugins. In our case each plugin is bounded in a separate Java library.
In GWT to enable a component you need to:
include it in the module file:
<inherits name='com.google.gwt.user.User'/>
call it somewhere.
The first point can’t be automatized, or you can but you need to manipulate the module file during the packaging phase. This can be done using Maven facilities, but I will avoid it. Each packaging in the Cotrix application has his own GWT module, all of them inheriting from a common super module. The module for a specific environment adds the components (plugins) that I want to add.
The second point is a little tricky. Let’s assume that all the plugin implement a Plugin interface:
public interface Plugin {
/**
* Returns the plugin name.
* @return the plugin name.
*/
public String getName();
/**
* Activates the plugin.
*/
public void activate();
}
Once we have all the required plugin included in the main GWT module we need to discover them. In GWT you can’t use reflection or Service locator mechanism. With GIN I don’t have found any way to retrieve all the interface implementations (as you can do win CDI).
Therefore I’ve decided to go with a generator solution. In this solution the generator will be responsible to collect all the Plugin interface implementations and to generate the code that will instantiate them.
First I’ve declared a PluginProvider which will return all the Plugin instances:
public interface PluginProvider {
/**
* Returns all the plugin instance.
* @return the plugin instances.
*/
List<Plugin> getPlugins();
}
Then how to retrieve the Plugin interface implementations? One way is using reflections during the compilation phase. Another one is to use the module configuration properties. A GWT module can declare a configuration property, both single value and multi-value. A third module can extend the property adding it more values.
I’ve used the property mechanism in order to let each module containing a Plugin implementation to extends a particular property with the Plugin implementation class name. The code generator will read this property values in order to know the Plugin interface implementations.
In the main GWT module we will declare the property used to declare the plugins:
<define-configuration-property name="plugins" is-multi-valued="true" />
Then each module containing a Plugin interface implementations will extend the property with the name of the implementation class:
<set-configuration-property name="plugins" value="org.gwtplugin.client.MyFirstPlugin"/>
Let see the PluginProvider generator code:
public class PluginProviderGenerator extends Generator {
private static final String PROPERTY_NAME = "plugins";
@Override
public String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException {
try {
JClassType classType = context.getTypeOracle().getType(typeName);
String packageName = classType.getPackage().getName();
String simpleName = classType.getSimpleSourceName() + "Generated";
PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName);
String name = typeName + "Generated";
if (printWriter == null) return name;
ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, simpleName);
composer.addImplementedInterface(classType.getName());
composer.addImport(Plugin.class.getName());
composer.addImport(List.class.getName());
composer.addImport(ArrayList.class.getName());
composer.addImport(Collections.class.getName());
SourceWriter src = composer.createSourceWriter(context, printWriter);
List<String> plugins = getPlugins(context.getPropertyOracle());
src.println("public List<Plugin> getPlugins() {");
src.indent();
if (plugins.isEmpty()) src.println("return Collections.emptyList();");
else {
src.println("List<Plugin> plugins = new ArrayList<Plugin>("+plugins.size()+");");
for (String plugin:plugins) {
src.println("plugins.add(new "+plugin+"());");
}
src.println("return plugins;");
}
src.outdent();
src.println("}");
src.commit(logger);
return typeName + "Generated";
} catch (NotFoundException e) {
e.printStackTrace();
throw new UnableToCompleteException();
}
}
private List<String> getPlugins(PropertyOracle propertyOracle) {
try {
ConfigurationProperty propertyValue = propertyOracle.getConfigurationProperty(PROPERTY_NAME);
return propertyValue.getValues();
} catch (BadPropertyValueException e) {
// intentional
}
return Collections.emptyList();
}
}
The generator retrieves the “plugins” property values, then use each value to instantiate the Plugin implementation, returning it in a list.
In the main GWT module we need to declare the generator :
<generate-with class="org.gwtplugin.gen.PluginProviderGenerator">
<when-type-assignable class="org.gwtplugin.client.plugin.PluginProvider" />
</generate-with>
Finally we will use the generated PluginProvider in this way:
PluginProvider pluginsProvider = GWT.create(PluginProvider.class);
/**
* This is the entry point method.
*/
public void onModuleLoad() {
for (Plugin plugin:pluginsProvider.getPlugins()) plugin.activate();
}
I’ve created a sample project in GitHub: https://github.com/fedy2/gwtplugin