001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    package org.granite.tide.data;
023    
024    import java.util.ArrayList;
025    import java.util.HashMap;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Map.Entry;
029    
030    import org.granite.clustering.DistributedData;
031    import org.granite.context.GraniteContext;
032    import org.granite.logging.Logger;
033    
034    import flex.messaging.messages.AsyncMessage;
035    
036    
037    /**
038     *  Base implementation for data update dispatchers.
039     *  It should be built at beginning of each request during initialization of <code>DataContext</code>.
040     *  The dispatch is a three step process :
041     * 
042     *      <ul>
043     *  <li>Initialization in the constructor</li>
044     *  <li><code>observe()</code> builds the server selector depending on the data that are processed</li>
045     *  <li><code>publish()</code> handles the actual publishing</li>
046     *  </ul>
047     *  
048     *  Actual implementations should only override <code>changeDataSelector</code> and <code>publishUpdate</code>/
049     * 
050     *  @see DataDispatcher
051     *  @see DataContext
052     * 
053     *  @author William Drai
054     */
055    public abstract class AbstractDataDispatcher implements DataDispatcher {
056    
057            private static final Logger log = Logger.getLogger(AbstractDataDispatcher.class);
058    
059        
060        protected boolean enabled;
061        protected String topicName = null;
062        protected DataTopicParams paramsProvider = null;
063        protected String sessionId = null;
064        protected String clientId = null;
065        protected String subscriptionId = null;
066        
067        
068            public AbstractDataDispatcher(String topicName, Class<? extends DataTopicParams> dataTopicParamsClass) {
069                    this.topicName = topicName;
070                    
071                    try {
072                            paramsProvider = dataTopicParamsClass.newInstance();
073                    }
074                    catch (Exception e) {
075                            log.error("Could not instantiate class " + dataTopicParamsClass, e);
076                    }
077            }
078            
079            
080            public void observe() {
081                    // Prepare the selector even if we are not yet subscribed
082                    DataObserveParams params = null;
083                    if (paramsProvider != null) {
084                            // Collect selector parameters from component
085                            params = new DataObserveParams();
086                            paramsProvider.observes(params);
087                    }               
088                    
089                    // Ensure that the current Gravity consumer listens about this data topic and params
090                    GraniteContext graniteContext = GraniteContext.getCurrentInstance();
091                    if (graniteContext == null)
092                            return;
093                    
094                    DistributedData gdd = graniteContext.getGraniteConfig().getDistributedDataFactory().getInstance();
095                    if (gdd == null)
096                            return; // Session expired
097                    
098                    List<DataObserveParams> selectors = DataObserveParams.fromSerializableForm(gdd.getDestinationDataSelectors(topicName));
099                    List<DataObserveParams> newSelectors = new ArrayList<DataObserveParams>(selectors);
100                    
101                    boolean dataSelectorChanged = false;
102                    String dataSelector = gdd.getDestinationSelector(topicName);
103                    if (params != null) {
104                            String newDataSelector = params.updateDataSelector(dataSelector, newSelectors);
105                            dataSelectorChanged = !newDataSelector.equals(dataSelector);
106                            if (dataSelectorChanged) {
107                                    log.debug("Data selector changed: %s", newDataSelector);
108                                    gdd.setDestinationSelector(topicName, newDataSelector);
109                                    dataSelector = newDataSelector;
110                            }
111                    }
112                    
113                    if (!DataObserveParams.containsSame(selectors, newSelectors)) {
114                            log.debug("Selectors changed: %s", newSelectors);
115                            gdd.setDestinationDataSelectors(topicName, DataObserveParams.toSerializableForm(newSelectors));
116                    }
117                    
118                    if (!enabled)
119                            return;
120                    
121                    if (dataSelectorChanged)
122                            changeDataSelector(dataSelector);
123            }
124            
125            protected abstract void changeDataSelector(String dataSelector);
126            
127            
128            public void publish(Object[][] dataUpdates) {
129                    if (!enabled)
130                            return;
131                    
132                    try {
133                            Map<Map<String, String>, List<Object>> updates = new HashMap<Map<String, String>, List<Object>>();
134                            if (paramsProvider != null) {
135                                    for (Object[] dataUpdate : dataUpdates) {
136                                            DataPublishParams params = new DataPublishParams();
137                                            paramsProvider.publishes(params, dataUpdate[1]);
138                                            
139                                            Map<String, String> headers = params.getHeaders();
140                                            List<Object> list = updates.get(headers);
141                                            if (list == null) {
142                                                    list = new ArrayList<Object>();
143                                                    updates.put(headers, list);
144                                            }
145                                            list.add(dataUpdate);
146                                    }
147                            }
148                            
149                            for (Entry<Map<String, String>, List<Object>> me : updates.entrySet()) {
150                                    Map<String, String> headers = new HashMap<String, String>(me.getKey());
151                                    headers.put(AsyncMessage.SUBTOPIC_HEADER, TIDE_DATA_SUBTOPIC);
152                                    headers.put(GDS_SESSION_ID, sessionId);
153                                    headers.put(TIDE_DATA_TYPE_KEY, TIDE_DATA_TYPE_VALUE);
154                                    publishUpdate(headers, me.getValue().toArray());
155                            }
156                    }
157                    catch (Exception e) {
158                            log.error(e, "Could not publish data update on topic %s", topicName);
159                    }
160            }
161            
162            protected abstract void publishUpdate(Map<String, String> params, Object body);
163    }