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}