GWT: How to share a java Object (ie: EventBus) between two modules - java

I’m building a large application and I would like to split it in several modules like Core Module for initialization, users management, etc…, Customer Module, Production Module, etc…
I want to split it in multiples GWT modules (not using GWT splitting technique) and share an EventBus for broadcast some events like LoginEvent, LogoutEvent. I don’t want uses the code splitting technique because I want reduce the compile time and re-compile only the module that I modified.
This allow also to enable or disable a module by commenting the script tag in the HTML host page.
I’ve write the following code with using JSNI:
CoreModule’s EntryPoint:
private static SimpleEventBus eventBus = null;
public void onModuleLoad() {
export();
getEventBus().addHandler(MyEvent.TYPE, new MyEventHandler() {
#Override
public void onEvent(MyEvent myEvent) {
Window.alert(myEvent.getMessage());
}
});
}
public static SimpleEventBus getEventBus() {
if (eventBus == null)
eventBus = new SimpleEventBus();
return eventBus;
}
public static native void export() /*-{
$wnd.getEventBus = $entry(#testExporter.client.TestExporter::getEventBus());
}-*/;
CustomerModule’s EntryPoint:
public void onModuleLoad() {
Button button = new Button("Click me");
button.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
getEventBus().fireEvent(new MyEvent("Button clicked !"));
}
});
RootPanel.get().add(button);
}
public static native SimpleEventBus getEventBus() /*-{
// Create a useless eventBus because the GWT compiler make a call to a null instance
var eventBus = #com.google.gwt.event.shared.SimpleEventBus::new()();
eventBus = $wnd.getEventBus();
return eventBus;
}-*/;
But I’ve the following exception in Firebug when executing in the browser:
uncaugth exception [object Object]
I copied also the MyEvent and MyEventHandler classes that implements/interfaces a customer event.
P.S.: I know also the technique that consist to comment the other modules references to avoid to compile it.

A simpler answer is to not use multiple entry points.
==========================================
If what you are trying to achieve is breaking you code into manageable units but want to use all of them in the same page, you can:
create an "Application.gwt.xml" module with an entry point (equivalent to your initialization module, if I understand correctly)
create "UserManagement.gwt.xml" module without an entry point class
create other XXX modules without entry points
To create a module without entry point just remove the
<entry-point class='xxx'/>
from your gwt.xml files except for the "Application" one
You then need to include these modules into the "Application" module using
<inherits name="com.yourpackage.Module1Name" />
<inherits name="com.yourpackage.Module2Name" />
You then need to compile all of them together in one GWT build for module "com.yourpackage.Application".
When you do that make sure that both the compiled *.class and the source .java files for all your modules are available on the classpath.
Your "Application" entry point just needs to initialize and use the objects from the other modules

You cannot share code between different GWT compiled modules, unless you make some parts of your code available via jsni and call these exported methods via jsni, like you are trying in your query.
But be aware that: first, shared classes would be incompatible because each compilation would rename the classes/methods in a different way, and second, each compilation would remove different dead code pieces.
So in your case the SimpleEventBus returned in your window.getEventBus exported method is not known in other modules, although the other modules are using SimpleEventBus as well
The easiest way to do what you want, is to use GWT-exporter. First select correctly the js-api you want to export in each module, how you want to name it, and implement Exportable and annotate methods conveniently. Second take in account which objects would you use for the communication, because some of then could be incompatible. I would use primitive types, javascript object, and functions which are supported in GWT-exporter
I think that with GWT-exporter, for shared classes, if you annotate them in the same namespace and you export the same methods, hopefully you could use then in all modules but I'm not sure.
So export a js API via jsni or gwt-exporter and transfer pure primitive or js objects between them.

You can use the Frames and setup communication between the modules via WebMessage protocol. It will help only if the modules in one page and modules in separated war.

Related

Best approach to dynamically load modules (classes) in Java

