EN CONSTTRUCCIÓN ...



<fw:socket> es un componente UI que abre una conexión push basada en websocket unidireccional (de servidor a cliente) en el lado del cliente a la que se puede acceder desde el lado del servidor a través de la interfaz PushContext inyectada mediante la anotación @Push.

Configuración

Primero hay que activar los websockets mediante el siguiente parámetro de contexto booleano en web.xml:

<context-param>
    <param-name>es.um.atica.fundeweb.SOCKET_ENDPOINT_ENABLED</param-name>
    <param-value>true</param-value>
</context-param>

Lo que registrara un SocketEndpoint en el servidor. Mirar WS spec issue 211.


Utilización en el Cliente

Declare la etiqueta <fw:socket> en la vista (página) con al menos un nombre de canal (channel) y una función JavaScript llamada onmessage. El nombre del canal no puede ser una expresión EL y solo puede contener caracteres alfanuméricos, guiones, guiones bajos y puntos. Aquí hay un ejemplo que hace referencia a una función de escucha de JavaScript existente (¡no incluya los paréntesis!).

<fw:socket channel="someChannel" onmessage="socketListener" />
function socketListener(message, channel, event) {
    console.log(message);
}

También se puede añadir la función JavaScript en el propio componente:

<fw:socket channel="someChannel" onmessage="function(message) { console.log(message); }" />

El listener JavaScript onmessage, se invocará con tres argumentos:


En caso de que su servidor esté configurado para ejecutar el contenedor WS en un puerto TCP diferente al contenedor HTTP, puede usar el atributo de puerto opcional para especificar explícitamente el puerto.

<fw:socket port="8000" ... />


Cuando se conecta correctamente, el websocket está abierto de forma predeterminada siempre que el documento esté abierto, y se volverá a conectar automáticamente a intervalos cada vez mayores cuando la conexión se cierre o se cancele como resultado de, por ejemplo, un error. un error de red o reinicio del servidor. No se volverá a conectar automáticamente cuando el primer intento de conexión ya falle. El websocket se cerrará implícitamente una vez que se descargue el documento (por ejemplo, al navegar, cerrar la ventana/pestaña del navegador, etc.). Para volver a conectarse con éxito después de reiniciar el servidor o al cambiar a un nuevo nodo de servidor, debe asegurarse de que la persistencia de la sesión esté habilitada en el servidor.


Utilización en el Servidor

En el backend, se puede inyectar una instancia de PushContext a través de la anotación @Push, junto con el nombre del canal en cualquier componente Seam, donde se necesite enviar un mensaje push. Para enviar el mensaje, se invoca al método PushContext.send(Object) con cualquier objeto Java que represente el mensaje.

@Push
private PushContext someChannel;

public void sendMessage(Object message) {
    someChannel.send(message);
}


Por defecto, el nombre del canal se toma del nombre de la variable en la que se realiza la inyección. También se puede especificar opcionalmente mediante el atributo channel de la anotación @Push. El siguiente ejemplo se inyecta el contexto push del canal foo en la variable bar.

@Push(channel="foo")
private PushContext bar;


El mensaje se codificará como JSON y se entregará como parámetro a la función JavaScript onmessage asociada con el nombre del canal. Puede ser cualquier objeto, desde un String a colección o un mapa e incluso un POJO (JAVABEAN). Para conocer los tipos de parámetros admitidos (y descendientes) por Json.encode(Object) son: Boolean, Number, CharSequence, Date, Enum, Temporal, Collections, Map; Si el tipo de objeto no coincide con ninguno de ellos, entonces se inspeccionara el objeto como un POJO (JAVABEAN), donde las propiedades públicas (con getter público) se codificarán como un objeto JS. Los objetos de las clases Date y Temporal, cumples el estándar RFC 1123, por lo que, si es necesario, puedes usar new Date() en JavaScript.

