/*
* CachingFacility.java
* Copyright (C) 2011,2012 Wannes De Smet
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.xenmaster.api.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.log4j.Logger;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
import org.xenmaster.api.entity.Event;
import org.xenmaster.api.entity.Task;
import org.xenmaster.api.entity.XenApiEntity;
import org.xenmaster.controller.BadAPICallException;
import org.xenmaster.controller.Controller;
import org.xenmaster.monitoring.EventHandler.EventListener;
import org.xenmaster.monitoring.MonitoringAgent;
/**
*
* @created Dec 19, 2011
* @author double-u
*/
public class CachingFacility {
protected Mode mode;
protected Cache<String, XenApiEntity> cache;
protected CopyOnWriteArrayList<Class> loadedEntityClasses;
protected EmbeddedCacheManager ecm;
private static CachingFacility instance;
private CachingFacility(boolean distributed) {
this.mode = Mode.LAZY;
this.cache = buildCache(distributed);
this.loadedEntityClasses = new CopyOnWriteArrayList<>();
registerCacheUpdater();
}
protected final void registerCacheUpdater() {
MonitoringAgent.get().getEventHandler().addListener(new EventListener() {
@Override
public void eventOcurred(Event event) {
// Tasks are bound to a process, not the a result so we're not interested in storing these
if (event.getSnapshot() == null || Task.class.isAssignableFrom(event.getSnapshot().getClass())) {
return;
}
if (event.getOperation() == Event.Operation.DEL) {
remove(event.getSnapshot());
} else {
update(event.getSnapshot(), event.getOperation() == Event.Operation.ADD);
}
}
});
}
protected Cache buildCache(boolean distributed) {
ConfigurationBuilder cb = new ConfigurationBuilder();
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
gcb.transport().transport(new JGroupsTransport());
gcb.transport().clusterName("XenMaster");
cb.clustering().cacheMode((distributed ? CacheMode.DIST_SYNC : CacheMode.LOCAL));
if (distributed) {
ecm = new DefaultCacheManager(gcb.build(), cb.build());
} else {
ecm = new DefaultCacheManager(cb.build());
}
return ecm.getCache();
}
public static CachingFacility instance() {
return instance(true);
}
public static CachingFacility instance(boolean distributed) {
if (instance == null) {
instance = new CachingFacility(distributed);
}
return instance;
}
public void stop() {
cache.stop();
}
public EmbeddedCacheManager getCacheManager() {
return ecm;
}
public static enum Mode {
PREHEAT, LAZY
}
protected <T extends XenApiEntity> void heatCache(Class<T> target) {
try {
Map<String, Object> objects = (Map<String, Object>) Controller.dispatch(XenApiEntity.getAPIName(target) + ".get_all_records");
Constructor<T> ctor = target.getConstructor(String.class, boolean.class);
for (Map.Entry<String, Object> entry : objects.entrySet()) {
T obj = ctor.newInstance(entry.getKey(), false);
obj.fillOut((Map<String, Object>) entry.getValue());
cache.put(entry.getKey(), obj);
}
loadedEntityClasses.add(target);
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(getClass()).debug("Failed to contruct object of type " + target.getCanonicalName(), ex);
} catch (BadAPICallException ex) {
Logger.getLogger(getClass()).debug(target.getCanonicalName() + " does not have a getAll method", ex);
}
}
public static <T extends XenApiEntity> T get(String reference, Class<T> target) {
return instance().getEntity(reference, target);
}
public <T extends XenApiEntity> void update(T object, boolean force) {
if (object == null) {
throw new IllegalArgumentException("Cannot update null");
}
if (isCached(object.getReference(false), object.getClass()) || (object.getReference() != null && force)) {
cache.put(object.getReference(), object);
} else {
// We only are interested in updates for things we've cached, others will always be newest available ones when they are retreived
Logger.getLogger(getClass()).debug("Object " + object.getReference(false) + '(' + object.getClass().getCanonicalName() + ") was not inside cache and therefore could not be updated.");
}
}
public void remove(XenApiEntity object) {
if (object == null) {
throw new IllegalArgumentException("Cannot remove null");
}
// No reference is provided with the event, so we'll have to walk trough each object in cache and see if the uuid matches
for(Cache.Entry<String, XenApiEntity> entry : cache.entrySet()) {
if (entry.getValue().getUUID().equals(object.getUUID())) {
cache.remove(entry.getKey());
return;
}
}
}
public boolean isCached(String reference, Class target) {
return reference != null && cache.containsKey(reference) && target != null && target.isAssignableFrom(cache.get(reference).getClass());
}
public <T extends XenApiEntity> T getEntity(String reference, Class<T> target) {
if (reference == null) {
return null;
}
if (!cache.containsKey(reference) && !loadedEntityClasses.contains(target)) {
heatCache(target);
}
if (cache.containsKey(reference)) {
if (target != null && target.isAssignableFrom(cache.get(reference).getClass())) {
return (T) cache.get(reference);
} else {
Logger.getLogger(getClass()).error("Cached entity has an illegal type " + cache.get(reference).getClass().getCanonicalName() + " instead of " + target.getCanonicalName());
return null;
}
}
try {
Constructor c = target.getConstructor(String.class, boolean.class);
T newObject = (T) c.newInstance(reference, !reference.isEmpty());
return newObject;
} catch (IllegalAccessException | IllegalArgumentException | InstantiationException | InvocationTargetException | NoSuchMethodException ex) {
Logger.getLogger(getClass()).error("Failed to initialize object of type " + target.getCanonicalName(), ex);
}
return null;
}
}