/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * *** * * Community License: GPL 3.0 * * This file is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * This file 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * *** * * Available Commercial License: GraniteDS SLA 1.0 * * This is the appropriate option if you are creating proprietary * applications and you are not prepared to distribute and share the * source code of your application under the GPL v3 license. * * Please visit http://www.granitedataservices.com/license for more * details. */ package org.granite.client.tide.data; import java.util.ArrayList; import java.util.List; import org.granite.client.messaging.Consumer; import org.granite.client.messaging.ResponseListener; import org.granite.client.messaging.ResultFaultIssuesResponseListener; import org.granite.client.messaging.TopicMessageListener; import org.granite.client.messaging.channel.ResponseMessageFuture; import org.granite.client.messaging.events.FaultEvent; import org.granite.client.messaging.events.IssueEvent; import org.granite.client.messaging.events.ResultEvent; import org.granite.client.messaging.events.TopicMessageEvent; import org.granite.client.messaging.messages.push.TopicMessage; import org.granite.client.tide.Context; import org.granite.client.tide.ContextAware; import org.granite.client.tide.NameAware; import org.granite.client.tide.data.EntityManager.UpdateKind; import org.granite.client.tide.data.impl.ChangeEntity; import org.granite.client.tide.data.impl.ChangeEntityRef; import org.granite.client.tide.data.spi.MergeContext; import org.granite.client.tide.server.ServerSession; import org.granite.logging.Logger; import org.granite.tide.data.Change; import org.granite.tide.data.ChangeRef; /** * @author William DRAI */ public class DataObserver implements ContextAware, NameAware { private static Logger log = Logger.getLogger(DataObserver.class); public static final String DATA_OBSERVER_TOPIC_NAME = "tideDataTopic"; private Context context; private ServerSession serverSession = null; private EntityManager entityManager = null; private String channelType = null; private String destination = null; private Consumer consumer = null; private boolean subscribing = false; private boolean unsubscribing = false; protected DataObserver() { // CDI proxying... } public DataObserver(ServerSession serverSession) { this.serverSession = serverSession; if (serverSession.getContext() != null) this.entityManager = serverSession.getContext().getEntityManager(); } public DataObserver(String channelType, ServerSession serverSession) { this.channelType = channelType; this.serverSession = serverSession; if (serverSession.getContext() != null) this.entityManager = serverSession.getContext().getEntityManager(); } public DataObserver(ServerSession serverSession, EntityManager entityManager) { this.serverSession = serverSession; this.entityManager = entityManager; } public DataObserver(String destination, ServerSession serverSession, EntityManager entityManager) { this.destination = destination; this.serverSession = serverSession; this.entityManager = entityManager; } public DataObserver(String destination, String channelType, ServerSession serverSession, EntityManager entityManager) { this.destination = destination; this.channelType = channelType; this.serverSession = serverSession; this.entityManager = entityManager; } public void setContext(Context context) { this.context = context; if (this.entityManager == null) this.entityManager = context.getEntityManager(); } public void setName(String name) { if (this.destination == null) this.destination = name; } public void start() { consumer = serverSession.getConsumer(destination, DATA_OBSERVER_TOPIC_NAME, channelType); unsubscribing = false; } public void stop() { if (consumer != null && consumer.isSubscribed()) unsubscribe(true); consumer = null; } /** * Subscribe the data topic */ public synchronized void subscribe() { if (consumer == null) throw new IllegalStateException("Cannot subscribe, DataObserver " + this.destination + " not started"); if (subscribing) return; subscribing = true; consumer.addMessageListener(messageListener); consumer.subscribe(subscriptionListener); } public void unsubscribe() { unsubscribe(false); } public synchronized void unsubscribe(boolean onStop) { if (consumer == null) throw new IllegalStateException("Cannot unsubscribe, DataObserver " + this.destination + " not started"); if (!consumer.isSubscribed() || unsubscribing) return; unsubscribing = true; consumer.removeMessageListener(messageListener); if (!onStop) consumer.unsubscribe(unsubscriptionListener); else { ResponseMessageFuture future = consumer.unsubscribe(unsubscriptionListener); try { future.get(2500L); // 2.5s. } catch (Exception e) { log.error(e, "Destination %s could not be unsubscribed on stop: %s", destination); } } } private ResponseListener subscriptionListener = new ResultFaultIssuesResponseListener() { @Override public void onResult(ResultEvent event) { log.info("Destination %s subscribed sid: %s", destination, consumer.getSubscriptionId()); subscribing = false; } @Override public void onFault(FaultEvent event) { log.error("Destination %s could not be subscribed: %s", destination, event.getCode()); subscribing = false; } @Override public void onIssue(IssueEvent event) { log.error("Destination %s could not be subscribed: %s", destination, event.getType()); subscribing = false; } }; private ResponseListener unsubscriptionListener = new ResultFaultIssuesResponseListener() { @Override public void onResult(ResultEvent event) { log.info("Destination %s unsubscribed", destination); unsubscribing = false; } @Override public void onFault(FaultEvent event) { log.error("Destination %s could not be unsubscribed: %s", destination, event.getCode()); unsubscribing = false; } @Override public void onIssue(IssueEvent event) { log.error("Destination %s could not be unsubscribed: %s", destination, event.getType()); unsubscribing = false; } }; private TopicMessageListener messageListener = new TopicMessageListener() { /** * Message handler that merges data from the JMS topic in the current context.<br/> * Could be overriden to provide custom behaviour. * * @param event message event from the Consumer */ @Override public void onMessage(TopicMessageEvent event) { log.debug("Destination %s message event received %s", destination, event.toString()); final TopicMessage message = event.getMessage(); context.callLater(new Runnable() { @Override public void run() { try { String receivedSessionId = (String)message.getHeader("GDSSessionID"); if (receivedSessionId != null && receivedSessionId.equals(serverSession.getSessionId())) receivedSessionId = null; MergeContext mergeContext = entityManager.initMerge(serverSession); Object[] updates = (Object[])message.getData(); List<EntityManager.Update> upds = new ArrayList<EntityManager.Update>(); for (Object update : updates) { String updateType = ((Object[])update)[0].toString().toUpperCase(); Object entity = ((Object[])update)[1]; if (UpdateKind.REFRESH.toString().toLowerCase().equals(updateType) && entity instanceof String) entity = serverSession.getAliasRegistry().getAliasForType((String)entity); else if (entity instanceof ChangeRef) entity = new ChangeEntityRef(entity, serverSession.getAliasRegistry()); else if (entity instanceof Change) entity = new ChangeEntity((Change)entity, serverSession.getAliasRegistry()); upds.add(new EntityManager.Update(UpdateKind.forName(updateType), entity)); } entityManager.handleUpdates(mergeContext, receivedSessionId, upds); entityManager.raiseUpdateEvents(context, upds); } catch (Exception e) { log.error(e, "Error during received message processing"); } finally { MergeContext.destroy(entityManager); } } }); } }; }