package org.infinispan.atomic; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.infinispan.Cache; import org.infinispan.InvalidCacheUsageException; import org.infinispan.atomic.container.Container; import org.infinispan.atomic.container.ContainerSignature; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * @author Pierre Sutra * @since 7.2 */ public class AtomicObjectFactory { // // CLASS FIELDS // private static Log log = LogFactory.getLog(AtomicObjectFactory.class); private static Map<Cache<?, ?>, AtomicObjectFactory> factories = new HashMap<>(); public synchronized static AtomicObjectFactory forCache(Cache<?, ?> cache) { if(!factories.containsKey(cache)) factories.put(cache, new AtomicObjectFactory(cache)); return factories.get(cache); } protected static final int MAX_CONTAINERS = 1000;// 0 means no limit public static final Map<Class<?>, List<String>> updateMethods; static{ updateMethods = new HashMap<>(); updateMethods.put(List.class, new ArrayList<>()); updateMethods.get(List.class).add("add"); updateMethods.get(List.class).add("addAll"); updateMethods.put(Set.class, new ArrayList<>()); updateMethods.get(Set.class).add("add"); updateMethods.get(Set.class).add("addAll"); updateMethods.put(Map.class, new ArrayList<>()); updateMethods.get(Map.class).add("put"); updateMethods.get(Map.class).add("putAll"); } // // OBJECT FIELDS // private Cache<?, ?> cache; private Map<ContainerSignature,Container> registeredContainers; private int maxSize; private final ExecutorService evictionExecutor = Executors.newCachedThreadPool(); /** * * Returns an object factory built on top of cache <i>c</i> with a bounded amount <i>m</i> of * containers in it. Upon the removal of a container, the object is stored persistently in the cache. * * @param c it must be synchronous.and transactional (with autoCommit set to true, its default value). * @param m max amount of containers kept by this factory. * @throws InvalidCacheUsageException */ public AtomicObjectFactory(Cache<?, ?> c, int m) throws InvalidCacheUsageException{ cache = c; maxSize = m; registeredContainers= new LinkedHashMap<ContainerSignature,Container>() { @Override protected boolean removeEldestEntry(final java.util.Map.Entry<ContainerSignature,Container> eldest) { if (maxSize != 0 && this.size() >= maxSize) { evictionExecutor.submit(new Callable<Void>() { @Override public Void call() throws IOException { try { if (log.isDebugEnabled()) log.debugf("Disposing %s", eldest.getValue().toString()); eldest.getValue().dispose(false); } catch (Exception e) { e.printStackTrace(); } return null; } }); return true; } return false; } }; log = LogFactory.getLog(this.getClass()); } /** * * Return an AtomicObjectFactory built on top of cache <i>c</i>. * * @param c a cache, it must be synchronous.and transactional (with autoCommit set to true, its default value). */ public AtomicObjectFactory(Cache<?, ?> c) throws InvalidCacheUsageException { this(c,MAX_CONTAINERS); } /** * * Returns an atomic object of class <i>clazz</i>. * The class of this object must be initially serializable, as well as all the parameters of its methods. * Furthermore, the class must be deterministic. * * @param clazz a class object * @param key to use in order to store the object. * @return an object of the class <i>clazz</i> * @throws InvalidCacheUsageException */ public synchronized <T> T getInstanceOf(Class<T> clazz, Object key) throws InvalidCacheUsageException { return getInstanceOf(clazz, key, false); } /** * * Returns an object of class <i>clazz</i>. * The class of this object must be initially serializable, as well as all the parameters of its methods. * Furthermore, the class must be deterministic. * * The object is atomic if <i>withReadOptimization</i> equals false; otherwise it is sequentially consistent.. * In more details, if <i>withReadOptimization</i> is set, every call to the object is first executed locally on a copy of the object, and in case * the call does not modify the state of the object, the value returned is the result of this tentative execution. * * @param clazz a class object * @param key the key to use in order to store the object. * @param withReadOptimization set the read optimization on/off. * @return an object of the class <i>clazz</i> * @throws InvalidCacheUsageException */ public <T> T getInstanceOf(Class<T> clazz, Object key, boolean withReadOptimization) throws InvalidCacheUsageException { return getInstanceOf(clazz, key, withReadOptimization, null, true); } /** * * Returns an object of class <i>clazz</i>. * The class of this object must be initially serializable, as well as all the parameters of its methods. * Furthermore, the class must be deterministic. * * The object is atomic if <i>withReadOptimization</i> equals false; otherwise it is sequentially consistent.. * In more details, if <i>withReadOptimization</i> is set, every call to the object is executed locally on a copy of the object, and in case * the call does not modify the state of the object, the value returned is the result of this tentative execution. * If the method <i>equalsMethod</i> is not null, it overrides the default <i>clazz.equals()</i> when testing that the state of the object and * its copy are identical. * * @param clazz a class object * @param key the key to use in order to store the object. * @param withReadOptimization set the read optimization on/off. * @param equalsMethod overriding the default <i>clazz.equals()</i>. * @param forceNew force the creation of the object, even if it exists already in the cache * @return an object of the class <i>clazz</i> * @throws InvalidCacheUsageException */ public <T> T getInstanceOf(Class<T> clazz, Object key, boolean withReadOptimization, Method equalsMethod, boolean forceNew, Object ... initArgs) throws InvalidCacheUsageException { if (!(Serializable.class.isAssignableFrom(clazz))) { throw new InvalidCacheUsageException("The object must be serializable."); } ContainerSignature signature = new ContainerSignature(clazz,key); Container container; try { synchronized (registeredContainers) { container = registeredContainers.get(signature); } if (container == null) { List<String> methods = Collections.emptyList(); if (Updatable.class.isAssignableFrom(clazz)) { methods = new ArrayList<>(); for (Method m : clazz.getDeclaredMethods()) { if (m.isAnnotationPresent(Update.class)) methods.add(m.getName()); } } else { for (Class<?> c : updateMethods.keySet()) { if (c.isAssignableFrom(clazz)) { methods = updateMethods.get(c); break; } } if (methods.isEmpty()) { methods = new ArrayList<>(); for (Method m : clazz.getDeclaredMethods()) { methods.add(m.getName()); } } } container = new Container(cache, clazz, key, withReadOptimization, forceNew,methods, initArgs); synchronized (registeredContainers) { if (registeredContainers.containsKey(signature)) { container.dispose(false); } else { registeredContainers.put(signature, container); } } } } catch (Exception e) { e.printStackTrace(); throw new InvalidCacheUsageException(e.getCause()); } return (T) container.getProxy(); } /** * Remove the object stored at <i>key</i>from the local state. * If flag <i>keepPersistent</i> is set, a persistent copy of the current state of the object is also stored in the cache. * * @param clazz a class object * @param key the key to use in order to store the object. * @param keepPersistent indicates that a persistent copy is stored in the cache or not. */ public void disposeInstanceOf(Class<?> clazz, Object key, boolean keepPersistent) throws InvalidCacheUsageException { ContainerSignature signature = new ContainerSignature(clazz,key); if (log.isDebugEnabled()) log.debugf("Disposing %s",signature.toString()); Container container; synchronized (registeredContainers) { container = registeredContainers.get(signature); if( container == null ) return; if( ! container.getClazz().equals(clazz) ) throw new InvalidCacheUsageException("The object is not of the right class."); registeredContainers.remove(signature); } try { container.dispose(keepPersistent); } catch (Exception e) { e.printStackTrace(); throw new InvalidCacheUsageException("Error while disposing object " + key); } } }