org.pentaho.reporting.platform.plugin.cache.FileSystemCacheBackend.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.reporting.platform.plugin.cache.FileSystemCacheBackend.java

Source

/* !
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 *  You should have received a copy of the GNU Lesser General Public License along with this
 *  program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  or from the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  This program 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.
 *
 *  Copyright (c) 2002-2017 Hitachi Vantara..  All rights reserved.
 *
 */
package org.pentaho.reporting.platform.plugin.cache;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.util.IOUtils;

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.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Default interface for cache backend
 */
public class FileSystemCacheBackend implements ICacheBackend {

    private static final Log logger = LogFactory.getLog(FileSystemCacheBackend.class);
    public static final String REPLACEMENT = "_";
    public static final String SLASHES = "[/\\\\]+";
    public static final String EXT = "\\.metadata|\\.data";
    public static final String DATA = ".data";
    public static final String METADATA = ".metadata";
    private final Map<List<String>, ReentrantReadWriteLock> syncMap;

    private String cachePath;

    public FileSystemCacheBackend() {
        syncMap = new HashMap<>();
    }

    public void setCachePath(final String cachePath) {
        this.cachePath = getSystemTmp() + cachePath;
    }

    @Override
    public boolean write(final List<String> key, final Serializable value,
            final Map<String, Serializable> metaData) {
        final List<String> cleanKey = sanitizeKeySegments(key);
        final List<Lock> locks = lockForWrite(cleanKey);
        try {
            final String filePath = cachePath + StringUtils.join(cleanKey, File.separator);
            if (writeFile(value, filePath + DATA)) {
                return false;
            }

            final HashMap<String, Serializable> writeableMetaData = new HashMap<>();
            if (metaData != null) {
                writeableMetaData.putAll(metaData);
            }
            if (writeFile(writeableMetaData, filePath + METADATA)) {
                return false;
            }
            return true;
        } finally {
            unlock(locks);
        }
    }

    private boolean writeFile(Serializable value, String filePath) {
        final File file = new File(filePath);
        try {
            //create file structure
            file.getParentFile().mkdirs();
            if (!file.exists()) {
                file.createNewFile();
            }
            //closable resources
            try (final FileOutputStream fout = new FileOutputStream(file);
                    final ObjectOutputStream oos = new ObjectOutputStream(fout)) {
                oos.writeObject(value);
            }
        } catch (final IOException e) {
            logger.error("Can't write cache: ", e);
            return true;
        }
        return false;
    }

    @Override
    public Serializable read(final List<String> key) {
        Object result = null;
        final List<String> cleanKey = sanitizeKeySegments(key);
        final List<Lock> locks = lockForRead(cleanKey);
        try {
            final String filePath = cachePath + StringUtils.join(cleanKey, File.separator) + DATA;
            final File f = new File(filePath);
            if (!f.exists()) {
                return null;
            }

            try (final FileInputStream fis = new FileInputStream(f);
                    final ObjectInputStream ois = new ObjectInputStream(fis)) {
                result = ois.readObject();
            } catch (final Exception e) {
                logger.debug("Can't read cache: ", e);
            }
            return (Serializable) result;
        } finally {
            unlock(locks);
        }
    }

    public Map<String, Serializable> readMetaData(final List<String> key) {
        Object result = null;
        final List<String> cleanKey = sanitizeKeySegments(key);
        final List<String> noExtCleanKey = new ArrayList<>(cleanKey.size());

        for (final String cleanSegment : cleanKey) {
            noExtCleanKey.add(cleanSegment.replaceAll(EXT, ""));
        }

        final List<Lock> locks = lockForRead(noExtCleanKey);
        try {
            final String filePath = cachePath + StringUtils.join(noExtCleanKey, File.separator) + METADATA;
            final File f = new File(filePath);
            if (!f.exists()) {
                return null;
            }

            try (final FileInputStream fis = new FileInputStream(f);
                    final ObjectInputStream ois = new ObjectInputStream(fis)) {
                result = ois.readObject();
            } catch (final Exception e) {
                logger.debug("Can't read cache: ", e);
            }
            if (result instanceof Map) {
                return (Map<String, Serializable>) result;
            } else {
                return null;
            }
        } finally {
            unlock(locks);
        }
    }

    /**
     * Locks are released in reverse order. First we release the more specialized locks and traverse upwards towards the
     * root directory.
     *
     * @param locks
     */
    private void unlock(final List<Lock> locks) {
        for (int i = locks.size() - 1; i >= 0; i--) {
            final Lock lock = locks.get(i);
            lock.unlock();
        }
    }

    private List<Lock> lockForRead(List<String> key) {
        List<Lock> retval;
        if (!key.isEmpty()) {
            final List<String> parent = key.subList(0, key.size() - 1);
            retval = lockForRead(parent);
        } else {
            retval = new ArrayList<>();
        }

        final Lock lock = getLock(key).readLock();
        lock.lock();
        retval.add(lock);
        return retval;
    }

