1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20 package org.apache.james.mime4j.storage;
21
22 import java.io.BufferedInputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.Set;
32
33 /**
34 * A {@link StorageProvider} that stores the data in temporary files. The files
35 * are stored either in a user-specified directory or the default temporary-file
36 * directory (specified by system property <code>java.io.tmpdir</code>).
37 * <p>
38 * Example usage:
39 *
40 * <pre>
41 * File directory = new File("/tmp/mime4j");
42 * StorageProvider provider = new TempFileStorageProvider(directory);
43 * DefaultStorageProvider.setInstance(provider);
44 * </pre>
45 */
46 public class TempFileStorageProvider extends AbstractStorageProvider {
47
48 private static final String DEFAULT_PREFIX = "m4j";
49
50 private final String prefix;
51 private final String suffix;
52 private final File directory;
53
54 /**
55 * Equivalent to using constructor
56 * <code>TempFileStorageProvider("m4j", null, null)</code>.
57 */
58 public TempFileStorageProvider() {
59 this(DEFAULT_PREFIX, null, null);
60 }
61
62 /**
63 * Equivalent to using constructor
64 * <code>TempFileStorageProvider("m4j", null, directory)</code>.
65 */
66 public TempFileStorageProvider(File directory) {
67 this(DEFAULT_PREFIX, null, directory);
68 }
69
70 /**
71 * Creates a new <code>TempFileStorageProvider</code> using the given
72 * values.
73 *
74 * @param prefix
75 * prefix for generating the temporary file's name; must be at
76 * least three characters long.
77 * @param suffix
78 * suffix for generating the temporary file's name; may be
79 * <code>null</code> to use the suffix <code>".tmp"</code>.
80 * @param directory
81 * the directory in which the file is to be created, or
82 * <code>null</code> if the default temporary-file directory is
83 * to be used (specified by the system property
84 * <code>java.io.tmpdir</code>).
85 * @throws IllegalArgumentException
86 * if the given prefix is less than three characters long or the
87 * given directory does not exist and cannot be created (if it
88 * is not <code>null</code>).
89 */
90 public TempFileStorageProvider(String prefix, String suffix, File directory) {
91 if (prefix == null || prefix.length() < 3)
92 throw new IllegalArgumentException("invalid prefix");
93
94 if (directory != null && !directory.isDirectory()
95 && !directory.mkdirs())
96 throw new IllegalArgumentException("invalid directory");
97
98 this.prefix = prefix;
99 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 }