001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 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.tide.data;
022    
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.IdentityHashMap;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    
030    import org.granite.gravity.Gravity;
031    import org.granite.logging.Logger;
032    import org.granite.tide.data.DataEnabled.PublishMode;
033    
034    
035    /**
036     * @author William DRAI
037     */
038    public class DataContext {
039        
040            private static final Logger log = Logger.getLogger(DataContext.class);
041            
042        private static ThreadLocal<DataContext> dataContext = new ThreadLocal<DataContext>();
043        
044        private static DataContext NULL_DATA_CONTEXT = new NullDataContext(); 
045        
046        private DataDispatcher dataDispatcher = null;
047        private PublishMode publishMode = null;
048        private Object[][] updates = null;
049        private DataUpdatePostprocessor dataUpdatePostprocessor = null;
050        private Map<Object, Object> entityExtraDataMap = new IdentityHashMap<Object, Object>();
051        
052        
053        public static void init() {
054            if (dataContext.get() == null)
055                    dataContext.set(NULL_DATA_CONTEXT);
056        }
057        
058        public static void init(String topic, Class<? extends DataTopicParams> dataTopicParamsClass, PublishMode publishMode) {
059            DataContext dc = new DataContext(null, topic, dataTopicParamsClass, publishMode);
060            dataContext.set(dc);
061        }
062        
063        public static void init(Gravity gravity, String topic, Class<? extends DataTopicParams> dataTopicParamsClass, PublishMode publishMode) {
064            DataContext dc = new DataContext(gravity, topic, dataTopicParamsClass, publishMode);
065            dataContext.set(dc);
066        }
067        
068        public static void init(DataDispatcher dataDispatcher, PublishMode publishMode) {
069                    DataContext dc = new DataContext(dataDispatcher, publishMode);
070                    dataContext.set(dc);
071        }
072        
073        private DataContext(Gravity gravity, String topic, Class<? extends DataTopicParams> dataTopicParamsClass, PublishMode publishMode) {
074                    log.debug("Init Gravity data context for topic %s and mode %s", topic, publishMode);
075                    this.dataDispatcher = new DefaultDataDispatcher(gravity, topic, dataTopicParamsClass);
076                    this.publishMode = publishMode;
077        }
078    
079        private DataContext(DataDispatcher dataDispatcher, PublishMode publishMode) {
080                    log.debug("Init data context with custom dispatcher %s and mode %s", dataDispatcher, publishMode);
081                    this.dataDispatcher = dataDispatcher;
082                    this.publishMode = publishMode;
083        }
084    
085        public static DataContext get() {
086            return dataContext.get();
087        }
088        
089        public static void remove() {
090                    log.debug("Remove data context");
091            dataContext.remove();
092        }
093        
094        public static boolean isNull() {
095            return dataContext.get() == NULL_DATA_CONTEXT;
096        }
097        
098        private final List<EntityUpdate> dataUpdates = new ArrayList<EntityUpdate>();
099        private boolean published = false;
100    
101        
102        public List<EntityUpdate> getDataUpdates() {
103            return dataUpdates;
104        }
105        
106        public Object[][] getUpdates() {
107            if (updates != null)
108                    return updates;
109            
110            if (dataUpdates == null || dataUpdates.isEmpty())
111                    return null;
112            
113            List<EntityUpdate> processedDataUpdates = dataUpdates;
114            if (dataUpdatePostprocessor != null)
115                    processedDataUpdates = dataUpdatePostprocessor.process(dataUpdates);
116            
117            // Order updates : persist then updates then removals 
118            Collections.sort(processedDataUpdates);
119            
120            updates = new Object[processedDataUpdates.size()][];
121            int i = 0;
122            Iterator<EntityUpdate> iu = processedDataUpdates.iterator();
123            while (iu.hasNext()) {
124                    EntityUpdate u = iu.next();
125                    updates[i++] = new Object[] { u.type.name(), u.entity }; 
126            }
127                    return updates;
128        }
129        
130        public void setDataUpdatePostprocessor(DataUpdatePostprocessor dataUpdatePostprocessor) {
131            this.dataUpdatePostprocessor = dataUpdatePostprocessor;
132        }
133        
134        public static void addUpdate(EntityUpdateType type, Object entity) {
135            addUpdate(type, entity, 0);     
136        }
137        public static void addUpdate(EntityUpdateType type, Object entity, int priority) {
138            DataContext dc = get();
139            if (dc != null && dc.dataDispatcher != null) {
140                    for (EntityUpdate update : dc.dataUpdates) {
141                            if (update.type.equals(type) && update.entity.equals(entity)) {
142                                    if (update.priority < priority)
143                                            update.priority = priority;
144                                    return;
145                            }
146                    }
147                    dc.dataUpdates.add(new EntityUpdate(type, entity, priority));
148                    dc.updates = null;
149            }
150        }
151        
152        public static void addEntityExtraData(Object entity, Object extraData) {
153            DataContext dc = get();
154            if (dc != null && dc.entityExtraDataMap != null)
155                    dc.entityExtraDataMap.put(entity, extraData);
156        }
157        
158        public static Object getEntityExtraData(Object entity) {
159            DataContext dc = get();
160            return dc != null && dc.entityExtraDataMap != null ? dc.entityExtraDataMap.get(entity) : null;
161        }
162            
163        public static void observe() {
164            DataContext dc = get();
165            if (dc != null && dc.dataDispatcher != null) {
166                    log.debug("Observe data updates");
167                    dc.dataDispatcher.observe();
168            }
169        }
170        
171        public static void publish() {
172            publish(PublishMode.MANUAL);
173        }
174        public static void publish(PublishMode publishMode) {
175            DataContext dc = get();
176            if (dc != null && dc.dataDispatcher != null && !dc.dataUpdates.isEmpty() && !dc.published 
177                    && (publishMode == PublishMode.MANUAL || (dc.publishMode.equals(publishMode)))) {
178                    log.debug("Publish %s data updates with mode %s", dc.dataUpdates.size(), dc.publishMode);
179                    dc.dataDispatcher.publish(dc.getUpdates());
180                    // Publish can be called only once but we have to keep the updates until the end of a GraniteDS request
181                    dc.published = true;    
182            }
183        }
184        
185        
186        public enum EntityUpdateType {
187            PERSIST,
188            UPDATE,
189            REMOVE
190        }
191        
192        public static class EntityUpdate implements Comparable<EntityUpdate> {
193            public EntityUpdateType type;
194            public Object entity;
195            public int priority = 0;
196    
197            public EntityUpdate(EntityUpdateType type, Object entity, int priority) {
198                    this.type = type;
199                    this.entity = entity;
200                    this.priority = priority;
201            }
202    
203                    public int compareTo(EntityUpdate u) {
204                            if (type.ordinal() != u.type.ordinal())
205                                    return type.ordinal() - u.type.ordinal();
206                        if (!entity.equals(u.entity))
207                            return entity.hashCode() - u.entity.hashCode();
208                    return priority - u.priority;
209                    }
210        }
211        
212        private static class NullDataContext extends DataContext {
213            
214            public NullDataContext() {
215                    super(null, null);
216            }
217            
218            @Override
219            public List<EntityUpdate> getDataUpdates() {
220                    return Collections.emptyList();
221            }
222        }
223    }