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