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.message;
21  
22  import java.io.IOException;
23  import java.io.OutputStream;
24  
25  import org.apache.james.mime4j.codec.CodecUtil;
26  import org.apache.james.mime4j.field.ContentTypeField;
27  import org.apache.james.mime4j.field.FieldName;
28  import org.apache.james.mime4j.parser.Field;
29  import org.apache.james.mime4j.util.ByteArrayBuffer;
30  import org.apache.james.mime4j.util.ByteSequence;
31  import org.apache.james.mime4j.util.ContentUtil;
32  import org.apache.james.mime4j.util.MimeUtil;
33  
34  /**
35   * Writes a message (or a part of a message) to an output stream.
36   * <p>
37   * This class cannot be instantiated; instead the static instance
38   * {@link #DEFAULT} implements the default strategy for writing a message.
39   * <p>
40   * This class may be subclassed to implement custom strategies for writing
41   * messages.
42   */
43  public class MessageWriter {
44  
45      private static final byte[] CRLF = { '\r', '\n' };
46      private static final byte[] DASHES = { '-', '-' };
47  
48      /**
49       * The default message writer.
50       */
51      public static final MessageWriter DEFAULT = new MessageWriter();
52  
53      /**
54       * Protected constructor prevents direct instantiation.
55       */
56      protected MessageWriter() {
57      }
58  
59      /**
60       * Write the specified <code>Body</code> to the specified
61       * <code>OutputStream</code>.
62       * 
63       * @param body
64       *            the <code>Body</code> to write.
65       * @param out
66       *            the OutputStream to write to.
67       * @throws IOException
68       *             if an I/O error occurs.
69       */
70      public void writeBody(Body body, OutputStream out) throws IOException {
71          if (body instanceof Message) {
72              writeEntity((Message) body, out);
73          } else if (body instanceof Multipart) {
74              writeMultipart((Multipart) body, out);
75          } else if (body instanceof SingleBody) {
76              ((SingleBody) body).writeTo(out);
77          } else
78              throw new IllegalArgumentException("Unsupported body class");
79      }
80  
81      /**
82       * Write the specified <code>Entity</code> to the specified
83       * <code>OutputStream</code>.
84       * 
85       * @param entity
86       *            the <code>Entity</code> to write.
87       * @param out
88       *            the OutputStream to write to.
89       * @throws IOException
90       *             if an I/O error occurs.
91       */
92      public void writeEntity(Entity entity, OutputStream out) throws IOException {
93          final Header header = entity.getHeader();
94          if (header == null)
95              throw new IllegalArgumentException("Missing header");
96  
97          writeHeader(header, out);
98  
99          final Body body = entity.getBody();
100         if (body == null)
101             throw new IllegalArgumentException("Missing body");
102 
103         boolean binaryBody = body instanceof BinaryBody;
104         OutputStream encOut = encodeStream(out, entity
105                 .getContentTransferEncoding(), binaryBody);
106 
107         writeBody(body, encOut);
108 
109         // close if wrapped (base64 or quoted-printable)
110         if (encOut != out)
111             encOut.close();
112     }
113 
114     /**
115      * Write the specified <code>Multipart</code> to the specified
116      * <code>OutputStream</code>.
117      * 
118      * @param multipart
119      *            the <code>Multipart</code> to write.
120      * @param out
121      *            the OutputStream to write to.
122      * @throws IOException
123      *             if an I/O error occurs.
124      */
125     public void writeMultipart(Multipart multipart, OutputStream out)
126             throws IOException {
127         ContentTypeField contentType = getContentType(multipart);
128 
129         ByteSequence boundary = getBoundary(contentType);
130 
131         writeBytes(multipart.getPreambleRaw(), out);
132         out.write(CRLF);
133 
134         for (BodyPart bodyPart : multipart.getBodyParts()) {
135             out.write(DASHES);
136             writeBytes(boundary, out);
137             out.write(CRLF);
138 
139             writeEntity(bodyPart, out);
140             out.write(CRLF);
141         }
142 
143         out.write(DASHES);
144         writeBytes(boundary, out);
145         out.write(DASHES);
146         out.write(CRLF);
147 
148         writeBytes(multipart.getEpilogueRaw(), out);
149     }
150 
151     /**
152      * Write the specified <code>Header</code> to the specified
153      * <code>OutputStream</code>.
154      * 
155      * @param header
156      *            the <code>Header</code> to write.
157      * @param out
158      *            the OutputStream to write to.
159      * @throws IOException
160      *             if an I/O error occurs.
161      */
162     public void writeHeader(Header header, OutputStream out) throws IOException {
163         for (Field field : header) {
164             writeBytes(field.getRaw(), out);
165             out.write(CRLF);
166         }
167 
168         out.write(CRLF);
169     }
170 
171     protected OutputStream encodeStream(OutputStream out, String encoding,
172             boolean binaryBody) throws IOException {
173         if (MimeUtil.isBase64Encoding(encoding)) {
174             return CodecUtil.wrapBase64(out);
175         } else if (MimeUtil.isQuotedPrintableEncoded(encoding)) {
176             return CodecUtil.wrapQuotedPrintable(out, binaryBody);
177         } else {
178             return out;
179         }
180     }
181 
182     private ContentTypeField getContentType(Multipart multipart) {
183         Entity parent = multipart.getParent();
184         if (parent == null)
185             throw new IllegalArgumentException(
186                     "Missing parent entity in multipart");
187 
188         Header header = parent.getHeader();
189         if (header == null)
190             throw new IllegalArgumentException(
191                     "Missing header in parent entity");
192 
193         ContentTypeField contentType = (ContentTypeField) header
194                 .getField(FieldName.CONTENT_TYPE);
195         if (contentType == null)
196             throw new IllegalArgumentException(
197                     "Content-Type field not specified");
198 
199         return contentType;
200     }
201 
202     private ByteSequence getBoundary(ContentTypeField contentType) {
203         String boundary = contentType.getBoundary();
204         if (boundary == null)
205             throw new IllegalArgumentException(
206                     "Multipart boundary not specified");
207 
208         return ContentUtil.encode(boundary);
209     }
210 
211     private void writeBytes(ByteSequence byteSequence, OutputStream out)
212             throws IOException {
213         if (byteSequence instanceof ByteArrayBuffer) {
214             ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence;
215             out.write(bab.buffer(), 0, bab.length());
216         } else {
217             out.write(byteSequence.toByteArray());
218         }
219     }
220 
221 }