Package net.sf.ehcache.store.compound.factories

Source Code of net.sf.ehcache.store.compound.factories.DiskPersistentStorageFactory$PersistentPlaceholder

/**
*  Copyright 2003-2010 Terracotta, Inc.
*
*  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 net.sf.ehcache.store.compound.factories;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.Lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.store.FifoPolicy;
import net.sf.ehcache.store.LfuPolicy;
import net.sf.ehcache.store.LruPolicy;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import net.sf.ehcache.store.Policy;
import net.sf.ehcache.store.DiskStore.DiskElement;
import net.sf.ehcache.store.compound.CompoundStore;
import net.sf.ehcache.store.compound.ElementSubstitute;
import net.sf.ehcache.store.compound.ElementSubstituteFilter;

/**
* This will be the disk-persistent element substitute factory
*
* @author Chris Dennis
*/
public class DiskPersistentStorageFactory extends DiskStorageFactory<ElementSubstitute> {

    private static final Logger LOG = LoggerFactory.getLogger(DiskPersistentStorageFactory.class);
    private static final int MAX_EVICT = 5;
    private static final int SAMPLE_SIZE = 30;

    private final ElementSubstituteFilter<DiskSubstitute> inMemoryFilter = new InMemoryFilter();
    private final ElementSubstituteFilter<CachingDiskMarker> flushableFilter = new FlushableFilter();
    private final ElementSubstituteFilter<CachingDiskMarker> onDiskFilter = new OnDiskFilter();

    private final AtomicInteger inMemory = new AtomicInteger();
    private final AtomicInteger onDisk = new AtomicInteger();

    private final File indexFile;

    private final IndexWriteTask flushTask;

    private volatile int diskCapacity;
    private volatile int memoryCapacity;

    private volatile Policy memoryPolicy;

    /**
     * Constructs an disk persistent factory for the given cache and disk path.
     *
     * @param cache cache that fronts this factory
     * @param diskPath path to store data in
     */
    public DiskPersistentStorageFactory(Ehcache cache, String diskPath) {
        super(getDataFile(diskPath, cache), cache.getCacheConfiguration().getDiskExpiryThreadIntervalSeconds(),
                cache.getCacheConfiguration().getDiskSpoolBufferSizeMB(), cache.getCacheEventNotificationService(),
                false, cache.getCacheConfiguration().getDiskAccessStripes());

        indexFile = new File(getDataFile().getParentFile(), getIndexFileName(cache));
        flushTask = new IndexWriteTask(indexFile, cache.getCacheConfiguration().isClearOnFlush());

        if (!getDataFile().exists() || (getDataFile().length() == 0)) {
            LOG.debug("Matching data file missing (or empty) for index file. Deleting index file " + indexFile);
            indexFile.delete();
        } else if (getDataFile().exists() && indexFile.exists()) {
            if (getDataFile().lastModified() > (indexFile.lastModified() + TimeUnit.SECONDS.toMillis(1))) {
                LOG.warn("The index for data file {} is out of date, probably due to an unclean shutdown. "
                        + "Deleting index file {}", getDataFile(), indexFile);
                indexFile.delete();
            }
        }

        diskCapacity = cache.getCacheConfiguration().getMaxElementsOnDisk();
        memoryCapacity = cache.getCacheConfiguration().getMaxElementsInMemory();
        memoryPolicy = determineEvictionPolicy(cache.getCacheConfiguration());
    }

    /**
     * Chooses the Policy from the cache configuration
     */
    private static final Policy determineEvictionPolicy(CacheConfiguration config) {
        MemoryStoreEvictionPolicy policySelection = config.getMemoryStoreEvictionPolicy();

        if (policySelection.equals(MemoryStoreEvictionPolicy.LRU)) {
            return new LruPolicy();
        } else if (policySelection.equals(MemoryStoreEvictionPolicy.FIFO)) {
            return new FifoPolicy();
        } else if (policySelection.equals(MemoryStoreEvictionPolicy.LFU)) {
            return new LfuPolicy();
        }

        throw new IllegalArgumentException(policySelection + " isn't a valid eviction policy");
    }

