/**
* Copyright (c) 2011-2014 Optimax Software Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Optimax Software, ElasticInbox, nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.elasticinbox.core.model;
import java.net.URI;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.elasticinbox.core.blob.BlobURI;
/**
* Representation of MIME message containing message headers, labels, markers,
* bodies (HTML and plain) and attachments ({@link MimePart}).
* <p>
* Attachments are indexed by MIME part number, where part number is a string of
* integers delimited by period which index into a body part list as per the
* IMAP4 RFC3501 specification.
*
* @author Rustam Aliyev
* @see {@link MimePart}
* @see <a href="http://tools.ietf.org/html/rfc3501#section-6.4.5">RFC3501</a>
*/
@JsonInclude(Include.NON_NULL)
public class Message
{
private URI location;
private Long size;
// Message headers
private AddressList from;
private AddressList to;
private AddressList cc;
private AddressList bcc;
private String subject;
private Date date;
private String messageId;
private AddressList replyTo;
// Message content
private String plainBody;
private String htmlBody;
private HashSet<Integer> labels = new HashSet<Integer>();
private EnumSet<Marker> markers = EnumSet.noneOf(Marker.class);
/**
* List of MIME non-body parts (attachments) indexed by part number.
*/
private Map<String, MimePart> parts = new HashMap<String, MimePart>(1);
/**
* Reverse index for message Content-id, where key is content-id and value
* is part number.
*/
@JsonIgnore
private Map<String, String> partsByContentId = new HashMap<String, String>(1);
/**
* Additional headers which are not stored in metadata, but used for filtering.
*/
@JsonIgnore
private Map<String, String> minorHeaders = new HashMap<String, String>(1);
/**
* Returns the value of the "From" header fields. If this
* header field is absent, the "Sender" header field is used.
*
* @return
*/
public AddressList getFrom() {
return from;
}
public void setFrom(AddressList from) {
this.from = from;
}
/**
* Returns the value of the "To" header fields.
*
* @return
*/
public AddressList getTo() {
return to;
}
public void setTo(AddressList to) {
this.to = to;
}
/**
* Returns the value of the "CC" header fields.
*
* @return
*/
public AddressList getCc() {
return cc;
}
public void setCc(AddressList cc) {
this.cc = cc;
}
/**
* Returns the value of the "BCC" header fields.
*
* @return
*/
public AddressList getBcc() {
return bcc;
}
public void setBcc(AddressList bcc) {
this.bcc = bcc;
}
/**
* Returns the value of the "Subject" header field.
*
* @return
*/
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
/**
* Returns the value of the "Date" header field.
*
* @return
*/
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
/**
* Returns the value of the "Message-ID" header field.
*
* @return
*/
public String getMessageId() {
return messageId;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
/**
* Returns the value of the "Reply-To" header field.
*
* @return
*/
public AddressList getReplyTo() {
return replyTo;
}
public void setReplyTo(AddressList replyTo) {
this.replyTo = replyTo;
}
/**
* Return the size of the content of this message in bytes.
*
* @return
*/
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
/**
* Returns blob URI containing raw message content
*
* @return
*/
public URI getLocation() {
return location;
}
public void setLocation(URI uri)
{
// validate URI, should throw exception if invalid
new BlobURI().fromURI(uri);
this.location = uri;
}
/**
* Returns plain text message body if available.
*
* @return
*/
public String getPlainBody() {
return plainBody;
}
public void setPlainBody(String plainBody) {
this.plainBody = plainBody;
}
/**
* Returns HTML message body if available.
*
* @return
*/
public String getHtmlBody() {
return htmlBody;
}
public void setHtmlBody(String htmlBody) {
this.htmlBody = htmlBody;
}
/**
* Returns information about all message attachments (MIME parts)
*
* @return
*/
public Map<String, MimePart> getParts() {
return this.parts.isEmpty() ? null : parts;
}
public void setParts(Map<String, MimePart> attachments) {
this.parts = attachments;
}
@JsonIgnore
public void addPart(String partId, MimePart part) {
part.setPartId(partId);
this.parts.put(partId, part);
// add to content-id index if any
if (part.getContentId() != null) {
this.partsByContentId.put(part.getContentId(), partId);
}
}
/**
* Returns binary content for the given MIME part ID.
*
* @param partId
*/
@JsonIgnore
public MimePart getPart(String partId)
{
if(!this.parts.containsKey(partId))
throw new IllegalArgumentException(
"Message does not contain part with ID " + partId);
return this.parts.get(partId);
}
/**
* Returns binary content for the given MIME Content-ID.
*
* @param contentId
* @return
*/
@JsonIgnore
public MimePart getPartByContentId(String contentId)
{
if (!this.partsByContentId.containsKey(contentId))
throw new IllegalArgumentException(
"Message does not contain part with Content-ID " + contentId);
return getPart(this.partsByContentId.get(contentId));
}
@JsonIgnore
public boolean hasParts() {
return !this.parts.isEmpty();
}
// @JsonIgnore
// public boolean hasAttachment() {
// //TODO: check if any content-disposition is attachment, ignore inline
// return false;
// }
/**
* Returns all labels assigned to the message.
*
* @return
*/
public Set<Integer> getLabels() {
return this.labels;
}
public void setLabels(Set<Integer> labels) {
this.labels = new HashSet<Integer>(labels);
}
@JsonIgnore
public void addLabel(Integer labelId) {
this.labels.add(labelId);
}
/**
* Returns all markers assigned to the message.
*
* @return
*/
public Set<Marker> getMarkers() {
return this.markers;
}
public void setMarkers(Set<Marker> markers) {
this.markers = EnumSet.copyOf(markers);
}
@JsonIgnore
public void addMarker(Marker marker) {
this.markers.add(marker);
}
/**
* Get message header by name
*
* @param headerName
* @return
*/
@JsonIgnore
public String getMinorHeader(String headerName) {
return this.minorHeaders.get(headerName.toLowerCase());
}
/**
* Add message header (will update if exists).
*
* @param headerName
* Name of the message header
* @param headerValue
* Value of the message header
*/
@JsonIgnore
public void addMinorHeader(String headerName, String headerValue)
{
if (headerValue != null) {
this.minorHeaders.put(headerName.toLowerCase(), headerValue);
}
}
/**
* Get Message parameters as {@link LabelCounters} object
*
* @return
*/
@JsonIgnore
public LabelCounters getLabelCounters()
{
LabelCounters lc = new LabelCounters();
lc.setTotalMessages(1L);
if (this.size != null) {
lc.setTotalBytes(this.size);
}
if (this.markers == null || !this.markers.contains(Marker.SEEN)) {
lc.setUnreadMessages(1L);
}
return lc;
}
}