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.IOException;
023import java.io.InputStream;
024import java.nio.charset.Charset;
025
026import org.apache.james.mime4j.codec.DecodeMonitor;
027import org.apache.james.mime4j.dom.BinaryBody;
028import org.apache.james.mime4j.dom.Disposable;
029import org.apache.james.mime4j.dom.SingleBody;
030import org.apache.james.mime4j.dom.TextBody;
031import org.apache.james.mime4j.message.BodyFactory;
032import org.apache.james.mime4j.util.CharsetUtil;
033
034/**
035 * Factory for creating message bodies.
036 */
037public 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}