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.io.UnsupportedEncodingException;
021    import java.nio.charset.Charset;
022    import java.nio.charset.IllegalCharsetNameException;
023    import java.util.ArrayList;
024    import java.util.Enumeration;
025    import java.util.HashMap;
026    import java.util.Iterator;
027    import java.util.Map;
028    
029    import javax.activation.DataHandler;
030    import javax.activation.DataSource;
031    import javax.mail.Address;
032    import javax.mail.BodyPart;
033    import javax.mail.Header;
034    import javax.mail.Message;
035    import javax.mail.MessagingException;
036    import javax.mail.Multipart;
037    import javax.mail.Part;
038    import javax.mail.internet.InternetAddress;
039    import javax.mail.internet.MimeBodyPart;
040    import javax.mail.internet.MimeMessage;
041    import javax.mail.internet.MimeMultipart;
042    import javax.mail.util.ByteArrayDataSource;
043    
044    import org.apache.camel.Exchange;
045    import org.apache.camel.RuntimeCamelException;
046    import org.apache.camel.converter.IOConverter;
047    import org.apache.camel.converter.ObjectConverter;
048    import org.apache.camel.impl.DefaultHeaderFilterStrategy;
049    import org.apache.camel.spi.HeaderFilterStrategy;
050    import org.apache.camel.util.CollectionHelper;
051    import org.apache.camel.util.ObjectHelper;
052    import org.slf4j.Logger;
053    import org.slf4j.LoggerFactory;
054    
055    /**
056     * A Strategy used to convert between a Camel {@link Exchange} and {@link Message} to and
057     * from a Mail {@link MimeMessage}
058     *
059     * @version 
060     */
061    public class MailBinding {
062    
063        private static final transient Logger LOG = LoggerFactory.getLogger(MailBinding.class);
064        private HeaderFilterStrategy headerFilterStrategy;
065        private ContentTypeResolver contentTypeResolver;
066    
067        public MailBinding() {
068            headerFilterStrategy = new DefaultHeaderFilterStrategy();
069        }
070    
071        public MailBinding(HeaderFilterStrategy headerFilterStrategy, ContentTypeResolver contentTypeResolver) {
072            this.headerFilterStrategy = headerFilterStrategy;
073            this.contentTypeResolver = contentTypeResolver;
074        }
075    
076        public void populateMailMessage(MailEndpoint endpoint, MimeMessage mimeMessage, Exchange exchange)
077            throws MessagingException, IOException {
078    
079            // camel message headers takes precedence over endpoint configuration
080            if (hasRecipientHeaders(exchange)) {
081                setRecipientFromCamelMessage(mimeMessage, exchange);
082            } else {
083                // fallback to endpoint configuration
084                setRecipientFromEndpointConfiguration(mimeMessage, endpoint);
085            }
086            
087            // set the replyTo if it was passed in as an option in the uri. Note: if it is in both the URI
088            // and headers the headers win.
089            String replyTo = exchange.getIn().getHeader("Reply-To", String.class);
090            if (replyTo == null) {
091                replyTo = endpoint.getConfiguration().getReplyTo();
092            }
093            if (replyTo != null) {
094                ArrayList<InternetAddress> replyToAddresses = new ArrayList<InternetAddress>();
095                for (String reply : splitRecipients(replyTo)) {
096                    replyToAddresses.add(new InternetAddress(reply.trim()));
097                }
098                mimeMessage.setReplyTo(replyToAddresses.toArray(new InternetAddress[replyToAddresses.size()]));
099            }
100            
101            // must have at least one recipients otherwise we do not know where to send the mail
102            if (mimeMessage.getAllRecipients() == null) {
103                throw new IllegalArgumentException("The mail message does not have any recipients set.");
104            }
105    
106            // set the subject if it was passed in as an option in the uri. Note: if it is in both the URI
107            // and headers the headers win.
108            String subject = endpoint.getConfiguration().getSubject();
109            if (subject != null) {
110                mimeMessage.setSubject(subject, IOConverter.getCharsetName(exchange, false));
111            }
112    
113            // append the rest of the headers (no recipients) that could be subject, reply-to etc.
114            appendHeadersFromCamelMessage(mimeMessage, endpoint.getConfiguration(), exchange);
115    
116            if (empty(mimeMessage.getFrom())) {
117                // lets default the address to the endpoint destination
118                String from = endpoint.getConfiguration().getFrom();
119                mimeMessage.setFrom(new InternetAddress(from));
120            }
121    
122            // if there is an alternative body provided, set up a mime multipart alternative message
123            if (hasAlternativeBody(endpoint.getConfiguration(), exchange)) {
124                createMultipartAlternativeMessage(mimeMessage, endpoint.getConfiguration(), exchange);
125            } else {
126                if (exchange.getIn().hasAttachments()) {
127                    appendAttachmentsFromCamel(mimeMessage, endpoint.getConfiguration(), exchange);
128                } else {
129                    populateContentOnMimeMessage(mimeMessage, endpoint.getConfiguration(), exchange);
130                }
131            }
132        }
133    
134        protected String determineContentType(MailConfiguration configuration, Exchange exchange) {
135            // see if we got any content type set
136            String contentType = configuration.getContentType();
137            if (exchange.getIn().getHeader("contentType") != null) {
138                contentType = exchange.getIn().getHeader("contentType", String.class);
139            } else if (exchange.getIn().getHeader(Exchange.CONTENT_TYPE) != null) {
140                contentType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class);
141            }
142    
143            // fix content-type to have space after semi colons, otherwise some mail servers will choke
144            if (contentType != null && contentType.contains(";")) {
145                contentType = MailUtils.padContentType(contentType);
146            }
147    
148            if (contentType != null) {
149                // no charset in content-type, then try to see if we can determine one
150                String charset = determineCharSet(configuration, exchange);
151                // must replace charset, even with null in case its an unsupported charset
152                contentType = MailUtils.replaceCharSet(contentType, charset);
153            }
154    
155            LOG.trace("Determined Content-Type: {}", contentType);
156    
157            return contentType;
158        }
159    
160        protected String determineCharSet(MailConfiguration configuration, Exchange exchange) {
161    
162             // see if we got any content type set
163            String contentType = configuration.getContentType();
164            if (exchange.getIn().getHeader("contentType") != null) {
165                contentType = exchange.getIn().getHeader("contentType", String.class);
166            } else if (exchange.getIn().getHeader(Exchange.CONTENT_TYPE) != null) {
167                contentType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class);
168            }
169    
170            // look for charset
171            String charset = MailUtils.getCharSetFromContentType(contentType);
172            if (charset != null) {
173                charset = IOConverter.normalizeCharset(charset);
174                if (charset != null) {
175                    boolean supported;
176                    try {
177                        supported = Charset.isSupported(charset);
178                    } catch (IllegalCharsetNameException e) {
179                        supported = false;
180                    }
181                    if (supported) {
182                        return charset;
183                    } else if (!configuration.isIgnoreUnsupportedCharset()) {
184                        return charset;
185                    } else if (configuration.isIgnoreUnsupportedCharset()) {
186                        LOG.warn("Charset: " + charset + " is not supported and cannot be used as charset in Content-Type header.");
187                        return null;
188                    }
189                }
190            }
191    
192            // Using the charset header of exchange as a fall back
193            return IOConverter.getCharsetName(exchange, false);
194        }
195    
196        protected String populateContentOnMimeMessage(MimeMessage part, MailConfiguration configuration, Exchange exchange)
197            throws MessagingException, IOException {
198    
199            String contentType = determineContentType(configuration, exchange);
200    
201            LOG.trace("Using Content-Type {} for MimeMessage: {}", contentType, part);
202            
203            String body = exchange.getIn().getBody(String.class);
204            if (body == null) {
205                body = "";
206            }
207    
208            // always store content in a byte array data store to avoid various content type and charset issues
209            DataSource ds = new ByteArrayDataSource(body, contentType);
210            part.setDataHandler(new DataHandler(ds));
211    
212            // set the content type header afterwards
213            part.setHeader("Content-Type", contentType);
214    
215            return contentType;
216        }
217    
218        protected String populateContentOnBodyPart(BodyPart part, MailConfiguration configuration, Exchange exchange)
219            throws MessagingException, IOException {
220    
221            String contentType = determineContentType(configuration, exchange);
222    
223            LOG.trace("Using Content-Type {} for BodyPart: {}", contentType, part);
224    
225            // always store content in a byte array data store to avoid various content type and charset issues
226            DataSource ds = new ByteArrayDataSource(exchange.getIn().getBody(String.class), contentType);
227            part.setDataHandler(new DataHandler(ds));
228    
229            // set the content type header afterwards
230            part.setHeader("Content-Type", contentType);
231    
232            return contentType;
233        }
234    
235        /**
236         * Extracts the body from the Mail message
237         */
238        public Object extractBodyFromMail(Exchange exchange, MailMessage mailMessage) {
239            Message message = mailMessage.getMessage();
240            try {
241                if (((MailEndpoint)exchange.getFromEndpoint()).getConfiguration().isMapMailMessage()) {
242                    return message.getContent();
243                }
244                return message; // raw message
245            } catch (Exception e) {
246                // try to fix message in case it has an unsupported encoding in the Content-Type header
247                UnsupportedEncodingException uee = ObjectHelper.getException(UnsupportedEncodingException.class, e);
248                if (uee != null) {
249                    LOG.debug("Unsupported encoding detected: " + uee.getMessage());
250                    try {
251                        String contentType = message.getContentType();
252                        String type = ObjectHelper.before(contentType, "charset=");
253                        if (type != null) {
254                            // try again with fixed content type
255                            LOG.debug("Trying to extract mail message again with fixed Content-Type: " + type);
256                            // Since message is read-only, we need to use a copy
257                            MimeMessage messageCopy = new MimeMessage((MimeMessage)message);
258                            messageCopy.setHeader("Content-Type", type);
259                            Object body = messageCopy.getContent();
260                            // If we got this far, our fix worked...
261                            // Replace the MailMessage's Message with the copy
262                            mailMessage.setMessage(messageCopy);
263                            return body;
264                        }
265                    } catch (Exception e2) {
266                        // fall through and let original exception be thrown
267                    }
268                }
269    
270                throw new RuntimeCamelException("Failed to extract body due to: " + e.getMessage()
271                    + ". Exchange: " + exchange + ". Message: " + message, e);
272            }
273        }
274    
275        /**
276         * Parses the attachments of the given mail message and adds them to the map
277         *
278         * @param  message  the mail message with attachments
279         * @param  map      the map to add found attachments (attachmentFilename is the key)
280         */
281        public void extractAttachmentsFromMail(Message message, Map<String, DataHandler> map)
282            throws javax.mail.MessagingException, IOException {
283    
284            LOG.trace("Extracting attachments +++ start +++");
285    
286            Object content = message.getContent();
287            if (content instanceof Multipart) {
288                extractAttachmentsFromMultipart((Multipart)content, map);
289            } else if (content != null) {
290                LOG.trace("No attachments to extract as content is not Multipart: " + content.getClass().getName());
291            }
292    
293            LOG.trace("Extracting attachments +++ done +++");
294        }
295    
296        protected void extractAttachmentsFromMultipart(Multipart mp, Map<String, DataHandler> map)
297            throws javax.mail.MessagingException, IOException {
298    
299            for (int i = 0; i < mp.getCount(); i++) {
300                Part part = mp.getBodyPart(i);
301                LOG.trace("Part #" + i + ": " + part);
302    
303                if (part.isMimeType("multipart/*")) {
304                    LOG.trace("Part #" + i + ": is mimetype: multipart/*");
305                    extractAttachmentsFromMultipart((Multipart)part.getContent(), map);
306                } else {
307                    String disposition = part.getDisposition();
308                    if (LOG.isTraceEnabled()) {
309                        LOG.trace("Part #{}: Disposition: {}", i, part.getDisposition());
310                        LOG.trace("Part #{}: Description: {}", i, part.getDescription());
311                        LOG.trace("Part #{}: ContentType: {}", i, part.getContentType());
312                        LOG.trace("Part #{}: FileName: {}", i, part.getFileName());
313                        LOG.trace("Part #{}: Size: {}", i, part.getSize());
314                        LOG.trace("Part #{}: LineCount: {}", i, part.getLineCount());
315                    }
316    
317                    if (disposition != null && (disposition.equalsIgnoreCase(Part.ATTACHMENT) || disposition.equalsIgnoreCase(Part.INLINE))) {
318                        // only add named attachments
319                        String fileName = part.getFileName();
320                        if (fileName != null) {
321                            LOG.debug("Mail contains file attachment: " + fileName);
322                            if (!map.containsKey(fileName)) {
323                                // Parts marked with a disposition of Part.ATTACHMENT are clearly attachments
324                                map.put(fileName, part.getDataHandler());
325                            } else {
326                                LOG.warn("Cannot extract duplicate attachment: " + fileName);
327                            }
328                        }
329                    }
330                }
331            }
332        }
333    
334        /**
335         * Appends the Mail headers from the Camel {@link MailMessage}
336         */
337        protected void appendHeadersFromCamelMessage(MimeMessage mimeMessage, MailConfiguration configuration, Exchange exchange)
338            throws MessagingException {
339    
340            for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
341                String headerName = entry.getKey();
342                Object headerValue = entry.getValue();
343                if (headerValue != null) {
344                    if (headerFilterStrategy != null
345                            && !headerFilterStrategy.applyFilterToCamelHeaders(headerName, headerValue, exchange)) {
346                        if (headerName.equalsIgnoreCase("subject")) {
347                            mimeMessage.setSubject(asString(exchange, headerValue), IOConverter.getCharsetName(exchange, false));
348                            continue;
349                        }
350                        if (isRecipientHeader(headerName)) {
351                            // skip any recipients as they are handled specially
352                            continue;
353                        }
354    
355                        // alternative body should also be skipped
356                        if (headerName.equalsIgnoreCase(configuration.getAlternativeBodyHeader())) {
357                            // skip alternative body
358                            continue;
359                        }
360    
361                        // Mail messages can repeat the same header...
362                        if (ObjectConverter.isCollection(headerValue)) {
363                            Iterator iter = ObjectHelper.createIterator(headerValue);
364                            while (iter.hasNext()) {
365                                Object value = iter.next();
366                                mimeMessage.addHeader(headerName, asString(exchange, value));
367                            }
368                        } else {
369                            mimeMessage.setHeader(headerName, asString(exchange, headerValue));
370                        }
371                    }
372                }
373            }
374        }
375    
376        private void setRecipientFromCamelMessage(MimeMessage mimeMessage, Exchange exchange) throws MessagingException {
377            for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
378                String headerName = entry.getKey();
379                Object headerValue = entry.getValue();
380                if (headerValue != null && isRecipientHeader(headerName)) {
381                    // special handling of recipients
382                    if (ObjectConverter.isCollection(headerValue)) {
383                        Iterator iter = ObjectHelper.createIterator(headerValue);
384                        while (iter.hasNext()) {
385                            Object recipient = iter.next();
386                            appendRecipientToMimeMessage(mimeMessage, headerName, asString(exchange, recipient));
387                        }
388                    } else {
389                        appendRecipientToMimeMessage(mimeMessage, headerName, asString(exchange, headerValue));
390                    }
391                }
392            }
393        }
394    
395        /**
396         * Appends the Mail headers from the endpoint configuration.
397         */
398        protected void setRecipientFromEndpointConfiguration(MimeMessage mimeMessage, MailEndpoint endpoint)
399            throws MessagingException {
400    
401            Map<Message.RecipientType, String> recipients = endpoint.getConfiguration().getRecipients();
402            if (recipients.containsKey(Message.RecipientType.TO)) {
403                appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.TO.toString(), recipients.get(Message.RecipientType.TO));
404            }
405            if (recipients.containsKey(Message.RecipientType.CC)) {
406                appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.CC.toString(), recipients.get(Message.RecipientType.CC));
407            }
408            if (recipients.containsKey(Message.RecipientType.BCC)) {
409                appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.BCC.toString(), recipients.get(Message.RecipientType.BCC));
410            }
411        }
412    
413        /**
414         * Appends the Mail attachments from the Camel {@link MailMessage}
415         */
416        protected void appendAttachmentsFromCamel(MimeMessage mimeMessage, MailConfiguration configuration, Exchange exchange)
417            throws MessagingException, IOException {
418    
419            // Put parts in message
420            mimeMessage.setContent(createMixedMultipartAttachments(configuration, exchange));
421        }
422    
423        private MimeMultipart createMixedMultipartAttachments(MailConfiguration configuration, Exchange exchange)
424            throws MessagingException, IOException {
425    
426            // fill the body with text
427            MimeMultipart multipart = new MimeMultipart();
428            multipart.setSubType("mixed");
429            addBodyToMultipart(configuration, multipart, exchange);
430            String partDisposition = configuration.isUseInlineAttachments() ? Part.INLINE : Part.ATTACHMENT;
431            if (exchange.getIn().hasAttachments()) {
432                addAttachmentsToMultipart(multipart, partDisposition, exchange);
433            }
434            return multipart;
435        }
436    
437        protected void addAttachmentsToMultipart(MimeMultipart multipart, String partDisposition, Exchange exchange) throws MessagingException {
438            LOG.trace("Adding attachments +++ start +++");
439            int i = 0;
440            for (Map.Entry<String, DataHandler> entry : exchange.getIn().getAttachments().entrySet()) {
441                String attachmentFilename = entry.getKey();
442                DataHandler handler = entry.getValue();
443    
444                if (LOG.isTraceEnabled()) {
445                    LOG.trace("Attachment #{}: Disposition: {}", i, partDisposition);
446                    LOG.trace("Attachment #{}: DataHandler: {}", i, handler);
447                    LOG.trace("Attachment #{}: FileName: {}", i, attachmentFilename);
448                }
449                if (handler != null) {
450                    if (shouldAddAttachment(exchange, attachmentFilename, handler)) {
451                        // Create another body part
452                        BodyPart messageBodyPart = new MimeBodyPart();
453                        // Set the data handler to the attachment
454                        messageBodyPart.setDataHandler(handler);
455    
456                        if (attachmentFilename.toLowerCase().startsWith("cid:")) {
457                            // add a Content-ID header to the attachment
458                            // must use angle brackets according to RFC: http://www.ietf.org/rfc/rfc2392.txt
459                            messageBodyPart.addHeader("Content-ID", "<" + attachmentFilename.substring(4) + ">");
460                            // Set the filename without the cid
461                            messageBodyPart.setFileName(attachmentFilename.substring(4));
462                        } else {
463                            // Set the filename
464                            messageBodyPart.setFileName(attachmentFilename);
465                        }
466    
467                        LOG.trace("Attachment #" + i + ": ContentType: " + messageBodyPart.getContentType());
468    
469                        if (contentTypeResolver != null) {
470                            String contentType = contentTypeResolver.resolveContentType(attachmentFilename);
471                            LOG.trace("Attachment #" + i + ": Using content type resolver: " + contentTypeResolver + " resolved content type as: " + contentType);
472                            if (contentType != null) {
473                                String value = contentType + "; name=" + attachmentFilename;
474                                messageBodyPart.setHeader("Content-Type", value);
475                                LOG.trace("Attachment #" + i + ": ContentType: " + messageBodyPart.getContentType());
476                            }
477                        }
478    
479                        // Set Disposition
480                        messageBodyPart.setDisposition(partDisposition);
481                        // Add part to multipart
482                        multipart.addBodyPart(messageBodyPart);
483                    } else {
484                        LOG.trace("shouldAddAttachment: false");
485                    }
486                } else {
487                    LOG.warn("Cannot add attachment: " + attachmentFilename + " as DataHandler is null");
488                }
489                i++;
490            }
491            LOG.trace("Adding attachments +++ done +++");
492        }
493    
494        protected void createMultipartAlternativeMessage(MimeMessage mimeMessage, MailConfiguration configuration, Exchange exchange)
495            throws MessagingException, IOException {
496    
497            MimeMultipart multipartAlternative = new MimeMultipart("alternative");
498            mimeMessage.setContent(multipartAlternative);
499    
500            MimeBodyPart plainText = new MimeBodyPart();
501            plainText.setText(getAlternativeBody(configuration, exchange), determineCharSet(configuration, exchange));
502            // remove the header with the alternative mail now that we got it
503            // otherwise it might end up twice in the mail reader
504            exchange.getIn().removeHeader(configuration.getAlternativeBodyHeader());
505            multipartAlternative.addBodyPart(plainText);
506    
507            // if there are no attachments, add the body to the same mulitpart message
508            if (!exchange.getIn().hasAttachments()) {
509                addBodyToMultipart(configuration, multipartAlternative, exchange);
510            } else {
511                // if there are attachments, but they aren't set to be inline, add them to
512                // treat them as normal. It will append a multipart-mixed with the attachments and the body text
513                if (!configuration.isUseInlineAttachments()) {
514                    BodyPart mixedAttachments = new MimeBodyPart();
515                    mixedAttachments.setContent(createMixedMultipartAttachments(configuration, exchange));
516                    multipartAlternative.addBodyPart(mixedAttachments);
517                } else {
518                    // if the attachments are set to be inline, attach them as inline attachments
519                    MimeMultipart multipartRelated = new MimeMultipart("related");
520                    BodyPart related = new MimeBodyPart();
521    
522                    related.setContent(multipartRelated);
523                    multipartAlternative.addBodyPart(related);
524    
525                    addBodyToMultipart(configuration, multipartRelated, exchange);
526    
527                    addAttachmentsToMultipart(multipartRelated, Part.INLINE, exchange);
528                }
529            }
530        }
531    
532        protected void addBodyToMultipart(MailConfiguration configuration, MimeMultipart activeMultipart, Exchange exchange)
533            throws MessagingException, IOException {
534    
535            BodyPart bodyMessage = new MimeBodyPart();
536            populateContentOnBodyPart(bodyMessage, configuration, exchange);
537            activeMultipart.addBodyPart(bodyMessage);
538        }
539    
540        /**
541         * Strategy to allow filtering of attachments which are added on the Mail message
542         */
543        protected boolean shouldAddAttachment(Exchange exchange, String attachmentFilename, DataHandler handler) {
544            return true;
545        }
546    
547        protected Map<String, Object> extractHeadersFromMail(Message mailMessage, Exchange exchange) throws MessagingException {
548            Map<String, Object> answer = new HashMap<String, Object>();
549            Enumeration names = mailMessage.getAllHeaders();
550    
551            while (names.hasMoreElements()) {
552                Header header = (Header) names.nextElement();
553                String value = header.getValue();
554                if (headerFilterStrategy != null && !headerFilterStrategy.applyFilterToExternalHeaders(header.getName(), value, exchange)) {
555                    CollectionHelper.appendValue(answer, header.getName(), value);
556                }
557            }
558    
559            return answer;
560        }
561    
562        private static void appendRecipientToMimeMessage(MimeMessage mimeMessage, String type, String recipient) throws MessagingException {
563            for (String line : splitRecipients(recipient)) {
564                mimeMessage.addRecipients(asRecipientType(type), line.trim());
565            }
566        }
567        
568        private static String[] splitRecipients(String recipients) {
569            // we support that multi recipient can be given as a string separated by comma or semicolon
570            // regex ignores comma and semicolon inside of double quotes
571            return recipients.split("[,;]++(?=(?:(?:[^\\\"]*+\\\"){2})*+[^\\\"]*+$)");
572        }
573    
574        /**
575         * Does the given camel message contain any To, CC or BCC header names?
576         */
577        private static boolean hasRecipientHeaders(Exchange exchange) {
578            for (String key : exchange.getIn().getHeaders().keySet()) {
579                if (isRecipientHeader(key)) {
580                    return true;
581                }
582            }
583            return false;
584        }
585    
586        protected static boolean hasAlternativeBody(MailConfiguration configuration, Exchange exchange) {
587            return getAlternativeBody(configuration, exchange) != null;
588        }
589    
590        protected static String getAlternativeBody(MailConfiguration configuration, Exchange exchange) {
591            String alternativeBodyHeader = configuration.getAlternativeBodyHeader();
592            return exchange.getIn().getHeader(alternativeBodyHeader, java.lang.String.class);
593        }
594    
595        /**
596         * Is the given key a mime message recipient header (To, CC or BCC)
597         */
598        private static boolean isRecipientHeader(String key) {
599            if (Message.RecipientType.TO.toString().equalsIgnoreCase(key)) {
600                return true;
601            } else if (Message.RecipientType.CC.toString().equalsIgnoreCase(key)) {
602                return true;
603            } else if (Message.RecipientType.BCC.toString().equalsIgnoreCase(key)) {
604                return true;
605            }
606            return false;
607        }
608    
609        /**
610         * Returns the RecipientType object.
611         */
612        private static Message.RecipientType asRecipientType(String type) {
613            if (Message.RecipientType.TO.toString().equalsIgnoreCase(type)) {
614                return Message.RecipientType.TO;
615            } else if (Message.RecipientType.CC.toString().equalsIgnoreCase(type)) {
616                return Message.RecipientType.CC;
617            } else if (Message.RecipientType.BCC.toString().equalsIgnoreCase(type)) {
618                return Message.RecipientType.BCC;
619            }
620            throw new IllegalArgumentException("Unknown recipient type: " + type);
621        }
622    
623    
624        private static boolean empty(Address[] addresses) {
625            return addresses == null || addresses.length == 0;
626        }
627    
628        private static String asString(Exchange exchange, Object value) {
629            return exchange.getContext().getTypeConverter().convertTo(String.class, exchange, value);
630        }
631    
632    }