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