001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.camel.component.mail; 018 019 import java.io.IOException; 020 import java.util.Enumeration; 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.Map; 024 import javax.activation.DataHandler; 025 import javax.activation.DataSource; 026 import javax.mail.Address; 027 import javax.mail.BodyPart; 028 import javax.mail.Header; 029 import javax.mail.Message; 030 import javax.mail.MessagingException; 031 import javax.mail.Part; 032 import javax.mail.internet.InternetAddress; 033 import javax.mail.internet.MimeBodyPart; 034 import javax.mail.internet.MimeMessage; 035 import javax.mail.internet.MimeMultipart; 036 import javax.mail.util.ByteArrayDataSource; 037 038 import org.apache.camel.Exchange; 039 import org.apache.camel.RuntimeCamelException; 040 import org.apache.camel.converter.ObjectConverter; 041 import org.apache.camel.impl.DefaultHeaderFilterStrategy; 042 import org.apache.camel.spi.HeaderFilterStrategy; 043 import org.apache.camel.util.CollectionHelper; 044 045 /** 046 * A Strategy used to convert between a Camel {@link Exchange} and {@link Message} to and 047 * from a Mail {@link MimeMessage} 048 * 049 * @version $Revision: 760920 $ 050 */ 051 public class MailBinding { 052 053 private HeaderFilterStrategy headerFilterStrategy; 054 055 public MailBinding() { 056 headerFilterStrategy = new DefaultHeaderFilterStrategy(); 057 } 058 059 public MailBinding(HeaderFilterStrategy headerFilterStrategy) { 060 this.headerFilterStrategy = headerFilterStrategy; 061 } 062 063 public void populateMailMessage(MailEndpoint endpoint, MimeMessage mimeMessage, Exchange exchange) 064 throws MessagingException, IOException { 065 066 // camel message headers takes presedence over endpoint configuration 067 if (hasRecipientHeaders(exchange.getIn())) { 068 setRecipientFromCamelMessage(mimeMessage, exchange, exchange.getIn()); 069 } else { 070 // fallback to endpoint configuration 071 setRecipientFromEndpointConfiguration(mimeMessage, endpoint); 072 } 073 074 // must have at least one recipients otherwise we do not know where to send the mail 075 if (mimeMessage.getAllRecipients() == null) { 076 throw new IllegalArgumentException("The mail message does not have any recipients set."); 077 } 078 079 // append the rest of the headers (no recipients) that could be subject, reply-to etc. 080 appendHeadersFromCamelMessage(mimeMessage, exchange, exchange.getIn()); 081 082 if (empty(mimeMessage.getFrom())) { 083 // lets default the address to the endpoint destination 084 String from = endpoint.getConfiguration().getFrom(); 085 mimeMessage.setFrom(new InternetAddress(from)); 086 } 087 088 // if there is an alternativebody provided, set up a mime multipart alternative message 089 if (hasAlternativeBody(endpoint.getConfiguration(), exchange.getIn())) { 090 createMultipartAlternativeMessage(mimeMessage, exchange.getIn(), endpoint.getConfiguration()); 091 } else { 092 if (exchange.getIn().hasAttachments()) { 093 appendAttachmentsFromCamel(mimeMessage, exchange.getIn(), endpoint.getConfiguration()); 094 } else { 095 if ("text/html".equals(endpoint.getConfiguration().getContentType())) { 096 DataSource ds = new ByteArrayDataSource(exchange.getIn().getBody(String.class), "text/html"); 097 mimeMessage.setDataHandler(new DataHandler(ds)); 098 } else { 099 // its just text/plain 100 mimeMessage.setText(exchange.getIn().getBody(String.class)); 101 } 102 } 103 } 104 } 105 106 /** 107 * Extracts the body from the Mail message 108 */ 109 public Object extractBodyFromMail(MailExchange exchange, Message message) { 110 try { 111 return message.getContent(); 112 } catch (Exception e) { 113 throw new RuntimeCamelException("Failed to extract body due to: " + e.getMessage() 114 + ". Exchange: " + exchange + ". Message: " + message, e); 115 } 116 } 117 118 /** 119 * Appends the Mail headers from the Camel {@link MailMessage} 120 */ 121 protected void appendHeadersFromCamelMessage(MimeMessage mimeMessage, Exchange exchange, 122 org.apache.camel.Message camelMessage) 123 throws MessagingException { 124 125 for (Map.Entry<String, Object> entry : camelMessage.getHeaders().entrySet()) { 126 String headerName = entry.getKey(); 127 Object headerValue = entry.getValue(); 128 if (headerValue != null) { 129 if (headerFilterStrategy != null 130 && !headerFilterStrategy.applyFilterToCamelHeaders(headerName, headerValue)) { 131 132 if (isRecipientHeader(headerName)) { 133 // skip any recipients as they are handled specially 134 continue; 135 } 136 137 // Mail messages can repeat the same header... 138 if (ObjectConverter.isCollection(headerValue)) { 139 Iterator iter = ObjectConverter.iterator(headerValue); 140 while (iter.hasNext()) { 141 Object value = iter.next(); 142 mimeMessage.addHeader(headerName, asString(exchange, value)); 143 } 144 } else { 145 mimeMessage.setHeader(headerName, asString(exchange, headerValue)); 146 } 147 } 148 } 149 } 150 } 151 152 private void setRecipientFromCamelMessage(MimeMessage mimeMessage, Exchange exchange, 153 org.apache.camel.Message camelMessage) 154 throws MessagingException { 155 156 for (Map.Entry<String, Object> entry : camelMessage.getHeaders().entrySet()) { 157 String headerName = entry.getKey(); 158 Object headerValue = entry.getValue(); 159 if (headerValue != null && isRecipientHeader(headerName)) { 160 // special handling of recipients 161 if (ObjectConverter.isCollection(headerValue)) { 162 Iterator iter = ObjectConverter.iterator(headerValue); 163 while (iter.hasNext()) { 164 Object recipient = iter.next(); 165 appendRecipientToMimeMessage(mimeMessage, headerName, asString(exchange, recipient)); 166 } 167 } else { 168 appendRecipientToMimeMessage(mimeMessage, headerName, asString(exchange, headerValue)); 169 } 170 } 171 } 172 } 173 174 /** 175 * Appends the Mail headers from the endpoint configuraiton. 176 */ 177 protected void setRecipientFromEndpointConfiguration(MimeMessage mimeMessage, MailEndpoint endpoint) 178 throws MessagingException { 179 180 Map<Message.RecipientType, String> recipients = endpoint.getConfiguration().getRecipients(); 181 if (recipients.containsKey(Message.RecipientType.TO)) { 182 appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.TO.toString(), recipients.get(Message.RecipientType.TO)); 183 } 184 if (recipients.containsKey(Message.RecipientType.CC)) { 185 appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.CC.toString(), recipients.get(Message.RecipientType.CC)); 186 } 187 if (recipients.containsKey(Message.RecipientType.BCC)) { 188 appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.BCC.toString(), recipients.get(Message.RecipientType.BCC)); 189 } 190 191 // fallback to use destination if no TO provided at all 192 String destination = endpoint.getConfiguration().getDestination(); 193 if (destination != null && mimeMessage.getRecipients(Message.RecipientType.TO) == null) { 194 appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.TO.toString(), destination); 195 } 196 } 197 198 /** 199 * Appends the Mail attachments from the Camel {@link MailMessage} 200 */ 201 protected void appendAttachmentsFromCamel(MimeMessage mimeMessage, org.apache.camel.Message camelMessage, 202 MailConfiguration configuration) 203 throws MessagingException { 204 205 // Put parts in message 206 mimeMessage.setContent(createMixedMultipartAttachments(camelMessage, configuration)); 207 } 208 209 private MimeMultipart createMixedMultipartAttachments(org.apache.camel.Message camelMessage, MailConfiguration configuration) throws MessagingException { 210 // fill the body with text 211 MimeMultipart multipart = new MimeMultipart(); 212 multipart.setSubType("mixed"); 213 addBodyToMultipart(camelMessage, configuration, multipart); 214 String partDisposition = configuration.isUseInlineAttachments() ? Part.INLINE : Part.ATTACHMENT; 215 addAttachmentsToMultipart(camelMessage, multipart, partDisposition); 216 return multipart; 217 } 218 219 protected void addAttachmentsToMultipart(org.apache.camel.Message camelMessage, MimeMultipart multipart, String partDisposition) throws MessagingException { 220 for (Map.Entry<String, DataHandler> entry : camelMessage.getAttachments().entrySet()) { 221 String attachmentFilename = entry.getKey(); 222 DataHandler handler = entry.getValue(); 223 if (handler != null) { 224 if (shouldOutputAttachment(camelMessage, attachmentFilename, handler)) { 225 // Create another body part 226 BodyPart messageBodyPart = new MimeBodyPart(); 227 // Set the data handler to the attachment 228 messageBodyPart.setDataHandler(handler); 229 230 if (attachmentFilename.toLowerCase().startsWith("cid:")) { 231 // add a Content-ID header to the attachment 232 messageBodyPart.addHeader("Content-ID", attachmentFilename.substring(4)); 233 } 234 // Set the filename 235 messageBodyPart.setFileName(attachmentFilename); 236 // Set Disposition 237 messageBodyPart.setDisposition(partDisposition); 238 // Add part to multipart 239 multipart.addBodyPart(messageBodyPart); 240 } 241 } 242 } 243 } 244 245 protected void createMultipartAlternativeMessage(MimeMessage mimeMessage, org.apache.camel.Message camelMessage, MailConfiguration configuration) 246 throws MessagingException { 247 248 MimeMultipart multipartAlternative = new MimeMultipart("alternative"); 249 mimeMessage.setContent(multipartAlternative); 250 251 BodyPart plainText = new MimeBodyPart(); 252 plainText.setText(getAlternativeBody(configuration, camelMessage)); 253 multipartAlternative.addBodyPart(plainText); 254 255 // if there are no attachments, add the body to the same mulitpart message 256 if (!camelMessage.hasAttachments()) { 257 addBodyToMultipart(camelMessage, configuration, multipartAlternative); 258 } else { 259 // if there are attachments, but they aren't set to be inline, add them to 260 // treat them as normal. It will append a multipart-mixed with the attachments and the 261 // body text 262 if (!configuration.isUseInlineAttachments()) { 263 BodyPart mixedAttachments = new MimeBodyPart(); 264 mixedAttachments.setContent(createMixedMultipartAttachments(camelMessage, configuration)); 265 multipartAlternative.addBodyPart(mixedAttachments); 266 //appendAttachmentsFromCamel(mimeMessage, camelMessage, configuration); 267 } else { // if the attachments are set to be inline, attach them as inline attachments 268 MimeMultipart multipartRelated = new MimeMultipart("related"); 269 BodyPart related = new MimeBodyPart(); 270 271 related.setContent(multipartRelated); 272 multipartAlternative.addBodyPart(related); 273 274 addBodyToMultipart(camelMessage, configuration, multipartRelated); 275 276 addAttachmentsToMultipart(camelMessage, multipartRelated, Part.INLINE); 277 } 278 } 279 280 } 281 282 protected void addBodyToMultipart(org.apache.camel.Message camelMessage, MailConfiguration configuration, MimeMultipart activeMultipart) throws MessagingException { 283 BodyPart bodyMessage = new MimeBodyPart(); 284 bodyMessage.setContent(camelMessage.getBody(String.class), configuration.getContentType()); 285 activeMultipart.addBodyPart(bodyMessage); 286 } 287 288 289 /** 290 * Strategy to allow filtering of attachments which are put on the Mail message 291 */ 292 protected boolean shouldOutputAttachment(org.apache.camel.Message camelMessage, String attachmentFilename, DataHandler handler) { 293 return true; 294 } 295 296 protected Map<String, Object> extractHeadersFromMail(Message mailMessage) throws MessagingException { 297 Map<String, Object> answer = new HashMap<String, Object>(); 298 Enumeration names = mailMessage.getAllHeaders(); 299 300 while (names.hasMoreElements()) { 301 Header header = (Header)names.nextElement(); 302 String[] value = mailMessage.getHeader(header.getName()); 303 if (headerFilterStrategy != null 304 && !headerFilterStrategy.applyFilterToExternalHeaders(header.getName(), value)) { 305 // toLowerCase() for doing case insensitive search 306 if (value.length == 1) { 307 CollectionHelper.appendValue(answer, header.getName().toLowerCase(), value[0]); 308 } else { 309 CollectionHelper.appendValue(answer, header.getName().toLowerCase(), value); 310 } 311 } 312 } 313 314 return answer; 315 } 316 317 private static void appendRecipientToMimeMessage(MimeMessage mimeMessage, String type, String recipient) 318 throws MessagingException { 319 320 // we support that multi recipient can be given as a string seperated by comma or semi colon 321 String[] lines = recipient.split("[,|;]"); 322 for (String line : lines) { 323 line = line.trim(); 324 mimeMessage.addRecipients(asRecipientType(type), line); 325 } 326 } 327 328 /** 329 * Does the given camel message contain any To, CC or BCC header names? 330 */ 331 private static boolean hasRecipientHeaders(org.apache.camel.Message camelMessage) { 332 for (String key : camelMessage.getHeaders().keySet()) { 333 if (isRecipientHeader(key)) { 334 return true; 335 } 336 } 337 return false; 338 } 339 340 protected static boolean hasAlternativeBody(MailConfiguration configuration, org.apache.camel.Message camelMessage) { 341 return getAlternativeBody(configuration, camelMessage) != null; 342 } 343 344 protected static String getAlternativeBody(MailConfiguration configuration, org.apache.camel.Message camelMessage) { 345 String alternativeBodyHeader = configuration.getAlternateBodyHeader(); 346 return camelMessage.getHeader(alternativeBodyHeader, java.lang.String.class); 347 } 348 349 /** 350 * Is the given key a mime message recipient header (To, CC or BCC) 351 */ 352 private static boolean isRecipientHeader(String key) { 353 if (Message.RecipientType.TO.toString().equalsIgnoreCase(key)) { 354 return true; 355 } else if (Message.RecipientType.CC.toString().equalsIgnoreCase(key)) { 356 return true; 357 } else if (Message.RecipientType.BCC.toString().equalsIgnoreCase(key)) { 358 return true; 359 } 360 return false; 361 } 362 363 /** 364 * Returns the RecipientType object. 365 */ 366 private static Message.RecipientType asRecipientType(String type) { 367 if (Message.RecipientType.TO.toString().equalsIgnoreCase(type)) { 368 return Message.RecipientType.TO; 369 } else if (Message.RecipientType.CC.toString().equalsIgnoreCase(type)) { 370 return Message.RecipientType.CC; 371 } else if (Message.RecipientType.BCC.toString().equalsIgnoreCase(type)) { 372 return Message.RecipientType.BCC; 373 } 374 throw new IllegalArgumentException("Unknown recipient type: " + type); 375 } 376 377 378 private static boolean empty(Address[] addresses) { 379 return addresses == null || addresses.length == 0; 380 } 381 382 private static String asString(Exchange exchange, Object value) { 383 return exchange.getContext().getTypeConverter().convertTo(String.class, exchange, value); 384 } 385 386 }