001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.client.tide.data;
022    
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import javax.annotation.PostConstruct;
027    import javax.annotation.PreDestroy;
028    
029    import org.granite.client.messaging.Consumer;
030    import org.granite.client.messaging.ResponseListener;
031    import org.granite.client.messaging.ResultFaultIssuesResponseListener;
032    import org.granite.client.messaging.TopicMessageListener;
033    import org.granite.client.messaging.events.FaultEvent;
034    import org.granite.client.messaging.events.IssueEvent;
035    import org.granite.client.messaging.events.ResultEvent;
036    import org.granite.client.messaging.events.TopicMessageEvent;
037    import org.granite.client.messaging.messages.push.TopicMessage;
038    import org.granite.client.tide.Context;
039    import org.granite.client.tide.ContextAware;
040    import org.granite.client.tide.NameAware;
041    import org.granite.client.tide.data.EntityManager.UpdateKind;
042    import org.granite.client.tide.data.spi.MergeContext;
043    import org.granite.client.tide.server.ServerSession;
044    import org.granite.logging.Logger;
045    
046    /**
047     * @author William DRAI
048     */
049    public class DataObserver implements ContextAware, NameAware {
050        
051        private static Logger log = Logger.getLogger(DataObserver.class);
052        
053        public static final String DATA_OBSERVER_TOPIC_NAME = "tideDataTopic";
054    
055        private Context context;
056        private ServerSession serverSession = null;
057        private EntityManager entityManager = null;
058        private String destination = null;
059        
060            private Consumer consumer = null;
061    
062            
063        protected DataObserver() {
064            // CDI proxying...
065        }
066        
067            public DataObserver(ServerSession serverSession, EntityManager entityManager) {
068                    this.serverSession = serverSession;
069                    this.entityManager = entityManager;
070            }
071            
072            public DataObserver(String destination, ServerSession serverSession, EntityManager entityManager) {
073                    this.destination = destination;
074                    this.serverSession = serverSession;
075                    this.entityManager = entityManager;
076            }
077            
078            public void setContext(Context context) {
079                    this.context = context;
080            }
081            
082            public void setName(String name) {
083                    if (this.destination == null)
084                            this.destination = name;
085            }
086            
087            @PostConstruct
088            public void start() {
089            consumer = serverSession.getConsumer(destination, DATA_OBSERVER_TOPIC_NAME);
090            }       
091            
092            @PreDestroy
093            public void stop() {
094                    if (consumer.isSubscribed())
095                            unsubscribe();
096            }
097            
098            
099            /**
100             *      Subscribe the data topic
101             */
102            public void subscribe() {
103                    consumer.addMessageListener(messageListener);
104                consumer.subscribe(subscriptionListener);
105                serverSession.checkWaitForLogout();
106            }
107            
108            public void unsubscribe() {
109                    if (consumer.isSubscribed()) {
110                            consumer.removeMessageListener(messageListener);
111                            consumer.unsubscribe(unsubscriptionListener);
112                        serverSession.checkWaitForLogout();
113                    }
114            }
115            
116            private ResponseListener subscriptionListener = new SubscriptionListenerImpl(); 
117            private ResponseListener unsubscriptionListener = new UnsubscriptionListenerImpl(); 
118            
119            private class SubscriptionListenerImpl extends ResultFaultIssuesResponseListener {
120                    @Override
121                    public void onResult(ResultEvent event) {
122                            log.info("Destination %s subscribed", destination);
123                            
124                            serverSession.tryLogout();
125                    }
126    
127                    @Override
128                    public void onFault(FaultEvent event) {
129                            log.error("Destination %s could not be subscribed: %s", destination, event.getCode());
130                            
131                            serverSession.tryLogout();
132                    }
133    
134                    @Override
135                    public void onIssue(IssueEvent event) {
136                            log.error("Destination %s could not be subscribed: %s", destination, event.getType());
137                            
138                            serverSession.tryLogout();
139                    }
140            }
141            
142            private class UnsubscriptionListenerImpl extends ResultFaultIssuesResponseListener {
143                    @Override
144                    public void onResult(ResultEvent event) {
145                            log.info("Destination %s unsubscribed", destination);
146                            
147                            serverSession.tryLogout();
148                    }
149    
150                    @Override
151                    public void onFault(FaultEvent event) {
152                            log.error("Destination %s could not be unsubscribed: %s", destination, event.getCode());
153                            
154                            serverSession.tryLogout();
155                    }
156    
157                    @Override
158                    public void onIssue(IssueEvent event) {
159                            log.error("Destination %s could not be unsubscribed: %s", destination, event.getType());
160                            
161                            serverSession.tryLogout();
162                    }
163            }
164    
165            
166            private TopicMessageListener messageListener = new TopicMessageListenerImpl();
167            
168            /**
169             *      Message handler that merges data from the JMS topic in the current context.<br/>
170             *  Could be overriden to provide custom behaviour.
171             * 
172             *  @param event message event from the Consumer
173             */
174        public class TopicMessageListenerImpl implements TopicMessageListener {
175                    @Override
176                    public void onMessage(TopicMessageEvent event) {
177                    log.debug("Destination %s message event received %s", destination, event.toString());
178                    
179                    final TopicMessage message = event.getMessage();
180                    
181                    context.callLater(new Runnable() {
182                                    @Override
183                                    public void run() {
184                                    try {
185                                            String receivedSessionId = (String)message.getHeader("GDSSessionID");
186                                            if (receivedSessionId != null && receivedSessionId.equals(serverSession.getSessionId()))
187                                                    receivedSessionId = null;
188                                            
189                                            MergeContext mergeContext = entityManager.initMerge();
190                                            
191                                            Object[] updates = (Object[])message.getData();
192                                            List<EntityManager.Update> upds = new ArrayList<EntityManager.Update>();
193                                            for (Object update : updates)
194                                                    upds.add(new EntityManager.Update(UpdateKind.forName(((Object[])update)[0].toString().toUpperCase()), ((Object[])update)[1]));
195                                            
196                                            entityManager.handleUpdates(mergeContext, receivedSessionId, upds);
197                                            entityManager.raiseUpdateEvents(context, upds);
198                                    }
199                                    catch (Exception e) {
200                                            log.error(e, "Error during received message processing");
201                                    }
202                                    finally {
203                                            MergeContext.destroy(entityManager);
204                                    }
205                                    }
206                    });
207                    }
208        }
209    }