de.ingrid.iplug.csw.dsc.cache.impl.DefaultFileCache.java Source code

Java tutorial

Introduction

Here is the source code for de.ingrid.iplug.csw.dsc.cache.impl.DefaultFileCache.java

Source

/*
 * **************************************************-
 * ingrid-iplug-csw-dsc:war
 * ==================================================
 * Copyright (C) 2014 - 2019 wemove digital solutions GmbH
 * ==================================================
 * Licensed under the EUPL, Version 1.1 or  as soon they will be
 * approved by the European Commission - subsequent versions of the
 * EUPL (the "Licence");
 * 
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 * http://ec.europa.eu/idabc/eupl5
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 * **************************************************#
 */
/*
 * Copyright (c) 2009 wemove digital solutions. All rights reserved.
 */

package de.ingrid.iplug.csw.dsc.cache.impl;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;

import de.ingrid.iplug.csw.dsc.cache.Cache;
import de.ingrid.iplug.csw.dsc.cswclient.CSWFactory;
import de.ingrid.iplug.csw.dsc.cswclient.CSWRecord;
import de.ingrid.iplug.csw.dsc.cswclient.constants.ElementSetName;
import de.ingrid.iplug.csw.dsc.tools.FileUtils;
import de.ingrid.iplug.csw.dsc.tools.StringUtils;

public class DefaultFileCache implements Cache, Serializable {

    private static final long serialVersionUID = DefaultFileCache.class.getName().hashCode();

    final protected static Log log = LogFactory.getLog(DefaultFileCache.class);

    protected CSWFactory factory = null;

    protected boolean isInitialized = false;
    protected boolean inTransaction = false;

    /**
     * The original path of the cache. If the cache is not in transaction mode,
     * all content is served from and stored in there. Commited content will be copied to there.
     */
    protected String cachePath = null;

    /**
     * The temporary path of the cache, that is used in transaction mode.
     */
    protected String tmpPath = null;

    /**
     * The initial cache from which a transaction was started.
     */
    protected Cache initialCache = null;

    /**
     * File name filter for recognizing cached files
     */
    protected class CacheFileFilter implements FileFilter {
        public boolean accept(File file) {
            return !file.isDirectory() && file.getName().endsWith(".xml");
        }
    };

    /**
     * Constructor
     */
    public DefaultFileCache() {
        this.initialCache = this;
    }

    /**
     * Check if the cache is already initialized
     * @return boolean
     */
    protected boolean isInitialized() {
        return this.isInitialized;
    }

    /**
     * Initialize the cache
     */
    protected void initialize() {
        if (!this.isInitialized) {
            // check configuration
            if (this.factory == null)
                throw new RuntimeException("Cache is not configured properly. The 'factory' property is not set.");

            // check for original path
            String originalPath = this.getCachePath();
            if (originalPath == null)
                throw new RuntimeException("DefaultFileCache is not configured properly: cachePath not set.");

            // check if the original path exists and create it if not
            File cacheLocation = new File(originalPath);
            if (!cacheLocation.exists())
                cacheLocation.mkdirs();

            this.isInitialized = true;
        }
    }

    /**
     * Get the work path of the cache. 
     * If the cache is in transaction mode, the path will differ from cachePath.
     * @return String
     */
    protected String getWorkPath() {
        if (this.isInTransaction()) {
            return this.getTempPath();
        } else {
            return this.getCachePath();
        }
    }

    /**
     * Encode an id to be used in a filename. 
     * @return String
     */
    protected String encodeId(String id) {
        return FileUtils.encodeFileName(id);
    }

    /**
     * Decode an id that was used in a filename. 
     * @return String
     */
    protected String decodeId(String id) {
        return FileUtils.decodeFileName(id);
    }

    /**
     * Get the record id from a cache filename
     * @param filename The filename without the path
     * @return String
     */
    protected String getIdFromFilename(String filename) {
        File file = new File(filename);
        String basename = file.getName();
        String id = this.decodeId(basename.substring(0, basename.lastIndexOf("_")));
        return id;
    }