    private static File getDataFile(String diskPath, Ehcache cache) {
        if (diskPath == null) {
            throw new CacheException(cache.getName() + " Cache: Could not create disk store. "
                    + "This CacheManager configuration does not allow creation of DiskStores. "
                    + "If you wish to create DiskStores, please configure a diskStore path.");
        }

        final File diskDir = new File(diskPath);
        // Make sure the cache directory exists
        if (diskDir.exists() && !diskDir.isDirectory()) {
            throw new CacheException("Store directory \"" + diskDir.getAbsolutePath() + "\" exists and is not a directory.");
        }
        if (!diskDir.exists() && !diskDir.mkdirs()) {
            throw new CacheException("Could not create cache directory \"" + diskDir.getAbsolutePath() + "\".");
        }

        File data = new File(diskDir, getDataFileName(cache));

        //if diskpath contains auto generated string
        if (diskPath.indexOf(AUTO_DISK_PATH_DIRECTORY_PREFIX) != -1) {
            LOG.warn("Data in persistent disk stores is ignored for stores from automatically created directories"
                    + " (they start with " + AUTO_DISK_PATH_DIRECTORY_PREFIX + ").\n"
                    + "Remove diskPersistent or resolve the conflicting disk paths in cache configuration.\n"
                    + "Deleting data file " + data.getAbsolutePath());
            data.delete();
        }

        return data;
    }

    private static final String getDataFileName(Ehcache cache) {
        String safeName = cache.getName().replace('/', '_');
        return safeName + ".data";
    }

    private static final String getIndexFileName(Ehcache cache) {
        String safeName = cache.getName().replace('/', '_');
        return safeName + ".index";
    }