    /**
     * Acquires read locks for all sub-directories, and a final write lock for the current working directory. It acquires
     * the parent locks first, before trying to get more local locks.
     *
     * @param key
     * @return
     */
    private List<Lock> lockForWrite(final List<String> key) {
        final List<Lock> retval;
        if (CollectionUtils.isNotEmpty(key)) {
            final List<String> parent = key.subList(0, key.size() - 1);
            retval = lockForRead(parent);
        } else {
            retval = new ArrayList<>();
        }

        final Lock lock = getLock(key).writeLock();
        lock.lock();
        retval.add(lock);
        return retval;
    }

    /**
     * Returns an object for read/write synchronization
     *
     * @param key compound key
     * @return lock object
     */
    private synchronized ReentrantReadWriteLock getLock(final List<String> key) {
        final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
        if (!syncMap.containsKey(key)) {
            syncMap.put(key, lock);
            return lock;
        }
        return syncMap.get(key);
    }

    public void purgeSegment(final List<String> key, final BiPredicate<List<String>, Map<String, Serializable>> p) {
        final List<String> cleanKey = sanitizeKeySegments(key);
        final List<Lock> locks = lockForWrite(cleanKey);
        try {
            for (String name : listKeys(cleanKey)) {
                ArrayList<String> entryKey = new ArrayList<>(cleanKey);
                entryKey.add(name);
                final Map<String, Serializable> metaData = readMetaData(entryKey);
                if (p.test(entryKey, metaData)) {
                    purge(entryKey);
                }
            }
            for (String name : listSegments(cleanKey)) {
                ArrayList<String> entryKey = new ArrayList<>(cleanKey);
                entryKey.add(name);
                purgeSegment(entryKey, p);
            }
        } finally {
            unlock(locks);
        }
    }

    @Override
    public boolean purge(final List<String> key) {
        final List<String> cleanKey = sanitizeKeySegments(key);
        final List<Lock> locks = lockForWrite(cleanKey);
        try {
            final String fileName = cachePath + StringUtils.join(cleanKey, File.separator);

            if (fileName.endsWith(DATA)) {
                final File data = new File(fileName);
                if (!data.exists()) {
                    return true;
                }
                final File meta = new File(fileName.replace(DATA, METADATA));
                syncMap.remove(cleanKey);
                return data.delete() && meta.delete();
            }

            final File file = new File(fileName);

            if (file.isDirectory()) {
                final Set<String> subKeys = listKeys(cleanKey);
                for (final String subKey : subKeys) {
                    final ArrayList<String> subEntry = new ArrayList<>(cleanKey);
                    subEntry.add(subKey);
                    purge(subEntry);
                }

                syncMap.remove(cleanKey);
                FileUtils.deleteDirectory(file);
                return !file.exists();
            }

            final File data = new File(fileName + DATA);

            if (!file.exists() && !data.exists()) {
                return true;
            }

            final File metadata = new File(fileName + METADATA);
            syncMap.remove(cleanKey);
            return data.delete() && metadata.delete();

        } catch (final Exception e) {
            logger.debug("Can't delete cache: ", e);
            return false;
        } finally {
            unlock(locks);
        }
    }

    private Set<String> listKeys(final List<String> unsafeKey) {
        final List<String> sanitized = sanitizeKeySegments(unsafeKey);
        final Set<String> resultSet = new HashSet<>();
        final File directory = new File(cachePath + StringUtils.join(sanitized, File.separator));
        final File[] fList = directory.listFiles();
        if (fList != null) {
            for (final File file : fList) {
                final String name = file.getName();
                if (file.isFile() && name.endsWith(DATA)) {
                    resultSet.add(IOUtils.getInstance().getFileName(name));
                }
            }
        }
        return resultSet;
    }

    private Set<String> listSegments(final List<String> unsafeKey) {
        final List<String> sanitized = sanitizeKeySegments(unsafeKey);
        final Set<String> resultSet = new HashSet<>();
        final File directory = new File(cachePath + StringUtils.join(sanitized, File.separator));
        final File[] fList = directory.listFiles();
        if (fList != null) {
            for (final File file : fList) {
                if (file.isDirectory()) {
                    resultSet.add(file.getName());
                }
            }
        }
        return resultSet;
    }

    //for testing purpose
    String getSystemTmp() {
        String s = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
        final char c = s.charAt(s.length() - 1);
        if ((c != '/') && (c != '\\')) {
            s += File.separator;
            System.setProperty("java.io.tmpdir", s); //$NON-NLS-1$//$NON-NLS-2$
        }
        return s;
    }

    private static List<String> sanitizeKeySegments(final List<String> key) {
        final List<String> clean = new ArrayList<>();
        if (key != null) {
            for (final String segment : key) {
                clean.add(segment.replaceAll(SLASHES, REPLACEMENT));
            }
        }
        return clean;
    }

}