Aunque los websockets admiten la comunicación bidireccional, el envío de <fw:socket> está diseñado para la comunicación unidireccional, de servidor a cliente. En caso de que desee enviar datos de cliente a servidor, contina realizando peticiones Faces AJAX como se hace de manera habitual. Si es necesario hacerlo desde JavaScript, puedes usar <p:remoteCommand>. De esa forma se mantiene el estado de vista de Faces, la sesión HTTP y, lo que es más importante, todas las restricciones de seguridad especificada para los servicios. Es decir, esas restricciones de seguridad no están disponibles durante un mensaje de websockets. Véase también. WS spec issue 238.


Ámbitos (Scopes) y Usuarios

Por defecto, los websockets tienen ámbito aplicación (application scope), es decir,  cualquier vista/sesión de la aplicación web, teniendo el mismo canal websocket abierto, recibirá el mismo mensaje push. El mensaje push puede ser enviado por cualquier usuario o por la propia aplicación. Está característica es útil para recibir notificaciones globales a nivel de aplicación lanzados por ella y que permiten actualizar una página concreta de esta, ejemplo: estadísticas del sitio web, listas top100, actualización de stock, etc.


El atributo opcional scope puede establecerse con el valor session, para restringir los mensajes push a todas las vistas de la sesión actual del usuario. El mensaje solo puede ser enviado por el usuario y no por la aplicación. Está característica es útil para recibir notificaciones a nivel de sesión sobre vistas lanzadas por el mismo usuario, ejemplo: el resultado de una tarea asíncrona lanzada por una acción del usuario).

<fw:socket channel="someChannel" scope="session" ... />


El atributo opcional scope puede establecerse con el valor session, para restringir los mensajes push sólo a la vista actual. Los mensajee push no son vistos por otras vistas en la misma sesión, incluso si son la misma URL. El mensaje solo puede ser enviado por el usuario y no por la aplicación. Está característica es útil para recibir notificaciones a nivel de vista (página) lanzadas por el mismo usuario, ejemplo: una barra de progreso sobre una acción especifica en la vista (página) actual).

<fw:socket channel="someChannel" scope="view" ... />


Los valores permitidos en el atributo scope son: application, session y view; (en minúsculas) y NO se permiten expresiones EL.


Además, el atributo opcional user puede establecerse con un identificador único, que hace referencia al usuario autenticado. Pude ser el nombre del usuario o un ID calculado. De esta forma, un mensaje push puede ser enviado a un usuario especifico y puede ser enviado por cualquier usuario o por la propia aplicación.  El valor el atributo user tiene que implementar la interface Serializable y debe gastar poca memoria, por lo que la entidad que representa al usuario como la clase org.umu.atica.servicios.gesper.gente.entity.Persona o org.jboss.seam.security.Identity (y descendientes) no son recomendables. Podemos generar identificadores únicos mediante la clase java.util.UUID. Ejemplo de uso con el username:

<fw:socket channel="someChannel" user="#{credentials.username}" ... />


Cuando especificamos el atributo user  el ámbito por defecto se establece en session y no se puede establecer en application. Puede establecerse con view, pero es inusual y solo debe utilizarse si el usuario autenticado representado por el valor user, tiene un ciclo de vida más corto que la sesión HTTP, ejemplo: cuando la aplicación permite cambiar entre usuarios autenticados durante la misma sesión HTTP sin invalidar al anterior usuario, que es un muy mala práctica de seguridad. Si en el caso, de reutilizar un socket de ámbito sesión, se pueden producir comportamientos indeseados cuando se envía un mensaje push dirigido a un usuario determinado. Este puede ser enviado a un usuario anterior. Este problema se resuelve estableciendo el ámbito a view, recomendando realizar una invalidación de la sesión HTTP del usuario.


