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.io.FileUtils; 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.apache.commons.vfs2.FileObject; 027import org.apache.commons.vfs2.FileSelector; 028import org.apache.commons.vfs2.FileSystemException; 029import org.apache.commons.vfs2.VfsLog; 030import org.apache.commons.vfs2.provider.AbstractVfsComponent; 031import org.apache.commons.vfs2.provider.FileReplicator; 032import org.apache.commons.vfs2.provider.TemporaryFileStore; 033import org.apache.commons.vfs2.provider.UriParser; 034import org.apache.commons.vfs2.util.Messages; 035 036/** 037 * A simple file replicator and temporary file store. 038 */ 039public class DefaultFileReplicator extends AbstractVfsComponent implements FileReplicator, TemporaryFileStore { 040 041 private static final Log log = LogFactory.getLog(DefaultFileReplicator.class); 042 private static final int MASK = 0xffff; 043 044 private static final Random RANDOM = new Random(); 045 046 private static final char[] TMP_RESERVED_CHARS = {'?', '/', '\\', ' ', '&', '"', '\'', '*', '#', ';', ':', '<', '>', '|'}; 047 048 private final ArrayList<Object> copies = new ArrayList<>(); 049 private long fileCount; 050 private File tempDir; 051 private boolean tempDirMessageLogged; 052 053 /** 054 * Constructs a new instance. 055 */ 056 public DefaultFileReplicator() { 057 } 058 059 /** 060 * Constructor to set the location of the temporary directory. 061 * 062 * @param tempDir The temporary directory. 063 */ 064 public DefaultFileReplicator(final File tempDir) { 065 this.tempDir = tempDir; 066 } 067 068 /** 069 * Adds a file. 070 * 071 * @param file the file to add. 072 */ 073 protected void addFile(final Object file) { 074 synchronized (copies) { 075 copies.add(file); 076 } 077 } 078 079 /** 080 * Allocates a new temporary file. 081 * 082 * @param baseName the base file name. 083 * @return The created File. 084 * @throws FileSystemException if an error occurs. 085 */ 086 @Override 087 public File allocateFile(final String baseName) throws FileSystemException { 088 // Create a unique-ish file name 089 final String actualBaseName = createFilename(baseName); 090 synchronized (this) { 091 fileCount++; 092 } 093 094 return createAndAddFile(tempDir, actualBaseName); 095 } 096 097 /** 098 * Closes the replicator, deleting all temporary files. 099 */ 100 @Override 101 public void close() { 102 // Delete the temporary files 103 synchronized (copies) { 104 while (!copies.isEmpty()) { 105 deleteFile((File) removeFile()); 106 } 107 } 108 109 // Clean up the temp directory, if it is empty 110 if (tempDir != null && tempDir.exists()) { 111 final String[] list = tempDir.list(); 112 if (list != null && list.length == 0) { 113 tempDir.delete(); 114 tempDir = null; 115 } 116 } 117 } 118 119 /** 120 * Adds a file. 121 * 122 * @param parent ignored. 123 * @param baseName the base file name. 124 * @return a File. 125 * @throws FileSystemException if a file system error occurs. 126 */ 127 protected File createAndAddFile(final File parent, final String baseName) throws FileSystemException { 128 final File file = createFile(tempDir, baseName); 129 // Keep track to delete later 130 addFile(file); 131 return file; 132 } 133 134 /** 135 * Create the temporary file. 136 * 137 * @param parent The file to use as the parent of the file being created. 138 * @param name The name of the file to create. 139 * @return The File that was created. 140 * @throws FileSystemException if an error occurs creating the file. 141 */ 142 protected File createFile(final File parent, final String name) throws FileSystemException { 143 return new File(parent, UriParser.decode(name)); 144 } 145 146 /** 147 * Create the temporary file name. 148 * 149 * @param baseName The base to prepend to the file name being created. 150 * @return the name of the File. 151 */ 152 protected String createFilename(final String baseName) { 153 // BUG29007 154 // return baseName + "_" + getFilecount() + ".tmp"; 155 156 // imario@apache.org: BUG34976 get rid of maybe reserved and dangerous characters 157 // e.g. to allow replication of http://hostname.org/fileservlet?file=abc.txt 158 final String safeBaseName = UriParser.encode(baseName, TMP_RESERVED_CHARS).replace('%', '_'); 159 return "tmp_" + getFilecount() + "_" + safeBaseName; 160 } 161 162 /** 163 * Physically deletes the file from the file system. 164 * 165 * @param file The File to delete. 166 */ 167 protected void deleteFile(final File file) { 168 try { 169 final FileObject fileObject = getContext().toFileObject(file); 170 fileObject.deleteAll(); 171 } catch (final FileSystemException e) { 172 final String message = Messages.getString("vfs.impl/delete-temp.warn", file.getName()); 173 VfsLog.warn(getLogger(), log, message, e); 174 } 175 } 176 177 /** 178 * Gets the file count. 179 * 180 * @return the file count. 181 */ 182 protected long getFilecount() { 183 return fileCount; 184 } 185 186 /** 187 * Initializes this component. 188 * 189 * @throws FileSystemException if an error occurs. 190 */ 191 @Override 192 public void init() throws FileSystemException { 193 if (tempDir == null) { 194 tempDir = new File(FileUtils.getTempDirectoryPath(), "vfs_cache").getAbsoluteFile(); 195 } 196 197 fileCount = RANDOM.nextInt() & MASK; 198 199 if (!tempDirMessageLogged) { 200 final String message = Messages.getString("vfs.impl/temp-dir.debug", tempDir); 201 VfsLog.debug(getLogger(), log, message); 202 203 tempDirMessageLogged = true; 204 } 205 } 206 207 /** 208 * Removes a file from the copies list. Will be used for cleanup. 209 * <p> 210 * Notice: The system awaits that the returning object can be cast to a {@link File}. 211 * 212 * @return the File that was removed. 213 */ 214 protected Object removeFile() { 215 synchronized (copies) { 216 return copies.remove(0); 217 } 218 } 219 220 /** 221 * Removes an instance from the list of copies. 222 * 223 * @param file The File to remove. 224 */ 225 protected void removeFile(final Object file) { 226 synchronized (copies) { 227 copies.remove(file); 228 } 229 } 230 231 /** 232 * Creates a local copy of the file, and all its descendants. 233 * 234 * @param srcFile The file to copy. 235 * @param selector The FileSelector. 236 * @return the created File. 237 * @throws FileSystemException if an error occurs copying the file. 238 */ 239 @Override 240 public File replicateFile(final FileObject srcFile, final FileSelector selector) throws FileSystemException { 241 final String baseName = srcFile.getName().getBaseName(); 242 final File file = allocateFile(baseName); 243 244 // Copy from the source file 245 final FileObject destFile = getContext().toFileObject(file); 246 destFile.copyFrom(srcFile, selector); 247 248 return file; 249 } 250}