/**
* MailArchiver is an application that provides services for storing and managing e-mail messages through a Web Services SOAP interface.
* Copyright (C) 2012 Marcio Andre Scholl Levien and Fernando Alberto Reuter Wendt and Jose Ronaldo Nogueira Fonseca Junior
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/******************************************************************************\
*
* This product was developed by
*
* SERVIÇO FEDERAL DE PROCESSAMENTO DE DADOS (SERPRO),
*
* a government company established under Brazilian law (5.615/70),
* at Department of Development of Porto Alegre.
*
\******************************************************************************/
package serpro.mailarchiver.domain.metaarchive;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import org.apache.james.mime4j.dom.datetime.DateTime;
import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
import org.apache.james.mime4j.io.LineNumberInputStream;
import com.google.common.collect.LinkedListMultimap;
import serpro.mailarchiver.domain.BaseObject;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentEncodingField;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentLanguageField;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentLengthField;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentLocationField;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentTransferEncodingField;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.MessageIdSequenceField;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.MimeVersionField;
import serpro.mailarchiver.util.BodyVisitor;
import serpro.mailarchiver.util.Logger;
@PersistenceCapable
public abstract class Entity
extends BaseObject
{
@NotPersistent
private static final Logger log = Logger.getLocalLogger();
//**** P E R S I S T E N T ****
private String oid;
private int startLine;
private int separatorLine;
private int endLine;
private int size;
private Body body;
private ArrayList fields = new ArrayList();
//*****************************
@NotPersistent
private final LineNumberInputStream.Entity lnisEntity;
public Entity(LineNumberInputStream.Entity lnisEntity) {
this.lnisEntity = lnisEntity;
}
public Entity() {
lnisEntity = null;
}
public abstract Message getRootMessage();
public void sync() {
startLine = lnisEntity.getStartLine();
separatorLine = lnisEntity.getSeparatorLine();
endLine = lnisEntity.getEndLine();
if(body instanceof SingleBody) {
SingleBody singleBody = (SingleBody)body;
singleBody.setOffset(lnisEntity.getBodyOffset());
singleBody.setLength(lnisEntity.getBodyLength());
}
else if(body instanceof MessageBody) {
MessageBody messageBody = (MessageBody)body;
EmbeddedMessage embeddedMessage = messageBody.getEmbeddedMessage();
embeddedMessage.sync();
}
else if(body instanceof Multipart) {
Multipart multipart = (Multipart)body;
for(BodyPart bodyPart : multipart.getBodyParts()) {
bodyPart.sync();
}
}
}
public final String getOid() {
return oid;
}
public final void setOid(String oid) {
this.oid = oid;
}
public final int getStartLine() {
return startLine;
}
public final void setStartLine(int startLine) {
this.startLine = startLine;
}
public final int getSeparatorLine() {
return separatorLine;
}
public final void setSeparatorLine(int separatorLine) {
this.separatorLine = separatorLine;
}
public final int getEndLine() {
return endLine;
}
public final void setEndLine(int endLine) {
this.endLine = endLine;
}
public final int getSize() {
return size;
}
public final void setSize(int size) {
this.size = size;
}
//--------------------------------------------------------------------------
public final Body getBody() {
return body;
}
public final void setBody(Body body) {
if(this.body != null) {
this.body.internal_setEntity(null);
}
this.body = body;
if(this.body != null) {
this.body.internal_setEntity(this);
}
}
final void internal_setBody(Body body) {
this.body = body;
}
//--------------------------------------------------------------------------
public final List extends Field> getFields() {
return Collections.unmodifiableList(fields);
}
public final int indexOf(Field field) {
return fields.indexOf(field);
}
public final void addField(Field field) {
if(field != null) {
field.setEntity(this);
}
}
final void internal_addField(Field field) {
fields.add(field);
dirtyCache = true;
}
public final void removeField(Field field) {
if((field != null) && (field.getEntity() == this)) {
field.setEntity(null);
}
}
final void internal_removeField(Field field) {
fields.remove(field);
dirtyCache = true;
}
//--------------------------------------------------------------------------
public final void incSize(int size) {
setSize(getSize() + size);
if(this instanceof BodyEntity) {
((BodyEntity)this).getComposite().getEntity().incSize(size);
}
}
//--------------------------------------------------------------------------
public final void visitBodies(BodyVisitor visitor) {
visitBodies(visitor, "multipart/*", "message/*", "text/html", "text/plain", "text/*");
}
public final void visitBodies(BodyVisitor visitor, String... alternativeMimeTypesPriority) {
List patterns = new ArrayList();
for(String s : alternativeMimeTypesPriority) {
patterns.add(Pattern.compile("^" + s.trim().replaceAll("\\*", "(.+)") + "$", Pattern.CASE_INSENSITIVE));
}
visitBodies(visitor, patterns.toArray(new Pattern[patterns.size()]));
}
public final void visitBodies(BodyVisitor visitor, Pattern... alternativeMimeTypesPriority) {
if(visitor.isQuitted()) {
return;
}
if(body instanceof SingleBody) {
SingleBody singleBody = (SingleBody)body;
visitor.visitSingleBody(singleBody);
}
else if(body instanceof MessageBody) {
MessageBody messageBody = (MessageBody)body;
Entity embeddedMessage = messageBody.getEmbeddedMessage();
if(embeddedMessage != null) {
embeddedMessage.visitBodies(visitor, alternativeMimeTypesPriority);
}
}
else if(body instanceof Multipart) {
Multipart multipart = (Multipart)body;
ContentTypeField contentTypeField = getContentTypeField();
if((contentTypeField != null) && (contentTypeField.isMultipartAlternativeMimeType())) {
// multipart-alternative
boolean found = false;
priority:
for(Pattern mimeType : alternativeMimeTypesPriority) {
for(Entity bodyPart : multipart.getBodyParts()) {
ContentTypeField bodyPartContentTypeField = bodyPart.getContentTypeField();
if((bodyPartContentTypeField != null) && (mimeType.matcher(bodyPartContentTypeField.getMimeType()).matches())) {
found = true;
bodyPart.visitBodies(visitor, alternativeMimeTypesPriority);
break priority;
}
}
}
if(!found) {
//choose the plainest (first) alternative. see RFC2046
Entity bodyPart = multipart.getBodyParts().get(0);
bodyPart.visitBodies(visitor, alternativeMimeTypesPriority);
}
}
else {
for(Entity bodyPart : multipart.getBodyParts()) {
bodyPart.visitBodies(visitor, alternativeMimeTypesPriority);
if(visitor.isQuitted()) {
break;
}
}
}
}
}
//--------------------------------------------------------------------------
public final String dumpPath() {
StringBuilder sb = new StringBuilder();
dumpPath(sb, true);
return sb.toString();
}
abstract int dumpPath(StringBuilder sb, boolean quit);
public final String dumpTree() {
StringBuilder sb = new StringBuilder();
dumpTree(sb, "");
return sb.toString();
}
final void dumpTree(StringBuilder sb, String pad) {
int c = fields.size();
sb.append(toString(pad + (((c > 0) || (body != null)) ? "| " : " ")));
for(int i = 0; i < c; i++) {
sb.append("\n")
.append(pad).append("|\n")
.append(pad).append("+---");
if((i < (c - 1)) || (body != null)) {
fields.get(i).dumpTree(sb, pad + "| ");
}
else {
fields.get(i).dumpTree(sb, pad + " ");
}
}
if(body != null) {
sb.append("\n")
.append(pad).append("|\n")
.append(pad).append("+---");
body.dumpTree(sb, pad + " ");
}
}
@Override
public final String toString() {
return toString(" ");
}
abstract String toString(String pad);
//
@NotPersistent
private LinkedListMultimap readCache = LinkedListMultimap.create();
@NotPersistent
private boolean dirtyCache = true;
public synchronized final Field getField(String name) {
if(dirtyCache) {
updateCache();
}
List fs = readCache.get(name);
if(fs.isEmpty()) {
return null;
}
return fs.get(0);
}
public synchronized final List extends Field> getFields(String name) {
if(dirtyCache) {
updateCache();
}
return Collections.unmodifiableList(readCache.get(name));
}
private void updateCache() {
readCache.clear();
for(Field f : getFields()) {
readCache.put(f.getName().toLowerCase(), f);
}
dirtyCache = false;
}
//----
public final ContentTypeField getContentTypeField() {
return (ContentTypeField) getField("content-type");
}
public final ContentDispositionField getContentDispositionField() {
return (ContentDispositionField) getField("content-disposition");
}
//----
public final DateTimeField getDateField() {
return (DateTimeField) getField("date");
}
public final List getResentDateFields() {
return (List) getFields("resent-date");
}
//----
public final MailboxField getSenderField() {
return (MailboxField) getField("sender");
}
public final List getResentSenderFields() {
return (List) getFields("resent-sender");
}
//----
public final MailboxListField getFromField() {
return (MailboxListField) getField("from");
}
public final MailboxListField getDispositionNotificationToField() {
return (MailboxListField) getField("disposition-notification-to");
}
public final List getResentFromFields() {
return (List) getFields("resent-from");
}
//----
public final AddressListField getToField() {
return (AddressListField) getField("to");
}
public final AddressListField getCcField() {
return (AddressListField) getField("cc");
}
public final AddressListField getBccField() {
return (AddressListField) getField("bcc");
}
public final AddressListField getReplyToField() {
return (AddressListField) getField("reply-to");
}
public final List getResentToFields() {
return (List) getFields("resent-to");
}
public final List getResentCcFields() {
return (List) getFields("resent-cc");
}
public final List getResentBccFields() {
return (List) getFields("resent-bcc");
}
//----
public final UnstructuredField getSubjectField() {
return (UnstructuredField) getField("subject");
}
public final UnstructuredField getContentDescriptionField() {
return (UnstructuredField) getField("content-description");
}
public final UnstructuredField getMessageIdField() {
return (UnstructuredField) getField("message-id");
}
public final UnstructuredField getContentIdField() {
return (UnstructuredField) getField("content-id");
}
public final UnstructuredField getContentMD5Field() {
return (UnstructuredField) getField("content-md5");
}
public final List getResentMsgIdFields() {
return (List) getFields("resent-msg-id");
}
public final List getCommentsFields() {
return (List) getFields("comments");
}
public final List getKeywordsFields() {
return (List) getFields("keywords");
}
//---
public final MessageIdSequenceField getReferencesField() {
UnstructuredField field = (UnstructuredField)getField("references");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final MessageIdSequenceField getInReplyToField() {
UnstructuredField field = (UnstructuredField)getField("in-reply-to");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final MimeVersionField getMimeVersionField() {
UnstructuredField field = (UnstructuredField)getField("mime-version");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final ContentLengthField getContentLengthField() {
UnstructuredField field = (UnstructuredField)getField("content-length");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final ContentLanguageField getContentLanguageField() {
UnstructuredField field = (UnstructuredField)getField("content-language");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final ContentLocationField getContentLocationField() {
UnstructuredField field = (UnstructuredField)getField("content-location");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final ContentEncodingField getContentEncodingField() {
UnstructuredField field = (UnstructuredField)getField("content-encoding");
if(field != null) {
return field.getDecorator();
}
return null;
}
public final ContentTransferEncodingField getContentTransferEncoding() {
UnstructuredField field = (UnstructuredField)getField("content-transfer-encoding");
if(field != null) {
return field.getDecorator();
}
return null;
}
//---
public final Date getLastReceivedDate() {
List receivedFields = (List) getFields("received");
if(receivedFields.size() > 0) {
String receivedFieldText = receivedFields.get(0).getText();
int k = receivedFieldText.lastIndexOf(";");
if(k != -1) {
StringReader reader = new StringReader(receivedFieldText.substring(k + 1));
DateTimeParser dtp = new DateTimeParser(reader);
try {
DateTime dt = dtp.parseAll();
if(dt != null) {
return dt.getDate();
}
}
catch(Exception ex) {
log.error(ex, "received date: %s", receivedFieldText);
}
}
else {
log.error("received date: %s", receivedFieldText);
}
}
return null;
}
/* RFC 5322 - Internet Message Format
+----------------+--------+------------+----------------------------+
| Field | Min | Max number | Notes |
| | number | | |
+----------------+--------+------------+----------------------------+
| trace | 0 | unlimited | Block prepended - see |
| | | | 3.6.7 |
| resent-date | 0* | unlimited* | One per block, required if |
| | | | other resent fields are |
| | | | present - see 3.6.6 |
| resent-from | 0 | unlimited* | One per block - see 3.6.6 |
| resent-sender | 0* | unlimited* | One per block, MUST occur |
| | | | with multi-address |
| | | | resent-from - see 3.6.6 |
| resent-to | 0 | unlimited* | One per block - see 3.6.6 |
| resent-cc | 0 | unlimited* | One per block - see 3.6.6 |
| resent-bcc | 0 | unlimited* | One per block - see 3.6.6 |
| resent-msg-id | 0 | unlimited* | One per block - see 3.6.6 |
| orig-date | 1 | 1 | |
| from | 1 | 1 | See sender and 3.6.2 |
| sender | 0* | 1 | MUST occur with |
| | | | multi-address from - see |
| | | | 3.6.2 |
| reply-to | 0 | 1 | |
| to | 0 | 1 | |
| cc | 0 | 1 | |
| bcc | 0 | 1 | |
| message-id | 0* | 1 | SHOULD be present - see |
| | | | 3.6.4 |
| in-reply-to | 0* | 1 | SHOULD occur in some |
| | | | replies - see 3.6.4 |
| references | 0* | 1 | SHOULD occur in some |
| | | | replies - see 3.6.4 |
| subject | 0 | 1 | |
| comments | 0 | unlimited | |
| keywords | 0 | unlimited | |
| optional-field | 0 | unlimited | |
+----------------+--------+------------+----------------------------+
*/
//
}