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    
020    package org.apache.james.mime4j.storage;
021    
022    import java.io.BufferedInputStream;
023    import java.io.File;
024    import java.io.FileInputStream;
025    import java.io.FileOutputStream;
026    import java.io.IOException;
027    import java.io.InputStream;
028    import java.io.OutputStream;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import 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     */
046    public 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    }