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.io.FileUtils;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.commons.vfs2.FileObject;
27  import org.apache.commons.vfs2.FileSelector;
28  import org.apache.commons.vfs2.FileSystemException;
29  import org.apache.commons.vfs2.VfsLog;
30  import org.apache.commons.vfs2.provider.AbstractVfsComponent;
31  import org.apache.commons.vfs2.provider.FileReplicator;
32  import org.apache.commons.vfs2.provider.TemporaryFileStore;
33  import org.apache.commons.vfs2.provider.UriParser;
34  import org.apache.commons.vfs2.util.Messages;
35  
36  /**
37   * A simple file replicator and temporary file store.
38   */
39  public class DefaultFileReplicator extends AbstractVfsComponent implements FileReplicator, TemporaryFileStore {
40  
41      private static final Log log = LogFactory.getLog(DefaultFileReplicator.class);
42      private static final int MASK = 0xffff;
43  
44      private static final Random RANDOM = new Random();
45  
46      private static final char[] TMP_RESERVED_CHARS = {'?', '/', '\\', ' ', '&', '"', '\'', '*', '#', ';', ':', '<', '>', '|'};
47  
48      private final ArrayList<Object> copies = new ArrayList<>();
49      private long fileCount;
50      private File tempDir;
51      private boolean tempDirMessageLogged;
52  
53      /**
54       * Constructs a new instance.
55       */
56      public DefaultFileReplicator() {
57      }
58  
59      /**
60       * Constructor to set the location of the temporary directory.
61       *
62       * @param tempDir The temporary directory.
63       */
64      public DefaultFileReplicator(final File tempDir) {
65          this.tempDir = tempDir;
66      }
67  
68      /**
69       * Adds a file.
70       *
71       * @param file the file to add.
72       */
73      protected void addFile(final Object file) {
74          synchronized (copies) {
75              copies.add(file);
76          }
77      }
78  
79      /**
80       * Allocates a new temporary file.
81       *
82       * @param baseName the base file name.
83       * @return The created File.
84       * @throws FileSystemException if an error occurs.
85       */
86      @Override
87      public File allocateFile(final String baseName) throws FileSystemException {
88          // Create a unique-ish file name
89          final String actualBaseName = createFilename(baseName);
90          synchronized (this) {
91              fileCount++;
92          }
93  
94          return createAndAddFile(tempDir, actualBaseName);
95      }
96  
97      /**
98       * Closes the replicator, deleting all temporary files.
99       */
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 }