    /**
     * {@inheritDoc}
     */
    public ElementSubstitute create(Object key, Element element) throws IllegalArgumentException {
        int size = inMemory.incrementAndGet();
        inMemoryEvict(size, key);
        return new PersistentPlaceholder(element);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void free(Lock exclusion, ElementSubstitute object) {
        if (object instanceof PersistentPlaceholder) {
            inMemory.decrementAndGet();
        } else if (object instanceof CachingDiskMarker) {
            CachingDiskMarker marker = (CachingDiskMarker) object;
            onDisk.decrementAndGet();
            if (marker.flush()) {
                inMemory.decrementAndGet();
            }
        }
        super.free(exclusion, object);
    }

    /**
     * {@inheritDoc}
     */
    public Element retrieve(Object key, ElementSubstitute object) {
        if (object instanceof CachingDiskMarker) {
            try {
                CachingDiskMarker marker = (CachingDiskMarker) object;
                Element element = marker.getCached();
                if (element == null) {
                    element = read(marker);
                    if (marker.cache(element)) {
                        int size = inMemory.incrementAndGet();
                        inMemoryEvict(size, key);
                    }
                }
                return element;
            } catch (IOException e) {
                throw new CacheException(e);
            } catch (ClassNotFoundException e) {
                throw new CacheException(e);
            }
        } else if (object instanceof DiskStorageFactory.Placeholder) {
            return ((Placeholder) object).getElement();
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean created(Object object) {
        if (object instanceof ElementSubstitute) {
            return ((ElementSubstitute) object).getFactory() == this;
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void bind(CompoundStore store) {
        super.bind(store);
        loadIndex();
    }

    /**
     * {@inheritDoc}
     */
    public void unbind(CompoundStore store) {
        try {
            flushTask.call();
            shutdown();
            if (getDataFile().getAbsolutePath().contains(AUTO_DISK_PATH_DIRECTORY_PREFIX)) {
                indexFile.delete();
                delete();
            }
        } catch (IOException e) {
            LOG.error("Could not shut down disk cache. Initial cause was " + e.getMessage(), e);
        }
    }

    /**
     * Schedule a flush (index write) for this factory.
     */
    public Future<Void> flush() {
        return schedule(flushTask);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected DiskMarker createMarker(long position, int size, Element element) {
        return new CachingDiskMarker(this, position, size, element);
    }

    /**
     * Disk marker implementation that can hold a cached Element reference.
     */
    private final static class CachingDiskMarker extends DiskMarker implements Serializable {

        private static final long serialVersionUID = 43;

        private static final AtomicReferenceFieldUpdater<CachingDiskMarker, Element> CACHED_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(CachingDiskMarker.class, Element.class, "cached");

        private transient volatile Element cached;
        private volatile long expiry;

        CachingDiskMarker(DiskPersistentStorageFactory factory, long position, int size, Element element) {
            super(factory, position, size, element);
            this.expiry = element.getExpirationTime();
        }

        CachingDiskMarker(DiskPersistentStorageFactory factory, DiskElement element) {
            super(factory, element.getPosition(), element.getSize(), element.getObjectKey(), element.getHitCount());
            this.expiry = element.getExpiry();
        }

        boolean cache(Element element) {
            return CACHED_UPDATER.compareAndSet(this, null, element);
        }

        boolean flush() {
          Element old = CACHED_UPDATER.getAndSet(this, null);
          if (old != null) {
            expiry = old.getExpirationTime();
            return true;
          } else {
            return false;
          }
        }

        Element getCached() {
            return CACHED_UPDATER.get(this);
        }

        boolean isCaching() {
            return getCached() != null;
        }

        @Override
        long getExpirationTime() {
            Element e = getCached();
            return e == null ? expiry : e.getExpirationTime();
        }
    }

    /**
     * Filters for in-memory elements created by this factory
     */
    private class InMemoryFilter implements ElementSubstituteFilter<DiskSubstitute> {

        /**
         * {@inheritDoc}
         */
        public boolean allows(Object object) {
            if (!created(object)) {
                return false;
            }

            if (object instanceof PersistentPlaceholder) {
                return true;
            } else if (object instanceof CachingDiskMarker) {
                return ((CachingDiskMarker) object).isCaching();
            } else {
                return false;
            }
        }
    }

    /**
     * Filters for in-memory elements created by this factory
     */
    private class FlushableFilter implements ElementSubstituteFilter<CachingDiskMarker> {

        /**
         * {@inheritDoc}
         */
        public boolean allows(Object object) {
            if (!created(object)) {
                return false;
            }

            return (object instanceof CachingDiskMarker) && ((CachingDiskMarker) object).isCaching();
        }
    }

    /**
     * Filters for on-disk elements created by this factory
     */
    private class OnDiskFilter implements ElementSubstituteFilter<CachingDiskMarker> {

        /**
         * {@inheritDoc}
         */
        public boolean allows(Object object) {
            if (!created(object)) {
                return false;
            }

            if (object instanceof CachingDiskMarker) {
                return !((CachingDiskMarker) object).isCaching();
            } else {
                return false;
            }
        }
    }

    /**
     * Return {@code true} if the given element is in memory
     */
    public boolean isInMemory(Object object) {
        return inMemoryFilter.allows(object);
    }

    /**
     * Return {@code true} if the given element is on disk
     */
    public boolean isOnDisk(Object object) {
        return onDiskFilter.allows(object);
    }

    /**
     * Return the number of in-memory elements
     */
    public int getInMemorySize() {
        return inMemory.get();
    }

    /**
     * Return the approximate serialized size of the in-memory elements
     */
    public long getInMemorySizeInBytes() {
        long size = 0;
        for (Object o : store.getKeys()) {
            Object e = store.unretrievedGet(o);
            if (inMemoryFilter.allows(e)) {
                size += getElement((DiskSubstitute) e).getSerializedSize();
            }
        }
        return size;
    }

    /**
     * Return the number of on-disk elements
     */
    public int getOnDiskSize() {
        return onDisk.get();
    }

    /**
     * Set the maximum in-memory capacity for this factory.
     */
    public void setInMemoryCapacity(int capacity) {
        memoryCapacity = capacity;
    }

    /**
     * Set the maximum on-disk capacity for this factory.
     */
    public void setOnDiskCapacity(int capacity) {
        diskCapacity = capacity;
    }

    /**
     * Set the in-memory eviction policy to be used by this store.
     */
    public void setInMemoryEvictionPolicy(Policy policy) {
        memoryPolicy = policy;
    }

    /**
     * Return the in-memory eviction policy used by this store.
     */
    public Policy getInMemoryEvictionPolicy() {
        return memoryPolicy;
    }

    private void inMemoryEvict(int size, Object keyHint) {
        if (memoryCapacity > 0) {
            int overflow = size - memoryCapacity;
            for (int i = 0; i < Math.min(MAX_EVICT, overflow); i++) {
                CachingDiskMarker target = getMemoryEvictionTarget(keyHint, size);
                if (target != null && target.flush()) {
                    if (inMemory.decrementAndGet() <= memoryCapacity) {
                        break;
                    }
                }
            }
        }
    }

    private CachingDiskMarker getMemoryEvictionTarget(Object keyHint, int size) {
        List<CachingDiskMarker> sample = store.getRandomSample(flushableFilter, Math.min(SAMPLE_SIZE, size), keyHint);

        CachingDiskMarker target = null;

        for (CachingDiskMarker substitute : sample) {
            if (target == null) {
                target = substitute;
            } else {
                Element targetElement = getElement(target);
                Element element = getElement(substitute);
                if (targetElement == null || (element != null && memoryPolicy.compare(targetElement, element))) {
                    target = substitute;
                }
            }
        }
        return target;
    }

    private static final Element getElement(DiskSubstitute substitute) {
        if (substitute instanceof CachingDiskMarker) {
            return ((CachingDiskMarker) substitute).getCached();
        } else {
            return ((PersistentPlaceholder) substitute).getElement();
        }
    }

    private void onDiskEvict(int size, Object keyHint) {
        if (diskCapacity > 0) {
            int overflow = size - diskCapacity;
            for (int i = 0; i < Math.min(MAX_EVICT, overflow); i++) {
                DiskSubstitute target = getDiskEvictionTarget(keyHint, size);
                if (target == null) {
                    continue;
                } else {
                    if (store.evict(target.getKey(), target) && (onDisk.get() <= diskCapacity)) {
                        break;
                    }
                }
            }
        }
    }

    private DiskSubstitute getDiskEvictionTarget(Object keyHint, int size) {
        List<CachingDiskMarker> sample = store.getRandomSample(onDiskFilter, Math.min(SAMPLE_SIZE, size), keyHint);
        CachingDiskMarker target = null;
        for (CachingDiskMarker substitute : sample) {
            if ((target == null) || (substitute.getHitCount() < target.getHitCount())) {
                target = substitute;
            }
        }
        return target;
    }

    /**
     * Placeholder implementation for disk persistent stores.
     */
    private final class PersistentPlaceholder extends Placeholder {

        /**
         * Create a disk persistent placeholder around the given key, element pair.
         */
        PersistentPlaceholder(Element element) {
            super(DiskPersistentStorageFactory.this, element);
        }

        public void installed() {
            DiskPersistentStorageFactory.this.schedule(new PersistentDiskWriteTask(this));
        }
    }

    /**
     * Disk write task implementation for disk persistent stores.
     */
    private final class PersistentDiskWriteTask extends DiskWriteTask {

        /**
         * Create a disk persistent disk-write task for this placeholder.
         */
        PersistentDiskWriteTask(PersistentPlaceholder p) {
            super(p);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public CachingDiskMarker call() {
            CachingDiskMarker result = (CachingDiskMarker) super.call();
            //don't want to increment on exception throw
            int disk = onDisk.incrementAndGet();
            onDiskEvict(disk, getPlaceholder().getKey());
            if (result != null && result.cache(getPlaceholder().getElement())) {
                int memory = inMemory.incrementAndGet();
                inMemoryEvict(memory, getPlaceholder().getKey());
            }
            return result;
        }
    }

    /**
     * Task that writes the index file for this factory.
     */
    class IndexWriteTask implements Callable<Void> {

        private final File index;
        private final boolean clearOnFlush;

        /**
         * Create a disk flush task that writes to the given file.
         */
        IndexWriteTask(File index, boolean clear) {
            this.index = index;
            this.clearOnFlush = clear;
        }

        /**
         * {@inheritDoc}
         */
        public synchronized Void call() throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(index));
            try {
                for (Object key : store.keySet()) {
                    Object o = store.unretrievedGet(key);
                    if (o instanceof PersistentPlaceholder) {
                        o = new PersistentDiskWriteTask((PersistentPlaceholder) o).call();
                        if (o == null) {
                            o = store.unretrievedGet(key);
                        }
                    }

                    if (o instanceof CachingDiskMarker) {
                        CachingDiskMarker marker = (CachingDiskMarker) o;

                        if (clearOnFlush && marker.flush()) {
                            inMemory.decrementAndGet();
                        }

                        oos.writeObject(key);
                        oos.writeObject(marker);
                    }
                }
            } finally {
                oos.close();
            }
            return null;
        }

    }

    private void loadIndex() {
        if (!indexFile.exists()) {
            return;
        }

        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(indexFile));
            try {
                Object key = ois.readObject();
                Object value = ois.readObject();

                if (key instanceof Map && value instanceof List) {
                    LOG.info("Loading old format index file.");
                    loadOldIndex((Map) key);
                } else {
                    CachingDiskMarker marker = (CachingDiskMarker) value;
                    while (true) {
                        marker.bindFactory(this);
                        if (store.putRawIfAbsent(key, marker)) {
                            onDisk.incrementAndGet();
                        } else {
                            throw new AssertionError();
                        }
                        markUsed(marker);
                        key = ois.readObject();
                        marker = (CachingDiskMarker) ois.readObject();
                    }
                }
            } finally {
                ois.close();
            }
        } catch (EOFException e) {
            return;
        } catch (Exception e) {
            LOG.warn("Index file {} is corrupt, deleting and ignoring it : {}", indexFile, e);
            e.printStackTrace();
            store.removeAll();
            indexFile.delete();
        } finally {
            shrinkDataFile();
        }
    }

    private void loadOldIndex(Map<Object, DiskElement> elements) {
        for (Entry<Object, DiskElement> entry : elements.entrySet()) {
            Object key = entry.getKey();
            CachingDiskMarker marker = new CachingDiskMarker(this, entry.getValue());

            if (store.putRawIfAbsent(key, marker)) {
                onDisk.incrementAndGet();
            } else {
                throw new AssertionError();
            }
            markUsed(marker);
        }
    }

    /**
     * Return the index file for this store.
     */
    public File getIndexFile() {
        return indexFile;
    }
}
TOP

Related Classes of net.sf.ehcache.store.compound.factories.DiskPersistentStorageFactory$PersistentPlaceholder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.