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.IOException;
023    import java.io.InputStream;
024    import java.nio.charset.Charset;
025    
026    import org.apache.james.mime4j.codec.DecodeMonitor;
027    import org.apache.james.mime4j.dom.BinaryBody;
028    import org.apache.james.mime4j.dom.Disposable;
029    import org.apache.james.mime4j.dom.SingleBody;
030    import org.apache.james.mime4j.dom.TextBody;
031    import org.apache.james.mime4j.message.BodyFactory;
032    import org.apache.james.mime4j.util.CharsetUtil;
033    
034    /**
035     * Factory for creating message bodies.
036     */
037    public class StorageBodyFactory implements BodyFactory {
038    
039        private static final Charset FALLBACK_CHARSET = CharsetUtil.DEFAULT_CHARSET;
040    
041        private final StorageProvider storageProvider;
042        private final DecodeMonitor monitor;
043    
044        /**
045         * Creates a new <code>BodyFactory</code> instance that uses the default
046         * storage provider for creating message bodies from input streams.
047         */
048        public StorageBodyFactory() {
049            this(null, null);
050        }
051    
052        /**
053         * Creates a new <code>BodyFactory</code> instance that uses the given
054         * storage provider for creating message bodies from input streams.
055         *
056         * @param storageProvider
057         *            a storage provider or <code>null</code> to use the default
058         *            one.
059         */
060        public StorageBodyFactory(
061                final StorageProvider storageProvider,
062                final DecodeMonitor monitor) {
063            this.storageProvider =
064                storageProvider != null ? storageProvider : DefaultStorageProvider.getInstance();
065            this.monitor =
066                monitor != null ? monitor : DecodeMonitor.SILENT;
067        }
068    
069        /**
070         * Returns the <code>StorageProvider</code> this <code>BodyFactory</code>
071         * uses to create message bodies from input streams.
072         *
073         * @return a <code>StorageProvider</code>.
074         */
075        public StorageProvider getStorageProvider() {
076            return storageProvider;
077        }
078    
079        /**
080         * Creates a {@link BinaryBody} that holds the content of the given input
081         * stream.
082         *
083         * @param is
084         *            input stream to create a message body from.
085         * @return a binary body.
086         * @throws IOException
087         *             if an I/O error occurs.
088         */
089        public BinaryBody binaryBody(InputStream is) throws IOException {
090            if (is == null)
091                throw new IllegalArgumentException();
092    
093            Storage storage = storageProvider.store(is);
094            return new StorageBinaryBody(new MultiReferenceStorage(storage));
095        }
096    
097        /**
098         * Creates a {@link BinaryBody} that holds the content of the given
099         * {@link Storage}.
100         * <p>
101         * Note that the caller must not invoke {@link Storage#delete() delete()} on
102         * the given <code>Storage</code> object after it has been passed to this
103         * method. Instead the message body created by this method takes care of
104         * deleting the storage when it gets disposed of (see
105         * {@link Disposable#dispose()}).
106         *
107         * @param storage
108         *            storage to create a message body from.
109         * @return a binary body.
110         * @throws IOException
111         *             if an I/O error occurs.
112         */
113        public BinaryBody binaryBody(Storage storage) throws IOException {
114            if (storage == null)
115                throw new IllegalArgumentException();
116    
117            return new StorageBinaryBody(new MultiReferenceStorage(storage));
118        }
119    
120        /**
121         * Creates a {@link TextBody} that holds the content of the given input
122         * stream.
123         * <p>
124         * &quot;us-ascii&quot; is used to decode the byte content of the
125         * <code>Storage</code> into a character stream when calling
126         * {@link TextBody#getReader() getReader()} on the returned object.
127         *
128         * @param is
129         *            input stream to create a message body from.
130         * @return a text body.
131         * @throws IOException
132         *             if an I/O error occurs.
133         */
134        public TextBody textBody(InputStream is) throws IOException {
135            if (is == null)
136                throw new IllegalArgumentException();
137    
138            Storage storage = storageProvider.store(is);
139            return new StorageTextBody(new MultiReferenceStorage(storage),
140                    CharsetUtil.DEFAULT_CHARSET);
141        }
142    
143        /**
144         * Creates a {@link TextBody} that holds the content of the given input
145         * stream.
146         * <p>
147         * The charset corresponding to the given MIME charset name is used to
148         * decode the byte content of the input stream into a character stream when
149         * calling {@link TextBody#getReader() getReader()} on the returned object.
150         * If the MIME charset has no corresponding Java charset or the Java charset
151         * cannot be used for decoding then &quot;us-ascii&quot; is used instead.
152         *
153         * @param is
154         *            input stream to create a message body from.
155         * @param mimeCharset
156         *            name of a MIME charset.
157         * @return a text body.
158         * @throws IOException
159         *             if an I/O error occurs.
160         */
161        public TextBody textBody(InputStream is, String mimeCharset)
162                throws IOException {
163            if (is == null)
164                throw new IllegalArgumentException();
165            if (mimeCharset == null)
166                throw new IllegalArgumentException();
167    
168            Storage storage = storageProvider.store(is);
169            Charset charset = toJavaCharset(mimeCharset, false, monitor);
170            return new StorageTextBody(new MultiReferenceStorage(storage), charset);
171        }
172    
173        /**
174         * Creates a {@link TextBody} that holds the content of the given
175         * {@link Storage}.
176         * <p>
177         * &quot;us-ascii&quot; is used to decode the byte content of the
178         * <code>Storage</code> into a character stream when calling
179         * {@link TextBody#getReader() getReader()} on the returned object.
180         * <p>
181         * Note that the caller must not invoke {@link Storage#delete() delete()} on
182         * the given <code>Storage</code> object after it has been passed to this
183         * method. Instead the message body created by this method takes care of
184         * deleting the storage when it gets disposed of (see
185         * {@link Disposable#dispose()}).
186         *
187         * @param storage
188         *            storage to create a message body from.
189         * @return a text body.
190         * @throws IOException
191         *             if an I/O error occurs.
192         */
193        public TextBody textBody(Storage storage) throws IOException {
194            if (storage == null)
195                throw new IllegalArgumentException();
196    
197            return new StorageTextBody(new MultiReferenceStorage(storage),
198                    CharsetUtil.DEFAULT_CHARSET);
199        }
200    
201        /**
202         * Creates a {@link TextBody} that holds the content of the given
203         * {@link Storage}.
204         * <p>
205         * The charset corresponding to the given MIME charset name is used to
206         * decode the byte content of the <code>Storage</code> into a character
207         * stream when calling {@link TextBody#getReader() getReader()} on the
208         * returned object. If the MIME charset has no corresponding Java charset or
209         * the Java charset cannot be used for decoding then &quot;us-ascii&quot; is
210         * used instead.
211         * <p>
212         * Note that the caller must not invoke {@link Storage#delete() delete()} on
213         * the given <code>Storage</code> object after it has been passed to this
214         * method. Instead the message body created by this method takes care of
215         * deleting the storage when it gets disposed of (see
216         * {@link Disposable#dispose()}).
217         *
218         * @param storage
219         *            storage to create a message body from.
220         * @param mimeCharset
221         *            name of a MIME charset.
222         * @return a text body.
223         * @throws IOException
224         *             if an I/O error occurs.
225         */
226        public TextBody textBody(Storage storage, String mimeCharset)
227                throws IOException {
228            if (storage == null)
229                throw new IllegalArgumentException();
230            if (mimeCharset == null)
231                throw new IllegalArgumentException();
232    
233            Charset charset = toJavaCharset(mimeCharset, false, monitor);
234            return new StorageTextBody(new MultiReferenceStorage(storage), charset);
235        }
236    
237        /**
238         * Creates a {@link TextBody} that holds the content of the given string.
239         * <p>
240         * &quot;us-ascii&quot; is used to encode the characters of the string into
241         * a byte stream when calling
242         * {@link SingleBody#writeTo(java.io.OutputStream) writeTo(OutputStream)} on
243         * the returned object.
244         *
245         * @param text
246         *            text to create a message body from.
247         * @return a text body.
248         */
249        public TextBody textBody(String text) {
250            if (text == null)
251                throw new IllegalArgumentException();
252    
253            return new StringTextBody(text, CharsetUtil.DEFAULT_CHARSET);
254        }
255    
256        /**
257         * Creates a {@link TextBody} that holds the content of the given string.
258         * <p>
259         * The charset corresponding to the given MIME charset name is used to
260         * encode the characters of the string into a byte stream when calling
261         * {@link SingleBody#writeTo(java.io.OutputStream) writeTo(OutputStream)} on
262         * the returned object. If the MIME charset has no corresponding Java
263         * charset or the Java charset cannot be used for encoding then
264         * &quot;us-ascii&quot; is used instead.
265         *
266         * @param text
267         *            text to create a message body from.
268         * @param mimeCharset
269         *            name of a MIME charset.
270         * @return a text body.
271         */
272        public TextBody textBody(String text, String mimeCharset) {
273            if (text == null)
274                throw new IllegalArgumentException();
275            if (mimeCharset == null)
276                throw new IllegalArgumentException();
277    
278            Charset charset = toJavaCharset(mimeCharset, true, monitor);
279            return new StringTextBody(text, charset);
280        }
281    
282        private static Charset toJavaCharset(
283                final String mimeCharset,
284                boolean forEncoding,
285                final DecodeMonitor monitor) {
286            Charset charset = CharsetUtil.lookup(mimeCharset);
287            if (charset == null) {
288                if (monitor.isListening()) {
289                    monitor.warn(
290                            "MIME charset '" + mimeCharset + "' has no "
291                            + "corresponding Java charset", "Using "
292                            + FALLBACK_CHARSET + " instead.");
293                }
294                return FALLBACK_CHARSET;
295            }
296            return charset;
297        }
298    
299    }