Cuando el atributo user es establecido mediante una expresión EL, y este cambia durante un petición AJAX, entonces el usuario del socket también se actualizará, incluso si ninguna petición AJAX cubre la definición del <fw:socket>. Hay  que asegurarse de que el valor esta ligado al menos a una propiedad de la vista en caso de querer controlarlo durante el ámbito de la vista.

En el lado del servidor, el mensaje push puede ser dirigido al usuario especificado en el atributo user mediante PushContext.send(Object, Serializable). El mensaje push puede ser enviado por todos los usuarios o por la propia aplicación. Es útil para notificar a un usuario determinado por otros usuarios, por ejemplo: chat, mensajes por parte del administrador, etc.; o por tareas en segundo plano de la aplicación como por ejemplo: notificaciones, listeners, etc..

@Push
private PushContext someChannel;

public void sendMessage(Object message, User recipientUser) {
    Long recipientUserId = recipientUser.getId();
    someChannel.send(message, recipientUserId);
}


Los mensajes push pueden enviarse a varios usuarios al mismo tiempo, pasando una Collection que contenga los identificadores de los usuarios a PushContext.send(Object, Collection).

@Push
private PushContext someChannel;

public void sendMessage(Object message, Group recipientGroup) {
    Collection<Long> recipientUserIds = recipientGroup.getUserIds();
    someChannel.send(message, recipientUserIds);
}


Consejos para el Diseño de Canales

Se pueden declarar varios canales push con diferentes ámbitos con o sin especificar usuarios de la aplicación. Incluso el mismo nombre de canal se puede reutilizar entre varias vistas, incluso si su ámbito es de vista. Es más eficiente utilizar el menor número de nombres de canal y ligar el nombre del canal de la conexión push, a combinaciones especificas de ámbito/usuario y no para vistas Faces especificas. En caso de tener varios canales de ámbito vista para diferentes propósitos, lo mejor es tener un único canal de ámbito vista y tener una listener Javascript global que pueda distinguir los tareas de los diferentes mensajes enviados. Por ejemplo, enviando el mensaje al servidor como se muestra a continuación:

Map<String, Object> message = new HashMap<>();
message.put("functionName", "someFunction");
message.put("functionData", functionData); // Can be Map or Bean.
someChannel.send(message);

que es procesado por el listener Javascript onmessage como se indica a continuación:

function someSocketListener(message) {
    window[message.functionName](message.functionData);
}

function someFunction(data) {
    // ...
}

function otherFunction(data) {
    // ...
}

// ...


Control de Conexiones

Se puede usar el atributo opcional connected para controlar cuando se auto-conecta o no, la conexión web. Acepta valores booleanos que pueden obtenerse mediante expresiones EL

<fw:socket channel="someChannel" ... connected="#{bean.pushable}" />


El valor por defecto es true. Si el valor del atributo connected o rendered es una expresión EL, y se vuelve a false durante una solicitud AJAX, entonces cualquier conexión push abierta se cerrará explícitamente al acabar la petición AJAX, incluso aunque no haya actualizado el componente <fw:socket> en la petición AJAX. Hay  que asegurarse de que el valor esta ligado al menos a una propiedad de la vista en caso de querer controlarlo durante el ámbito de la vista.


Se puede establecer el valor del atributo connected directamente a false y controlar manualmente la apertura de la conexión push en la parte cliente invocando con Javascript a FundeWeb.Push.open(channel), pasando el nombre del canal como parámetro. Por ejemplo, cuando se lance el listener onclick de un botón que inicia una ejecución de una tarea asíncrona de larga duración en la parte servidor. Es realmente útil, para conexiones de ámbito vista, que no necesitan realizar una carga en página de manera inmediata.

<p:commandButton ... onclick="FundeWeb.Push.open('foo')">
    <p:ajax ... />
</p:commandButton>
<fw:socket channel="foo" scope="view" ... connected="false" />

 

