Java tutorial
/* * (C) Copyright 2015 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library 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. * * Contributors: * Nuxeo - initial API and implementation */ package org.nuxeo.transientstore; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.Environment; 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.runtime.api.Framework; import org.nuxeo.runtime.model.impl.ExtensionImpl; import org.nuxeo.transientstore.api.MaximumTransientSpaceExceeded; import org.nuxeo.transientstore.api.StorageEntry; import org.nuxeo.transientstore.api.TransientStore; import org.nuxeo.transientstore.api.TransientStoreConfig; /** * Base class for {@link TransientStore} implementation. * * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> * @since 7.2 */ public abstract class AbstractTransientStore implements TransientStore { protected final TransientStoreConfig config; protected static final Log log = LogFactory.getLog(AbstractTransientStore.class); protected File cacheDir; protected Cache l1Cache; protected Cache l2Cache; AbstractTransientStore(TransientStoreConfig config) { this.config = config; initCaches(); } protected void initCaches() { CacheService cs = Framework.getService(CacheService.class); if (cs == null) { throw new UnsupportedOperationException("Cache service is required"); } // register the caches // // temporary until we have a clean API CacheDescriptor l1cd = config.getL1CacheConfig(); CacheDescriptor l2cd = config.getL2CacheConfig(); ExtensionImpl ext = new ExtensionImpl(); ext.setContributions(new Object[] { l1cd, l2cd }); ((CacheServiceImpl) cs).registerExtension(ext); l1cd.start(); l2cd.start(); // get caches l1Cache = cs.getCache(l1cd.name); l2Cache = cs.getCache(l2cd.name); } protected abstract void incrementStorageSize(StorageEntry entry); protected abstract void decrementStorageSize(StorageEntry entry); protected abstract long getStorageSize(); protected abstract void setStorageSize(long newSize); protected Cache getL1Cache() { return l1Cache; } protected Cache getL2Cache() { return l2Cache; } @Override public void put(StorageEntry entry) throws IOException { if (config.getAbsoluteMaxSizeMB() < 0 || getStorageSize() < config.getAbsoluteMaxSizeMB() * (1024 * 1024)) { incrementStorageSize(entry); entry = persistEntry(entry); getL1Cache().put(entry.getId(), entry); } else { throw new MaximumTransientSpaceExceeded(); } } protected StorageEntry persistEntry(StorageEntry entry) throws IOException { entry.persist(getCachingDirectory(entry.getId())); return entry; } @Override public StorageEntry get(String key) throws IOException { StorageEntry entry = (StorageEntry) getL1Cache().get(key); if (entry == null) { entry = (StorageEntry) getL2Cache().get(key); } if (entry != null) { entry.load(getCachingDirectory(key)); } return entry; } @Override public void remove(String key) throws IOException { StorageEntry entry = (StorageEntry) getL1Cache().get(key); if (entry == null) { entry = (StorageEntry) getL2Cache().get(key); getL2Cache().invalidate(key); } else { getL1Cache().invalidate(key); } if (entry != null) { decrementStorageSize(entry); entry.beforeRemove(); } } @Override public void canDelete(String key) throws IOException { StorageEntry entry = (StorageEntry) getL1Cache().get(key); if (entry != null) { getL1Cache().invalidate(key); if (getStorageSize() <= config.getTargetMaxSizeMB() * (1024 * 1024) || config.getTargetMaxSizeMB() < 0) { getL2Cache().put(key, entry); } } } @Override public void removeAll() throws IOException { getL1Cache().invalidateAll(); getL2Cache().invalidateAll(); doGC(); } @Override public TransientStoreConfig getConfig() { return config; } @Override public int getStorageSizeMB() { return (int) getStorageSize() / (1024 * 1024); } protected String getCachingDirName(String key) { return key; } protected String getKeyCachingDirName(String dir) { return dir; } public File getCachingDirectory(String key) { File cachingDir = new File(getCachingDirectory(), getCachingDirName(key)); if (!cachingDir.exists()) { cachingDir.mkdir(); } return cachingDir; } protected File getCachingDirectory() { if (cacheDir == null) { File data = new File(Environment.getDefault().getData(), config.getName()); if (data.exists()) { try { FileUtils.deleteDirectory(data); } catch (IOException cause) { throw new RuntimeException("Cannot create cache dir " + data, cause); } } data.mkdirs(); return cacheDir = data.getAbsoluteFile(); } return cacheDir; } public void doGC() { File dir = getCachingDirectory(); long newSize = 0; try { try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(dir.getAbsolutePath()))) { for (Path entry : stream) { String key = getKeyCachingDirName(entry.getFileName().toString()); try { // XXX should not get entry since it can mess the LRU if (getL1Cache().hasEntry(key)) { newSize += getSize(entry); continue; } if (getL2Cache().hasEntry(key)) { newSize += getSize(entry); continue; } FileUtils.deleteDirectory(entry.toFile()); } catch (IOException e) { log.error("Error while performing GC", e); } } } } catch (IOException e) { log.error("Error while performing GC", e); } setStorageSize(newSize); } protected long getSize(Path entry) { long size = 0; for (File file : entry.toFile().listFiles()) { size += file.length(); } return size; } }