com.braffdev.server.core.container.resourceloader.ResourceLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.braffdev.server.core.container.resourceloader.ResourceLoader.java

Source

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                         *
 *  Copyright (C) 2015, Markus Staudt <info@braffdev.com>                  *
 *                                                                         *
 *  This program is free software: you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation, either version 3 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  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 General Public License for more details.                           *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package com.braffdev.server.core.container.resourceloader;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.StringUtils;

import com.braffdev.server.core.Constants;
import com.braffdev.server.core.Shutdownable;
import com.braffdev.server.core.io.logger.Logger;
import com.braffdev.server.core.utilities.IOUtils;

/**
 * A ResourceLoader in responsible for loading files from a specific container
 */
public class ResourceLoader implements Shutdownable {

    private static final Logger LOGGER = Logger.getLogger(ResourceLoader.class);

    private List<ResourceInputStream> streams;
    private LRUMap<String, byte[]> cache;
    private File root;
    private boolean enabled;

    /**
     * Constructs a new ResourceLoader that uses the given file as root folder
     *
     * @param root the root folder.
     */
    public ResourceLoader(File root) {
        this.root = root;
        this.enabled = true;
        this.streams = new ArrayList<ResourceInputStream>();
        this.cache = new LRUMap<>(Constants.Resource.CACHE_MAX_SIZE);
    }

    /**
     * Opens an InputStream to the resource at the given path. <b>NOTE:</b> the path must <i>not</i> start with the container's name. Below is an example:<br>
     * <br>
     * [WRONG] /servlet/web/index.html ("servlet" is the container's name)<br>
     * [RIGHT] /index.html
     * [RIGHT] /web/index.html
     *
     * @param path
     * @return an InputStream or null if this ResourceLoader has been closed
     * @throws FileNotFoundException
     * @throws IOException
     */
    public InputStream load(String path) throws FileNotFoundException, IOException {
        if (!this.enabled) {
            LOGGER.warning("Cannot deliver file because the ResourceLoader has been closed.");
            return null;
        }

        // check cache first
        InputStream in = this.loadFromCache(path);
        if (in != null) {
            return in;
        }

        return this.loadFromDisk(path);
    }

    /**
     * Loads the file denoted by the given path from the hard drive.
     *
     * @param path the path to laod.
     * @return an InputStream pointing to the file to load.
     * @throws FileNotFoundException if the file cannot be found.
     */
    private InputStream loadFromDisk(String path) throws FileNotFoundException {
        if (!path.startsWith(Constants.File.DIR_CONTAINER_WEB)) {
            path = Constants.File.DIR_CONTAINER_WEB + path;
        }

        // load from disk
        File requestedFile = new File(this.root, path);
        if (!requestedFile.exists()) {
            throw new FileNotFoundException(path);

        } else {
            // file exists - add to cace
            byte[] content = IOUtils.readFully(new BufferedInputStream(new FileInputStream(requestedFile)));
            this.cache.put(path, content);
        }

        ResourceInputStream in = new ResourceInputStream(new FileInputStream(requestedFile), this);
        this.addStream(in);

        return in;
    }

    /**
     * Loads the file denoted by the given path from the cache.<br />
     * To be cached, files need to be loaded from the hard drive at least once.
     *
     * @param path the path to load.
     * @return an InputStream pointing to the file to load or <code>null</code> if the file at the given path is not cached yet.
     */
    private InputStream loadFromCache(String path) {
        byte[] bytes = this.cache.get(path);
        if (bytes != null) {
            return new BufferedInputStream(new ByteArrayInputStream(bytes));
        }

        return null;
    }

    /**
     * Determines the content type of the file denoted by the given path.<br />
     * This method asks the file system to determine the content type.
     *
     * @param path the path.
     * @return the content type
     * @throws Exception if something bad happens.
     */
    public String lookupContentType(String path) throws Exception {
        return Files.probeContentType(Paths.get(new File(this.root, path).toURI()));
    }

    /**
     * Loads the resource at the given path. <b>NOTE:</b> the path must <i>not</i> start with the container name. Below is a example:<br>
     * <br>
     * [WRONG] /servlet/index.html ("servlet" is the container's name)<br>
     * [RIGHT] index.html
     *
     * @param path
     * @return the loaded content of the file at the given path
     * @throws FileNotFoundException
     * @throws IOException
     */
    public String read(String path) throws FileNotFoundException, IOException {
        // check cache first
        byte[] bytes = this.cache.get(path);
        if (bytes != null) {
            return new String(bytes);
        }

        // load from disk
        String content = this.loadContent(path);
        if (StringUtils.isEmpty(content)) {
            content = null;
        } else {
            this.cache.put(path, content.getBytes());
        }

        return content;
    }

    /**
     * Loads the content of the file denoted by the given path.
     *
     * @param path the path.
     * @return the content of the file as String.
     */
    private String loadContent(String path) {
        StringBuffer buffer = new StringBuffer();

        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(this.load(path)));
            String line = null;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
        } catch (IOException e) {
            LOGGER.error("Cannot read file " + path);
        } finally {
            IOUtils.closeQuietly(reader);
        }

        return buffer.toString().trim();
    }

    /**
     * Adds a stream to the list of currently open streams.
     *
     * @param in the stream.
     */
    private void addStream(ResourceInputStream in) {
        synchronized (this.streams) {
            this.streams.add(in);
        }
    }

    /**
     * Removes the given stream from the list of currently open streams.
     *
     * @param in the stream.
     */
    public void removeStream(ResourceInputStream in) {
        synchronized (this.streams) {
            ListIterator<ResourceInputStream> iter = this.streams.listIterator();
            while (iter.hasNext()) {
                if (in == iter.next()) {
                    iter.remove();
                    break;
                }
            }
        }
    }

    /**
     * Frees all resource that may are being used (Closes all open InputStreams and prevents the server from opening new ones);
     */
    @Override
    public void shutdown() {
        this.enabled = false;

        // close all streams
        synchronized (this.streams) {
            ListIterator<ResourceInputStream> iter = this.streams.listIterator();
            while (iter.hasNext()) {
                InputStream in = iter.next();
                IOUtils.closeQuietly(in);

                iter.remove();
            }
        }
    }

    /**
     * @return the current size of the cache.
     */
    public int getCacheLoad() {
        return this.cache.size();
    }

    /**
     * @return the map representing the cache of this ResourceLoader.
     */
    public LRUMap<String, byte[]> getCache() {
        return this.cache;
    }
}