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.util.Collections;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  import org.apache.james.mime4j.field.ContentDispositionField;
28  import org.apache.james.mime4j.field.ContentTransferEncodingField;
29  import org.apache.james.mime4j.field.ContentTypeField;
30  import org.apache.james.mime4j.field.FieldName;
31  import org.apache.james.mime4j.field.Fields;
32  import org.apache.james.mime4j.parser.Field;
33  import org.apache.james.mime4j.util.MimeUtil;
34  
35  /**
36   * MIME entity. An entity has a header and a body (see RFC 2045).
37   */
38  public abstract class Entity implements Disposable {
39      private Header header = null;
40      private Body body = null;
41      private Entity parent = null;
42  
43      /**
44       * Creates a new <code>Entity</code>. Typically invoked implicitly by a
45       * subclass constructor.
46       */
47      protected Entity() {
48      }
49  
50      /**
51       * Creates a new <code>Entity</code> from the specified
52       * <code>Entity</code>. The <code>Entity</code> instance is initialized
53       * with copies of header and body of the specified <code>Entity</code>.
54       * The parent entity of the new entity is <code>null</code>.
55       * 
56       * @param other
57       *            entity to copy.
58       * @throws UnsupportedOperationException
59       *             if <code>other</code> contains a {@link SingleBody} that
60       *             does not support the {@link SingleBody#copy() copy()}
61       *             operation.
62       * @throws IllegalArgumentException
63       *             if <code>other</code> contains a <code>Body</code> that
64       *             is neither a {@link Message}, {@link Multipart} or
65       *             {@link SingleBody}.
66       */
67      protected Entity(Entity other) {
68          if (other.header != null) {
69              header = new Header(other.header);
70          }
71  
72          if (other.body != null) {
73              Body bodyCopy = BodyCopier.copy(other.body);
74              setBody(bodyCopy);
75          }
76      }
77  
78      /**
79       * Gets the parent entity of this entity.
80       * Returns <code>null</code> if this is the root entity.
81       * 
82       * @return the parent or <code>null</code>.
83       */
84      public Entity getParent() {
85          return parent;
86      }
87      
88      /**
89       * Sets the parent entity of this entity.
90       * 
91       * @param parent the parent entity or <code>null</code> if
92       *        this will be the root entity.
93       */
94      public void setParent(Entity parent) {
95          this.parent = parent;
96      }
97      
98      /**
99       * Gets the entity header.
100      * 
101      * @return the header.
102      */
103     public Header getHeader() {
104         return header;
105     }
106     
107     /**
108      * Sets the entity header.
109      * 
110      * @param header the header.
111      */
112     public void setHeader(Header header) {
113         this.header = header;
114     }
115     
116     /**
117      * Gets the body of this entity.
118      * 
119      * @return the body,
120      */
121     public Body getBody() {
122         return body;
123     }
124 
125     /**
126      * Sets the body of this entity.
127      * 
128      * @param body the body.
129      * @throws IllegalStateException if the body has already been set.
130      */
131     public void setBody(Body body) {
132         if (this.body != null)
133             throw new IllegalStateException("body already set");
134 
135         this.body = body;
136         body.setParent(this);
137     }
138 
139     /**
140      * Removes and returns the body of this entity. The removed body may be
141      * attached to another entity. If it is no longer needed it should be
142      * {@link Disposable#dispose() disposed} of.
143      * 
144      * @return the removed body or <code>null</code> if no body was set.
145      */
146     public Body removeBody() {
147         if (body == null)
148             return null;
149 
150         Body body = this.body;
151         this.body = null;
152         body.setParent(null);
153 
154         return body;
155     }
156 
157     /**
158      * Sets the specified message as body of this entity and the content type to
159      * &quot;message/rfc822&quot;. A <code>Header</code> is created if this
160      * entity does not already have one.
161      * 
162      * @param message
163      *            the message to set as body.
164      */
165     public void setMessage(Message message) {
166         setBody(message, "message/rfc822", null);
167     }
168 
169     /**
170      * Sets the specified multipart as body of this entity. Also sets the
171      * content type accordingly and creates a message boundary string. A
172      * <code>Header</code> is created if this entity does not already have
173      * one.
174      * 
175      * @param multipart
176      *            the multipart to set as body.
177      */
178     public void setMultipart(Multipart multipart) {
179         String mimeType = "multipart/" + multipart.getSubType();
180         Map<String, String> parameters = Collections.singletonMap("boundary",
181                 MimeUtil.createUniqueBoundary());
182 
183         setBody(multipart, mimeType, parameters);
184     }
185 
186     /**
187      * Sets the specified multipart as body of this entity. Also sets the
188      * content type accordingly and creates a message boundary string. A
189      * <code>Header</code> is created if this entity does not already have
190      * one.
191      * 
192      * @param multipart
193      *            the multipart to set as body.
194      * @param parameters
195      *            additional parameters for the Content-Type header field.
196      */
197     public void setMultipart(Multipart multipart, Map<String, String> parameters) {
198         String mimeType = "multipart/" + multipart.getSubType();
199         if (!parameters.containsKey("boundary")) {
200             parameters = new HashMap<String, String>(parameters);
201             parameters.put("boundary", MimeUtil.createUniqueBoundary());
202         }
203 
204         setBody(multipart, mimeType, parameters);
205     }
206 
207     /**
208      * Sets the specified <code>TextBody</code> as body of this entity and the
209      * content type to &quot;text/plain&quot;. A <code>Header</code> is
210      * created if this entity does not already have one.
211      * 
212      * @param textBody
213      *            the <code>TextBody</code> to set as body.
214      * @see BodyFactory#textBody(String)
215      */
216     public void setText(TextBody textBody) {
217         setText(textBody, "plain");
218     }
219 
220     /**
221      * Sets the specified <code>TextBody</code> as body of this entity. Also
222      * sets the content type according to the specified sub-type. A
223      * <code>Header</code> is created if this entity does not already have
224      * one.
225      * 
226      * @param textBody
227      *            the <code>TextBody</code> to set as body.
228      * @param subtype
229      *            the text subtype (e.g. &quot;plain&quot;, &quot;html&quot; or
230      *            &quot;xml&quot;).
231      * @see BodyFactory#textBody(String)
232      */
233     public void setText(TextBody textBody, String subtype) {
234         String mimeType = "text/" + subtype;
235 
236         Map<String, String> parameters = null;
237         String mimeCharset = textBody.getMimeCharset();
238         if (mimeCharset != null && !mimeCharset.equalsIgnoreCase("us-ascii")) {
239             parameters = Collections.singletonMap("charset", mimeCharset);
240         }
241 
242         setBody(textBody, mimeType, parameters);
243     }
244 
245     /**
246      * Sets the body of this entity and sets the content-type to the specified
247      * value. A <code>Header</code> is created if this entity does not already
248      * have one.
249      * 
250      * @param body
251      *            the body.
252      * @param mimeType
253      *            the MIME media type of the specified body
254      *            (&quot;type/subtype&quot;).
255      */
256     public void setBody(Body body, String mimeType) {
257         setBody(body, mimeType, null);
258     }
259 
260     /**
261      * Sets the body of this entity and sets the content-type to the specified
262      * value. A <code>Header</code> is created if this entity does not already
263      * have one.
264      * 
265      * @param body
266      *            the body.
267      * @param mimeType
268      *            the MIME media type of the specified body
269      *            (&quot;type/subtype&quot;).
270      * @param parameters
271      *            additional parameters for the Content-Type header field.
272      */
273     public void setBody(Body body, String mimeType,
274             Map<String, String> parameters) {
275         setBody(body);
276 
277         Header header = obtainHeader();
278         header.setField(Fields.contentType(mimeType, parameters));
279     }
280 
281     /**
282      * Determines the MIME type of this <code>Entity</code>. The MIME type
283      * is derived by looking at the parent's Content-Type field if no
284      * Content-Type field is set for this <code>Entity</code>.
285      * 
286      * @return the MIME type.
287      */
288     public String getMimeType() {
289         ContentTypeField child = 
290             (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE);
291         ContentTypeField parent = getParent() != null 
292             ? (ContentTypeField) getParent().getHeader().
293                                                 getField(FieldName.CONTENT_TYPE)
294             : null;
295         
296         return ContentTypeField.getMimeType(child, parent);
297     }
298     
299     /**
300      * Determines the MIME character set encoding of this <code>Entity</code>.
301      * 
302      * @return the MIME character set encoding.
303      */
304     public String getCharset() {
305         return ContentTypeField.getCharset( 
306             (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE));
307     }
308     
309     /**
310      * Determines the transfer encoding of this <code>Entity</code>.
311      * 
312      * @return the transfer encoding.
313      */
314     public String getContentTransferEncoding() {
315         ContentTransferEncodingField f = (ContentTransferEncodingField) 
316                         getHeader().getField(FieldName.CONTENT_TRANSFER_ENCODING);
317         
318         return ContentTransferEncodingField.getEncoding(f);
319     }
320 
321     /**
322      * Sets the transfer encoding of this <code>Entity</code> to the specified
323      * value.
324      * 
325      * @param contentTransferEncoding
326      *            transfer encoding to use.
327      */
328     public void setContentTransferEncoding(String contentTransferEncoding) {
329         Header header = obtainHeader();
330         header.setField(Fields.contentTransferEncoding(contentTransferEncoding));
331     }
332 
333     /**
334      * Return the disposition type of the content disposition of this
335      * <code>Entity</code>.
336      * 
337      * @return the disposition type or <code>null</code> if no disposition
338      *         type has been set.
339      */
340     public String getDispositionType() {
341         ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION);
342         if (field == null)
343             return null;
344 
345         return field.getDispositionType();
346     }
347     
348     /**
349      * Sets the content disposition of this <code>Entity</code> to the
350      * specified disposition type. No filename, size or date parameters
351      * are included in the content disposition.
352      * 
353      * @param dispositionType
354      *            disposition type value (usually <code>inline</code> or
355      *            <code>attachment</code>).
356      */
357     public void setContentDisposition(String dispositionType) {
358         Header header = obtainHeader();
359         header.setField(Fields.contentDisposition(dispositionType, null, -1,
360                 null, null, null));
361     }
362 
363     /**
364      * Sets the content disposition of this <code>Entity</code> to the
365      * specified disposition type and filename. No size or date parameters are
366      * included in the content disposition.
367      * 
368      * @param dispositionType
369      *            disposition type value (usually <code>inline</code> or
370      *            <code>attachment</code>).
371      * @param filename
372      *            filename parameter value or <code>null</code> if the
373      *            parameter should not be included.
374      */
375     public void setContentDisposition(String dispositionType, String filename) {
376         Header header = obtainHeader();
377         header.setField(Fields.contentDisposition(dispositionType, filename,
378                 -1, null, null, null));
379     }
380 
381     /**
382      * Sets the content disposition of this <code>Entity</code> to the
383      * specified values. No date parameters are included in the content
384      * disposition.
385      * 
386      * @param dispositionType
387      *            disposition type value (usually <code>inline</code> or
388      *            <code>attachment</code>).
389      * @param filename
390      *            filename parameter value or <code>null</code> if the
391      *            parameter should not be included.
392      * @param size
393      *            size parameter value or <code>-1</code> if the parameter
394      *            should not be included.
395      */
396     public void setContentDisposition(String dispositionType, String filename,
397             long size) {
398         Header header = obtainHeader();
399         header.setField(Fields.contentDisposition(dispositionType, filename,
400                 size, null, null, null));
401     }
402 
403     /**
404      * Sets the content disposition of this <code>Entity</code> to the
405      * specified values.
406      * 
407      * @param dispositionType
408      *            disposition type value (usually <code>inline</code> or
409      *            <code>attachment</code>).
410      * @param filename
411      *            filename parameter value or <code>null</code> if the
412      *            parameter should not be included.
413      * @param size
414      *            size parameter value or <code>-1</code> if the parameter
415      *            should not be included.
416      * @param creationDate
417      *            creation-date parameter value or <code>null</code> if the
418      *            parameter should not be included.
419      * @param modificationDate
420      *            modification-date parameter value or <code>null</code> if
421      *            the parameter should not be included.
422      * @param readDate
423      *            read-date parameter value or <code>null</code> if the
424      *            parameter should not be included.
425      */
426     public void setContentDisposition(String dispositionType, String filename,
427             long size, Date creationDate, Date modificationDate, Date readDate) {
428         Header header = obtainHeader();
429         header.setField(Fields.contentDisposition(dispositionType, filename,
430                 size, creationDate, modificationDate, readDate));
431     }
432 
433     /**
434      * Returns the filename parameter of the content disposition of this
435      * <code>Entity</code>.
436      * 
437      * @return the filename parameter of the content disposition or
438      *         <code>null</code> if the filename has not been set.
439      */
440     public String getFilename() {
441         ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION);
442         if (field == null)
443             return null;
444 
445         return field.getFilename();
446     }
447     
448     /**
449      * Sets the filename parameter of the content disposition of this
450      * <code>Entity</code> to the specified value. If this entity does not
451      * have a content disposition header field a new one with disposition type
452      * <code>attachment</code> is created.
453      * 
454      * @param filename
455      *            filename parameter value or <code>null</code> if the
456      *            parameter should be removed.
457      */
458     public void setFilename(String filename) {
459         Header header = obtainHeader();
460         ContentDispositionField field = (ContentDispositionField) header
461                 .getField(FieldName.CONTENT_DISPOSITION);
462         if (field == null) {
463             if (filename != null) {
464                 header.setField(Fields.contentDisposition(
465                         ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT,
466                         filename, -1, null, null, null));
467             }
468         } else {
469             String dispositionType = field.getDispositionType();
470             Map<String, String> parameters = new HashMap<String, String>(field
471                     .getParameters());
472             if (filename == null) {
473                 parameters.remove(ContentDispositionField.PARAM_FILENAME);
474             } else {
475                 parameters
476                         .put(ContentDispositionField.PARAM_FILENAME, filename);
477             }
478             header.setField(Fields.contentDisposition(dispositionType,
479                     parameters));
480         }
481     }
482 
483     /**
484      * Determines if the MIME type of this <code>Entity</code> matches the
485      * given one. MIME types are case-insensitive.
486      * 
487      * @param type the MIME type to match against.
488      * @return <code>true</code> on match, <code>false</code> otherwise.
489      */
490     public boolean isMimeType(String type) {
491         return getMimeType().equalsIgnoreCase(type);
492     }
493     
494     /**
495      * Determines if the MIME type of this <code>Entity</code> is
496      * <code>multipart/*</code>. Since multipart-entities must have
497      * a boundary parameter in the <code>Content-Type</code> field this
498      * method returns <code>false</code> if no boundary exists.
499      * 
500      * @return <code>true</code> on match, <code>false</code> otherwise.
501      */
502     public boolean isMultipart() {
503         ContentTypeField f = 
504             (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE);
505         return f != null && f.getBoundary() != null 
506             && getMimeType().startsWith(ContentTypeField.TYPE_MULTIPART_PREFIX);
507     }
508 
509     /**
510      * Disposes of the body of this entity. Note that the dispose call does not
511      * get forwarded to the parent entity of this Entity.
512      * 
513      * Subclasses that need to free resources should override this method and
514      * invoke super.dispose().
515      * 
516      * @see org.apache.james.mime4j.message.Disposable#dispose()
517      */
518     public void dispose() {
519         if (body != null) {
520             body.dispose();
521         }
522     }
523 
524     /**
525      * Obtains the header of this entity. Creates and sets a new header if this
526      * entity's header is currently <code>null</code>.
527      * 
528      * @return the header of this entity; never <code>null</code>.
529      */
530     Header obtainHeader() {
531         if (header == null) {
532             header = new Header();
533         }
534         return header;
535     }
536 
537     /**
538      * Obtains the header field with the specified name.
539      * 
540      * @param <F>
541      *            concrete field type.
542      * @param fieldName
543      *            name of the field to retrieve.
544      * @return the header field or <code>null</code> if this entity has no
545      *         header or the header contains no such field.
546      */
547     <F extends Field> F obtainField(String fieldName) {
548         Header header = getHeader();
549         if (header == null)
550             return null;
551 
552         @SuppressWarnings("unchecked")
553         F field = (F) header.getField(fieldName);
554         return field;
555     }
556 
557 }