I'm currently writing an application that requires to operate on different type of devices. My approach would be to make a "modular" application that can dynamically load different classes according to the device they need to operate on.
To make the application easily extensible, my goal is to assign a specific path to the additional modules (either .jar or .class files) leaving the core program as it is. This would be crucial when having different customers requiring different modules (without having to compile a different application for each of them).
These modules would implement a common interface, while the "core" application can use these methods defined on the interface and let the single implementations do the work. What's the best way to load them on demand? I was considering the use of URLClassLoader but i don't know if this approach is up-to-date according to new patterns and Java trends, as I would like to avoid a poorly designed application and deprecated techniques. What's an alternative best approach to make a modular and easily extensible application with JDK 9 (that can be extended just by adding module files to a folder) ?
Additionnaly to the ServicerLoader usage given by #SeverityOne, you can use the module-info.java to declare the different instanciation of the interface, using "uses"/"provides" keywords.
Then you use a module path instead of a classpath, it loads all the directory containing your modules, don't need to create a specific classLoader
The serviceLoader usage:
public static void main(String[] args) {
ServiceLoader<IGreeting> sl = ServiceLoader.load(IGreeting.class);
IGreeting greeting = sl.findFirst().orElseThrow(NullPointerException::new);
System.out.println( greeting.regular("world"));
}
In the users project:
module pl.tfij.java9modules.app {
exports pl.tfij.java9modules.app;
uses pl.tfij.java9modules.app.IGreeting;
}
In the provider project:
module pl.tfij.java9modules.greetings {
requires pl.tfij.java9modules.app;
provides pl.tfij.java9modules.app.IGreeting
with pl.tfij.java9modules.greetings.Greeting;
}
And finally the CLI usage
java --module-path mods --module pl.tfij.java9modules.app
Here is an example; Github example (Thanks for "tfij/" repository initial exemple)
Edit, I realized the repository already provides decoupling examples:
https://github.com/tfij/Java-9-modules---reducing-coupling-of-modules
It sounds like you might want to use the ServicerLoader interface, which has been available since Java 6. However, bear in mind that, if you want to use Spring dependency injection, this is probably not what you want.
There are two scenarios.
Implementation jar's are on classpath
In this scenario you can simply use ServiceLoader API (refer to #pdem answer)
Implementation jar's not on classpath
Lets Assume BankController is your interface and CoreController is your implementation.
If you want to load its implementation dynamically from dynamic path,c create a new module layer and load class.
Refer to the following piece of code:
private final BankController loadController(final BankConfig config) {
System.out.println("Loading bank with config : " + JSON.toJson(config));
try {
//Curent ModuleLayer is usually boot layer. but it can be different if you are using multiple layers
ModuleLayer currentModuleLayer = this.getClass().getModule().getLayer(); //ModuleLayer.boot();
final Set<Path> modulePathSet = Set.of(new File("path of implementation").toPath());
//ModuleFinder to find modules
final ModuleFinder moduleFinder = ModuleFinder.of(modulePathSet.toArray(new Path[0]));
//I really dont know why does it requires empty finder.
final ModuleFinder emptyFinder = ModuleFinder.of(new Path[0]);
//ModuleNames to be loaded
final Set<String> moduleNames = moduleFinder.findAll().stream().map(moduleRef -> moduleRef.descriptor().name()).collect(Collectors.toSet());
// Unless you want to use URLClassloader for tomcat like situation, use Current Class Loader
final ClassLoader loader = this.getClass().getClassLoader();
//Derive new configuration from current module layer configuration
final Configuration configuration = currentModuleLayer.configuration().resolveAndBind(moduleFinder, emptyFinder, moduleNames);
//New Module layer derived from current modulee layer
final ModuleLayer moduleLayer = currentModuleLayer.defineModulesWithOneLoader(configuration, loader);
//find module and load class Load class
final Class<?> controllerClass = moduleLayer.findModule("org.util.npci.coreconnect").get().getClassLoader().loadClass("org.util.npci.coreconnect.CoreController");
//create new instance of Implementation, in this case org.util.npci.coreconnect.CoreController implements org.util.npci.api.BankController
final BankController bankController = (BankController) controllerClass.getConstructors()[0].newInstance(config);
return bankController;
} catch (Exception e) {BootLogger.info(e);}
return null;
}
Reference : https://docs.oracle.com/javase/9/docs/api/java/lang/module/Configuration.html

Integrating an external pure Java library and having Android classes access on it

I'm having troubles trying to get my external Java project so I can use Android classes on it as well. The library is already integrated on the Android project. For instance: I have several model classes on it that I would want to implement Parcelable so they can be seriallized accordingly, but none of the Android classes are available on them.
Clarification I only did this in order to try to solve the issue
So far I've only tried:
Changing and matching the external library's package:
Package name in Android
com.domain.androidproject
Library's package originally
com.domain.libproject
Changed to:
com.dommain.androidproject.libproject
But no luck so far. I imported the library as a Gradle external project vía:
compile project(path: ':LibProject')
Thank you for your help.
You'll have to define a binding between your pure java library and android. You could use Dependency injection to inject the models using the class signature, and then define the parcelable models inside the app (or into another project, like a plugin). Or you could achieve the same using generics. keep in mind, since the java library is already compiled, technically, you can't change it by importing it into the android project (I've seen people "rewriting" some files from a dependency and then adding them with the whole original path to fool the classpath, but that's highly risky since you are not gonna be able to interact with the rest of the dependency's code and if something changes, the thing will break).
if you have access to the pure java's library sourcecode, then modify it to use factories or providers of models. If not, extend the models, add parcelable support, and attempt to use those instead of the original model classes.
Example:
let's suppose we have a model and some functions using it:
public class myModel{
private int id;
private String name;
public void setId(int id){
this.id = id;
}
//more getters and setters
}
public interface myModelCreator<T>{
public myModel create(T toModel);
public T uncreate(myModel fromModel);
}
public static void doSomething(myModel model){
//some library operations
}
Now, in the android project:
public class myAndroidModel extends myModel implements Parcelable{
/*Implements the parcelable methods using the class accessors, or you can change the myModel members to protected.*/
}
public class myAndroidModelCreator implements myModelCreator<myAndroidModel>{
#Override
public myModel create(myAndroidModel toModel){
//create the myModel using the parcelable class.
}
#Override
public myAndroidModel uncreate(myModel fromModel){
//reverse operation.
}
}
Now, in the android project, you can use the parcelable subclass everywhere, and everytime you need to call the library, you can supply the creator interface using the parcelables as arguments.
Another alternative would be changing the library method signatures to something like this:
public static void<T extends myModel> doSomething(T model){
//some library operations
}
So you can directly consume the parcelable subclasses. But depending on your hierarchy, that may be not possible. Lastly, you could attempt to implement dependency injection into the java project using Guice and Roboguice in the android project. Since roboguice uses guice, it is possible they can interoperate, but that's a long shot.
I like Fco P.'s answer, but for the sake of completness, here is an alternative answer.
Use json to serialize objects, rather than Parcelable. You can then put your serialized json as a string extra in intent or as string in bundles.
it's faster to implement than using Parcelable, with libraries such as Google GSON or Square moshi.
it's less performant than Parcelable
Generally if you want to make use of classes in another project/library:
File -> New -> Import Module -> Navigate to the directory of an old project/Library -> Ok
Check off the modules you want to import -> OK
Right click the app module -> Open Module Settings -> dependencies -> + -> Module -> The new Module.
Your project should then be usable in whatever project you just did that for.
Create an android library project with packagename com.domain.libproject
Copy all the sources in src folder.
Update jar dependencies in build.gradle and after that you can make your class in the library parcelable.
Let me know if any issues.
Best regds

Multi-module annotation processing in Android Studio

I have a project with multiple modules in Android Studio. A module may have a dependency on another module, for example:
Module PhoneApp -> Module FeatureOne -> Module Services
I've included my annotation processing in the root module but the android-apt annotation processing occurs only at the top most level (PhoneApp) so that it should theoretically have access to all the modules at compile time. However, what I'm seeing in the generated java file is only the classes annotated in PhoneApp and none from the other modules.
PhoneApp/build/generated/source/apt/debug/.../GeneratedClass.java
In the other modules, I am finding a generated file in the intermediates directory that contains only the annotated files from that module.
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.class
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.java
My goal is to have a single generated file in PhoneApp that allows me to access the annotated files from all modules. Not entirely sure why the code generation process is running for each and failing to aggregate all annotations at PhoneApp. Any help appreciated.
Code is fairly simple and straight forward so far, checkIsValid() omitted as it works correctly:
Annotation Processor:
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GuiceModule.class)) {
if (checkIsValid(annotatedElement)) {
AnnotatedClass annotatedClass = new AnnotatedClass((TypeElement) annotatedElement);
if (!annotatedClasses.containsKey(annotatedClass.getSimpleTypeName())) {
annotatedClasses.put(annotatedClass.getSimpleTypeName(), annotatedClass);
}
}
}
if (roundEnv.processingOver()) {
generateCode();
}
} catch (ProcessingException e) {
error(e.getElement(), e.getMessage());
} catch (IOException e) {
error(null, e.getMessage());
}
return true;
}
private void generateCode() throws IOException {
PackageElement packageElement = elementUtils.getPackageElement(getClass().getPackage().getName());
String packageName = packageElement.isUnnamed() ? null : packageElement.getQualifiedName().toString();
ClassName moduleClass = ClassName.get("com.google.inject", "Module");
ClassName contextClass = ClassName.get("android.content", "Context");
TypeName arrayOfModules = ArrayTypeName.of(moduleClass);
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("juice")
.addParameter(contextClass, "context")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(arrayOfModules);
methodBuilder.addStatement("$T<$T> collection = new $T<>()", List.class, moduleClass, ArrayList.class);
for (String key : annotatedClasses.keySet()) {
AnnotatedClass annotatedClass = annotatedClasses.get(key);
ClassName className = ClassName.get(annotatedClass.getElement().getEnclosingElement().toString(),
annotatedClass.getElement().getSimpleName().toString());
if (annotatedClass.isContextRequired()) {
methodBuilder.addStatement("collection.add(new $T(context))", className);
} else {
methodBuilder.addStatement("collection.add(new $T())", className);
}
}
methodBuilder.addStatement("return collection.toArray(new $T[collection.size()])", moduleClass);
TypeSpec classTypeSpec = TypeSpec.classBuilder("FreshlySqueezed")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(methodBuilder.build())
.build();
JavaFile.builder(packageName, classTypeSpec)
.build()
.writeTo(filer);
}
This is just for a demo of annotation processing that works with Guice, if anyone is curious.
So how can I get all the annotated classes to be included in the generated PhoneApp .java file from all modules?
It's never too late to answer a question on SO, so...
I have faced a very similar complication during one of tasks at work.
And I was able to resolve it.
Short version
All you need to know about generated classes from moduleB in moduleA is package and class name. That can be stored in some kind of MyClassesRegistrar generated class placed in known package. Use suffixes to avoid names clashing, get registrars by package. Instantiate them and use data from them.
Lond version
First of all - you will NOT be able to include your compile-time-only dependency ONLY at topmost module (lets call it "app" module as your typical android project structure does). Annotation processing just does not work that way and, as far as I could find out - nothing can be done about this.
Now to the details. My task was this:
I have human-written annotated classes. I'll name them "events". At compile time I need to generate helper-classes for those events to incorporate their structure and content (both statically-available (annotation values, consts, etc) and runtime available (I am passing event objects to those helpers when using latter). Helper class name depends on event class name with a suffix so I don't know it until code generation finished.
So after helpers are generated I create a factory and generate code to provide new helper instance based on MyEvent.class provided. Here's the problem: I only needed one factory in app module, but it should be able to provide helpers for events from library module - this can't be done straightforward.
What I did was:
skip generating factory for modules that my app module depends upon;
in non-app modules generate a so-called HelpersRegistrar implementation(s):
– they all share same package (you'll know why later);
– their names don't clash because of suffix (see below);
– differentiation between app module and library-module is done via javac "-Amylib.suffix=MyModuleName" param, that user MUST set - this is a limitation, but a minor one. No suffix must be specified for app module;
– HelpersRegistrar generated implementation can provide all I need for future factory code generating: event class name, helper class name, package (these two share package for package-visibility between helper and event) - all Strings, incorporated in POJO;
in app module I generate helpers - as usual, then I obtain HelperRegistrars by their package, instantiate them, run through their content to enrich my factory with code that provides helpers from other modules. All I needed for this was class names and a package.
Voilà! My factory can provide instances of helpers both from app module and from other modules.
The only uncertainty left is order of creating and running processor-class instances in app module and in other modules. I have not found any solid info on this, but running my example shows that compiler (and, therefore, code generation) first runs in module that we depend upon, and then - in app module (otherwise compilation of app module will be f..cked). This gives us reason to expect known order of code processor executions in different modules.
Another, slightly similar, approach is this: skip registrars, generate factories in all modules and write factory in app module to use other factories, that you get and name same way as registrars above.
Example can be seen here: https://github.com/techery/janet-analytics - this is a library where I applied this approach (the one without registrars since I have factories, but that can be not the case for you).
P. S.: suffix param can be switched to simpler "-Amylibraryname.library=true" and factories/registrars names can be autogenerated/incremented
Instead of using Filer to save generated file, use regular java file writing instead. You will need to serialize objects to temp files when processing because even static variables won't save in between modules. Configure gradle to delete the temp files before compilation.

A good way to "centralize" instances provided by a library?

I have written this project and already use it in other libraries of mine.
However, I find something amiss. Namely, in each user of this library, I create a utility class whose only role is to provide one or more MessageBundles. And this sucks.
I'd like to have, built into the library, a mechanism in order to have library users be able to register/recall bundles.
My first idea would be to have a singleton factory with a .register() and .get() method (with appropriate checks for duplicate keys etc) and call these from within static initialization blocks...
... But there is a problem: there is no guarantee as to which static initialization block will be called first.
Knowing that I'd like to keep the dependencies of this library "intact" (which is to mean, no external dependency at all), what solution would you recommend?
(note: this is Java 6+)
You could use the standard support for service providers: ServiceLoader. You would simply require each user of your library to provide an implementation of some interface, for example
public interface MessageBundleProvider {
List<MessageBundle> getBundles();
}
The name of the class implementing this interface would have to be specified in a file of the jar file of the user library named META-INF/services/com.example.MessageBundleProvider.
At runtime, your library would automatically discover all the message bundle providers using the following code:
private static final ServiceLoader<MessageBundleProvider> LOADER
= ServiceLoader.load(MessageBundleProvider.class);
private static final List<MessageBundle> BUNDLES;
static {
BUNDLES = new ArrayList<MessageBundle>();
for (MessageBundleProvider provider : loader) {
for (MessageBundle bundle : provider.getBundles()) {
BUNDLES.add(bundle);
}
}
}
Disclaimer: I know that ServiceLoader exists, but I've never used it before. It's how all the standard Java service providers are discovered, though (like JDBC drivers, charset providers, etc.).

Determining whether library class is used from GWT app or regular Java

I'm writing a couple of library classes that I am sharing between several projects. Some of these projects are plain-old Java and others are GWT applications. For some of these classes the exact implementation is different whether they need to run in GWT or in Java (Let's not get into exactly why, but just as one of many examples, Date.getMonth is deprecated in Java, but the Calendar replacement isn't available in GWT).
Is there a way to mark certain sections of code as pertaining to one or the other scenario?
I looked at using deferred binding and class-replacement in GWT, but that requires instantiation of classes using GWT.create() which isn't available for a plain-old Java app and will therefore lead to compile errors.
Found a solution that works beautifully: the <super-source> tag in my library's .gwt.xml file!
Basically, I have two versions of the following EnvironmentFlags class in my library. One in the actual library that is used by Java located in folder "lib":
my.library.EnvironmentFlags looks like this:
package my.library;
public class EnvironmentFlags {
public static final boolean IS_GWT = false;
public static final boolean IS_DEV_MODE = false;
}
And then a file in the folder "super/my/library" that looks like this:
package my.library;
import com.google.gwt.core.client.GWT;
public class EnvironmentFlags {
public static final boolean IS_GWT = true;
public static final boolean IS_DEV_MODE = !GWT.isProdMode();
}
Now the magic: The .gwt.xml file of my library looks like this:
<module>
<source path='lib' />
<super-source path='super' />
</module>
This leads to plain-old Java using the first version of the EnvironmentFlags class, which simply sets both flags to false, while the GWT compiler replaces the source of that class with the second version loaded from the super-source directory, which sets the GWT flag to true and the DEV_MODE flag to whatever it gets from GWT.
Now, in my code I can simply use the following:
if (EnvironmentFlags.IS_GWT) {
// Do GWT stuff
} else {
// Do plain-old Java stuff
}
and both, the Java and the GWT compiler should drop the respective unreachable/unneeded code from the compiled result, i.e. no run-time overhead needed.
PS: The IS_DEV_MODE flag doesn't actually have anything to do with my original question. I just included it as a freebie which allows me to have my code act differently (more verbose, for example) depending on whether I am testing or deploying my GWT app.
Sounds like you could use the static GWT.isClient() which returns true if your code is running in GWT environment (Dev or Production) or false elsewhere. You'll have to include gwt-user.jar in your server classpath. For example, running the following in a JVM:
import com.google.gwt.core.shared.GWT;
public class Main {
public static void main(String[] args) {
System.out.println(GWT.isClient() ? "Running client-side."
: "Running server-side.");
}
}
Will produce Running server-side. in your console.

Categories