/*
* Copyright 2008-2014 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaleidofoundry.core.cache;
import static org.kaleidofoundry.core.cache.CacheManagerContextBuilder.Classloader;
import static org.kaleidofoundry.core.cache.CacheManagerContextBuilder.FileStoreRef;
import static org.kaleidofoundry.core.cache.CacheManagerContextBuilder.FileStoreUri;
import static org.kaleidofoundry.core.i18n.InternalBundleHelper.CacheMessageBundle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.kaleidofoundry.core.context.ProviderException;
import org.kaleidofoundry.core.context.RuntimeContext;
import org.kaleidofoundry.core.i18n.InternalBundleHelper;
import org.kaleidofoundry.core.lang.annotation.NotNull;
import org.kaleidofoundry.core.lang.annotation.Nullable;
import org.kaleidofoundry.core.store.FileStore;
import org.kaleidofoundry.core.store.FileStoreContextBuilder;
import org.kaleidofoundry.core.store.FileStoreFactory;
import org.kaleidofoundry.core.store.FileStoreProvider;
import org.kaleidofoundry.core.store.ResourceException;
import org.kaleidofoundry.core.store.ResourceNotFoundException;
import org.kaleidofoundry.core.store.SingleFileStore;
import org.kaleidofoundry.core.util.Registry;
import org.kaleidofoundry.core.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jraduget
*/
public abstract class AbstractCacheManager implements CacheManager {
/** CacheManager default logger */
static final Logger LOGGER = LoggerFactory.getLogger(CacheManager.class);
/** force the use of this cache configuration instead of the context one (if defined) */
protected final String forcedConfiguration;
/** internal runtime context */
protected final RuntimeContext<CacheManager> context;
/** external persistent singleStore */
@Nullable
protected final SingleFileStore singleFileStore;
/** Local cache instances */
@SuppressWarnings("rawtypes")
protected final transient Registry<String, Cache> cachesByName;
/**
* constructor will use runtime context to get configuration uri
*
* @param context
*/
public AbstractCacheManager(@NotNull final RuntimeContext<CacheManager> context) {
this(null, context);
}
/**
* @param configuration override the context configuration file (if defined)
* @param context
* @throws CacheConfigurationException cache configuration resource exception
*/
@SuppressWarnings("rawtypes")
public AbstractCacheManager(final String configuration, @NotNull final RuntimeContext<CacheManager> context) {
forcedConfiguration = StringHelper.isEmpty(configuration) ? null : configuration;
// no need of configuration
if (forcedConfiguration == null && StringHelper.isEmpty(context.getString(FileStoreUri))) {
LOGGER.info(CacheMessageBundle.getMessage("cachemanager.loading.default", getMetaInformations(), getDefaultConfiguration()));
singleFileStore = null;
}
// configuration is given
else {
final String fileStoreRef = context.getString(FileStoreRef);
final FileStore fileStore;
String fileStoreUri = StringHelper.isEmpty(forcedConfiguration) ? context.getString(FileStoreUri) : forcedConfiguration;
try {
if (!StringHelper.isEmpty(fileStoreRef)) {
RuntimeContext<FileStore> fileStoreContext = new RuntimeContext<FileStore>(fileStoreRef, FileStore.class, context);
String baseUri = FileStoreProvider.buildFullResourceURi(fileStoreContext.getString(FileStoreContextBuilder.BaseUri));
if (!fileStoreUri.contains(baseUri)) {
fileStoreUri = baseUri + fileStoreUri;
}
fileStore = FileStoreFactory.provides(baseUri, fileStoreContext);
} else {
fileStore = FileStoreFactory.provides(fileStoreUri, new FileStoreContextBuilder("cachemanagers." + context.getName() + ".unnamed").build());
}
LOGGER.info(CacheMessageBundle.getMessage("cachemanager.loading.custom", getMetaInformations(), fileStoreUri));
singleFileStore = new SingleFileStore(fileStoreUri, fileStore);
} catch (final ProviderException pe) {
if (pe.getCause() instanceof ResourceNotFoundException) { throw new CacheConfigurationNotFoundException("cache.configuration.notfound",
getMetaInformations(), getCurrentConfiguration()); }
if (pe.getCause() instanceof ResourceException) { throw new CacheConfigurationException("cache.configuration.error", pe.getCause(),
getMetaInformations(), getCurrentConfiguration()); }
throw pe;
}
}
this.context = context;
cachesByName = new Registry<String, Cache>();
}
/**
* don't use it,
* this constructor is only needed and used by some IOC framework like spring.
*/
AbstractCacheManager() {
context = null;
singleFileStore = null;
cachesByName = null;
forcedConfiguration = null;
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#getCache(java.lang.Class)
*/
@Override
public <K extends Serializable, V extends Serializable> Cache<K, V> getCache(@NotNull final Class<V> cl) {
return getCache(cl, new RuntimeContext<Cache<K, V>>());
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#getCache(java.lang.Class, org.kaleidofoundry.core.context.RuntimeContext)
*/
@Override
public <K extends Serializable, V extends Serializable> Cache<K, V> getCache(final Class<V> cl, final RuntimeContext<Cache<K, V>> context) {
return getCache(cl.getName(), context);
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#getCache(java.lang.String)
*/
@Override
public <K extends Serializable, V extends Serializable> Cache<K, V> getCache(final String name) {
return getCache(name, new RuntimeContext<Cache<K, V>>());
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#getName()
*/
@Override
public String getName() {
return context.getName();
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#getCurrentConfiguration()
*/
@Override
public String getCurrentConfiguration() {
return !StringHelper.isEmpty(forcedConfiguration) ? forcedConfiguration : (StringHelper.isEmpty(context.getString(FileStoreUri)) ? context
.getString(FileStoreUri) : getDefaultConfiguration());
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#destroy(java.lang.Class)
*/
@Override
public void destroy(@NotNull final Class<?> cl) {
destroy(cl.getName());
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#destroyAll()
*/
@Override
public void destroyAll() {
try {
if (singleFileStore != null) {
singleFileStore.unload();
}
} catch (final ResourceException rse) {
LOGGER.error(InternalBundleHelper.CacheMessageBundle.getMessage("cachemanager.destroyall.store.error"), rse);
} finally {
// unregister cacheManager from registry
CacheManagerFactory.getRegistry().remove(context.getName());
LOGGER.debug("Cache manager registry after destroyAll: {}", CacheManagerFactory.getRegistry());
}
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheFactory#getCacheNames()
*/
@Override
public Set<String> getCacheNames() {
return cachesByName.keySet();
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#dumpStatistics()
*/
@Override
@NotNull
public Map<String, Map<String, Object>> dumpStatistics() {
final Map<String, Map<String, Object>> dumpStatistics = new LinkedHashMap<String, Map<String, Object>>();
for (final String cacheName : getCacheNames()) {
dumpStatistics.put(cacheName, dumpStatistics(cacheName));
}
return dumpStatistics;
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#clearStatistics()
*/
@Override
public void clearStatistics() {
for (final String cacheName : getCacheNames()) {
clearStatistics(cacheName);
}
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#printStatistics(java.io.OutputStream)
*/
@Override
public void printStatistics(@NotNull final OutputStream out) throws IOException {
Writer writer = null;
writer = new OutputStreamWriter(out);
printStatistics(writer);
writer.flush();
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#printStatistics(java.io.Writer)
*/
@Override
public void printStatistics(@NotNull final Writer writer) throws IOException {
final Map<String, Map<String, Object>> stats = dumpStatistics();
if (stats != null && !stats.isEmpty()) {
writer.append("\n");
// create table width informations
final Map<String, Integer> tableWidths = new LinkedHashMap<String, Integer>();
tableWidths.put("CacheName", 80);
// column compute width label and store it in tableWidths
final Map<String, Object> firstCacheInfo = stats.values().iterator().next();
for (final String column : firstCacheInfo.keySet()) {
tableWidths.put(column, column.length() + 4);
}
// first line of separator
for (final String column : tableWidths.keySet()) {
writer.append(StringHelper.rightPad("-", tableWidths.get(column) - 1, '-')).append(" ");
}
writer.append("\n");
// columns name
for (final String column : tableWidths.keySet()) {
writer.append(StringHelper.rightPad(StringHelper.truncate(column, tableWidths.get(column)), tableWidths.get(column), ' '));
}
writer.append("\n");
// second line of separator
for (final String column : tableWidths.keySet()) {
writer.append(StringHelper.rightPad("-", tableWidths.get(column) - 1, '-')).append(" ");
}
writer.append("\n");
if (stats != null) {
// for each cache
for (final String cacheName : stats.keySet()) {
// first column is cache name
writer.append(StringHelper.rightPad(StringHelper.truncate(String.valueOf(cacheName), tableWidths.get("CacheName")),
tableWidths.get("CacheName"), ' '));
// create a column for each stats of the current cache
final Map<String, Object> statsValue = stats.get(cacheName);
// append each column value to the writer
for (final String statKey : statsValue.keySet()) {
writer.append(StringHelper.rightPad(String.valueOf(statsValue.get(statKey)), tableWidths.get(statKey), ' '));
}
writer.append("\n");
}
}
}
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.core.cache.CacheManager#printStatistics()
*/
@Override
@NotNull
public String printStatistics() throws IOException {
final StringWriter writer = new StringWriter();
printStatistics(writer);
return writer.toString();
}
/**
* Search configuration file
* <ul>
* <li>first via URI if correct</li>
* <li>second on file system</li>
* <li>second on classpath (and jars resources)</li>
* </ul>
* <b>Don't forget to close the inputStream</b>
*
* @param configurationUri
* @return InputStream of the input configuration url, or null if none found
* @throws CacheException
*/
protected InputStream getConfiguration(final String configurationUri) throws CacheException {
if (singleFileStore != null) {
try {
return singleFileStore.get().getInputStream();
} catch (final ResourceNotFoundException rse) {
throw new CacheConfigurationNotFoundException("cache.configuration.notfound", getMetaInformations(), getCurrentConfiguration());
} catch (final ResourceException rse) {
throw new CacheConfigurationException("cache.configuration.error", rse, getMetaInformations(), getCurrentConfiguration());
}
} else {
File fconf = null;
InputStream fconfIn = null;
// 1. if file exist via URI first
try {
final URI uri = new URI(configurationUri);
fconf = new File(uri);
} catch (final URISyntaxException use) {
} catch (final IllegalArgumentException iae) {
} finally {
}
// 2. if file exist on file system
if (fconf == null || !fconf.exists()) {
final URL url = currentClassLoader().getResource(configurationUri);
if (url != null) {
fconf = new File(url.getPath());
}
}
// create inputStream if file was found
if (fconf != null && fconf.exists()) {
try {
fconfIn = new FileInputStream(fconf);
} catch (final FileNotFoundException fnfe) {
}
}
// 3. search in classpath (jars) resources
else {
fconfIn = currentClassLoader().getResourceAsStream(configurationUri);
}
return fconfIn;
}
}
/**
* @return current classLoader instance (thread instance, or application instance) (never null will be return)
*/
protected ClassLoader currentClassLoader() {
final String contextClassLoader = context.getString(Classloader);
ClassLoader currentClassLoader = null;
if (!StringHelper.isEmpty(contextClassLoader)) {
try {
currentClassLoader = Class.forName(context.getString(Classloader)).getClass().getClassLoader();
} catch (final ClassNotFoundException cnfe) {
}
}
if (currentClassLoader == null) {
currentClassLoader = new ThreadLocal<Object>().getClass().getClassLoader();
if (currentClassLoader == null) {
currentClassLoader = AbstractCacheManager.class.getClassLoader();
}
}
return currentClassLoader;
}
}