001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.impl; 018 019import java.io.File; 020import java.util.ArrayList; 021import java.util.Random; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.apache.commons.vfs2.FileObject; 026import org.apache.commons.vfs2.FileSelector; 027import org.apache.commons.vfs2.FileSystemException; 028import org.apache.commons.vfs2.VfsLog; 029import org.apache.commons.vfs2.provider.AbstractVfsComponent; 030import org.apache.commons.vfs2.provider.FileReplicator; 031import org.apache.commons.vfs2.provider.TemporaryFileStore; 032import org.apache.commons.vfs2.provider.UriParser; 033import org.apache.commons.vfs2.util.Messages; 034 035/** 036 * A simple file replicator and temporary file store. 037 */ 038public class DefaultFileReplicator extends AbstractVfsComponent implements FileReplicator, TemporaryFileStore { 039 private static final Log log = LogFactory.getLog(DefaultFileReplicator.class); 040 private static final int MASK = 0xffff; 041 042 private static final Random random = new Random(); 043 044 private static final char[] TMP_RESERVED_CHARS = new char[] { '?', '/', '\\', ' ', '&', '"', '\'', '*', '#', ';', 045 ':', '<', '>', '|' }; 046 047 private final ArrayList<Object> copies = new ArrayList<>(); 048 private long filecount; 049 private File tempDir; 050 private boolean tempDirMessageLogged; 051 052 public DefaultFileReplicator() { 053 } 054 055 /** 056 * Constructor to set the location of the temporary directory. 057 * 058 * @param tempDir The temporary directory. 059 */ 060 public DefaultFileReplicator(final File tempDir) { 061 this.tempDir = tempDir; 062 } 063 064 protected void addFile(final Object file) { 065 synchronized (copies) { 066 copies.add(file); 067 } 068 } 069 070 /** 071 * Allocates a new temporary file. 072 * 073 * @param baseName the base file name. 074 * @return The created File. 075 * @throws FileSystemException if an error occurs. 076 */ 077 @Override 078 public File allocateFile(final String baseName) throws FileSystemException { 079 // Create a unique-ish file name 080 final String basename = createFilename(baseName); 081 synchronized (this) { 082 filecount++; 083 } 084 085 return createAndAddFile(tempDir, basename); 086 } 087 088 /** 089 * Closes the replicator, deleting all temporary files. 090 */ 091 @Override 092 public void close() { 093 // Delete the temporary files 094 synchronized (copies) { 095 while (copies.size() > 0) { 096 final File file = (File) removeFile(); 097 deleteFile(file); 098 } 099 } 100 101 // Clean up the temp directory, if it is empty 102 if (tempDir != null && tempDir.exists() && tempDir.list().length == 0) { 103 tempDir.delete(); 104 tempDir = null; 105 } 106 } 107 108 protected File createAndAddFile(final File parent, final String basename) throws FileSystemException { 109 final File file = createFile(tempDir, basename); 110 111 // Keep track to delete later 112 addFile(file); 113 114 return file; 115 } 116 117 /** 118 * Create the temporary file. 119 * 120 * @param parent The file to use as the parent of the file being created. 121 * @param name The name of the file to create. 122 * @return The File that was created. 123 * @throws FileSystemException if an error occurs creating the file. 124 */ 125 protected File createFile(final File parent, final String name) throws FileSystemException { 126 return new File(parent, UriParser.decode(name)); 127 } 128 129 /** 130 * Create the temporary file name. 131 * 132 * @param baseName The base to prepend to the file name being created. 133 * @return the name of the File. 134 */ 135 protected String createFilename(final String baseName) { 136 // BUG29007 137 // return baseName + "_" + getFilecount() + ".tmp"; 138 139 // imario@apache.org: BUG34976 get rid of maybe reserved and dangerous characters 140 // e.g. to allow replication of http://hostname.org/fileservlet?file=abc.txt 141 final String safeBasename = UriParser.encode(baseName, TMP_RESERVED_CHARS).replace('%', '_'); 142 return "tmp_" + getFilecount() + "_" + safeBasename; 143 } 144 145 /** 146 * Physically deletes the file from the filesystem. 147 * 148 * @param file The File to delete. 149 */ 150 protected void deleteFile(final File file) { 151 try { 152 final FileObject fileObject = getContext().toFileObject(file); 153 fileObject.deleteAll(); 154 } catch (final FileSystemException e) { 155 final String message = Messages.getString("vfs.impl/delete-temp.warn", file.getName()); 156 VfsLog.warn(getLogger(), log, message, e); 157 } 158 } 159 160 protected long getFilecount() { 161 return filecount; 162 } 163 164 /** 165 * Initializes this component. 166 * 167 * @throws FileSystemException if an error occurs. 168 */ 169 @Override 170 public void init() throws FileSystemException { 171 if (tempDir == null) { 172 final String baseTmpDir = System.getProperty("java.io.tmpdir"); 173 174 tempDir = new File(baseTmpDir, "vfs_cache").getAbsoluteFile(); 175 } 176 177 filecount = random.nextInt() & MASK; 178 179 if (!tempDirMessageLogged) { 180 final String message = Messages.getString("vfs.impl/temp-dir.debug", tempDir); 181 VfsLog.debug(getLogger(), log, message); 182 183 tempDirMessageLogged = true; 184 } 185 } 186 187 /** 188 * Removes a file from the copies list. Will be used for cleanup. 189 * <p> 190 * Notice: The system awaits that the returning object can be cast to a {@link java.io.File}. 191 * 192 * @return the File that was removed. 193 */ 194 protected Object removeFile() { 195 synchronized (copies) { 196 return copies.remove(0); 197 } 198 } 199 200 /** 201 * Removes a instance from the list of copies. 202 * 203 * @param file The File to remove. 204 */ 205 protected void removeFile(final Object file) { 206 synchronized (copies) { 207 copies.remove(file); 208 } 209 } 210 211 /** 212 * Creates a local copy of the file, and all its descendants. 213 * 214 * @param srcFile The file to copy. 215 * @param selector The FileSelector. 216 * @return the created File. 217 * @throws FileSystemException if an error occurs copying the file. 218 */ 219 @Override 220 public File replicateFile(final FileObject srcFile, final FileSelector selector) throws FileSystemException { 221 final String basename = srcFile.getName().getBaseName(); 222 final File file = allocateFile(basename); 223 224 // Copy from the source file 225 final FileObject destFile = getContext().toFileObject(file); 226 destFile.copyFrom(srcFile, selector); 227 228 return file; 229 } 230}