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