Luis Soeiro
Luis Soeiro

Reputation: 4272

Can an EJB server side send an event to an EJB client side?

I wonder if it is possible to do something along these lines:

1) Server side (EJB class)

@Statefull
public class SomeEJB implements SomeEJBRemote {

@Resource
private SessionContext sessionContext;

//(...)
public void someMethod(Record record){
    //(...)
    //Situation that requires the server to alert all registered listeners
    if(specialSituation){
        sessionContext.fireEventToListeners(new SomeEvent());
    }

    //Do something else...
}
}

2) Client side

//(...) 
SomeEJBRemote ejb = initialContext.lookup("SomeEJBRemote");
ejb.addListener(this);

void EJBEventListener(SomeEvent event){
    System.out.println("EJB server has sent an event!");
}

A) Is there anything in the Java EE specification that allows this?

B) I know that JBoss allows some sort of bidirectional communication, but can I do just like that example?

C) Is there anything like this in OpenEJB (or TOMEE)?

Upvotes: 4

Views: 1704

Answers (2)

David Blevins
David Blevins

Reputation: 19368

It's easy to do with an embedded EJB container and an MDB in the client. We have an example that does exactly that.

Check out the monitor module of this example.

At 10,000 feet this example does the following:

Server-side:

  • @Stateless bean that wraps access to an EntityManager
  • JMS Message sent to a topic on all add/delete operations

Client-side:

  • Embedded EJB Container/MDB receiving messages
  • On receipt a notification is issued to the user via the java.awt.SystemTray

So the interesting thing about this technique is that it is fully transactional -- the EntityManager updates and the JMS messages sent are all part of the transaction. If the database update fails, no JMS message will be sent.

Here's 100% of the client code from that example. Doesn't take much to do what's described.

The client "main" class

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;

public class NotificationMonitor {
    private static TrayIcon trayIcon;

    public static void main(String[] args) throws NamingException, InterruptedException, AWTException, MalformedURLException {
        addSystemTrayIcon();

        // Boot the embedded EJB Container 
        new InitialContext();

        System.out.println("Starting monitor...");
    }

    private static void addSystemTrayIcon() throws AWTException, MalformedURLException {
        SystemTray tray = SystemTray.getSystemTray();

        URL moviepng = NotificationMonitor.class.getClassLoader().getResource("movie.png");
        Image image = Toolkit.getDefaultToolkit().getImage(moviepng);

        ActionListener exitListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("Exiting monitor...");
                System.exit(0);
            }
        };

        PopupMenu popup = new PopupMenu();
        MenuItem defaultItem = new MenuItem("Exit");
        defaultItem.addActionListener(exitListener);
        popup.add(defaultItem);

        trayIcon = new TrayIcon(image, "Notification Monitor", popup);
        trayIcon.setImageAutoSize(true);
        tray.add(trayIcon);
    }

    public static void showAlert(String message) {
        synchronized (trayIcon) {
            trayIcon.displayMessage("Alert received", message, TrayIcon.MessageType.WARNING);
        }
    }
}

The Client-Side MDB

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "notifications")})
public class NotificationsBean implements MessageListener {

    public void onMessage(Message message) {
        try {
            TextMessage textMessage = (TextMessage) message;
            String text = textMessage.getText();

            NotificationMonitor.showAlert(text);
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

Client jndi.properties file

This configures the embedded EJB container. You could do this in code as well.

java.naming.factory.initial=org.apache.openejb.client.LocalInitialContextFactory
Default\ JMS\ Resource\ Adapter=new://Resource?type=ActiveMQResourceAdapter
Default\ JMS\ Resource\ Adapter.BrokerXmlConfig=broker:vm://localhost
Default\ JMS\ Resource\ Adapter.ServerUrl=tcp://localhost:61616

Upvotes: 4

Brett Kail
Brett Kail

Reputation: 33936

No, there is nothing like this in EJB. I would suggest having the client either listen to a JMS queue/topic. Alternatively, the client could export a listener object to RMI (effectively becoming a server itself), and then pass a reference to server; this technique would require quite a bit more infrastructure.

Upvotes: 1

Related Questions