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.isEmpty()) {
96                  deleteFile((File) removeFile());
97              }
98          }
99  
100         // Clean up the temp directory, if it is empty
101         if (tempDir != null && tempDir.exists()) {
102             final String[] list = tempDir.list();
103             if (list != null && list.length == 0) {
104                 tempDir.delete();
105                 tempDir = null;
106             }
107         }
108     }
109 
110     protected File createAndAddFile(final File parent, final String basename) throws FileSystemException {
111         final File file = createFile(tempDir, basename);
112 
113         // Keep track to delete later
114         addFile(file);
115 
116         return file;
117     }
118 
119     /**
120      * Create the temporary file.
121      *
122      * @param parent The file to use as the parent of the file being created.
123      * @param name The name of the file to create.
124      * @return The File that was created.
125      * @throws FileSystemException if an error occurs creating the file.
126      */
127     protected File createFile(final File parent, final String name) throws FileSystemException {
128         return new File(parent, UriParser.decode(name));
129     }
130 
131     /**
132      * Create the temporary file name.
133      *
134      * @param baseName The base to prepend to the file name being created.
135      * @return the name of the File.
136      */
137     protected String createFilename(final String baseName) {
138         // BUG29007
139         // return baseName + "_" + getFilecount() + ".tmp";
140 
141         // imario@apache.org: BUG34976 get rid of maybe reserved and dangerous characters
142         // e.g. to allow replication of http://hostname.org/fileservlet?file=abc.txt
143         final String safeBasename = UriParser.encode(baseName, TMP_RESERVED_CHARS).replace('%', '_');
144         return "tmp_" + getFilecount() + "_" + safeBasename;
145     }
146 
147     /**
148      * Physically deletes the file from the file system.
149      *
150      * @param file The File to delete.
151      */
152     protected void deleteFile(final File file) {
153         try {
154             final FileObject fileObject = getContext().toFileObject(file);
155             fileObject.deleteAll();
156         } catch (final FileSystemException e) {
157             final String message = Messages.getString("vfs.impl/delete-temp.warn", file.getName());
158             VfsLog.warn(getLogger(), log, message, e);
159         }
160     }
161 
162     protected long getFilecount() {
163         return filecount;
164     }
165 
166     /**
167      * Initializes this component.
168      *
169      * @throws FileSystemException if an error occurs.
170      */
171     @Override
172     public void init() throws FileSystemException {
173         if (tempDir == null) {
174             final String baseTmpDir = System.getProperty("java.io.tmpdir");
175 
176             tempDir = new File(baseTmpDir, "vfs_cache").getAbsoluteFile();
177         }
178 
179         filecount = random.nextInt() & MASK;
180 
181         if (!tempDirMessageLogged) {
182             final String message = Messages.getString("vfs.impl/temp-dir.debug", tempDir);
183             VfsLog.debug(getLogger(), log, message);
184 
185             tempDirMessageLogged = true;
186         }
187     }
188 
189     /**
190      * Removes a file from the copies list. Will be used for cleanup.
191      * <p>
192      * Notice: The system awaits that the returning object can be cast to a {@link java.io.File}.
193      *
194      * @return the File that was removed.
195      */
196     protected Object removeFile() {
197         synchronized (copies) {
198             return copies.remove(0);
199         }
200     }
201 
202     /**
203      * Removes a instance from the list of copies.
204      *
205      * @param file The File to remove.
206      */
207     protected void removeFile(final Object file) {
208         synchronized (copies) {
209             copies.remove(file);
210         }
211     }
212 
213     /**
214      * Creates a local copy of the file, and all its descendants.
215      *
216      * @param srcFile The file to copy.
217      * @param selector The FileSelector.
218      * @return the created File.
219      * @throws FileSystemException if an error occurs copying the file.
220      */
221     @Override
222     public File replicateFile(final FileObject srcFile, final FileSelector selector) throws FileSystemException {
223         final String basename = srcFile.getName().getBaseName();
224         final File file = allocateFile(basename);
225 
226         // Copy from the source file
227         final FileObject destFile = getContext().toFileObject(file);
228         destFile.copyFrom(srcFile, selector);
229 
230         return file;
231     }
232 }