View Javadoc

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