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