En el caso de querer tener una comunicación push de un solo mensaje, normalmente porque se quiere presentar el resultado de una tarea asíncrona lanzada desde la vista, como es el caso del ejemplo anterior, se puede cerrar la conexión push invocando en el cliente  FundeWeb.Push.close(channel), pasando el nombre del canal como parámetro. Por ejemplo, podemos usar el listener Javascript onmessage para relizar la acción:

function someSocketListener(message, channel) {
    // ...
    FundeWeb.Push.close(channel);
}


Es importante no mezclar las dos formas de controlar la conexión push. O se usa una expresión EL en el atributo connected, o se especifica connected="false" y la API Javascript  FundeWeb.Push para controlar la apertura/cierre de la conexión push. Mezclar ambas configuraciones puede provocar comportamientos impredecibles porque el estado de la vista Faces en el servidor no puede ser notificada de la apertura o cierre manual desde el cliente.


Eventos en el Cliente

El listener opcional de Javascript onopen, se usa cuando se abre el websocket en el cliente. Se lanza en el primer intento de conexión, independientemente de que la conexión se pueda realizar o no. No se invoca, en las auto-reconexiones que se hagan posteriormente. Acepta como único parámetro channel, el nombre del canal.

<fw:socket channel="foo" scope="view" ... onopen="socketOpenListener" />
function socketOpenListener(channel) {
    // ...
}


El listener opcional de Javascript onerror, se usa para detectar los errores de conexión antes de un nuevo reintento de conexión. Se invocará cuando el socket web pueda realizar un intento de reconexión automática en una conexión interrumpida después de la primera conexión correcta. No se invocará cuando falle el primer intento de conexión, o el servidor haya devuelto el código de motivo de cierre 1000 (cierre normal) o 1008 (política violada), o se haya excedido el número máximo de intentos de reconexión. En su lugar, se invocará listener Javascript onclose.

<fw:socket channel="foo" scope="view" ... onerror="socketErrorListener" />
function socketErrorListener(code, channel, event) {
    if (code == 1001) {
        // Server has returned an unexpected response code. E.g. 503, because it's shutting down.
    } else if (code == 1006) {
        // Server is not reachable anymore. I.e. it's not anymore listening on TCP/IP requests.
    } else {
        // Any other reason which is usually not -1, 1000 or 1008, as the onclose will be invoked instead.
    }

    // In any case, the web socket will attempt to reconnect. This function will be invoked again.
    // Once the web socket gives up reconnecting, the onclose will finally be invoked.
}


El listener de Javascript onerror acepta tres parámetros:


El listener opcional de Javascript onclose, se puede usar para detectar el cierro normal o no del websocket. Se invocará cuando falle el primer intento de conexión, o el servidor haya devuelto el código de motivo de cierre 1000 (cierre normal) o 1008 (política violada), o se haya excedido el máximo de intentos de reconexión. NO se invocará cuando el websocket pueda realizar un intento de reconexión automática en una conexión interrumpida después de la primera conexión correcta. En su lugar, se invocará onerror.

<fw:socket channel="foo" scope="view" ... onclose="socketCloseListener" />
function socketCloseListener(code, channel, event) {
    if (code == -1) {
        // Web sockets not supported by client.
    } else if (code == 1000) {
        // Normal close (as result of expired session or view).
    } else {
        // Abnormal close reason (as result of an error).
    }
}

El listener de Javascript onclose acepta tres parámetros:


Cuando una conexión de ámbito sesión o vista, es cerrado automáticamente por el servidor con un código de 1000 (y no cerrado manualmente con FundeWeb.Push.close(channel)), indica que la sesión o la vista a expirado. Si la conexión que expira tiene ámbito de sesión, podemos aprovechar la oportunidad para mostrar un mensaje de información y redirigir a la página de inicio o autenticación usando window.location. Para el caso de que una conexión con ámbito vista expire, la gestión dependerá del tipo de expiración. Una vista puede expirar cuando la sesión asociada expira, pero además se puede producir con una navegación o recarga accidental, o cuando la configuración Faces "view per session" tiene un valor bajo y el cliente tiene bastantes vistas (ventanas/pestañas) abiertas en la misma sesión. Puedes aprovechar la oportunidad para advertir al cliente y/o dejar que JavaScript vuelva a cargar la página, ya que enviar cualquier formulario generaría una excepción ViewExpiredException.


