Java tutorial
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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; } }