001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019
020package org.apache.james.mime4j.storage;
021
022import java.io.BufferedInputStream;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.Set;
032
033/**
034 * A {@link StorageProvider} that stores the data in temporary files. The files
035 * are stored either in a user-specified directory or the default temporary-file
036 * directory (specified by system property <code>java.io.tmpdir</code>).
037 * <p>
038 * Example usage:
039 *
040 * <pre>
041 * File directory = new File(&quot;/tmp/mime4j&quot;);
042 * StorageProvider provider = new TempFileStorageProvider(directory);
043 * DefaultStorageProvider.setInstance(provider);
044 * </pre>
045 */
046public class TempFileStorageProvider extends AbstractStorageProvider {
047
048    private static final String DEFAULT_PREFIX = "m4j";
049
050    private final String prefix;
051    private final String suffix;
052    private final File directory;
053
054    /**
055     * Equivalent to using constructor
056     * <code>TempFileStorageProvider("m4j", null, null)</code>.
057     */
058    public TempFileStorageProvider() {
059        this(DEFAULT_PREFIX, null, null);
060    }
061
062    /**
063     * Equivalent to using constructor
064     * <code>TempFileStorageProvider("m4j", null, directory)</code>.
065     */
066    public TempFileStorageProvider(File directory) {
067        this(DEFAULT_PREFIX, null, directory);
068    }
069
070    /**
071     * Creates a new <code>TempFileStorageProvider</code> using the given
072     * values.
073     *
074     * @param prefix
075     *            prefix for generating the temporary file's name; must be at
076     *            least three characters long.
077     * @param suffix
078     *            suffix for generating the temporary file's name; may be
079     *            <code>null</code> to use the suffix <code>".tmp"</code>.
080     * @param directory
081     *            the directory in which the file is to be created, or
082     *            <code>null</code> if the default temporary-file directory is
083     *            to be used (specified by the system property
084     *            <code>java.io.tmpdir</code>).
085     * @throws IllegalArgumentException
086     *             if the given prefix is less than three characters long or the
087     *             given directory does not exist and cannot be created (if it
088     *             is not <code>null</code>).
089     */
090    public TempFileStorageProvider(String prefix, String suffix, File directory) {
091        if (prefix == null || prefix.length() < 3)
092            throw new IllegalArgumentException("invalid prefix");
093
094        if (directory != null && !directory.isDirectory()
095                && !directory.mkdirs())
096            throw new IllegalArgumentException("invalid directory");
097
098        this.prefix = prefix;
099        this.suffix = suffix;
100        this.directory = directory;
101    }
102
103    public StorageOutputStream createStorageOutputStream() throws IOException {
104        File file = File.createTempFile(prefix, suffix, directory);
105        file.deleteOnExit();
106
107        return new TempFileStorageOutputStream(file);
108    }
109
110    private static final class TempFileStorageOutputStream extends
111            StorageOutputStream {
112        private File file;
113        private OutputStream out;
114
115        public TempFileStorageOutputStream(File file) throws IOException {
116            this.file = file;
117            this.out = new FileOutputStream(file);
118        }
119
120        @Override
121        public void close() throws IOException {
122            super.close();
123            out.close();
124        }
125
126        @Override
127        protected void write0(byte[] buffer, int offset, int length)
128                throws IOException {
129            out.write(buffer, offset, length);
130        }
131
132        @Override
133        protected Storage toStorage0() throws IOException {
134            // out has already been closed because toStorage calls close
135            return new TempFileStorage(file);
136        }
137    }
138
139    private static final class TempFileStorage implements Storage {
140
141        private File file;
142
143        private static final Set<File> filesToDelete = new HashSet<File>();
144
145        public TempFileStorage(File file) {
146            this.file = file;
147        }
148
149        public void delete() {
150            // deleting a file might not immediately succeed if there are still
151            // streams left open (especially under Windows). so we keep track of
152            // the files that have to be deleted and try to delete all these
153            // files each time this method gets invoked.
154
155            // a better but more complicated solution would be to start a
156            // separate thread that tries to delete the files periodically.
157
158            synchronized (filesToDelete) {
159                if (file != null) {
160                    filesToDelete.add(file);
161                    file = null;
162                }
163
164                for (Iterator<File> iterator = filesToDelete.iterator(); iterator
165                        .hasNext();) {
166                    File file = iterator.next();
167                    if (file.delete()) {
168                        iterator.remove();
169                    }
170                }
171            }
172        }
173
174        public InputStream getInputStream() throws IOException {
175            if (file == null)
176                throw new IllegalStateException("storage has been deleted");
177
178            return new BufferedInputStream(new FileInputStream(file));
179        }
180
181    }
182
183}