View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.impl;
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  import java.util.Random;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.commons.vfs2.FileObject;
26  import org.apache.commons.vfs2.FileSelector;
27  import org.apache.commons.vfs2.FileSystemException;
28  import org.apache.commons.vfs2.VfsLog;
29  import org.apache.commons.vfs2.provider.AbstractVfsComponent;
30  import org.apache.commons.vfs2.provider.FileReplicator;
31  import org.apache.commons.vfs2.provider.TemporaryFileStore;
32  import org.apache.commons.vfs2.provider.UriParser;
33  import org.apache.commons.vfs2.util.Messages;
34  
35  /**
36   * A simple file replicator and temporary file store.
37   */
38  public class DefaultFileReplicator extends AbstractVfsComponent implements FileReplicator, TemporaryFileStore {
39      private static final Log log = LogFactory.getLog(DefaultFileReplicator.class);
40      private static final int MASK = 0xffff;
41  
42      private static final Random random = new Random();
43  
44      private static final char[] TMP_RESERVED_CHARS = new char[] { '?', '/', '\\', ' ', '&', '"', '\'', '*', '#', ';',
45              ':', '<', '>', '|' };
46  
47      private final ArrayList<Object> copies = new ArrayList<>();
48      private long filecount;
49      private File tempDir;
50      private boolean tempDirMessageLogged;
51  
52      public DefaultFileReplicator() {
53      }
54  
55      /**
56       * Constructor to set the location of the temporary directory.
57       *
58       * @param tempDir The temporary directory.
59       */
60      public DefaultFileReplicator(final File tempDir) {
61          this.tempDir = tempDir;
62      }
63  
64      protected void addFile(final Object file) {
65          synchronized (copies) {
66              copies.add(file);
67          }
68      }
69  
70      /**
71       * Allocates a new temporary file.
72       *
73       * @param baseName the base file name.
74       * @return The created File.
75       * @throws FileSystemException if an error occurs.
76       */
77      @Override
78      public File allocateFile(final String baseName) throws FileSystemException {
79          // Create a unique-ish file name
80          final String basename = createFilename(baseName);
81          synchronized (this) {
82              filecount++;
83          }
84  
85          return createAndAddFile(tempDir, basename);
86      }
87  
88      /**
89       * Closes the replicator, deleting all temporary files.
90       */
91      @Override
92      public void close() {
93          // Delete the temporary files
94          synchronized (copies) {
95              while (copies.size() > 0) {
96                  final File file = (File) removeFile();
97                  deleteFile(file);
98              }
99          }
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 file system.
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 }