    /**
     * Get the filename for a record
     * @param id
     * @param elementSetName
     * @return String
     */
    protected String getFilename(String id, ElementSetName elementSetName) {
        return this.encodeId(id) + "_" + elementSetName.toString() + ".xml";
    }

    /**
     * Get the relative path to a record starting from the cache root
     * @param id
     * @param elementSetName
     * @return String
     */
    protected String getRelativePath(String id, ElementSetName elementSetName) {
        return this.encodeId(id).substring(0, 1);
    }

    /**
     * Get the relative path to a record starting from the cache root
     * @param id
     * @param elementSetName
     * @return String
     */
    protected String getAbsolutePath(String id, ElementSetName elementSetName) {
        StringBuffer buf = new StringBuffer();
        buf.append(this.getWorkPath()).append(File.separatorChar).append(this.getRelativePath(id, elementSetName));
        return new File(buf.toString()).getAbsolutePath();
    }

    /**
     * Get record ids from a directory and all sub directories
     * @param directory The start directory
     * @return Set
     */
    protected Set<String> getRecordIds(File directory) {
        Set<String> recordIds = new HashSet<String>();
        FileFilter cacheFileFilter = new CacheFileFilter();
        File[] files = directory.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                if (cacheFileFilter.accept(files[i]))
                    recordIds.add(this.getIdFromFilename(files[i].getName()));
                if (files[i].isDirectory())
                    recordIds.addAll(getRecordIds(files[i]));
            }
        }
        return recordIds;
    }

    /**
     * Set the original cache path (that is used if not in transaction mode)
     * @param cachePath
     */
    public void setCachePath(String cachePath) {
        this.cachePath = cachePath;
    }

    /**
     * Get the root path of the cache if it is not in transaction mode. 
     * @return String
     */
    public String getCachePath() {
        return this.cachePath;
    }

    /**
     * Get the root path of the cache if it is in transaction mode. 
     * @return String
     */
    public String getTempPath() {
        if (this.tmpPath == null) {
            File originalPath = new File(this.getCachePath());
            File newPath = new File(originalPath.getParent() + File.separatorChar + originalPath.getName() + "_"
                    + StringUtils.generateUuid());

            // check if the cache path exists and create it if not
            if (!newPath.exists())
                newPath.mkdirs();
            this.tmpPath = newPath.getAbsolutePath();
        }
        return this.tmpPath;
    }

    /**
     * Get the absolute filename of a record.
     * @param id
     * @param elementSetName
     * @param cacheOperation
     * @return String
     */
    public String getAbsoluteFilename(String id, ElementSetName elementSetName) {
        StringBuffer buf = new StringBuffer();
        buf.append(this.getAbsolutePath(id, elementSetName)).append(File.separatorChar)
                .append(this.getFilename(id, elementSetName));
        return new File(buf.toString()).getAbsolutePath();
    }

    /**
     * Cache interface implementation
     */

    @Override
    public void configure(CSWFactory factory) {
        this.factory = factory;
    }

    @Override
    public Set<String> getCachedRecordIds() {
        if (!this.isInitialized())
            initialize();

        return getRecordIds(new File(this.getWorkPath()));
    }

    @Override
    public boolean isCached(String id, ElementSetName elementSetName) throws IOException {
        if (!this.isInitialized())
            initialize();

        try {
            String filePath = this.getAbsoluteFilename(id, elementSetName);
            File file = new File(filePath);
            return file.exists();
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("Could not find {" + id + "," + elementSetName.name() + "} in cache.");
            }
            return false;
        }
    }

    @Override
    public CSWRecord getRecord(String id, ElementSetName elementSetName) throws IOException {
        if (!this.isInitialized())
            initialize();

        String filePath = this.getAbsoluteFilename(id, elementSetName);
        File file = new File(filePath);
        if (file.exists()) {

            StringBuilder content = new StringBuilder();
            BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

            try {
                String line = null;
                while ((line = input.readLine()) != null) {
                    content.append(line);
                    content.append(System.getProperty("line.separator"));
                }
                input.close();
                input = null;

                Document document = StringUtils.stringToDocument(content.toString());
                CSWRecord record = this.factory.createRecord();
                record.initialize(elementSetName, document.getFirstChild());
                return record;
            } catch (Exception e) {
                throw new IOException(e);
            } finally {
                if (input != null)
                    input.close();
            }
        } else
            throw new IOException("No cache entry with id " + id + " and elementset " + elementSetName + " found.");
    }

    @Override
    public void putRecord(CSWRecord record) throws IOException {
        if (!this.isInitialized())
            initialize();

        // ensure that the directory exists
        String path = this.getAbsolutePath(record.getId(), record.getElementSetName());
        new File(path).mkdirs();

        String filePath = this.getAbsoluteFilename(record.getId(), record.getElementSetName());
        BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "UTF8"));
        try {
            output.write(StringUtils.nodeToString(record.getOriginalResponse()));
            output.close();
            output = null;
        } finally {
            if (output != null)
                output.close();
        }
    }

    @Override
    public void removeAllRecords() {
        if (!this.isInitialized())
            initialize();

        File workPath = new File(this.getWorkPath());
        FileUtils.deleteRecursive(workPath);
        workPath.mkdirs();
    }

    @Override
    public void removeRecord(String id) {
        if (!this.isInitialized())
            initialize();

        // remove all cached files for this id
        ElementSetName[] names = ElementSetName.values();
        for (int i = 0; i < names.length; i++) {
            String filePath = this.getAbsoluteFilename(id, names[i]);
            File file = new File(filePath);
            if (file.exists())
                file.delete();
        }
    }

    @Override
    public boolean isInTransaction() {
        return this.inTransaction;
    }

    @Override
    public Cache startTransaction() throws IOException {
        if (!this.isInitialized())
            initialize();

        DefaultFileCache cache = new DefaultFileCache();
        cache.configure(this.factory);

        // the original content of the new cache instance
        // is the content of this cache
        cache.cachePath = this.getWorkPath();
        cache.initialCache = this;
        cache.inTransaction = true;

        // copy content of this instance to the new cache
        FileUtils.copyRecursive(new File(this.getWorkPath()), new File(cache.getWorkPath()));

        return cache;
    }

    @Override
    public void commitTransaction() throws IOException {
        if (!this.isInitialized())
            initialize();

        if (this.isInTransaction()) {
            // move content of this instance to the original cache
            File originalDir = new File(this.getCachePath());
            File tmpDir = new File(this.getWorkPath());
            if (log.isInfoEnabled()) {
                log.info("Remove cache: " + originalDir.getAbsolutePath());
            }

            FileUtils.deleteRecursive(originalDir);

            if (log.isInfoEnabled()) {
                log.info("Rename temp cache: " + tmpDir.getAbsolutePath() + " to " + originalDir.getAbsolutePath());
            }
            if (!tmpDir.renameTo(originalDir)) {
                log.error("Failed  to rename " + tmpDir.getAbsolutePath() + " to " + originalDir.getAbsolutePath());
            }

            this.inTransaction = false;
        } else
            throw new RuntimeException("The cache is not in transaction mode.");
    }

    @Override
    public void rollbackTransaction() {
        if (!this.isInitialized())
            initialize();

        if (this.isInTransaction()) {
            // remove content of this instance
            File tmpDir = new File(this.getWorkPath());
            FileUtils.deleteRecursive(tmpDir);

            this.inTransaction = false;
        } else
            throw new RuntimeException("The cache is not in transaction mode.");
    }

    @Override
    public Cache getInitialCache() {
        return this.initialCache;
    }

    @Override
    public Date getLastCommitDate() {
        // return the last modified date of the cache directory
        File cacheDir = new File(this.getCachePath());
        return new Date(cacheDir.lastModified());
    }

    @Override
    public String toString() {
        return this.getWorkPath() + ", " + super.toString();

    }
}