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("/tmp/mime4j");
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 }