/**
* 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.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.jdo.JDOHelper;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.james.mime4j.io.LineNumberInputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.codehaus.jettison.AbstractXMLStreamWriter;
import org.codehaus.jettison.badgerfish.BadgerFishXMLStreamWriter;
import org.codehaus.jettison.mapped.MappedNamespaceConvention;
import org.codehaus.jettison.mapped.MappedXMLStreamWriter;
import org.codehaus.staxmate.SMOutputFactory;
import org.codehaus.staxmate.out.SMOutputDocument;
import org.codehaus.staxmate.out.SMOutputElement;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.nio.file.TPath;
import serpro.mailarchiver.domain.metaarchive.UnstructuredField.ContentTransferEncodingField;
import serpro.mailarchiver.util.BodyVisitor;
import serpro.mailarchiver.util.JSONMappingConvention;
import serpro.mailarchiver.util.Logger;
import serpro.mailarchiver.util.UserAppConfig;
@PersistenceCapable
public class Message
extends Entity
{
@NotPersistent
private static final Logger log = Logger.getLocalLogger();
@NotPersistent private static final String TAG_SEEN = "seen";
@NotPersistent private static final String TAG_UNSEEN = "unseen";
@NotPersistent private static final String TAG_ANSWERED = "answered";
@NotPersistent private static final String TAG_UNANSWERED = "unanswered";
@NotPersistent private static final String TAG_FLAGGED = "flagged";
@NotPersistent private static final String TAG_UNFLAGGED = "unflagged";
@NotPersistent private static final String TAG_IMPORTANCE_HIGH = "importance_high";
@NotPersistent private static final String TAG_IMPORTANCE_NORMAL = "importance_normal";
@NotPersistent private static final String TAG_IMPORTANCE_LOW = "importance_low";
@NotPersistent private static final String TAG_FORWARDED = "forwarded";
@NotPersistent private static final String TAG_DELETED = "deleted";
@NotPersistent private static final String TAG_DRAFT = "draft";
@NotPersistent private static final String TAG_SPAM = "spam";
//**** P E R S I S T E N T ****
private Folder folder;
private Integer folderIdx;
private LinkedHashSet tags = new LinkedHashSet();
private Long queryCandidatesSet;
//*****************************
public Message(LineNumberInputStream.Entity lnisEntity) {
super(lnisEntity);
}
public Message() {}
@Override
public void jdoPreDelete() {
super.jdoPreDelete();
setFolder(null);
}
@Override
public Message getRootMessage() {
return this;
}
public final Folder getFolder() {
return folder;
}
public final void setFolder(Folder folder) {
if(this.folder != null) {
this.folder.internal_removeMessage(this);
}
this.folder = folder;
if(this.folder != null) {
this.folder.internal_addMessage(this);
}
}
public final int getFolderIdx() {
return (folderIdx != null) ? folderIdx
: (getFolder() != null) ? getFolder().indexOf(this)
: -1;
}
public final void setQueryCandidatesSet(Long queryCandidatesSet) {
this.queryCandidatesSet = queryCandidatesSet;
}
//--------------------------------------------------------------------------
public final Set getTags() {
return Collections.unmodifiableSet(tags);
}
public final boolean addTag(String value) {
if(value != null) {
String tag = value.trim().toLowerCase();
if(tag.equals(TAG_SEEN)) {
tags.remove(TAG_UNSEEN);
}
else if(tag.equals(TAG_UNSEEN)) {
tags.remove(TAG_SEEN);
}
else if(tag.equals(TAG_ANSWERED)) {
tags.remove(TAG_UNANSWERED);
}
else if(tag.equals(TAG_UNANSWERED)) {
tags.remove(TAG_ANSWERED);
}
else if(tag.equals(TAG_FLAGGED)) {
tags.remove(TAG_UNFLAGGED);
}
else if(tag.equals(TAG_UNFLAGGED)) {
tags.remove(TAG_FLAGGED);
}
else if(tag.equals(TAG_IMPORTANCE_HIGH)) {
tags.remove(TAG_IMPORTANCE_NORMAL);
tags.remove(TAG_IMPORTANCE_LOW);
}
else if(tag.equals(TAG_IMPORTANCE_NORMAL)) {
tags.remove(TAG_IMPORTANCE_HIGH);
tags.remove(TAG_IMPORTANCE_LOW);
}
else if(tag.equals(TAG_IMPORTANCE_LOW)) {
tags.remove(TAG_IMPORTANCE_HIGH);
tags.remove(TAG_IMPORTANCE_NORMAL);
}
return tags.add(tag);
}
return false;
}
public final boolean removeTag(String value) {
if(value != null) {
String tag = value.trim().toLowerCase();
if(tag.equals(TAG_SEEN)) {
tags.add(TAG_UNSEEN);
}
else if(tag.equals(TAG_UNSEEN)) {
tags.add(TAG_SEEN);
}
else if(tag.equals(TAG_ANSWERED)) {
tags.add(TAG_UNANSWERED);
}
else if(tag.equals(TAG_UNANSWERED)) {
tags.add(TAG_ANSWERED);
}
else if(tag.equals(TAG_FLAGGED)) {
tags.add(TAG_UNFLAGGED);
}
else if(tag.equals(TAG_UNFLAGGED)) {
tags.add(TAG_FLAGGED);
}
else if(tag.equals(TAG_IMPORTANCE_HIGH)) {
if(!(tags.contains(TAG_IMPORTANCE_NORMAL) || tags.contains(TAG_IMPORTANCE_LOW))) {
tags.add(TAG_IMPORTANCE_NORMAL);
}
}
else if(tag.equals(TAG_IMPORTANCE_NORMAL)) {
if(!(tags.contains(TAG_IMPORTANCE_HIGH) || tags.contains(TAG_IMPORTANCE_LOW))) {
tags.add(TAG_IMPORTANCE_HIGH);
}
}
else if(tag.equals(TAG_IMPORTANCE_LOW)) {
if(!(tags.contains(TAG_IMPORTANCE_HIGH) || tags.contains(TAG_IMPORTANCE_NORMAL))) {
tags.add(TAG_IMPORTANCE_NORMAL);
}
}
return tags.remove(tag);
}
return false;
}
//--------------------------------------------------------------------------
@Override
String toString(String pad) {
return String.format(
"Message%n"
+ "%1$sjdoState: %2$s%n"
+ "%1$soid: %3$s%n"
+ "%1$shash: %4$x%n"
+ "%1$sstartLine: %5$d%n"
+ "%1$sseparatorLine: %6$d%n"
+ "%1$sendLine: %7$d%n"
+ "%1$ssize: %8$d"
, pad
, JDOHelper.getObjectState(this)
, getOid()
, hashCode()
, getStartLine()
, getSeparatorLine()
, getEndLine()
, getSize());
}
@Override
int dumpPath(StringBuilder sb, boolean quit) {
if(quit) {
sb.append(toString(" "));
}
else {
sb.append(toString("| ")).append("\n")
.append("|\n")
.append("+---");
}
return 1;
}
//--------------------------------------------------------------------------
public final Path getRelativePath() {
if(getFolder() == null) {
return Paths.get("");
}
else {
return getFolder().getRelativePath().resolve(getOid() + ".eml");
}
}
public final Path getQualifiedPath() {
if(getFolder() == null) {
return Paths.get(getOid() + ".eml");
}
else {
return getFolder().getQualifiedPath().resolve(getOid() + ".eml");
}
}
@Autowired
@NotPersistent
private UserAppConfig userAppConfig;
public final Path getAbsolutePath() {
return userAppConfig.SERVER.getArchiveDir()
.resolve("mail")
.resolve(getQualifiedPath());
}
//--------------------------------------------------------------------------
public void setSeen(boolean seen) {
if(seen) {
addTag(TAG_SEEN);
}
else {
removeTag(TAG_SEEN);
}
}
public boolean isSeen() {
return tags.contains(TAG_SEEN);
}
public void setUnseen(boolean unseen) {
if(unseen) {
addTag(TAG_UNSEEN);
}
else {
removeTag(TAG_UNSEEN);
}
}
public boolean isUnseen() {
return tags.contains(TAG_UNSEEN);
}
//--------------------------------------------------------------------------
public void setAnswered(boolean answered) {
if(answered) {
addTag(TAG_ANSWERED);
}
else {
removeTag(TAG_ANSWERED);
}
}
public boolean isAnswered() {
return tags.contains(TAG_ANSWERED);
}
public void setUnanswered(boolean unanswered) {
if(unanswered) {
addTag(TAG_UNANSWERED);
}
else {
removeTag(TAG_UNANSWERED);
}
}
public boolean isUnanswered() {
return tags.contains(TAG_UNANSWERED);
}
//--------------------------------------------------------------------------
public void setFlagged(boolean flagged) {
if(flagged) {
addTag(TAG_FLAGGED);
}
else {
removeTag(TAG_FLAGGED);
}
}
public boolean isFlagged() {
return tags.contains(TAG_FLAGGED);
}
public void setUnflagged(boolean unflagged) {
if(unflagged) {
addTag(TAG_UNFLAGGED);
}
else {
removeTag(TAG_UNFLAGGED);
}
}
public boolean isUnflagged() {
return tags.contains(TAG_UNFLAGGED);
}
//--------------------------------------------------------------------------
public void setImportanceHigh(boolean importanceHigh) {
if(importanceHigh) {
addTag(TAG_IMPORTANCE_HIGH);
}
else {
removeTag(TAG_IMPORTANCE_HIGH);
}
}
public boolean isImportanceHigh() {
return tags.contains(TAG_IMPORTANCE_HIGH);
}
public void setImportanceNormal(boolean importanceNormal) {
if(importanceNormal) {
addTag(TAG_IMPORTANCE_NORMAL);
}
else {
removeTag(TAG_IMPORTANCE_NORMAL);
}
}
public boolean isImportanceNormal() {
return tags.contains(TAG_IMPORTANCE_NORMAL);
}
public void setImportanceLow(boolean importanceLow) {
if(importanceLow) {
addTag(TAG_IMPORTANCE_LOW);
}
else {
removeTag(TAG_IMPORTANCE_LOW);
}
}
public boolean isImportanceLow() {
return tags.contains(TAG_IMPORTANCE_LOW);
}
//--------------------------------------------------------------------------
public void setForwarded(boolean forwarded) {
if(forwarded) {
addTag(TAG_FORWARDED);
}
else {
removeTag(TAG_FORWARDED);
}
}
public boolean isForwarded() {
return tags.contains(TAG_FORWARDED);
}
//--------------------------------------------------------------------------
public void setDeleted(boolean deleted) {
if(deleted) {
addTag(TAG_DELETED);
}
else {
removeTag(TAG_DELETED);
}
}
public boolean isDeleted() {
return tags.contains(TAG_DELETED);
}
//--------------------------------------------------------------------------
public void setDraft(boolean draft) {
if(draft) {
addTag(TAG_DRAFT);
}
else {
removeTag(TAG_DRAFT);
}
}
public boolean isDraft() {
return tags.contains(TAG_DRAFT);
}
//--------------------------------------------------------------------------
public void setSpam(boolean spam) {
if(spam) {
addTag(TAG_SPAM);
}
else {
removeTag(TAG_SPAM);
}
}
public boolean isSpam() {
return tags.contains(TAG_SPAM);
}
//--------------------------------------------------------------------------
public final String getTagsJSONString() {
return getTagsJSONString(JSONMappingConvention.Mapped_Attributes);
}
public final String getTagsJSONString(JSONMappingConvention convention) {
StringWriter strWriter = new StringWriter();
try {
AbstractXMLStreamWriter xmlWriter = null;
if(convention.useMapped()) {
MappedNamespaceConvention con = new MappedNamespaceConvention(JSONMappingConvention.mappedConfiguration);
xmlWriter = new MappedXMLStreamWriter(con, strWriter);
}
else if(convention.useBadgerFish()) {
xmlWriter = new BadgerFishXMLStreamWriter(strWriter);
}
SMOutputDocument doc = SMOutputFactory.createOutputDocument(xmlWriter);
for(String tag : getTags()) {
SMOutputElement tagElement = doc.addElement("tag");
if(convention.useAttributes()) {
tagElement.addAttribute("value", tag);
}
else if(convention.useNestedElements()) {
tagElement.addElement("value").addCharacters(tag);
}
}
doc.closeRootAndWriter();
}
catch(XMLStreamException ex) {
log.error(ex, ex.getMessage());
return null;
}
return strWriter.toString();
}
//--------------------------------------------------------------------------
public String getAttachmentsJSONString() {
return getAttachmentsJSONString(JSONMappingConvention.Mapped_Attributes);
}
public String getAttachmentsJSONString(final JSONMappingConvention convention) {
StringWriter strWriter = new StringWriter();
try {
AbstractXMLStreamWriter xmlWriter = null;
if(convention.useMapped()) {
MappedNamespaceConvention con = new MappedNamespaceConvention(JSONMappingConvention.mappedConfiguration);
xmlWriter = new MappedXMLStreamWriter(con, strWriter);
}
else if(convention.useBadgerFish()) {
xmlWriter = new BadgerFishXMLStreamWriter(strWriter);
}
final SMOutputDocument doc = SMOutputFactory.createOutputDocument(xmlWriter);
visitBodies(new BodyVisitor() {
@Override
public void visitAttachmentBody(SingleBody singleBody) {
addAttachment(singleBody);
}
@Override
public void visitInlineBody(SingleBody singleBody) {
addAttachment(singleBody);
}
private void addAttachment(SingleBody singleBody) {
ContentTypeField contentTypeField = singleBody.getEntity().getContentTypeField();
if(contentTypeField != null) {
if(contentTypeField.isMessageDeliveryStatusMimeType()) {
return;
}
}
try {
SMOutputElement attachmentElement = doc.addElement("attachment");
String id = singleBody.getOid();
String name = singleBody.getFileName();
long size = singleBody.getSize();
String mediaType = "";
String subType = "";
if(contentTypeField != null) {
mediaType = contentTypeField.getMediaType();
subType = contentTypeField.getSubType();
}
String encoding = "";
ContentTransferEncodingField contentTransferEncodingField = singleBody.getEntity().getContentTransferEncoding();
if(contentTransferEncodingField != null) {
encoding = contentTransferEncodingField.getEncoding();
}
if(convention.useAttributes()) {
attachmentElement.addAttribute("id", id);
attachmentElement.addAttribute("name", name);
attachmentElement.addAttribute(null, "size", size);
attachmentElement.addAttribute("mediaType", mediaType);
attachmentElement.addAttribute("subType", subType);
attachmentElement.addAttribute("encoding", encoding);
}
else if(convention.useNestedElements()) {
attachmentElement.addElement("id").addCharacters(id);
attachmentElement.addElement("name").addCharacters(name);
attachmentElement.addElement("size").addValue(size);
attachmentElement.addElement("mediaType").addCharacters(mediaType);
attachmentElement.addElement("subType").addCharacters(subType);
attachmentElement.addElement("encoding").addCharacters(encoding);
}
}
catch (XMLStreamException ex) {
log.error(ex);
}
}
});
doc.closeRootAndWriter();
}
catch(Exception ex) {
log.error(ex, ex.getMessage());
return null;
}
return strWriter.toString();
}
//--------------------------------------------------------------------------
public String getPartsZipFileName(String extension) {
return "parts_" + getOid() + "." + extension;
}
public Path getPartsZipPath(String extension) {
return userAppConfig.SERVER.getArchiveDir().resolve("temp").resolve(getPartsZipFileName(extension));
}
public void publishPartsZip(String extension) throws IOException {
if(Files.exists(getPartsZipPath(extension))) {
//already published
return;
}
log.info("Publishing: %s", getPartsZipFileName(extension));
final TPath zip = new TPath(getPartsZipPath(extension));
final MutableObject ioex = new MutableObject();
visitBodies(new BodyVisitor() {
@Override
public void visitAttachmentBody(SingleBody singleBody) {
addAttachment(singleBody);
}
@Override
public void visitInlineBody(SingleBody singleBody) {
addAttachment(singleBody);
}
private void addAttachment(SingleBody singleBody) {
try {
InputStream is = singleBody.getDecoderInputStream();
TFile.cp(is, zip.resolve(singleBody.getFileName()).toFile());
is.close();
}
catch(IOException ex) {
ioex.setValue(ex);
quit();
}
}
});
TFile.umount(zip.toFile(), true);
if(ioex.getValue() != null) {
throw ioex.getValue();
}
}
}