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);
}
}
}