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