This is another topic I wanted to blog about for a long time: The use of Google’s EventBus in XPages applications. EventBus is a replacement for the Java in-process event distribution. It makes life a lot easier.
My first plan was to add the guava.jar into an OSGi plugin because of security reasons (otherwise you have to use the „grant{ permission java.security.AllPermission;}„) but I never had the time. That’s why I created a simple example which uses the jar in the lib/ext folder instead.
To use the EventBus in an application, you first have to define an event. This simple example has a message only, but you can add any properties you want.
package ch.hasselba.xpages;
public class FrontendEvent {
private final String message;
public FrontendEvent(final String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
Then we need a subscriber. For this we have to add the annotation @Subscribe to a method of a managed bean which should handle an event. The class type of the parameter tells the EventBus what kind of event our method should handle.
This is a managed bean which stores the last message sent over the EventBus:
package ch.hasselba.xpages;
import com.google.common.eventbus.Subscribe;
public class EventSubscriber {
private String message;
@Subscribe
public void handleEvent(FrontendEvent event) {
this.message = event.getMessage();
}
public void getMessage(String msg) {
this.message = msg;
}
public String getMessage() {
return this.message;
}
}
Then we have to create a managed bean which has an instance of EventBus, the FrontendEventBus. The bus has a managed property subscribers. As soon the bean is instantiated, the managed property is called and the list of objects is registered to the EventBus instance.
package ch.hasselba.xpages;
import java.io.Serializable;
import java.util.ArrayList;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionContext;
import com.google.common.eventbus.SubscriberExceptionHandler;
@SuppressWarnings("serial")
public class FrontendEventBus implements SubscriberExceptionHandler,
Serializable {
final EventBus eventBus = new EventBus();
ArrayList<Object> subscribers;
/**
* registers all subscribing managed beans from faces-config.xml
*
* @param subscribers
* ArrayList with references to managed beans
*/
public void setSubscribers(ArrayList<Object> subscribers) {
this.subscribers = subscribers;
for (Object subscriber : subscribers) {
register(subscriber);
}
}
public void post(final Object event) {
eventBus.post(event);
}
public void register(final Object object) {
eventBus.register(object);
}
public void unregister(final Object object) {
eventBus.unregister(object);
}
public final void handleException(Throwable exception,
SubscriberExceptionContext context) {
exception.printStackTrace();
}
}
There is only one problem: If a bean is in a lower scope, the initialization will fail. In this case we have to do the opposite to attach a subscriber to the bus. Instead of telling the bus which subscriber we want to register, we are telling the subscriber which bus to attach to.
package ch.hasselba.xpages;
public class EventSubscriberRequestScope extends EventSubscriber {
/**
* registers the current instance to the event bus
*
* @param eventBus
* the frontend event bus
*/
public void setEventBus(FrontendEventBus eventBus) {
eventBus.register(this);
}
}
As soon the bean is instantiated, the managed property will have an instance of our FrontendEventBus and the bean can register itself.
The faces-config.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
<managed-bean>
<managed-bean-name>frontendEventBus</managed-bean-name>
<managed-bean-class>ch.hasselba.xpages.FrontendEventBus</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>subscribers</property-name>
<list-entries>
<value-class>java.lang.Object</value-class>
<value>#{eventSubscriber}</value>
</list-entries>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>eventSubscriber</managed-bean-name>
<managed-bean-class>ch.hasselba.xpages.EventSubscriber</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>eventSubscriberRequestScope</managed-bean-name>
<managed-bean-class>ch.hasselba.xpages.EventSubscriberRequestScope</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>eventBus</property-name>
<value>#{frontendEventBus}</value>
</managed-property>
</managed-bean>
</faces-config>
The first subscribing bean is in the same scope as the FrontendEventBus, but the second one is a request scoped bean.
Now it’s time to send an event. This can be realized by creating a new instance of our FrontendEvent we have created earlier and posting it to the bus.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:button
id="button1" value="Click me">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.action>
<![CDATA[#{javascript:importPackage( ch.hasselba.xpages );
var msg = "Timestamp: " + java.lang.System.currentTimeMillis();
var se = new ch.hasselba.xpages.FrontendEvent( msg );
frontendEventBus.post( se );
}]]>
</xp:this.action>
</xp:eventHandler>
</xp:button>
<br />
<br />
<xp:label
value="#{eventSubscriber.message}"
id="labelSubscriberMessage">
</xp:label>
<br />
<xp:label
value="#{eventSubscriberRequestScope.message}"
id="labelSubscriberRequestScope">
</xp:label>
</xp:view>
As soon the button is clicked, both beans are receiving the event and updating their message.
Hope someone finds it usefull.