Eventos en el Servidor

Cuando un websocket es abierto, se lanza un evento es.umatica.faces.push.socketEvent.opened (definido en la constante SocketEvent.EVENT_SOCKET_OPENED). Cuando el atributo user de <fw:socket> cambia, se lanza un evento es.umatica.faces.push.socketEvent.switched (definido en la constante SocketEvent.EVENT_SOCKET_SWITCHED). Cuando un websocket es cerrado, se lanza un evento es.umatica.faces.push.socketEvent.closed (definido en la constante SocketEvent.EVENT_SOCKET_CLOSED). Observar estos eventos en componentes de ámbito EVENT/PAGE/SESSION no tiene sentido, ya que no se puede realizar una solicitud HTTP en ese momento.

@Name("socketObserver")
@Scope(ScopeType.APPLICATION)
public class SocketObserver {

    @Observer( SocketEvent.EVENT_SOCKET_OPENED )
    public void onOpen(SocketEvent event) {
        String channel = event.getChannel(); // Returns <o:socket channel>.
        Long userId = event.getUser(); // Returns <o:socket user>, if any.
        // Do your thing with it. E.g. collecting them in a concurrent/synchronized collection.
        // Do note that a single person can open multiple sockets on same channel/user.
    }

    @Observer( SocketEvent.EVENT_SOCKET_SWITCHED )
    public void onSwitch(SocketEvent event) {
        String channel = event.getChannel(); // Returns <o:socket channel>.
        Long currentUserId = event.getUser(); // Returns current <o:socket user>, if any.
        Long previousUserId = event.getPreviousUser(); // Returns previous <o:socket user>, if any.
        // Do your thing with it. E.g. updating in a concurrent/synchronized collection.
    }

    @Observer( SocketEvent.EVENT_SOCKET_CLOSED ) 
    public void onClose(@Observes @Closed SocketEvent event) {
        String channel = event.getChannel(); // Returns <o:socket channel>.
        Long userId = event.getUser(); // Returns <o:socket user>, if any.
        CloseCode code = event.getCloseCode(); // Returns close reason code.
        // Do your thing with it. E.g. removing them from collection.
    }

}

 Que nos da la oportunidad para enviar otro mensaje push a un socket de ámbito de aplicación, por ejemplo, "El usuario X ha iniciado sesión" (o cerrado) cuando se abre (o cierra) un socket de ámbito de sesión.

Consideraciones de Seguridad

Si el socket se declara en una página que tiene permitido el acceso a usuarios que han iniciado sesión con un rol específico, es posible agregar la URL de la solicitud de protocolo push al conjunto de las URL restringidas.

