blob: 56a12d46c27a6700937be6b800e5dc0d29d9f333 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.mime4j.message;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.james.mime4j.field.ContentDispositionField;
import org.apache.james.mime4j.field.ContentTransferEncodingField;
import org.apache.james.mime4j.field.ContentTypeField;
import org.apache.james.mime4j.field.FieldName;
import org.apache.james.mime4j.field.Fields;
import org.apache.james.mime4j.parser.Field;
import org.apache.james.mime4j.util.MimeUtil;
/**
* MIME entity. An entity has a header and a body (see RFC 2045).
*/
public abstract class Entity implements Disposable {
private Header header = null;
private Body body = null;
private Entity parent = null;
/**
* Creates a new <code>Entity</code>. Typically invoked implicitly by a
* subclass constructor.
*/
protected Entity() {
}
/**
* Creates a new <code>Entity</code> from the specified
* <code>Entity</code>. The <code>Entity</code> instance is initialized
* with copies of header and body of the specified <code>Entity</code>.
* The parent entity of the new entity is <code>null</code>.
*
* @param other
* entity to copy.
* @throws UnsupportedOperationException
* if <code>other</code> contains a {@link SingleBody} that
* does not support the {@link SingleBody#copy() copy()}
* operation.
* @throws IllegalArgumentException
* if <code>other</code> contains a <code>Body</code> that
* is neither a {@link Message}, {@link Multipart} or
* {@link SingleBody}.
*/
protected Entity(Entity other) {
if (other.header != null) {
header = new Header(other.header);
}
if (other.body != null) {
Body bodyCopy = BodyCopier.copy(other.body);
setBody(bodyCopy);
}
}
/**
* Gets the parent entity of this entity.
* Returns <code>null</code> if this is the root entity.
*
* @return the parent or <code>null</code>.
*/
public Entity getParent() {
return parent;
}
/**
* Sets the parent entity of this entity.
*
* @param parent the parent entity or <code>null</code> if
* this will be the root entity.
*/
public void setParent(Entity parent) {
this.parent = parent;
}
/**
* Gets the entity header.
*
* @return the header.
*/
public Header getHeader() {
return header;
}
/**
* Sets the entity header.
*
* @param header the header.
*/
public void setHeader(Header header) {
this.header = header;
}
/**
* Gets the body of this entity.
*
* @return the body,
*/
public Body getBody() {
return body;
}
/**
* Sets the body of this entity.
*
* @param body the body.
* @throws IllegalStateException if the body has already been set.
*/
public void setBody(Body body) {
if (this.body != null)
throw new IllegalStateException("body already set");
this.body = body;
body.setParent(this);
}
/**
* Removes and returns the body of this entity. The removed body may be
* attached to another entity. If it is no longer needed it should be
* {@link Disposable#dispose() disposed} of.
*
* @return the removed body or <code>null</code> if no body was set.
*/
public Body removeBody() {
if (body == null)
return null;
Body body = this.body;
this.body = null;
body.setParent(null);
return body;
}
/**
* Sets the specified message as body of this entity and the content type to
* &quot;message/rfc822&quot;. A <code>Header</code> is created if this
* entity does not already have one.
*
* @param message
* the message to set as body.
*/
public void setMessage(Message message) {
setBody(message, "message/rfc822", null);
}
/**
* Sets the specified multipart as body of this entity. Also sets the
* content type accordingly and creates a message boundary string. A
* <code>Header</code> is created if this entity does not already have
* one.
*
* @param multipart
* the multipart to set as body.
*/
public void setMultipart(Multipart multipart) {
String mimeType = "multipart/" + multipart.getSubType();
Map<String, String> parameters = Collections.singletonMap("boundary",
MimeUtil.createUniqueBoundary());
setBody(multipart, mimeType, parameters);
}
/**
* Sets the specified multipart as body of this entity. Also sets the
* content type accordingly and creates a message boundary string. A
* <code>Header</code> is created if this entity does not already have
* one.
*
* @param multipart
* the multipart to set as body.
* @param parameters
* additional parameters for the Content-Type header field.
*/
public void setMultipart(Multipart multipart, Map<String, String> parameters) {
String mimeType = "multipart/" + multipart.getSubType();
if (!parameters.containsKey("boundary")) {
parameters = new HashMap<String, String>(parameters);
parameters.put("boundary", MimeUtil.createUniqueBoundary());
}
setBody(multipart, mimeType, parameters);
}
/**
* Sets the specified <code>TextBody</code> as body of this entity and the
* content type to &quot;text/plain&quot;. A <code>Header</code> is
* created if this entity does not already have one.
*
* @param textBody
* the <code>TextBody</code> to set as body.
* @see BodyFactory#textBody(String)
*/
public void setText(TextBody textBody) {
setText(textBody, "plain");
}
/**
* Sets the specified <code>TextBody</code> as body of this entity. Also
* sets the content type according to the specified sub-type. A
* <code>Header</code> is created if this entity does not already have
* one.
*
* @param textBody
* the <code>TextBody</code> to set as body.
* @param subtype
* the text subtype (e.g. &quot;plain&quot;, &quot;html&quot; or
* &quot;xml&quot;).
* @see BodyFactory#textBody(String)
*/
public void setText(TextBody textBody, String subtype) {
String mimeType = "text/" + subtype;
Map<String, String> parameters = null;
String mimeCharset = textBody.getMimeCharset();
if (mimeCharset != null && !mimeCharset.equalsIgnoreCase("us-ascii")) {
parameters = Collections.singletonMap("charset", mimeCharset);
}
setBody(textBody, mimeType, parameters);
}
/**
* Sets the body of this entity and sets the content-type to the specified
* value. A <code>Header</code> is created if this entity does not already
* have one.
*
* @param body
* the body.
* @param mimeType
* the MIME media type of the specified body
* (&quot;type/subtype&quot;).
*/
public void setBody(Body body, String mimeType) {
setBody(body, mimeType, null);
}
/**
* Sets the body of this entity and sets the content-type to the specified
* value. A <code>Header</code> is created if this entity does not already
* have one.
*
* @param body
* the body.
* @param mimeType
* the MIME media type of the specified body
* (&quot;type/subtype&quot;).
* @param parameters
* additional parameters for the Content-Type header field.
*/
public void setBody(Body body, String mimeType,
Map<String, String> parameters) {
setBody(body);
Header header = obtainHeader();
header.setField(Fields.contentType(mimeType, parameters));
}
/**
* Determines the MIME type of this <code>Entity</code>. The MIME type
* is derived by looking at the parent's Content-Type field if no
* Content-Type field is set for this <code>Entity</code>.
*
* @return the MIME type.
*/
public String getMimeType() {
ContentTypeField child =
(ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE);
ContentTypeField parent = getParent() != null
? (ContentTypeField) getParent().getHeader().
getField(FieldName.CONTENT_TYPE)
: null;
return ContentTypeField.getMimeType(child, parent);
}
/**
* Determines the MIME character set encoding of this <code>Entity</code>.
*
* @return the MIME character set encoding.
*/
public String getCharset() {
return ContentTypeField.getCharset(
(ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE));
}
/**
* Determines the transfer encoding of this <code>Entity</code>.
*
* @return the transfer encoding.
*/
public String getContentTransferEncoding() {
ContentTransferEncodingField f = (ContentTransferEncodingField)
getHeader().getField(FieldName.CONTENT_TRANSFER_ENCODING);
return ContentTransferEncodingField.getEncoding(f);
}
/**
* Sets the transfer encoding of this <code>Entity</code> to the specified
* value.
*
* @param contentTransferEncoding
* transfer encoding to use.
*/
public void setContentTransferEncoding(String contentTransferEncoding) {
Header header = obtainHeader();
header.setField(Fields.contentTransferEncoding(contentTransferEncoding));
}
/**
* Return the disposition type of the content disposition of this
* <code>Entity</code>.
*
* @return the disposition type or <code>null</code> if no disposition
* type has been set.
*/
public String getDispositionType() {
ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION);
if (field == null)
return null;
return field.getDispositionType();
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified disposition type. No filename, size or date parameters
* are included in the content disposition.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
*/
public void setContentDisposition(String dispositionType) {
Header header = obtainHeader();
header.setField(Fields.contentDisposition(dispositionType, null, -1,
null, null, null));
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified disposition type and filename. No size or date parameters are
* included in the content disposition.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
*/
public void setContentDisposition(String dispositionType, String filename) {
Header header = obtainHeader();
header.setField(Fields.contentDisposition(dispositionType, filename,
-1, null, null, null));
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified values. No date parameters are included in the content
* disposition.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
*/
public void setContentDisposition(String dispositionType, String filename,
long size) {
Header header = obtainHeader();
header.setField(Fields.contentDisposition(dispositionType, filename,
size, null, null, null));
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified values.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
* @param creationDate
* creation-date parameter value or <code>null</code> if the
* parameter should not be included.
* @param modificationDate
* modification-date parameter value or <code>null</code> if
* the parameter should not be included.
* @param readDate
* read-date parameter value or <code>null</code> if the
* parameter should not be included.
*/
public void setContentDisposition(String dispositionType, String filename,
long size, Date creationDate, Date modificationDate, Date readDate) {
Header header = obtainHeader();
header.setField(Fields.contentDisposition(dispositionType, filename,
size, creationDate, modificationDate, readDate));
}
/**
* Returns the filename parameter of the content disposition of this
* <code>Entity</code>.
*
* @return the filename parameter of the content disposition or
* <code>null</code> if the filename has not been set.
*/
public String getFilename() {
ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION);
if (field == null)
return null;
return field.getFilename();
}
/**
* Sets the filename parameter of the content disposition of this
* <code>Entity</code> to the specified value. If this entity does not
* have a content disposition header field a new one with disposition type
* <code>attachment</code> is created.
*
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should be removed.
*/
public void setFilename(String filename) {
Header header = obtainHeader();
ContentDispositionField field = (ContentDispositionField) header
.getField(FieldName.CONTENT_DISPOSITION);
if (field == null) {
if (filename != null) {
header.setField(Fields.contentDisposition(
ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT,
filename, -1, null, null, null));
}
} else {
String dispositionType = field.getDispositionType();
Map<String, String> parameters = new HashMap<String, String>(field
.getParameters());
if (filename == null) {
parameters.remove(ContentDispositionField.PARAM_FILENAME);
} else {
parameters
.put(ContentDispositionField.PARAM_FILENAME, filename);
}
header.setField(Fields.contentDisposition(dispositionType,
parameters));
}
}
/**
* Determines if the MIME type of this <code>Entity</code> matches the
* given one. MIME types are case-insensitive.
*
* @param type the MIME type to match against.
* @return <code>true</code> on match, <code>false</code> otherwise.
*/
public boolean isMimeType(String type) {
return getMimeType().equalsIgnoreCase(type);
}
/**
* Determines if the MIME type of this <code>Entity</code> is
* <code>multipart/*</code>. Since multipart-entities must have
* a boundary parameter in the <code>Content-Type</code> field this
* method returns <code>false</code> if no boundary exists.
*
* @return <code>true</code> on match, <code>false</code> otherwise.
*/
public boolean isMultipart() {
ContentTypeField f =
(ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE);
return f != null && f.getBoundary() != null
&& getMimeType().startsWith(ContentTypeField.TYPE_MULTIPART_PREFIX);
}
/**
* Disposes of the body of this entity. Note that the dispose call does not
* get forwarded to the parent entity of this Entity.
*
* Subclasses that need to free resources should override this method and
* invoke super.dispose().
*
* @see org.apache.james.mime4j.message.Disposable#dispose()
*/
public void dispose() {
if (body != null) {
body.dispose();
}
}
/**
* Obtains the header of this entity. Creates and sets a new header if this
* entity's header is currently <code>null</code>.
*
* @return the header of this entity; never <code>null</code>.
*/
Header obtainHeader() {
if (header == null) {
header = new Header();
}
return header;
}
/**
* Obtains the header field with the specified name.
*
* @param <F>
* concrete field type.
* @param fieldName
* name of the field to retrieve.
* @return the header field or <code>null</code> if this entity has no
* header or the header contains no such field.
*/
<F extends Field> F obtainField(String fieldName) {
Header header = getHeader();
if (header == null)
return null;
@SuppressWarnings("unchecked")
F field = (F) header.getField(fieldName);
return field;
}
}