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