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 */ 022package org.granite.tide.data; 023 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.IdentityHashMap; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030 031import org.granite.gravity.Gravity; 032import org.granite.logging.Logger; 033import org.granite.tide.data.DataEnabled.PublishMode; 034 035 036/** 037 * @author William DRAI 038 */ 039public 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}