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.apache.commons.logging.Log;
052    import org.apache.commons.logging.LogFactory;
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 $Revision: 958535 $
059     */
060    public class MailBinding {
061    
062        private static final transient Log LOG = LogFactory.getLog(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 = 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                            // Parts marked with a disposition of Part.ATTACHMENT are clearly attachments
318                            CollectionHelper.appendValue(map, fileName, part.getDataHandler());
319                        }
320                    }
321                }
322            }
323        }
324    
325        /**
326         * Appends the Mail headers from the Camel {@link MailMessage}
327         */
328        protected void appendHeadersFromCamelMessage(MimeMessage mimeMessage, MailConfiguration configuration, Exchange exchange)
329            throws MessagingException {
330    
331            for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
332                String headerName = entry.getKey();
333                Object headerValue = entry.getValue();
334                if (headerValue != null) {
335                    if (headerFilterStrategy != null
336                            && !headerFilterStrategy.applyFilterToCamelHeaders(headerName, headerValue, exchange)) {
337                        if (headerName.equalsIgnoreCase("subject")) {
338                            mimeMessage.setSubject(asString(exchange, headerValue), IOConverter.getCharsetName(exchange, false));
339                            continue;
340                        }
341                        if (isRecipientHeader(headerName)) {
342                            // skip any recipients as they are handled specially
343                            continue;
344                        }
345    
346                        // alternative body should also be skipped
347                        if (headerName.equalsIgnoreCase(configuration.getAlternativeBodyHeader())) {
348                            // skip alternative body
349                            continue;
350                        }
351    
352                        // Mail messages can repeat the same header...
353                        if (ObjectConverter.isCollection(headerValue)) {
354                            Iterator iter = ObjectHelper.createIterator(headerValue);
355                            while (iter.hasNext()) {
356                                Object value = iter.next();
357                                mimeMessage.addHeader(headerName, asString(exchange, value));
358                            }
359                        } else {
360                            mimeMessage.setHeader(headerName, asString(exchange, headerValue));
361                        }
362                    }
363                }
364            }
365        }
366    
367        private void setRecipientFromCamelMessage(MimeMessage mimeMessage, Exchange exchange) throws MessagingException {
368            for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
369                String headerName = entry.getKey();
370                Object headerValue = entry.getValue();
371                if (headerValue != null && isRecipientHeader(headerName)) {
372                    // special handling of recipients
373                    if (ObjectConverter.isCollection(headerValue)) {
374                        Iterator iter = ObjectHelper.createIterator(headerValue);
375                        while (iter.hasNext()) {
376                            Object recipient = iter.next();
377                            appendRecipientToMimeMessage(mimeMessage, headerName, asString(exchange, recipient));
378                        }
379                    } else {
380                        appendRecipientToMimeMessage(mimeMessage, headerName, asString(exchange, headerValue));
381                    }
382                }
383            }
384        }
385    
386        /**
387         * Appends the Mail headers from the endpoint configuration.
388         */
389        protected void setRecipientFromEndpointConfiguration(MimeMessage mimeMessage, MailEndpoint endpoint)
390            throws MessagingException {
391    
392            Map<Message.RecipientType, String> recipients = endpoint.getConfiguration().getRecipients();
393            if (recipients.containsKey(Message.RecipientType.TO)) {
394                appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.TO.toString(), recipients.get(Message.RecipientType.TO));
395            }
396            if (recipients.containsKey(Message.RecipientType.CC)) {
397                appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.CC.toString(), recipients.get(Message.RecipientType.CC));
398            }
399            if (recipients.containsKey(Message.RecipientType.BCC)) {
400                appendRecipientToMimeMessage(mimeMessage, Message.RecipientType.BCC.toString(), recipients.get(Message.RecipientType.BCC));
401            }
402        }
403    
404        /**
405         * Appends the Mail attachments from the Camel {@link MailMessage}
406         */
407        protected void appendAttachmentsFromCamel(MimeMessage mimeMessage, MailConfiguration configuration, Exchange exchange)
408            throws MessagingException, IOException {
409    
410            // Put parts in message
411            mimeMessage.setContent(createMixedMultipartAttachments(configuration, exchange));
412        }
413    
414        private MimeMultipart createMixedMultipartAttachments(MailConfiguration configuration, Exchange exchange)
415            throws MessagingException, IOException {
416    
417            // fill the body with text
418            MimeMultipart multipart = new MimeMultipart();
419            multipart.setSubType("mixed");
420            addBodyToMultipart(configuration, multipart, exchange);
421            String partDisposition = configuration.isUseInlineAttachments() ? Part.INLINE : Part.ATTACHMENT;
422            if (exchange.getIn().hasAttachments()) {
423                addAttachmentsToMultipart(multipart, partDisposition, exchange);
424            }
425            return multipart;
426        }
427    
428        protected void addAttachmentsToMultipart(MimeMultipart multipart, String partDisposition, Exchange exchange) throws MessagingException {
429            LOG.trace("Adding attachments +++ start +++");
430            int i = 0;
431            for (Map.Entry<String, DataHandler> entry : exchange.getIn().getAttachments().entrySet()) {
432                String attachmentFilename = entry.getKey();
433                DataHandler handler = entry.getValue();
434    
435                if (LOG.isTraceEnabled()) {
436                    LOG.trace("Attachment #" + i + ": Disposition: " + partDisposition);
437                    LOG.trace("Attachment #" + i + ": DataHandler: " + handler);
438                    LOG.trace("Attachment #" + i + ": FileName: " + attachmentFilename);
439                }
440                if (handler != null) {
441                    if (shouldAddAttachment(exchange, attachmentFilename, handler)) {
442                        // Create another body part
443                        BodyPart messageBodyPart = new MimeBodyPart();
444                        // Set the data handler to the attachment
445                        messageBodyPart.setDataHandler(handler);
446    
447                        if (attachmentFilename.toLowerCase().startsWith("cid:")) {
448                            // add a Content-ID header to the attachment
449                            // must use angle brackets according to RFC: http://www.ietf.org/rfc/rfc2392.txt
450                            messageBodyPart.addHeader("Content-ID", "<" + attachmentFilename.substring(4) + ">");
451                            // Set the filename without the cid
452                            messageBodyPart.setFileName(attachmentFilename.substring(4));
453                        } else {
454                            // Set the filename
455                            messageBodyPart.setFileName(attachmentFilename);
456                        }
457    
458                        LOG.trace("Attachment #" + i + ": ContentType: " + messageBodyPart.getContentType());
459    
460                        if (contentTypeResolver != null) {
461                            String contentType = contentTypeResolver.resolveContentType(attachmentFilename);
462                            LOG.trace("Attachment #" + i + ": Using content type resolver: " + contentTypeResolver + " resolved content type as: " + contentType);
463                            if (contentType != null) {
464                                String value = contentType + "; name=" + attachmentFilename;
465                                messageBodyPart.setHeader("Content-Type", value);
466                                LOG.trace("Attachment #" + i + ": ContentType: " + messageBodyPart.getContentType());
467                            }
468                        }
469    
470                        // Set Disposition
471                        messageBodyPart.setDisposition(partDisposition);
472                        // Add part to multipart
473                        multipart.addBodyPart(messageBodyPart);
474                    } else {
475                        LOG.trace("shouldAddAttachment: false");
476                    }
477                } else {
478                    LOG.warn("Cannot add attachment: " + attachmentFilename + " as DataHandler is null");
479                }
480                i++;
481            }
482            LOG.trace("Adding attachments +++ done +++");
483        }
484    
485        protected void createMultipartAlternativeMessage(MimeMessage mimeMessage, MailConfiguration configuration, Exchange exchange)
486            throws MessagingException, IOException {
487    
488            MimeMultipart multipartAlternative = new MimeMultipart("alternative");
489            mimeMessage.setContent(multipartAlternative);
490    
491            MimeBodyPart plainText = new MimeBodyPart();
492            plainText.setText(getAlternativeBody(configuration, exchange), determineCharSet(configuration, exchange));
493            // remove the header with the alternative mail now that we got it
494            // otherwise it might end up twice in the mail reader
495            exchange.getIn().removeHeader(configuration.getAlternativeBodyHeader());
496            multipartAlternative.addBodyPart(plainText);
497    
498            // if there are no attachments, add the body to the same mulitpart message
499            if (!exchange.getIn().hasAttachments()) {
500                addBodyToMultipart(configuration, multipartAlternative, exchange);
501            } else {
502                // if there are attachments, but they aren't set to be inline, add them to
503                // treat them as normal. It will append a multipart-mixed with the attachments and the body text
504                if (!configuration.isUseInlineAttachments()) {
505                    BodyPart mixedAttachments = new MimeBodyPart();
506                    mixedAttachments.setContent(createMixedMultipartAttachments(configuration, exchange));
507                    multipartAlternative.addBodyPart(mixedAttachments);
508                } else {
509                    // if the attachments are set to be inline, attach them as inline attachments
510                    MimeMultipart multipartRelated = new MimeMultipart("related");
511                    BodyPart related = new MimeBodyPart();
512    
513                    related.setContent(multipartRelated);
514                    multipartAlternative.addBodyPart(related);
515    
516                    addBodyToMultipart(configuration, multipartRelated, exchange);
517    
518                    addAttachmentsToMultipart(multipartRelated, Part.INLINE, exchange);
519                }
520            }
521        }
522    
523        protected void addBodyToMultipart(MailConfiguration configuration, MimeMultipart activeMultipart, Exchange exchange)
524            throws MessagingException, IOException {
525    
526            BodyPart bodyMessage = new MimeBodyPart();
527            populateContentOnBodyPart(bodyMessage, configuration, exchange);
528            activeMultipart.addBodyPart(bodyMessage);
529        }
530    
531        /**
532         * Strategy to allow filtering of attachments which are added on the Mail message
533         */
534        protected boolean shouldAddAttachment(Exchange exchange, String attachmentFilename, DataHandler handler) {
535            return true;
536        }
537    
538        protected Map<String, Object> extractHeadersFromMail(Message mailMessage, Exchange exchange) throws MessagingException {
539            Map<String, Object> answer = new HashMap<String, Object>();
540            Enumeration names = mailMessage.getAllHeaders();
541    
542            while (names.hasMoreElements()) {
543                Header header = (Header) names.nextElement();
544                String value = header.getValue();
545                if (headerFilterStrategy != null && !headerFilterStrategy.applyFilterToExternalHeaders(header.getName(), value, exchange)) {
546                    CollectionHelper.appendValue(answer, header.getName(), value);
547                }
548            }
549    
550            return answer;
551        }
552    
553        private static void appendRecipientToMimeMessage(MimeMessage mimeMessage, String type, String recipient)
554            throws MessagingException {
555    
556            // we support that multi recipient can be given as a string separated by comma or semicolon
557            String[] lines = recipient.split("[,;]");
558            for (String line : lines) {
559                line = line.trim();
560                mimeMessage.addRecipients(asRecipientType(type), line);
561            }
562        }
563    
564        /**
565         * Does the given camel message contain any To, CC or BCC header names?
566         */
567        private static boolean hasRecipientHeaders(Exchange exchange) {
568            for (String key : exchange.getIn().getHeaders().keySet()) {
569                if (isRecipientHeader(key)) {
570                    return true;
571                }
572            }
573            return false;
574        }
575    
576        protected static boolean hasAlternativeBody(MailConfiguration configuration, Exchange exchange) {
577            return getAlternativeBody(configuration, exchange) != null;
578        }
579    
580        protected static String getAlternativeBody(MailConfiguration configuration, Exchange exchange) {
581            String alternativeBodyHeader = configuration.getAlternativeBodyHeader();
582            return exchange.getIn().getHeader(alternativeBodyHeader, java.lang.String.class);
583        }
584    
585        /**
586         * Is the given key a mime message recipient header (To, CC or BCC)
587         */
588        private static boolean isRecipientHeader(String key) {
589            if (Message.RecipientType.TO.toString().equalsIgnoreCase(key)) {
590                return true;
591            } else if (Message.RecipientType.CC.toString().equalsIgnoreCase(key)) {
592                return true;
593            } else if (Message.RecipientType.BCC.toString().equalsIgnoreCase(key)) {
594                return true;
595            }
596            return false;
597        }
598    
599        /**
600         * Returns the RecipientType object.
601         */
602        private static Message.RecipientType asRecipientType(String type) {
603            if (Message.RecipientType.TO.toString().equalsIgnoreCase(type)) {
604                return Message.RecipientType.TO;
605            } else if (Message.RecipientType.CC.toString().equalsIgnoreCase(type)) {
606                return Message.RecipientType.CC;
607            } else if (Message.RecipientType.BCC.toString().equalsIgnoreCase(type)) {
608                return Message.RecipientType.BCC;
609            }
610            throw new IllegalArgumentException("Unknown recipient type: " + type);
611        }
612    
613    
614        private static boolean empty(Address[] addresses) {
615            return addresses == null || addresses.length == 0;
616        }
617    
618        private static String asString(Exchange exchange, Object value) {
619            return exchange.getContext().getTypeConverter().convertTo(String.class, exchange, value);
620        }
621    
622    }