package er.wojrebel; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.zeroturnaround.javarebel.Logger; import org.zeroturnaround.javarebel.LoggerFactory; import com.webobjects.eoaccess.EOModel; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eocontrol.EOClassDescription; import com.webobjects.eocontrol.EOCooperatingObjectStore; import com.webobjects.eocontrol.EOKeyValueCoding; import com.webobjects.eocontrol.EOObjectStoreCoordinator; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSSelector; import com.webobjects.foundation.NSValidation; import com.webobjects.foundation._NSUtilities; public class WOJRebelEOModelReloadHandler { private static final String EO_ENTITY_CACHE_RESET = "EOEntityCacheReset"; private static final Class<?>[] NotificationClassArray = new Class[] { NSNotification.class }; private static boolean initialized = false; private static final WOJRebelEOModelReloadHandler instance = new WOJRebelEOModelReloadHandler(); private static final Logger log = LoggerFactory.getInstance(); private final Map<EOModel, Long> modelCache = Collections.synchronizedMap(new WeakHashMap<>()); private final Map<EOObjectStoreCoordinator, EOModelGroup> oscCache = new WeakHashMap<>(); private Field _ERXEntityCache; private Method _ERXEntityClassDescriptionCacheReset; private Object _ERXEntityClassDescriptionFactory; public static WOJRebelEOModelReloadHandler getInstance() { return instance; } public synchronized void updateLoadedModels(NSNotification n) { boolean reloaded = false; List<EOModel> modelList = new ArrayList<>(modelCache.keySet()); for (EOModel model : modelList) { reloaded |= shouldUpdateModel(model); } if (reloaded) { flushCaches(); for (EOModel model : modelList) { updateModel(model); } } } private boolean updateModel(EOModel model) { if (shouldUpdateModel(model)) { reloadModel(model); return true; } return false; } private boolean shouldUpdateModel(EOModel model) { if (modelCache.containsKey(model)) { if (lastModified(model) > modelCache.get(model)) { return true; } } return false; } private long lastModified(EOModel model) { URL url = model.pathURL(); File modeld = new File (url.getPath()); long lastmod = modeld.lastModified(); for (File file : modeld.listFiles()) { if (file.lastModified() > lastmod) lastmod = file.lastModified(); } return lastmod; } private void reloadModel(EOModel model) { log.echo("JRebel: reloading EOModel " + model.name() + " (" + model.hashCode() + ")"); EOModel newModel = new EOModel(model.pathURL()); EOModelGroup modelGroup = model.modelGroup(); modelGroup.removeModel(model); modelGroup.addModel(newModel); for (Map.Entry<EOObjectStoreCoordinator, EOModelGroup> entry : oscCache.entrySet()) { if (modelGroup == entry.getValue()) { EOObjectStoreCoordinator osc = entry.getKey(); for (Object obj : osc.cooperatingObjectStores()) { EOCooperatingObjectStore store = (EOCooperatingObjectStore) obj; osc.removeCooperatingObjectStore(store); } osc.invalidateAllObjects(); } } } public void modelAdded(NSNotification n) { if (modelCache.containsKey(n.object())) return; EOModel model = (EOModel) n.object(); if (model.pathURL() != null) { modelCache.put(model, lastModified(model)); } } public void modelRemoved(NSNotification n) { modelCache.remove(n.object()); } public void storeCoordinatorAdded(NSNotification n) { EOObjectStoreCoordinator osc = (EOObjectStoreCoordinator) n.object(); oscCache.put(osc, EOModelGroup.modelGroupForObjectStoreCoordinator(osc)); } public void storeCoordinatorRemoved(NSNotification n) { oscCache.remove(n.object()); } @SuppressWarnings("unchecked") public void initialize() { if (initialized) { return; } initialized = true; NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("modelAdded", NotificationClassArray), EOModelGroup.ModelAddedNotification, null); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("modelRemoved", NotificationClassArray), EOModelGroup.ModelInvalidatedNotification, null); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("storeCoordinatorAdded", NotificationClassArray), EOObjectStoreCoordinator.CooperatingObjectStoreWasAddedNotification, null); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("storeCoordinatorRemoved", NotificationClassArray), EOObjectStoreCoordinator.CooperatingObjectStoreWasRemovedNotification, null); Class<?> ERXClass = _NSUtilities.classWithName("er.extensions.foundation.ERXUtilities"); if (ERXClass != null) { try { _ERXEntityCache = ERXClass.getDeclaredField("_entityNameEntityCache"); _ERXEntityCache.setAccessible(true); ERXClass = _NSUtilities.classWithName("er.extensions.eof.ERXEntityClassDescription"); Method factory = ERXClass.getDeclaredMethod("factory"); _ERXEntityClassDescriptionFactory = factory.invoke(null, new Object[0]); ERXClass = _NSUtilities.classWithName("er.extensions.eof.ERXEntityClassDescription$Factory"); _ERXEntityClassDescriptionCacheReset = ERXClass.getDeclaredMethod("reset"); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("resetWonderEntityCache", NotificationClassArray), EO_ENTITY_CACHE_RESET, null); } catch (Exception e) { e.printStackTrace(); } } } public void resetWonderEntityCache(NSNotification notification) { try { _ERXEntityCache.set(null, null); _ERXEntityClassDescriptionCacheReset.invoke(_ERXEntityClassDescriptionFactory, new Object[0]); } catch (Exception e) { e.printStackTrace(); } } private void flushCaches() { log.echo("JRebel: flushing EOModel caches"); EOKeyValueCoding.DefaultImplementation._flushCaches(); EOClassDescription.invalidateClassDescriptionCache(); D2WAccessor.flushCaches(); NSValidation.DefaultImplementation._flushCaches(); NSNotificationCenter.defaultCenter().postNotification(EO_ENTITY_CACHE_RESET, null); } }