/**************************************************************** * 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.dom.Body; import org.apache.james.mime4j.dom.Disposable; import org.apache.james.mime4j.dom.Entity; import org.apache.james.mime4j.dom.Header; import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.Multipart; import org.apache.james.mime4j.dom.TextBody; import org.apache.james.mime4j.dom.field.ContentDispositionField; import org.apache.james.mime4j.dom.field.ContentTransferEncodingField; import org.apache.james.mime4j.dom.field.ContentTypeField; import org.apache.james.mime4j.dom.field.Field; import org.apache.james.mime4j.dom.field.FieldName; /** * MIME entity. An entity has a header and a body (see RFC 2045). */ public abstract class AbstractEntity implements Entity { private Header header = null; private Body body = null; private Entity parent = null; /** * Creates a new Entity. Typically invoked implicitly by a * subclass constructor. */ protected AbstractEntity() { } /** * Gets the parent entity of this entity. * Returns null if this is the root entity. * * @return the parent or null. */ public Entity getParent() { return parent; } /** * Sets the parent entity of this entity. * * @param parent the parent entity or null 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 null 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 * "message/rfc822". A Header 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 * Header 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 parameters = Collections.singletonMap("boundary", newUniqueBoundary()); 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 * Header 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 parameters) { String mimeType = "multipart/" + multipart.getSubType(); if (!parameters.containsKey("boundary")) { parameters = new HashMap(parameters); parameters.put("boundary", newUniqueBoundary()); } setBody(multipart, mimeType, parameters); } /** * Sets the specified TextBody as body of this entity and the * content type to "text/plain". A Header is * created if this entity does not already have one. * * @param textBody * the TextBody to set as body. * @see org.apache.james.mime4j.message.BodyFactory#textBody(String) */ public void setText(TextBody textBody) { setText(textBody, "plain"); } /** * Sets the specified TextBody as body of this entity. Also * sets the content type according to the specified sub-type. A * Header is created if this entity does not already have * one. * * @param textBody * the TextBody to set as body. * @param subtype * the text subtype (e.g. "plain", "html" or * "xml"). * @see org.apache.james.mime4j.message.BodyFactory#textBody(String) */ public void setText(TextBody textBody, String subtype) { String mimeType = "text/" + subtype; Map 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 Header is created if this entity does not already * have one. * * @param body * the body. * @param mimeType * the MIME media type of the specified body * ("type/subtype"). */ 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 Header is created if this entity does not already * have one. * * @param body * the body. * @param mimeType * the MIME media type of the specified body * ("type/subtype"). * @param parameters * additional parameters for the Content-Type header field. */ public void setBody(Body body, String mimeType, Map parameters) { setBody(body); Header header = obtainHeader(); header.setField(newContentType(mimeType, parameters)); } /** * Determines the MIME type of this Entity. The MIME type * is derived by looking at the parent's Content-Type field if no * Content-Type field is set for this Entity. * * @return the MIME type. */ public String getMimeType() { ContentTypeField child = getContentTypeField(); ContentTypeField parent = getParent() != null ? (ContentTypeField) getParent().getHeader(). getField(FieldName.CONTENT_TYPE) : null; return calcMimeType(child, parent); } private ContentTypeField getContentTypeField() { return (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE); } /** * Determines the MIME character set encoding of this Entity. * * @return the MIME character set encoding. */ public String getCharset() { return calcCharset((ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE)); } /** * Determines the transfer encoding of this Entity. * * @return the transfer encoding. */ public String getContentTransferEncoding() { ContentTransferEncodingField f = (ContentTransferEncodingField) getHeader().getField(FieldName.CONTENT_TRANSFER_ENCODING); return calcTransferEncoding(f); } /** * Sets the transfer encoding of this Entity to the specified * value. * * @param contentTransferEncoding * transfer encoding to use. */ public void setContentTransferEncoding(String contentTransferEncoding) { Header header = obtainHeader(); header.setField(newContentTransferEncoding(contentTransferEncoding)); } /** * Return the disposition type of the content disposition of this * Entity. * * @return the disposition type or null 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 Entity to the * specified disposition type. No filename, size or date parameters * are included in the content disposition. * * @param dispositionType * disposition type value (usually inline or * attachment). */ public void setContentDisposition(String dispositionType) { Header header = obtainHeader(); header.setField(newContentDisposition(dispositionType, null, -1, null, null, null)); } /** * Sets the content disposition of this Entity to the * specified disposition type and filename. No size or date parameters are * included in the content disposition. * * @param dispositionType * disposition type value (usually inline or * attachment). * @param filename * filename parameter value or null if the * parameter should not be included. */ public void setContentDisposition(String dispositionType, String filename) { Header header = obtainHeader(); header.setField(newContentDisposition(dispositionType, filename, -1, null, null, null)); } /** * Sets the content disposition of this Entity to the * specified values. No date parameters are included in the content * disposition. * * @param dispositionType * disposition type value (usually inline or * attachment). * @param filename * filename parameter value or null if the * parameter should not be included. * @param size * size parameter value or -1 if the parameter * should not be included. */ public void setContentDisposition(String dispositionType, String filename, long size) { Header header = obtainHeader(); header.setField(newContentDisposition(dispositionType, filename, size, null, null, null)); } /** * Sets the content disposition of this Entity to the * specified values. * * @param dispositionType * disposition type value (usually inline or * attachment). * @param filename * filename parameter value or null if the * parameter should not be included. * @param size * size parameter value or -1 if the parameter * should not be included. * @param creationDate * creation-date parameter value or null if the * parameter should not be included. * @param modificationDate * modification-date parameter value or null if * the parameter should not be included. * @param readDate * read-date parameter value or null 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(newContentDisposition(dispositionType, filename, size, creationDate, modificationDate, readDate)); } /** * Returns the filename parameter of the content disposition of this * Entity. * * @return the filename parameter of the content disposition or * null 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 * Entity to the specified value. If this entity does not * have a content disposition header field a new one with disposition type * attachment is created. * * @param filename * filename parameter value or null 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(newContentDisposition( ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT, filename, -1, null, null, null)); } } else { String dispositionType = field.getDispositionType(); Map parameters = new HashMap(field .getParameters()); if (filename == null) { parameters.remove(ContentDispositionField.PARAM_FILENAME); } else { parameters .put(ContentDispositionField.PARAM_FILENAME, filename); } header.setField(newContentDisposition(dispositionType, parameters)); } } /** * Determines if the MIME type of this Entity matches the * given one. MIME types are case-insensitive. * * @param type the MIME type to match against. * @return true on match, false otherwise. */ public boolean isMimeType(String type) { return getMimeType().equalsIgnoreCase(type); } /** * Determines if the MIME type of this Entity is * multipart/*. Since multipart-entities must have * a boundary parameter in the Content-Type field this * method returns false if no boundary exists. * * @return true on match, false otherwise. */ public boolean isMultipart() { ContentTypeField f = getContentTypeField(); 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.dom.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 null. * * @return the header of this entity; never null. */ Header obtainHeader() { if (header == null) { header = new HeaderImpl(); } return header; } /** * Obtains the header field with the specified name. * * @param * concrete field type. * @param fieldName * name of the field to retrieve. * @return the header field or null if this entity has no * header or the header contains no such field. */ F obtainField(String fieldName) { Header header = getHeader(); if (header == null) return null; @SuppressWarnings("unchecked") F field = (F) header.getField(fieldName); return field; } protected abstract String newUniqueBoundary(); protected abstract ContentDispositionField newContentDisposition( String dispositionType, String filename, long size, Date creationDate, Date modificationDate, Date readDate); protected abstract ContentDispositionField newContentDisposition( String dispositionType, Map parameters); protected abstract ContentTypeField newContentType(String mimeType, Map parameters); protected abstract ContentTransferEncodingField newContentTransferEncoding( String contentTransferEncoding); protected abstract String calcMimeType(ContentTypeField child, ContentTypeField parent); protected abstract String calcTransferEncoding(ContentTransferEncodingField f); protected abstract String calcCharset(ContentTypeField contentType); }