La URL de solicitud de protocolo push se compone del prefijo URI /fundeweb.push/, seguido del nombre del canal. Por ejemplo, si la seguridad administrada por el contenedor definida en el web.xml, ya ha controlado el acceso a la página /user/foo.xhtml, de los usuarios que hayan iniciado sesión con el rol USER en el patrón de URL de ejemplo /user/* como se muestra a continuación:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Restrict access to role USER.</web-resource-name>
        <url-pattern>/user/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>USER</role-name>
    </auth-constraint>
</security-constraint>

.. y la página /user/foo.xhtml a su vez contiene un <fw:socket channel="foo">, entonces necesita agregar una restricción en el patrón de URL de solicitud de protocolo push de /fundeweb.push/foo como se muestra a continuación:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Restrict access to role USER.</web-resource-name>
        <url-pattern>/user/*</url-pattern>
        <url-pattern>/fundeweb.push/foo</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>USER</role-name>
    </auth-constraint>
</security-constraint>


Como seguridad adicional, particularmente para aquellos canales públicos que no pueden ser restringidos por restricciones de seguridad, <fw:socket> registrará todos los canales declarados hasta el momento en la sesión HTTP, y cualquier solicitud de apertura de websocket entrante será verificada contra los canales registrados hasta el momento en la sesión HTTP.  En caso de que el canal sea desconocido (por ejemplo, adivinado al azar o falsificado por los usuarios finales o reconectado manualmente después de que expire la sesión), el websocket se cerrará inmediatamente con el código de motivo de cierre 1008 (CloseReason.CloseCodes.VIOLATED_POLICY). Además, cuando se destruye la sesión HTTP, todos los canales de sesión y vista que aún estén abiertos se cerrarán explícitamente desde el lado del servidor con el código de motivo de cierre 1000 (CloseReason.CloseCodes.NORMAL_CLOSURE). Solo los sockets de ámbito de aplicación permanecerán abiertos y accesibles desde el lado del servidor, incluso cuando la sesión o vista asociada con la página en el lado del cliente ha expirado.

Sugerencias de Diseño con EJBs

En caso de que desee activar un push desde el backend EAR/EJB a una conexión push con ámbito de aplicación, puede utilizar eventos de JBoss Seam. Primero, cree una clase de bean personalizada que represente el evento push, como por ejemplo, PushEvent, y que contenga lo necesario para pasar como mensaje push.

public final class PushEvent implemments org.jboss.seam.core.EventObject {

    private final String message;

    public PushEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

Después, utiliza org.jboss.seam.core.Events.raiseEvent(org.jboss.seam.core.EventObject), para lanzar el evento

public void onSomeEntityChange(Entity entity) {
    org.jboss.seam.core.Events.instance().raiseEvent((new PushEvent(entity.getSomeProperty()));
}

Para terminar, usar la anotación @Observer en algún componente de ámbito EVENT, STATELESS  o APPLICATION, y delegue en es.um.atica.faces.push.PushContext como se indica a continuación:

@Push
private PushContext someChannel;

@Observer
public void onPushEvent(PushEvent event) {
    someChannel.send(event.getMessage());
}


Es importante advertir, que el componente con ámbito EVENT, no debe ser el mismo que el de la página de origen, por la sencilla razón de que no hay forma de realizar una solicitud HTTP en ese momento.  Por esa misma razón,
un componente con ámbito PAGE o SESSION, no funcionaría (ya que requieren respectivamente el estado de vista Faces y la sesión HTTP que solo se pueden identificar mediante una solicitud HTTP). Una conexión push con ámbito de view o session, tampoco funcionaría, por lo que la conexión push realmente debe tener ámbito  application. El FacesContext tampoco estará disponible en el método que recibe el evento anterior.


En caso de que el disparador del evento del backend EAR/EJB sea un método de servicio asincrónico, que a su vez se inicia en el lado WAR, entonces puede hacer uso de callbacks del lado WAR. Deje que el método de servicio tome una instancia de callback como parámetro, como por ejemplo, la interfaz java.util.function.Consumer.

@Asynchronous
public void someAsyncServiceMethod(Entity entity, Consumer<Object> callback) {
    // ... (some long process)
    callback.accept(entity.getSomeProperty());
}

Y la invocación del método del servicio WAR es el siguiente:

@In(create = true)
private SomeService someService;

@Push
private PushContext someChannel;

public void someAction() {
    someService.someAsyncServiceMethod(entity, message -> someChannel.send(message));
}

Esta sería la única forma en caso de que desee enviar de forma asincrónica un mensaje a una conexión push con ámbito de view o session, y/o desee pasar algo desde FacesContext o de ámbito EVENT/PAGE/SESSION como argumento (con modificador final).

Sugerencias de Diseño en Cluster

En el caso que la aplicación este desplegada en un cluster con varios nodos, y el evento push se lanza desde un nodo diferente al que esta conectado el cliente (Navegador Web), entonces este no será notificado. Una solución es usar temas (topic) JMS, lanzar el evento push a través de JMS, y usar MDB (message driven bean) para reenviar el evento push a JBoss Seam en cada nodo.


Con JBoss Seam tenemos varios componentes para ayudar a trabajar con JMS. Ejemplo para la configuración de JMS con JBoss Seam, en el fichero components.xml registramos el manager del tema (topic) JMS, como norma general, el nombre del tema  comenzara con el nombre de la aplicación en minúsculas seguido de PushTopic, en el ejemplo usaremos pruebaPushTopic. Para el nombre del publisher, podemos usar el nombre que queramos. Veamos un ejemplo de configuración:

<jms:managed-topic-publisher name="pruebaPushPublisher" 
                             auto-create="true" 
                             topic-jndi-name="topic/pruebaPushTopic"/>

Ahora se puede inyectar el componente pruebaPushPublisher en cualquier componente:

@Name("pushChangeNotifier")
public class PushManager {

   @In
   private TopicPublisher pruebaPushPublisher;   

   @In
   private TopicSession topicSession;

   public void fireEvent(PushEvent event) {
        try {
           pruebaPushPublisher.publish(topicSession.createObjectMessage(event.getMessage()));
        } catch (Exception ex) {
           throw new RuntimeException(ex);
        } 
   }

}


Para recibir los mensajes JMS tenemos que configurar un MDB:

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName = "destinationType",
        propertyValue = "javax.jms.Topic"
    ),
    @ActivationConfigProperty(
        propertyName = "destination",
        propertyValue = "topic/pruebaPushTopic"
    )
})
@Name("pushReceiver")
public class pushReceiver implements MessageListener {

   @Logger
   private Log log;
  
   @Override
   public void onMessage(Message jmsMessage) {
      try {
         String message = jmsMessage.getStringProperty("message");
         Events.instance().raiseEvent(new PushEvent(message));
      } catch (JMSException ex) {
         log.error("Problem sending push event message.", ex);
      } 
   }

}


Ahora, usamos PushManager#fireEvent() para lanzar eventos JMS desde un nodo del cluster, que permite después lanzar los eventos de JBoss Seam en todos los nodos el cluster y asi enviar el mensaje al canal push correspondiente:

@In
private PushManager pushManager;

public void onSomeEntityChange(Entity entity) {
    pushManager.fireEvent(new PushEvent(entity.getSomeProperty()));
}


Más información sobre JMS:


Sugerencias de Diseño de la UI

Si se quieren realizar actualizaciones complejas de la UI, la forma más sencilla es usando <f:ajax> dentro de <fw:socket>. Ejemplo:

<h:panelGroup id="foo">
    ... (some complex UI here) ...
</h:panelGroup>

<h:form>
    <fw:socket channel="someChannel" scope="view">
        <f:ajax event="someEvent" listener="#{bean.pushed}" render=":foo" />
    </fw:socket>
</h:form>


Donde el mensaje push, solo contien el nombre del evento Ajax. Puedes usar cualquier nombre de evento personalizado.

someChannel.send("someEvent");


Otra alternativa, es combinar <fw:socket> con <p:remoteCommand>, ejemplo:

<h:panelGroup id="foo">
    ... (some complex UI here) ...
</h:panelGroup>

<fw:socket channel="someChannel" scope="view" onmessage="someCommandScript" />
<h:form>
    <p:remoteCommand name="someCommandScript" action="#{bean.pushed}" update=":foo" />
</h:form>


Si se pasa un Map<String, V> o un POJO (JAVABEAN) como el objeto en el mensaje push, después todas las entradas/propiedades estarán disponibles como parámetros de la solicitud en la acción del comando #{bean.pushed}.