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.util.Collections;
023    import java.util.Date;
024    import java.util.HashMap;
025    import java.util.Map;
026    
027    import org.apache.james.mime4j.field.ContentDispositionField;
028    import org.apache.james.mime4j.field.ContentTransferEncodingField;
029    import org.apache.james.mime4j.field.ContentTypeField;
030    import org.apache.james.mime4j.field.FieldName;
031    import org.apache.james.mime4j.field.Fields;
032    import org.apache.james.mime4j.parser.Field;
033    import org.apache.james.mime4j.util.MimeUtil;
034    
035    /**
036     * MIME entity. An entity has a header and a body (see RFC 2045).
037     */
038    public abstract class Entity implements Disposable {
039        private Header header = null;
040        private Body body = null;
041        private Entity parent = null;
042    
043        /**
044         * Creates a new <code>Entity</code>. Typically invoked implicitly by a
045         * subclass constructor.
046         */
047        protected Entity() {
048        }
049    
050        /**
051         * Creates a new <code>Entity</code> from the specified
052         * <code>Entity</code>. The <code>Entity</code> instance is initialized
053         * with copies of header and body of the specified <code>Entity</code>.
054         * The parent entity of the new entity is <code>null</code>.
055         * 
056         * @param other
057         *            entity to copy.
058         * @throws UnsupportedOperationException
059         *             if <code>other</code> contains a {@link SingleBody} that
060         *             does not support the {@link SingleBody#copy() copy()}
061         *             operation.
062         * @throws IllegalArgumentException
063         *             if <code>other</code> contains a <code>Body</code> that
064         *             is neither a {@link Message}, {@link Multipart} or
065         *             {@link SingleBody}.
066         */
067        protected Entity(Entity other) {
068            if (other.header != null) {
069                header = new Header(other.header);
070            }
071    
072            if (other.body != null) {
073                Body bodyCopy = BodyCopier.copy(other.body);
074                setBody(bodyCopy);
075            }
076        }
077    
078        /**
079         * Gets the parent entity of this entity.
080         * Returns <code>null</code> if this is the root entity.
081         * 
082         * @return the parent or <code>null</code>.
083         */
084        public Entity getParent() {
085            return parent;
086        }
087        
088        /**
089         * Sets the parent entity of this entity.
090         * 
091         * @param parent the parent entity or <code>null</code> if
092         *        this will be the root entity.
093         */
094        public void setParent(Entity parent) {
095            this.parent = parent;
096        }
097        
098        /**
099         * 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    }