/* * AngularBeans, CDI-AngularJS bridge * * Copyright (c) 2014, Bessem Hmidi. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * */ /** @author Bessem Hmidi */ package angularBeans.realtime; import static org.projectodd.sockjs.ReadyState.OPEN; import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.enterprise.event.Observes; import javax.inject.Inject; import org.projectodd.sockjs.SockJsConnection; import angularBeans.boot.BeanRegistry; import angularBeans.context.NGSessionScoped; import angularBeans.context.SessionMapper; import angularBeans.events.BroadcastManager; import angularBeans.events.RealTimeMessage; import angularBeans.events.RealTimeSessionCloseEvent; import angularBeans.events.RealTimeSessionReadyEvent; import angularBeans.events.ServerEvent; import angularBeans.log.NGLogger; import angularBeans.remote.DataReceivedEvent; import angularBeans.remote.RealTimeDataReceivedEvent; import angularBeans.util.AngularBeansUtils; import angularBeans.util.CommonUtils; import angularBeans.util.ModelQuery; import angularBeans.util.ModelQueryImpl; import angularBeans.util.NGBean; /** * * @author bessem * * when injected, a realTime client represent the current real time * session (websocket or fallback protocol) * **/ @NGSessionScoped public class RealTimeClient implements Serializable { private final Set<SockJsConnection> sessions = new HashSet<>(); @Inject BroadcastManager broadcastManager; @Inject GlobalConnectionHolder connectionHolder; @Inject AngularBeansUtils util; @Inject NGLogger logger; private boolean async = false; /** * the Realtime client will use will use AsyncRemote of the websocket api * <p> * usage: realTimeClient.async().*any-method()*. */ public RealTimeClient async() { async = true; return this; } public void onSessionReady( @Observes @RealTimeSessionReadyEvent RealTimeDataReceivedEvent event) { connectionHolder.getAllConnections().add(event.getConnection()); sessions.add(event.getConnection()); Set<NGBean> angularBeans = BeanRegistry.INSTANCE.getAngularBeans(); for (NGBean bean : angularBeans) { String httpSessionId = SessionMapper.getHTTPSessionID(event .getConnection().id); broadcastManager.subscribe(httpSessionId, bean.getTargetClass() .getSimpleName()); } event.setClient(this); } public void onClose( @Observes @RealTimeSessionCloseEvent RealTimeDataReceivedEvent event) { connectionHolder.getAllConnections().remove(event.getConnection()); } // public void onError( // @Observes @RealTimeErrorEvent RealTimeDataReceivedEvent event) { // // throw new RuntimeException(event.getData().toString()); // } public void onData( @Observes @DataReceivedEvent RealTimeDataReceivedEvent event) { // sessions.add(event.getConnection()); event.setClient(this); } /** * will close all current realTime sessions bound to the current HTTP * session */ public void invalidateSession() { for (SockJsConnection connection : sessions) { connection.close( javax.websocket.CloseReason.CloseCodes.CANNOT_ACCEPT .getCode(), "CLOSED BY BACKEND"); } } /** * send a message to the current session front end * * @param channel * : can be * * - The AngularBean class name OR A custom channel * * @param message * : the RealTimeMessage to send */ public void publish(String channel, RealTimeMessage message) { Map<String, Object> paramsToSend = prepareData(channel, message); publish(paramsToSend); } /** * send a ModelQuery to the current session front end AngularBean proxy to * update his models * * @param query * : the ModelQuery to send */ public void publish(ModelQuery query) { Map<String, Object> paramsToSend = prepareData(query); publish(paramsToSend); } /** * send a message to all front end open sessions * * @param channel * : can be * * - The AngularBean class name - A custom channel * * @param message * : the RealTimeMessage to send * @param withoutMe * : possible values: * <p> * true: the current session client will not receive the message. * <p> * false: the current session client will also receive the * message. */ public void broadcast(String channel, RealTimeMessage message, boolean withoutMe) { Map<String, Object> paramsToSend = prepareData(channel, message); broadcast(channel, withoutMe, paramsToSend); } /** * send a ModelQuery to all front end open sessions * * @param query * : the ModelQuery to send * * @param withoutMe * : possible values: * <p> * true: the current session client will not receive the query. * <p> * false: the current session client will also receive the query. */ public void broadcast(ModelQuery query, boolean withoutMe) { Map<String, Object> paramsToSend = prepareData(query); broadcast(query.getTargetServiceClass(), withoutMe, paramsToSend); } private Map<String, Object> prepareData(ModelQuery query) { Map<String, Object> paramsToSend = new HashMap<String, Object>(); ModelQueryImpl modelQuery = (ModelQueryImpl) query; ServerEvent ngEvent = new ServerEvent(); ngEvent.setName("modelQuery"); ngEvent.setData(CommonUtils.getBeanName(modelQuery.getOwner())); paramsToSend.putAll(modelQuery.getData()); paramsToSend.put("ngEvent", ngEvent); paramsToSend.put("log", logger.getLogPool()); paramsToSend.put("isRT", true); return paramsToSend; } private Map<String, Object> prepareData(String eventName, RealTimeMessage message) { Map<String, Object> paramsToSend = new HashMap<String, Object>( message.build()); ServerEvent ngEvent = new ServerEvent(); ngEvent.setName(eventName); ngEvent.setData(message.build()); paramsToSend.put("ngEvent", ngEvent); paramsToSend.put("log", logger.getLogPool()); paramsToSend.put("isRT", true); return paramsToSend; } private void broadcast(String channel, boolean withoutMe, Map<String, Object> paramsToSend) { for (SockJsConnection connection : connectionHolder.getAllConnections()) { if (!broadcastManager.isSubscribed(connection.id, channel)) { continue; } if (withoutMe) { if (sessions.contains(connection)) { continue; } } if (connection.getReadyState().equals(OPEN)) { String objectMessage = util.getJson(paramsToSend); connection.write(objectMessage, async); async = false; } } } private void publish(Map<String, Object> paramsToSend) { for (SockJsConnection session : new HashSet<SockJsConnection>(sessions)) { if (!session.getReadyState().equals(OPEN)) { sessions.remove(session); } else { session.write(util.getJson(paramsToSend), async); async = false; } } } }