/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.gwc; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.gwc.config.GWCConfig; import org.geoserver.gwc.layer.GeoServerTileLayer; import org.geoserver.platform.GeoServerExtensions; import org.geotools.util.logging.Logging; import org.geowebcache.storage.BlobStore; import org.geowebcache.storage.BlobStoreListener; import org.geowebcache.storage.BlobStoreListenerList; import org.geowebcache.storage.StorageException; import org.geowebcache.storage.TileObject; import org.geowebcache.storage.TileRange; import org.geowebcache.storage.blobstore.file.FileBlobStore; import org.geowebcache.storage.blobstore.memory.CacheConfiguration; import org.geowebcache.storage.blobstore.memory.CacheProvider; import org.geowebcache.storage.blobstore.memory.CacheStatistics; import org.geowebcache.storage.blobstore.memory.MemoryBlobStore; import org.geowebcache.storage.blobstore.memory.NullBlobStore; import org.geowebcache.storage.blobstore.memory.guava.GuavaCacheProvider; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; /** * {@link MemoryBlobStore} implementation used for changing {@link CacheProvider} and wrapped {@link BlobStore} at runtime. An instance of this class * requires to call the setChanged() method for modifying its configuration. * * @author Nicola Lagomarsini Geosolutions */ public class ConfigurableBlobStore extends MemoryBlobStore implements BlobStore { /** Logger instance for the class */ private final static Logger LOGGER = Logging.getLogger(ConfigurableBlobStore.class); /** Delegate Object to use for executing the operations */ private BlobStore delegate; /** {@link MemoryBlobStore} used for in memory caching */ private MemoryBlobStore memoryStore; /** Cache provider to add to the {@link MemoryBlobStore} */ private CacheProvider cache; /** {@link NullBlobStore} used for avoiding persistence */ private NullBlobStore nullStore; /** {@link FileBlobStore} used as default by GWC */ private BlobStore defaultStore; /** * Atomic counter used for keeping into account how many operations are executed in parallel */ private AtomicLong actualOperations; /** Atomic boolean indicating if the BlobStore has been configured */ private AtomicBoolean configured; /** * Map containing mapping for {@link CacheConfiguration}s associated to each CacheProvider */ private Map<String, CacheConfiguration> internalCacheConfigs; /** Map containing mapping for {@link CacheProvider} names */ private Map<String, String> cacheProvidersNames; /** Map containing mapping for {@link CacheProvider}s */ private Map<String, CacheProvider> cacheProviders; /** * Save the listeners to re-apply them to the delegate blobstore upon config changes */ private BlobStoreListenerList listeners = new BlobStoreListenerList(); public ConfigurableBlobStore(BlobStore defaultStore, MemoryBlobStore memoryStore, NullBlobStore nullStore) { // Initialization configured = new AtomicBoolean(false); actualOperations = new AtomicLong(0); this.delegate = defaultStore; this.defaultStore = defaultStore; this.memoryStore = memoryStore; this.nullStore = nullStore; // Creating three maps: // 1 containing a mapping key-cacheProvider // 2 containing a mapping key-cacheProvider description // 3 containing a mapping key-cacheConfiguration // where key is the cacheProvider classname HashMap<String, CacheProvider> cacheProviders = new HashMap<String, CacheProvider>(); HashMap<String, String> cacheProvidersNames = new HashMap<String, String>(); List<CacheProvider> extensions = GeoServerExtensions.extensions(CacheProvider.class); for (CacheProvider provider : extensions) { if (provider.isAvailable()) { cacheProviders.put(provider.getClass().toString(), provider); cacheProvidersNames.put(provider.getClass().toString(), provider.getName()); } } this.cacheProviders = Collections.unmodifiableMap(cacheProviders); this.cacheProvidersNames = Collections.unmodifiableMap(cacheProvidersNames); this.internalCacheConfigs = new HashMap<String, CacheConfiguration>(); } @Override public boolean delete(String layerName) throws StorageException { // NOTE that if the blobstore has already been configured, the user must // always call setConfig() for // setting the new configuration if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Delete the selected Layer return delegate.delete(layerName); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return true; } @Override public boolean deleteByGridsetId(String layerName, String gridSetId) throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Delete the TileObjects related to the selected gridset return delegate.deleteByGridsetId(layerName, gridSetId); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return true; } @Override public boolean delete(TileObject obj) throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Deletes the single TileObject return delegate.delete(obj); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return true; } @Override public boolean delete(TileRange obj) throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Deletes this TileRange return delegate.delete(obj); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return true; } @Override public boolean get(TileObject obj) throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Get a TileObject return delegate.get(obj); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return false; } @Override public void put(TileObject obj) throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Put the TileObject delegate.put(obj); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } } @Override public void clear() throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Clear the BlobStore delegate.clear(); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } } @Override public synchronized void destroy() { if (configured.getAndSet(false)) { // Avoid to call the While cycle before having started an operation // with configured == true actualOperations.incrementAndGet(); actualOperations.decrementAndGet(); // Wait until all the operations are finished while (actualOperations.get() > 0) { } // Destroy all super.destroy(); delegate.destroy(); cache.reset(); } } @Override public void addListener(BlobStoreListener listener) { //save it in case of further config changes this.listeners.addListener(listener); // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Add a new Listener to the NullBlobStore delegate.addListener(listener); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } } @Override public boolean removeListener(BlobStoreListener listener) { //remove it from the local backup this.listeners.removeListener(listener); // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Remove a Listener from the BlobStore return delegate.removeListener(listener); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return true; } @Override public boolean rename(String oldLayerName, String newLayerName) throws StorageException { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Rename a Layer return delegate.rename(oldLayerName, newLayerName); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return false; } @Override public String getLayerMetadata(String layerName, String key) { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Get The Layer metadata return delegate.getLayerMetadata(layerName, key); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return null; } @Override public void putLayerMetadata(String layerName, String key, String value) { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Put the Layer metadata delegate.putLayerMetadata(layerName, key, value); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } } @Override public CacheStatistics getCacheStatistics() { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Get Cache Statistics return cache.getStatistics(); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } // Not configured, returns an empty statistics return new CacheStatistics(); } public void clearCache() { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Clear the cache cache.clear(); } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } } public CacheProvider getCache() { // Check if the blobstore has already been configured if (configured.get()) { // Increment the number of current operations // This behavior is used in order to wait // the end of all the operations after setting // the configured parameter to false actualOperations.incrementAndGet(); try { // Returns the cache object used return cache; } finally { // Decrement the number of current operations. actualOperations.decrementAndGet(); } } return null; } /** * Returns a map of all the cache provider instances, where the key is the {@link CacheProvider} class. * * @return a Map containing all the CacheProvider instances */ public Map<String, CacheProvider> getCacheProviders() { return cacheProviders; } /** * Returns a map of all the cache provider description, where the key is the {@link CacheProvider} class. * * @return a Map containing all the CacheProvider descriptions */ public Map<String, String> getCacheProvidersNames() { return cacheProvidersNames; } /** * This method changes the {@link ConfigurableBlobStore} configuration. It can be used for changing cache configuration or the blobstore used. * * @param gwcConfig */ public synchronized void setChanged(GWCConfig gwcConfig, boolean initialization) { // Change the blobstore configuration configureBlobStore(gwcConfig, initialization); } private void configureBlobStore(GWCConfig gwcConfig, boolean initialization) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Configuring BlobStore"); } // reset the configuration configured.getAndSet(false); // Avoid to call the While cycle before having started an operation with // configured == true actualOperations.incrementAndGet(); actualOperations.decrementAndGet(); // Wait until all the operations are finished while (actualOperations.get() > 0) { } // Getting the cache provider to use String cacheProvider = gwcConfig.getCacheProviderClass(); // Check if it is present, else use the GuavaCacheProvider as default if (!getCacheProviders().containsKey(cacheProvider)) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.warning("Wrong CacheProvider defined, using default one"); } cacheProvider = GuavaCacheProvider.class.toString(); if(!initialization){ gwcConfig.setCacheProviderClass(cacheProvider); try { GWC.get().saveConfig(gwcConfig); } catch (IOException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } } } } // Getting Cache configuration for the CacheProvider CacheConfiguration cacheConfiguration = gwcConfig.getCacheConfigurations().get( cacheProvider); // Add the internal Cache configuration for the first time if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Configuring cache"); } // Setting cache cache = getCacheProviders().get(cacheProvider); // Modify configuration only if the CacheProvider can be modified if (!cache.isImmutable()) { CacheConfiguration internalCacheConfig = internalCacheConfigs.get(cacheProvider); if (internalCacheConfig == null) { internalCacheConfig = new CacheConfiguration(); internalCacheConfig.setConcurrencyLevel(cacheConfiguration.getConcurrencyLevel()); internalCacheConfig.setEvictionTime(cacheConfiguration.getEvictionTime()); internalCacheConfig.setHardMemoryLimit(cacheConfiguration.getHardMemoryLimit()); internalCacheConfig.setPolicy(cacheConfiguration.getPolicy()); cache.configure(cacheConfiguration); internalCacheConfigs.put(cacheProvider, internalCacheConfig); } else if (!internalCacheConfig.equals(cacheConfiguration)) { internalCacheConfig.setConcurrencyLevel(cacheConfiguration.getConcurrencyLevel()); internalCacheConfig.setEvictionTime(cacheConfiguration.getEvictionTime()); internalCacheConfig.setHardMemoryLimit(cacheConfiguration.getHardMemoryLimit()); internalCacheConfig.setPolicy(cacheConfiguration.getPolicy()); cache.configure(internalCacheConfig); } // If GWC has been already configured, we must cycle on all the // layers in order to check // which must not be cached if (!initialization) { Iterable<GeoServerTileLayer> geoServerTileLayers = GWC.get() .getGeoServerTileLayers(); for (GeoServerTileLayer layer : geoServerTileLayers) { if (layer.getInfo().isEnabled() && !layer.getInfo().isInMemoryCached()) { cache.addUncachedLayer(layer.getName()); } } } } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Configuring BlobStore delegate"); } // BlobStore configuration //remove listeners from old delegate for (BlobStoreListener listener : listeners.getListeners()) { delegate.removeListener(listener); } if (gwcConfig.isInnerCachingEnabled()) { memoryStore.setCacheProvider(cache); if (!gwcConfig.isPersistenceEnabled()) { memoryStore.setStore(nullStore); } else { memoryStore.setStore(defaultStore); } delegate = memoryStore; } else { delegate = defaultStore; } //apply listeners to new delegate for (BlobStoreListener listener : listeners.getListeners()) { delegate.addListener(listener); } // Update the configured parameter configured.getAndSet(true); } /** * @return the used {@link BlobStore} for testing purpose */ BlobStore getDelegate() { return delegate; } /** * Setter for the Tests * * @param cache */ void setCache(CacheProvider cache) { // Setting cache provider Map<String, CacheProvider> provs = new HashMap<String, CacheProvider>(cacheProviders); provs.put(cache.getClass().toString(), cache); cacheProviders = provs; this.cache = cache; memoryStore.setCacheProvider(cache); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // Do nothing return; } @Override public void setCacheProvider(CacheProvider cache) { throw new UnsupportedOperationException("Operation not supported"); } }