001: /* ====================================================================
002: The Jicarilla Software License
003:
004: Copyright (c) 2003 Leo Simons.
005: All rights reserved.
006:
007: Permission is hereby granted, free of charge, to any person obtaining
008: a copy of this software and associated documentation files (the
009: "Software"), to deal in the Software without restriction, including
010: without limitation the rights to use, copy, modify, merge, publish,
011: distribute, sublicense, and/or sell copies of the Software, and to
012: permit persons to whom the Software is furnished to do so, subject to
013: the following conditions:
014:
015: The above copyright notice and this permission notice shall be
016: included in all copies or substantial portions of the Software.
017:
018: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
019: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
020: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
021: IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
022: CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
023: TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
024: SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
025: ==================================================================== */
026: package org.jicarilla.io;
027:
028: import org.jicarilla.lang.Assert;
029:
030: import java.io.File;
031: import java.io.FileInputStream;
032: import java.io.FileNotFoundException;
033: import java.io.IOException;
034: import java.nio.channels.FileChannel;
035: import java.nio.channels.ReadableByteChannel;
036: import java.util.Map;
037: import java.util.WeakHashMap;
038:
039: /**
040: *
041: *
042: * @author <a href="lsimons at jicarilla dot org">Leo Simons</a>
043: * @version $Id: FilesystemImpl.java,v 1.6 2004/03/31 12:10:59 lsimons Exp $
044: */
045: public class FilesystemImpl implements Filesystem {
046: // ----------------------------------------------------------------------
047: // Properties
048: // ----------------------------------------------------------------------
049: protected File m_rootDirectory;
050: protected Map m_openFiles;
051:
052: // ----------------------------------------------------------------------
053: // Constructor
054: // ----------------------------------------------------------------------
055: public FilesystemImpl(final String rootDirectory) {
056: Assert.assertNotNull(rootDirectory);
057: final File root = new File(rootDirectory);
058: setRootDirectory(root);
059:
060: setOpenFiles(new WeakHashMap());
061: }
062:
063: // ----------------------------------------------------------------------
064: // Getters/Setters
065: // ----------------------------------------------------------------------
066: protected synchronized File getRootDirectory() {
067: return m_rootDirectory;
068: }
069:
070: protected synchronized void setRootDirectory(final File root) {
071: Assert.assertNotNull("root argument may not be null", root);
072: Assert.assertTrue("root must exist", root.exists());
073: Assert.assertTrue("root must be readable", root.canRead());
074: Assert.assertTrue("root must be a directory", root
075: .isDirectory());
076:
077: m_rootDirectory = root;
078: }
079:
080: protected synchronized Map getOpenFiles() {
081: return m_openFiles;
082: }
083:
084: protected synchronized void setOpenFiles(final Map openFiles) {
085: Assert.assertNotNull("openFiles argument may not be null",
086: openFiles);
087: m_openFiles = openFiles;
088: }
089:
090: // ----------------------------------------------------------------------
091: // Interface: Filesystem
092: // ----------------------------------------------------------------------
093: public synchronized ReadableByteChannel getFile(
094: final String relativePath) throws FileNotFoundException {
095: final File file = getFileFromRelativePath(relativePath);
096: final FileChannel fileChannel = getFileChannel(file);
097: return fileChannel;
098: }
099:
100: public synchronized void returnFile(
101: final ReadableByteChannel channel) {
102: if (!(channel instanceof FileChannel))
103: return;
104:
105: final FileChannel fileChannel = (FileChannel) channel;
106: returnChannel(fileChannel);
107: }
108:
109: // ----------------------------------------------------------------------
110: // Helper Methods
111: // ----------------------------------------------------------------------
112: /**
113: * Tries to make sure that the provided path lives somewhere inside
114: * @link m_rootDirectory}.
115: *
116: * @param absolutePath the path to check for location validity.
117: * @throws SecurityException if the file does not live inside
118: * the root directory.
119: */
120: protected void checkPathIsAllowed(String absolutePath) {
121: final String rootPath = getRootDirectory().getAbsolutePath();
122: final boolean startsWithRootPath = rootPath.equals(absolutePath
123: .substring(0, rootPath.length()));
124: if (!startsWithRootPath)
125: throw new SecurityException("Access denied to: "
126: + absolutePath);
127: }
128:
129: /**
130: * Transform a path relative to the root directory to an absolute path.
131: *
132: * @param relativePath the path to transform.
133: * @return an absolute path to the same location.
134: * @throws SecurityException if access to the file is denied.
135: */
136: protected String getFullPath(final String relativePath) {
137: // handle optional '/' and '\'
138: String path;
139: if (relativePath.charAt(0) != File.separatorChar
140: && relativePath.charAt(0) != '/'
141: && relativePath.charAt(0) != '\\')
142: path = getRootDirectory().getAbsolutePath()
143: + File.separator + relativePath;
144: else
145: path = getRootDirectory().getAbsolutePath() + relativePath;
146:
147: // convert '/' and '\'
148: path.replaceAll("/", File.separator);
149: path.replaceAll("\\\\", File.separator);
150:
151: // check against stuff like '../../../..'
152: checkPathIsAllowed(path);
153:
154: return path;
155: }
156:
157: /**
158: * Transform a path relative to the root directory to a {@link File}.
159: *
160: * @param relativePath the path to transform.
161: * @return a <code>File</code> instance pointing to the specified path.
162: * @throws FileNotFoundException if the specified file cannot be found,
163: * the provided path points to a directory or to a file from which
164: * cannot be read.
165: * @throws SecurityException if access to the file is denied.
166: */
167: protected File getFileFromRelativePath(final String relativePath)
168: throws FileNotFoundException {
169: final String path = getFullPath(relativePath);
170: final File file = new File(path);
171:
172: if (!file.exists() || !file.canRead() || !file.isDirectory())
173: throw new FileNotFoundException(path);
174:
175: return file;
176: }
177:
178: /**
179: * Retrieve a {@link FileChannel} pointing to the provided file.
180: *
181: * @param file the file to retrieve the associated <code>FileChannel</code>
182: * for.
183: * @return the {@link FileChannel} pointing to the provided file.
184: * @throws FileNotFoundException if the specified file cannot be found,
185: * the provided path points to a directory or to a file from which
186: * cannot be read.
187: * @throws SecurityException if access to the file is denied.
188: */
189: protected FileChannel getFileChannel(final File file)
190: throws FileNotFoundException {
191: final FileInputStream fis = new FileInputStream(file);
192: final FileChannel fileChannel = fis.getChannel();
193: addOpenFile(fileChannel, fis);
194: return fileChannel;
195: }
196:
197: /**
198: * Keep a reference to the provided {@link FileInputStream} around in order
199: * to be able to close it when it is fed to
200: * {@link #returnFile(ReadableByteChannel)}.
201: *
202: * @param fc the channel associated with the stream.
203: * @param fis the stream to keep around.
204: */
205: protected void addOpenFile(final FileChannel fc,
206: final FileInputStream fis) {
207: final FileChannelWrapper key = new FileChannelWrapper(fc);
208: getOpenFiles().put(key, fis);
209: }
210:
211: /**
212: * Remove the reference to the provided {@link FileInputStream} and close
213: * it if it existed before.
214: *
215: * @param fc the channel associated with the stream.
216: */
217: protected void returnChannel(final FileChannel fc) {
218: final FileChannelWrapper key = new FileChannelWrapper(fc);
219: if (getOpenFiles().containsKey(key)) {
220: final FileInputStream fs = (FileInputStream) getOpenFiles()
221: .remove(fc);
222: try {
223: fs.close();
224: } catch (IOException e) {
225: }
226: }
227: }
228:
229: /**
230: * Helper class to make sure a channel is put into the cache every time
231: * one is requested, and no entries are overridden.
232: */
233: protected static class FileChannelWrapper {
234: public FileChannel channel;
235:
236: public FileChannelWrapper(FileChannel channel) {
237: this.channel = channel;
238: }
239: }
240: }
|