/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Thierry Delprat <tdelprat@nuxeo.com> * Antoine Taillefer <ataillefer@nuxeo.com> */ package org.nuxeo.ecm.core.transientstore; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.cache.Cache; import org.nuxeo.ecm.core.cache.CacheDescriptor; import org.nuxeo.ecm.core.cache.CacheService; import org.nuxeo.ecm.core.cache.CacheServiceImpl; import org.nuxeo.ecm.core.cache.InMemoryCacheImpl; import org.nuxeo.ecm.core.transientstore.api.TransientStore; import org.nuxeo.ecm.core.transientstore.api.TransientStoreConfig; import org.nuxeo.runtime.api.Framework; /** * Default implementation (i.e., not cluster aware) of the {@link TransientStore}. Uses {@link StorageEntry} as a * representation of an entry in the store. * * @since 7.2 */ public class SimpleTransientStore extends AbstractTransientStore { protected Log log = LogFactory.getLog(SimpleTransientStore.class); protected Cache l1Cache; protected Cache l2Cache; protected CacheDescriptor l1cd; protected CacheDescriptor l2cd; protected AtomicLong storageSize = new AtomicLong(0); public SimpleTransientStore() { } @Override public void init(TransientStoreConfig config) { log.debug("Initializing SimpleTransientStore: " + config.getName()); super.init(config); CacheService cs = Framework.getService(CacheService.class); if (cs == null) { throw new UnsupportedOperationException("Cache service is required"); } // register the caches l1cd = getL1CacheConfig(); l2cd = getL2CacheConfig(); ((CacheServiceImpl) cs).registerCache(l1cd); ((CacheServiceImpl) cs).registerCache(l2cd); // get caches l1Cache = cs.getCache(l1cd.name); l2Cache = cs.getCache(l2cd.name); } @Override public void shutdown() { log.debug("Shutting down SimpleTransientStore: " + config.getName()); CacheService cs = Framework.getService(CacheService.class); if (cs != null) { if (l1cd != null) { ((CacheServiceImpl) cs).unregisterCache(l1cd); } if (l2cd != null) { ((CacheServiceImpl) cs).unregisterCache(l2cd); } } } @Override public boolean exists(String key) { return getL1Cache().hasEntry(key) || getL2Cache().hasEntry(key); } @Override public Set<String> keySet() { Set<String> keys = new HashSet<>(); keys.addAll(getL1Cache().keySet()); keys.addAll(getL2Cache().keySet()); return keys; } @Override public void putParameter(String key, String parameter, Serializable value) { synchronized (this) { StorageEntry entry = getStorageEntry(key); if (entry == null) { entry = new StorageEntry(); } entry.putParam(parameter, value); if (log.isDebugEnabled()) { log.debug(String.format("Setting parameter %s to value %s in StorageEntry stored at key %s", parameter, value, key)); } putStorageEntry(key, entry); } } @Override public Serializable getParameter(String key, String parameter) { StorageEntry entry = getStorageEntry(key); if (entry == null) { return null; } Serializable res = entry.getParam(parameter); if (log.isDebugEnabled()) { log.debug(String.format("Fetched parameter %s from StorageEntry stored at key %s: %s", parameter, key, res)); } return res; } @Override public void putParameters(String key, Map<String, Serializable> parameters) { synchronized (this) { StorageEntry entry = getStorageEntry(key); if (entry == null) { entry = new StorageEntry(); } entry.putParams(parameters); if (log.isDebugEnabled()) { log.debug(String.format("Setting parameters %s in StorageEntry stored at key %s", parameters, key)); } putStorageEntry(key, entry); } } @Override public Map<String, Serializable> getParameters(String key) { StorageEntry entry = getStorageEntry(key); if (entry == null) { return null; } Map<String, Serializable> res = entry.getParams(); if (log.isDebugEnabled()) { log.debug(String.format("Fetched parameters from StorageEntry stored at key %s: %s", key, res)); } return res; } @Override public List<Blob> getBlobs(String key) { StorageEntry entry = getStorageEntry(key); if (entry == null) { return null; } // Get blob information from the store List<Map<String, String>> blobInfos = entry.getBlobInfos(); if (blobInfos == null) { return new ArrayList<>(); } // Load blobs from the file system return loadBlobs(blobInfos); } @Override public long getSize(String key) { StorageEntry entry = getStorageEntry(key); if (entry == null) { return -1; } long size = entry.getSize(); if (log.isDebugEnabled()) { log.debug(String.format("Fetched field \"size\" from StorageEntry stored at key %s: %d", key, size)); } return size; } @Override public boolean isCompleted(String key) { StorageEntry entry = getStorageEntry(key); boolean completed = entry != null && entry.isCompleted(); if (log.isDebugEnabled()) { log.debug(String.format("Fetched field \"completed\" from StorageEntry stored at key %s: %s", key, completed)); } return completed; } @Override public void setCompleted(String key, boolean completed) { synchronized (this) { StorageEntry entry = getStorageEntry(key); if (entry == null) { entry = new StorageEntry(); } entry.setCompleted(completed); if (log.isDebugEnabled()) { log.debug(String.format("Setting field \"completed\" to value %s in StorageEntry stored at key %s", completed, key)); } putStorageEntry(key, entry); } } @Override public void remove(String key) { synchronized (this) { StorageEntry entry = (StorageEntry) getL1Cache().get(key); if (entry == null) { entry = (StorageEntry) getL2Cache().get(key); if (log.isDebugEnabled()) { log.debug(String.format("Invalidating StorageEntry stored at key %s form L2 cache", key)); } getL2Cache().invalidate(key); } else { if (log.isDebugEnabled()) { log.debug(String.format("Invalidating StorageEntry stored at key %s form L1 cache", key)); } getL1Cache().invalidate(key); } if (entry != null) { long entrySize = entry.getSize(); if (entrySize > 0) { decrementStorageSize(entrySize); } } FileUtils.deleteQuietly(getCachingDirectory(key)); } } @Override public void release(String key) { StorageEntry entry = (StorageEntry) getL1Cache().get(key); if (entry != null) { if (log.isDebugEnabled()) { log.debug(String.format("Invalidating StorageEntry stored at key %s form L1 cache", key)); } getL1Cache().invalidate(key); if (getStorageSize() <= config.getTargetMaxSizeMB() * (1024 * 1024) || config.getTargetMaxSizeMB() < 0) { if (log.isDebugEnabled()) { log.debug(String.format("Putting StorageEntry at key %s in L2 cache", key)); } getL2Cache().put(key, entry); } } } @Override protected void persistBlobs(String key, long sizeOfBlobs, List<Map<String, String>> blobInfos) { synchronized (this) { StorageEntry entry = getStorageEntry(key); // Update storage size if (entry == null) { if (sizeOfBlobs > 0) { incrementStorageSize(sizeOfBlobs); } entry = new StorageEntry(); } else { incrementStorageSize(sizeOfBlobs - entry.getSize()); } // Update entry size entry.setSize(sizeOfBlobs); // Set blob information entry.setBlobInfos(blobInfos); if (log.isDebugEnabled()) { log.debug(String.format("Setting blobs %s in StorageEntry stored at key %s", blobInfos, key)); } putStorageEntry(key, entry); } } @Override public long getStorageSize() { int intStorageSize = (int) storageSize.get(); if (log.isDebugEnabled()) { log.debug(String.format("Fetched storage size of store %s: %d", config.getName(), intStorageSize)); } return intStorageSize; } @Override protected void setStorageSize(long newSize) { if (log.isDebugEnabled()) { log.debug(String.format("Setting storage size of store %s to %d", config.getName(), newSize)); } storageSize.set(newSize); } @Override protected long incrementStorageSize(long size) { long incremented = storageSize.addAndGet(size); if (log.isDebugEnabled()) { log.debug(String.format("Incremented storage size of store %s to %s", config.getName(), incremented)); } return incremented; } @Override protected long decrementStorageSize(long size) { long decremented = storageSize.addAndGet(-size); if (log.isDebugEnabled()) { log.debug(String.format("Decremented storage size of store %s to %s", config.getName(), decremented)); } return decremented; } @Override protected void removeAllEntries() { log.debug("Invalidating all entries from L1 and L2 caches"); getL1Cache().invalidateAll(); getL2Cache().invalidateAll(); } public Cache getL1Cache() { return l1Cache; } public Cache getL2Cache() { return l2Cache; } protected CacheDescriptor getL1CacheConfig() { return new TransientCacheConfig(config.getName() + "L1", config.getFirstLevelTTL()); } protected CacheDescriptor getL2CacheConfig() { return new TransientCacheConfig(config.getName() + "L2", config.getSecondLevelTTL()); } protected class TransientCacheConfig extends CacheDescriptor { TransientCacheConfig(String name, int ttl) { super(); super.name = name; super.implClass = getCacheImplClass(); super.ttl = ttl; } } protected Class<? extends Cache> getCacheImplClass() { return InMemoryCacheImpl.class; } /** * Returns the {@link StorageEntry} representing the entry with the given {@code key} or {@code null} if it doesn't * exist. */ protected StorageEntry getStorageEntry(String key) { StorageEntry entry = (StorageEntry) getL1Cache().get(key); if (entry == null) { entry = (StorageEntry) getL2Cache().get(key); } return entry; } /** * Stores the given {@code entry} with the given {@code key}. * <p> * If an entry exists with the given {@code key} it is overwritten. */ protected void putStorageEntry(String key, StorageEntry entry) { getL1Cache().put(key, entry); } }