/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.tide.data; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.granite.gravity.Gravity; import org.granite.logging.Logger; import org.granite.tide.data.DataEnabled.PublishMode; /** * @author William DRAI */ public class DataContext { private static final Logger log = Logger.getLogger(DataContext.class); private static ThreadLocal<DataContext> dataContext = new ThreadLocal<DataContext>(); private static DataContext NULL_DATA_CONTEXT = new NullDataContext(); private DataDispatcher dataDispatcher = null; private PublishMode publishMode = null; private Object[][] updates = null; private DataUpdatePostprocessor dataUpdatePostprocessor = null; private Map<Object, Object> entityExtraDataMap = new IdentityHashMap<Object, Object>(); public static void init() { if (dataContext.get() == null) dataContext.set(NULL_DATA_CONTEXT); } public static void init(String topic, Class<? extends DataTopicParams> dataTopicParamsClass, PublishMode publishMode) { DataContext dc = new DataContext(null, topic, dataTopicParamsClass, publishMode); dataContext.set(dc); } public static void init(Gravity gravity, String topic, Class<? extends DataTopicParams> dataTopicParamsClass, PublishMode publishMode) { DataContext dc = new DataContext(gravity, topic, dataTopicParamsClass, publishMode); dataContext.set(dc); } public static void init(DataDispatcher dataDispatcher, PublishMode publishMode) { DataContext dc = new DataContext(dataDispatcher, publishMode); dataContext.set(dc); } private DataContext(Gravity gravity, String topic, Class<? extends DataTopicParams> dataTopicParamsClass, PublishMode publishMode) { log.debug("Init Gravity data context for topic %s and mode %s", topic, publishMode); this.dataDispatcher = new DefaultDataDispatcher(gravity, topic, dataTopicParamsClass); this.publishMode = publishMode; } private DataContext(DataDispatcher dataDispatcher, PublishMode publishMode) { log.debug("Init data context with custom dispatcher %s and mode %s", dataDispatcher, publishMode); this.dataDispatcher = dataDispatcher; this.publishMode = publishMode; } public static DataContext get() { return dataContext.get(); } public static void remove() { log.debug("Remove data context"); dataContext.remove(); } public static boolean isNull() { return dataContext.get() == NULL_DATA_CONTEXT; } private final List<EntityUpdate> dataUpdates = new ArrayList<EntityUpdate>(); private boolean published = false; public List<EntityUpdate> getDataUpdates() { return dataUpdates; } public Object[][] getUpdates() { if (updates != null) return updates; if (dataUpdates == null || dataUpdates.isEmpty()) return null; List<EntityUpdate> processedDataUpdates = dataUpdates; if (dataUpdatePostprocessor != null) processedDataUpdates = dataUpdatePostprocessor.process(dataUpdates); // Order updates : persist then updates then removals Collections.sort(processedDataUpdates); updates = new Object[processedDataUpdates.size()][]; int i = 0; Iterator<EntityUpdate> iu = processedDataUpdates.iterator(); while (iu.hasNext()) { EntityUpdate u = iu.next(); updates[i++] = u.toArray(); } return updates; } public void setDataUpdatePostprocessor(DataUpdatePostprocessor dataUpdatePostprocessor) { this.dataUpdatePostprocessor = dataUpdatePostprocessor; } public static void addUpdate(EntityUpdateType type, Object entity) { addUpdate(type, entity, entity, 0); } public static void addUpdate(EntityUpdateType type, Object entity, int priority) { addUpdate(type, entity, entity, priority); } public static void addUpdate(EntityUpdateType type, Object entity, Object source) { addUpdate(type, entity, source, 0); } public static void addUpdate(EntityUpdateType type, Object entity, Object source, int priority) { DataContext dc = get(); if (dc != null && dc.dataDispatcher != null) { if (entity instanceof Class<?>) entity = ((Class<?>)entity).getName(); for (EntityUpdate update : dc.dataUpdates) { if (update.type.equals(type) && update.entity.equals(entity)) { if (update.priority < priority) update.priority = priority; return; } } dc.dataUpdates.add(new EntityUpdate(type, entity, source, priority)); dc.updates = null; } } public static void addEntityExtraData(Object entity, Object extraData) { DataContext dc = get(); if (dc != null && dc.entityExtraDataMap != null) dc.entityExtraDataMap.put(entity, extraData); } public static Object getEntityExtraData(Object entity) { DataContext dc = get(); return dc != null && dc.entityExtraDataMap != null ? dc.entityExtraDataMap.get(entity) : null; } public static void observe() { DataContext dc = get(); if (dc != null && dc.dataDispatcher != null) { log.debug("Observe data updates"); dc.dataDispatcher.observe(); } } public static void publish() { publish(PublishMode.MANUAL); } public static void publish(PublishMode publishMode) { DataContext dc = get(); if (dc != null && dc.dataDispatcher != null && !dc.dataUpdates.isEmpty() && !dc.published && (publishMode == PublishMode.MANUAL || (dc.publishMode.equals(publishMode)))) { log.debug("Publish %s data updates with mode %s", dc.dataUpdates.size(), dc.publishMode); dc.dataDispatcher.publish(dc.getUpdates()); // Publish can be called only once but we have to keep the updates until the end of a GraniteDS request dc.published = true; } } public enum EntityUpdateType { PERSIST, UPDATE, REMOVE, REFRESH } public static class EntityUpdate implements Comparable<EntityUpdate> { public EntityUpdateType type; public Object entity; public Object source; public int priority = 0; public EntityUpdate(EntityUpdateType type, Object entity, Object source, int priority) { this.type = type; this.entity = entity; this.source = source; this.priority = priority; } @SuppressWarnings("unchecked") public int compareTo(EntityUpdate u) { if (type.ordinal() != u.type.ordinal()) return type.ordinal() - u.type.ordinal(); if (!entity.equals(u.entity)) { if (entity.getClass().equals(u.entity.getClass()) && Comparable.class.isAssignableFrom(entity.getClass())) return ((Comparable<Object>)entity).compareTo(u.entity); return Math.abs(entity.hashCode()) - Math.abs(u.entity.hashCode()); // hashCode can be negative !!! } return priority - u.priority; } public Object[] toArray() { return new Object[] { type.name(), entity, source }; } } private static class NullDataContext extends DataContext { public NullDataContext() { super(null, null); } @Override public List<EntityUpdate> getDataUpdates() { return Collections.emptyList(); } } }