001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Alexandre Russel 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.platform.mail.action; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.UnsupportedEncodingException; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import javax.mail.Address; 031import javax.mail.Message; 032import javax.mail.MessagingException; 033import javax.mail.Part; 034import javax.mail.internet.MimeMessage; 035import javax.mail.internet.MimeMultipart; 036import javax.mail.internet.MimeUtility; 037 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.ecm.core.api.Blob; 041import org.nuxeo.ecm.core.api.Blobs; 042 043/** 044 * Transforms the message using the transformer and puts it in the context under transformed. 045 * 046 * @author Alexandre Russel 047 */ 048public class TransformMessageAction implements MessageAction { 049 050 private static final Log log = LogFactory.getLog(TransformMessageAction.class); 051 052 protected final Map<String, Map<String, Object>> schemas = new HashMap<String, Map<String, Object>>(); 053 054 protected final Map<String, Object> mailSchema = new HashMap<String, Object>(); 055 056 protected final Map<String, Object> dcSchema = new HashMap<String, Object>(); 057 058 protected final Map<String, Object> filesSchema = new HashMap<String, Object>(); 059 060 protected final List<Map<String, Object>> files = new ArrayList<Map<String, Object>>(); 061 062 protected StringBuilder text = new StringBuilder(); 063 064 private final HashMap<String, List<Part>> messageBodyParts = new HashMap<String, List<Part>>(); 065 066 public TransformMessageAction() { 067 messageBodyParts.put("text", new ArrayList<Part>()); 068 messageBodyParts.put("html", new ArrayList<Part>()); 069 schemas.put("mail", mailSchema); 070 schemas.put("dublincore", dcSchema); 071 filesSchema.put("files", files); 072 schemas.put("files", filesSchema); 073 } 074 075 public boolean execute(ExecutionContext context) throws MessagingException { 076 Message message = context.getMessage(); 077 if (log.isDebugEnabled()) { 078 log.debug("Transforming message" + message.getSubject()); 079 } 080 if (message.getFrom() != null && message.getFrom().length != 0) { 081 List<String> contributors = new ArrayList<String>(); 082 for (Address ad : message.getFrom()) { 083 contributors.add(safelyDecodeText(ad.toString())); 084 } 085 dcSchema.put("contributors", contributors); 086 dcSchema.put("creator", contributors.get(0)); 087 dcSchema.put("created", message.getReceivedDate()); 088 } 089 if (message.getAllRecipients() != null && message.getAllRecipients().length != 0) { 090 List<String> recipients = new ArrayList<String>(); 091 for (Address address : message.getAllRecipients()) { 092 recipients.add(safelyDecodeText(address.toString())); 093 } 094 mailSchema.put("recipients", recipients); 095 } 096 if (message instanceof MimeMessage) { 097 try { 098 processMimeMessage((MimeMessage) message); 099 } catch (IOException e) { 100 throw new MessagingException(e.getMessage(), e); 101 } 102 } 103 mailSchema.put("text", text.toString()); 104 dcSchema.put("title", message.getSubject()); 105 context.put("transformed", schemas); 106 return true; 107 } 108 109 private void processMimeMessage(MimeMessage message) throws MessagingException, IOException { 110 Object object = message.getContent(); 111 if (object instanceof String) { 112 addToTextMessage(message.getContent().toString(), true); 113 } else if (object instanceof MimeMultipart) { 114 processMultipartMessage((MimeMultipart) object); 115 processSavedTextMessageBody(); 116 } 117 } 118 119 private void processMultipartMessage(MimeMultipart parts) throws MessagingException, IOException { 120 log.debug("processing multipart message."); 121 for (int i = 0; i < parts.getCount(); i++) { 122 Part part = parts.getBodyPart(i); 123 if (part.getDataHandler().getContent() instanceof MimeMultipart) { 124 log.debug("** found embedded multipart message"); 125 processMultipartMessage((MimeMultipart) part.getDataHandler().getContent()); 126 continue; 127 } 128 log.debug("processing single part message: " + part.getClass()); 129 processSingleMessagePart(part); 130 } 131 } 132 133 private void processSingleMessagePart(Part part) throws MessagingException, IOException { 134 String partContentType = part.getContentType(); 135 String partFileName = getFileName(part); 136 137 if (partFileName != null) { 138 log.debug("Add named attachment: " + partFileName); 139 setFile(partFileName, part.getInputStream()); 140 return; 141 } 142 143 if (!contentTypeIsReadableText(partContentType)) { 144 log.debug("Add unnamed binary attachment."); 145 setFile(null, part.getInputStream()); 146 return; 147 } 148 149 if (contentTypeIsPlainText(partContentType)) { 150 log.debug("found plain text unnamed attachment [save for later processing]"); 151 messageBodyParts.get("text").add(part); 152 return; 153 } 154 155 log.debug("found html unnamed attachment [save for later processing]"); 156 messageBodyParts.get("html").add(part); 157 } 158 159 private void processSavedTextMessageBody() throws MessagingException, IOException { 160 if (messageBodyParts.get("text").isEmpty()) { 161 log.debug("entering case 2: no plain text found -> html is the body of the message."); 162 addPartsToTextMessage(messageBodyParts.get("html")); 163 } else { 164 log.debug("entering case 1: text is saved as message body and html as attachment."); 165 addPartsToTextMessage(messageBodyParts.get("text")); 166 addPartsAsAttachements(messageBodyParts.get("html")); 167 } 168 } 169 170 private void addPartsToTextMessage(List<Part> someMessageParts) throws MessagingException, IOException { 171 for (Part part : someMessageParts) { 172 addToTextMessage(part.getContent().toString(), contentTypeIsPlainText(part.getContentType())); 173 } 174 } 175 176 private void addPartsAsAttachements(List<Part> someMessageParts) throws MessagingException, IOException { 177 for (Part part : someMessageParts) { 178 setFile(getFileName(part), part.getInputStream()); 179 } 180 } 181 182 private static boolean contentTypeIsReadableText(String contentType) { 183 boolean isText = contentTypeIsPlainText(contentType); 184 boolean isHTML = contentTypeIsHtml(contentType); 185 return isText || isHTML; 186 } 187 188 private static boolean contentTypeIsHtml(String contentType) { 189 contentType = contentType.trim().toLowerCase(); 190 return contentType.startsWith("text/html"); 191 } 192 193 private static boolean contentTypeIsPlainText(String contentType) { 194 contentType = contentType.trim().toLowerCase(); 195 return contentType.startsWith("text/plain"); 196 } 197 198 /** 199 * "javax.mail.internet.MimeBodyPart" is decoding the file name (with special characters) if it has the 200 * "mail.mime.decodefilename" sysstem property set but the "com.sun.mail.imap.IMAPBodyPart" subclass of MimeBodyPart 201 * is overriding getFileName() and never deal with encoded file names. the filename is decoded with the utility 202 * function: MimeUtility.decodeText(filename); so we force here a filename decode. MimeUtility.decodeText is doing 203 * nothing if the text is not encoded 204 */ 205 private static String getFileName(Part mailPart) throws MessagingException { 206 String sysPropertyVal = System.getProperty("mail.mime.decodefilename"); 207 boolean decodeFileName = sysPropertyVal != null && !sysPropertyVal.equalsIgnoreCase("false"); 208 209 String encodedFilename = mailPart.getFileName(); 210 211 if (!decodeFileName || encodedFilename == null) { 212 return encodedFilename; 213 } 214 215 try { 216 return MimeUtility.decodeText(encodedFilename); 217 } catch (UnsupportedEncodingException ex) { 218 throw new MessagingException("Can't decode attachment filename.", ex); 219 } 220 } 221 222 private static String safelyDecodeText(String textToDecode) { 223 try { 224 return MimeUtility.decodeText(textToDecode); 225 } catch (UnsupportedEncodingException ex) { 226 log.error("Can't decode text. Use undecoded!", ex); 227 return textToDecode; 228 } 229 } 230 231 private void setFile(String fileName, InputStream inputStream) throws IOException { 232 log.debug("* adding attachment: " + fileName); 233 Map<String, Object> map = new HashMap<String, Object>(); 234 Blob fileBlob = Blobs.createBlob(inputStream); 235 map.put("file", fileBlob); 236 map.put("filename", fileName); 237 files.add(map); 238 } 239 240 private void addToTextMessage(String message, boolean isPlainText) { 241 log.debug("* adding text to message body: " + message); 242 // if(isPlainText){ 243 // message = "<pre>" + message + "</pre>"; 244 // } 245 text.append(message); 246 } 247 248 public void reset(ExecutionContext context) { 249 mailSchema.clear(); 250 dcSchema.clear(); 251 files.clear(); 252 text = new StringBuilder(); 253 254 messageBodyParts.get("text").clear(); 255 messageBodyParts.get("html").clear(); 256 } 257 258}