/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This 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.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.cache.impl;
import org.exoplatform.commons.utils.Tools;
import org.exoplatform.container.component.ComponentPlugin;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.management.annotations.ManagedBy;
import org.exoplatform.services.cache.CacheService;
import org.exoplatform.services.cache.ExoCache;
import org.exoplatform.services.cache.ExoCacheConfig;
import org.exoplatform.services.cache.ExoCacheConfigPlugin;
import org.exoplatform.services.cache.ExoCacheFactory;
import org.exoplatform.services.cache.ExoCacheInitException;
import org.exoplatform.services.cache.SimpleExoCache;
import org.exoplatform.services.cache.invalidation.InvalidationExoCache;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Created by The eXo Platform SAS. Author : Tuan Nguyen
* tuan08@users.sourceforge.net Sat, Sep 13, 2003 @ Time: 1:12:22 PM
*/
@SuppressWarnings("deprecation")
@ManagedBy(CacheServiceManaged.class)
public class CacheServiceImpl implements CacheService
{
/**
* Logger.
*/
private static final Log LOG = ExoLogger.getLogger("exo.kernel.component.cache.CacheServiceImpl");
private final ExoCacheFactory DEFAULT_FACTORY = new SimpleExoCacheFactory();
private final HashMap<String, ExoCacheConfig> configs_ = new HashMap<String, ExoCacheConfig>();
private final ConcurrentHashMap<String, FutureExoCacheCreationTask> cacheMap_ =
new ConcurrentHashMap<String, FutureExoCacheCreationTask>();
private final ExoCacheConfig defaultConfig_;
private final LoggingCacheListener loggingListener_;
private final ExoCacheFactory factory_;
CacheServiceManaged managed;
/**
*
*/
public CacheServiceImpl(InitParams params) throws Exception
{
this(params, null);
}
public CacheServiceImpl(InitParams params, ExoCacheFactory factory) throws Exception
{
List<ExoCacheConfig> configs = params.getObjectParamValues(ExoCacheConfig.class);
for (ExoCacheConfig config : configs)
{
configs_.put(config.getName(), config);
}
defaultConfig_ = configs_.get("default");
loggingListener_ = new LoggingCacheListener();
factory_ = factory == null ? DEFAULT_FACTORY : factory;
}
public void addExoCacheConfig(ComponentPlugin plugin)
{
addExoCacheConfig((ExoCacheConfigPlugin)plugin);
}
public void addExoCacheConfig(ExoCacheConfigPlugin plugin)
{
List<ExoCacheConfig> configs = plugin.getConfigs();
for (ExoCacheConfig config : configs)
{
configs_.put(config.getName(), config);
}
}
@SuppressWarnings("unchecked")
public <K extends Serializable, V> ExoCache<K, V> getCacheInstance(final String region)
{
if (region == null)
{
throw new IllegalArgumentException("region cannot be null");
}
if (region.length() == 0)
{
throw new IllegalArgumentException("region cannot be empty");
}
FutureExoCacheCreationTask creationTask = cacheMap_.get(region);
if (creationTask == null)
{
Callable<ExoCache<? extends Serializable,?>> task = new Callable<ExoCache<? extends Serializable,?>>()
{
public ExoCache<? extends Serializable, ?> call() throws Exception
{
return createCacheInstance(region);
}
};
creationTask = new FutureExoCacheCreationTask(task);
FutureExoCacheCreationTask existingTask = cacheMap_.putIfAbsent(region, creationTask);
if (existingTask != null)
{
creationTask = existingTask;
}
else
{
creationTask.run();
}
}
try
{
return (ExoCache<K, V>)creationTask.get();
}
catch (CancellationException e)
{
cacheMap_.remove(region, creationTask);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
catch (ExecutionException e)
{
LOG.error("Could not create the cache for the region '" + region + "'", e.getCause());
}
return null;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private ExoCache<? extends Serializable, ?> createCacheInstance(String region) throws Exception
{
ExoCacheConfig config = configs_.get(region);
if (config == null)
config = defaultConfig_;
// Ensure the configuration integrity
final ExoCacheConfig safeConfig = config.clone();
// Set the region as name
safeConfig.setName(region);
ExoCache simple = null;
if (factory_ != DEFAULT_FACTORY && safeConfig.getClass().isAssignableFrom(ExoCacheConfig.class) //NOSONAR
&& safeConfig.getImplementation() != null)
{
// The implementation exists and the config is not a sub class of ExoCacheConfig
// we assume that we expect to use the default cache factory
try
{
// We check if the given implementation is a known class
Class<?> implClass = Tools.loadClass(safeConfig.getImplementation(), this);
// Implementation is an existing class
if (ExoCache.class.isAssignableFrom(implClass))
{
// The implementation is a sub class of eXo Cache so we use the default factory
simple = DEFAULT_FACTORY.createCache(safeConfig);
}
}
catch (ClassNotFoundException e)
{
if (LOG.isTraceEnabled())
{
LOG.trace("An exception occurred: " + e.getMessage());
}
}
}
if (simple == null)
{
// We use the configured cache factory
simple = factory_.createCache(safeConfig);
}
if (managed != null)
{
managed.registerCache(simple);
}
// If the flag avoid value replication is enabled and the cache is replicated
// or distributed we wrap the eXo cache instance into an InvalidationExoCache
// to enable the invalidation
return safeConfig.avoidValueReplication() && (safeConfig.isRepicated() || safeConfig.isDistributed())
? new InvalidationExoCache(simple) : simple;
}
public Collection<ExoCache<? extends Serializable, ?>> getAllCacheInstances()
{
Collection<ExoCache<? extends Serializable, ?>> caches =
new ArrayList<ExoCache<? extends Serializable,?>>(cacheMap_.size());
for (FutureTask<ExoCache<? extends Serializable,?>> task : cacheMap_.values())
{
ExoCache<? extends Serializable, ?> cache = null;
try
{
cache = task.get();
}
catch (CancellationException e)
{
if (LOG.isTraceEnabled())
{
LOG.trace("An exception occurred: " + e.getMessage());
}
}
catch (InterruptedException e)
{
if (LOG.isTraceEnabled())
{
LOG.trace("An exception occurred: " + e.getMessage());
}
}
catch (ExecutionException e)
{
if (LOG.isTraceEnabled())
{
LOG.trace("An exception occurred: " + e.getMessage());
}
}
if (cache != null)
{
caches.add(cache);
}
}
return caches;
}
/**
* Default implementation of an {@link org.exoplatform.services.cache.ExoCacheFactory}
*/
private class SimpleExoCacheFactory implements ExoCacheFactory
{
/**
* {@inheritDoc}
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public ExoCache createCache(ExoCacheConfig config) throws ExoCacheInitException
{
final ExoCache simple = createCacheInstance(config);
simple.setName(config.getName());
simple.setLabel(config.getLabel());
simple.setMaxSize(config.getMaxSize());
simple.setLiveTime(config.getLiveTime());
simple.setLogEnabled(config.isLogEnabled());
if (simple.isLogEnabled())
{
simple.addCacheListener(loggingListener_);
}
return simple;
}
/**
* Create a new instance of ExoCache according to the given configuration
* @param config the ExoCache configuration
* @return a new instance of ExoCache
* @throws ExoCacheInitException if any exception happens while initializing the cache
*/
@SuppressWarnings("rawtypes")
private ExoCache createCacheInstance(ExoCacheConfig config) throws ExoCacheInitException
{
if (config.getImplementation() == null)
{
// No implementation has been defined
return new SimpleExoCache();
}
else
{
// An implementation has been defined
try
{
final Class<?> clazz = Tools.loadClass(config.getImplementation(), this);
return (ExoCache)clazz.newInstance();
}
catch (ExceptionInInitializerError e)
{
throw new ExoCacheInitException("Cannot create instance of ExoCache of type "
+ config.getImplementation(), e);
}
catch (SecurityException e)
{
throw new ExoCacheInitException("Cannot create instance of ExoCache of type "
+ config.getImplementation(), e);
}
catch (ClassNotFoundException e)
{
throw new ExoCacheInitException("Cannot create instance of ExoCache of type "
+ config.getImplementation(), e);
}
catch (InstantiationException e)
{
throw new ExoCacheInitException("Cannot create instance of ExoCache of type "
+ config.getImplementation(), e);
}
catch (IllegalAccessException e)
{
throw new ExoCacheInitException("Cannot create instance of ExoCache of type "
+ config.getImplementation(), e);
}
}
}
}
/**
* This class is used to reduce the contention when the cache is already created
*/
private static class FutureExoCacheCreationTask extends FutureTask<ExoCache<? extends Serializable, ?>>
{
private volatile ExoCache<? extends Serializable, ?> cache;
/**
* @param callable
*/
public FutureExoCacheCreationTask(Callable<ExoCache<? extends Serializable, ?>> callable)
{
super(callable);
}
@Override
public ExoCache<? extends Serializable, ?> get() throws InterruptedException, ExecutionException
{
if (cache != null)
{
return cache;
}
return cache = super.get();
}
}
}