/** * 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(); } } }