diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java index 037c6cf3dc..03f2c2ff22 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java @@ -16,9 +16,12 @@ package org.springframework.integration.mail; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Properties; import javax.mail.Authenticator; @@ -27,6 +30,8 @@ import javax.mail.Flags; import javax.mail.Folder; import javax.mail.Message; import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Part; import javax.mail.Session; import javax.mail.Store; import javax.mail.URLName; @@ -40,7 +45,10 @@ import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; /** * Base class for {@link MailReceiver} implementations. @@ -88,10 +96,14 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl private volatile Expression selectorExpression; + private volatile HeaderMapper headerMapper; + protected volatile boolean initialized; private volatile String userFlag = DEFAULT_SI_USER_FLAG; + private volatile boolean embeddedPartsAsBytes = true; + public AbstractMailReceiver() { this.url = null; } @@ -205,6 +217,33 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl this.userFlag = userFlag; } + /** + * Set the header mapper; if a header mapper is not provided, the message payload is + * a {@link MimeMessage}, when provided, the headers are mapped and the payload is + * the {@link MimeMessage} content. + * @param headerMapper the header mapper. + * @since 4.3 + * @see #setEmbeddedPartsAsBytes(boolean) + */ + public void setHeaderMapper(HeaderMapper headerMapper) { + this.headerMapper = headerMapper; + } + + /** + * When a header mapper is provided determine whether an embedded {@link Part} (e.g + * {@link Message} or {@link Multipart} content is rendered as a byte[] in the + * payload. Otherwise, leave as a {@link Part}. These objects are not suitable for + * downstream serialization. Default: true. + *

This has no effect if there is no header mapper, in that case the payload is the + * {@link MimeMessage}. + * @param embeddedPartsAsBytes the embeddedPartsAsBytes to set. + * @since 4.3 + * @see #setHeaderMapper(HeaderMapper) + */ + public void setEmbeddedPartsAsBytes(boolean embeddedPartsAsBytes) { + this.embeddedPartsAsBytes = embeddedPartsAsBytes; + } + protected Folder getFolder() { return this.folder; } @@ -274,7 +313,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl } @Override - public Message[] receive() throws javax.mail.MessagingException { + public Object[] receive() throws javax.mail.MessagingException { synchronized (this.folderMonitor) { try { this.openFolder(); @@ -298,11 +337,25 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl this.logger.debug("Received " + messages.length + " messages"); } - Message[] filteredMessages = filterMessagesThruSelector(messages); + MimeMessage[] filteredMessages = filterMessagesThruSelector(messages); postProcessFilteredMessages(filteredMessages); - return filteredMessages; + if (this.headerMapper != null) { + org.springframework.messaging.Message[] converted = + new org.springframework.messaging.Message[filteredMessages.length]; + int n = 0; + for (MimeMessage message : filteredMessages) { + Map headers = this.headerMapper.toHeaders(message); + converted[n++] = getMessageBuilderFactory().withPayload(extractContent(message, headers)) + .copyHeaders(headers) + .build(); + } + return converted; + } + else { + return filteredMessages; + } } finally { MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages); @@ -310,16 +363,58 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl } } + private Object extractContent(MimeMessage message, Map headers) { + Object content; + try { + content = message.getContent(); + if (content instanceof String) { + String mailContentType = (String) headers.get(MailHeaders.CONTENT_TYPE); + if (mailContentType != null && mailContentType.toLowerCase().startsWith("text")) { + headers.put(MessageHeaders.CONTENT_TYPE, mailContentType); + } + else { + headers.put(MessageHeaders.CONTENT_TYPE, "text/plain"); + } + } + else if (content instanceof InputStream) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + FileCopyUtils.copy((InputStream) content, baos); + content = byteArrayToContent(headers, baos); + } + else if (content instanceof Multipart && this.embeddedPartsAsBytes) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((Multipart) content).writeTo(baos); + content = byteArrayToContent(headers, baos); + } + else if (content instanceof Part && this.embeddedPartsAsBytes) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((Part) content).writeTo(baos); + content = byteArrayToContent(headers, baos); + } + return content; + } + catch (Exception e) { + throw new org.springframework.messaging.MessagingException("Failed to extract content from " + message, e); + } + } + + private Object byteArrayToContent(Map headers, ByteArrayOutputStream baos) { + headers.put(MessageHeaders.CONTENT_TYPE, "application/octet-stream"); + return baos.toByteArray(); + } + private void postProcessFilteredMessages(Message[] filteredMessages) throws MessagingException { setMessageFlags(filteredMessages); if (shouldDeleteMessages()) { deleteMessages(filteredMessages); } - // Copy messages to cause an eager fetch - for (int i = 0; i < filteredMessages.length; i++) { - MimeMessage mimeMessage = new IntegrationMimeMessage((MimeMessage) filteredMessages[i]); - filteredMessages[i] = mimeMessage; + if (this.headerMapper == null) { + // Copy messages to cause an eager fetch + for (int i = 0; i < filteredMessages.length; i++) { + MimeMessage mimeMessage = new IntegrationMimeMessage((MimeMessage) filteredMessages[i]); + filteredMessages[i] = mimeMessage; + } } } @@ -358,8 +453,8 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl * Will filter Messages thru selector. Messages that did not pass selector filtering criteria * will be filtered out and remain on the server as never touched. */ - private Message[] filterMessagesThruSelector(Message[] messages) throws MessagingException { - List filteredMessages = new LinkedList(); + private MimeMessage[] filterMessagesThruSelector(Message[] messages) throws MessagingException { + List filteredMessages = new LinkedList(); for (int i = 0; i < messages.length; i++) { MimeMessage message = (MimeMessage) messages[i]; if (this.selectorExpression != null) { @@ -377,7 +472,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl filteredMessages.add(message); } } - return filteredMessages.toArray(new Message[filteredMessages.size()]); + return filteredMessages.toArray(new MimeMessage[filteredMessages.size()]); } /** diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapIdleChannelAdapter.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapIdleChannelAdapter.java index cfe9f8beaa..45e09d7eb6 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapIdleChannelAdapter.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapIdleChannelAdapter.java @@ -182,12 +182,15 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport implements Be } } - private Runnable createMessageSendingTask(final Message mailMessage) { + private Runnable createMessageSendingTask(final Object mailMessage) { Runnable sendingTask = new Runnable() { @Override public void run() { + @SuppressWarnings("unchecked") org.springframework.messaging.Message message = - ImapIdleChannelAdapter.this.getMessageBuilderFactory().withPayload(mailMessage).build(); + mailMessage instanceof Message + ? ImapIdleChannelAdapter.this.getMessageBuilderFactory().withPayload(mailMessage).build() + : (org.springframework.messaging.Message) mailMessage; if (TransactionSynchronizationManager.isActualTransactionActive()) { if (ImapIdleChannelAdapter.this.transactionSynchronizationFactory != null) { @@ -267,11 +270,11 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport implements Be } ImapIdleChannelAdapter.this.mailReceiver.waitForNewMessages(); if (ImapIdleChannelAdapter.this.mailReceiver.getFolder().isOpen()) { - Message[] mailMessages = ImapIdleChannelAdapter.this.mailReceiver.receive(); + Object[] mailMessages = ImapIdleChannelAdapter.this.mailReceiver.receive(); if (logger.isDebugEnabled()) { logger.debug("received " + mailMessages.length + " mail messages"); } - for (final Message mailMessage : mailMessages) { + for (final Object mailMessage : mailMessages) { Runnable messageSendingTask = createMessageSendingTask(mailMessage); diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailHeaders.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailHeaders.java index 84fbd9bf70..4de118f8a7 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailHeaders.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailHeaders.java @@ -21,8 +21,9 @@ package org.springframework.integration.mail; * Message attributes from/to integration Message Headers. * * @author Mark Fisher + * @author Gary Russell */ -public abstract class MailHeaders { +public final class MailHeaders { public static final String PREFIX = "mail_"; @@ -44,4 +45,21 @@ public abstract class MailHeaders { public static final String CONTENT_TYPE = PREFIX + "contentType"; + public static final String RAW_HEADERS = PREFIX + "raw"; + + public static final String FLAGS = PREFIX + "flags"; + + public static final String LINE_COUNT = PREFIX + "lineCount"; + + public static final String RECEIVED_DATE = PREFIX + "receivedDate"; + + public static final String SIZE = PREFIX + "size"; + + public static final String EXPUNGED = PREFIX + "expunged"; + + private MailHeaders() { + // empty + } + + } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceiver.java index f2dae17b84..afc60ab8e4 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceiver.java @@ -16,12 +16,6 @@ package org.springframework.integration.mail; -import javax.mail.Folder; -import javax.mail.Message; - -import org.springframework.util.Assert; - - /** * Strategy interface for receiving mail {@link javax.mail.Message Messages}. * @@ -30,30 +24,6 @@ import org.springframework.util.Assert; */ public interface MailReceiver { - javax.mail.Message[] receive() throws javax.mail.MessagingException; - - class MailReceiverContext { - - private final Folder folder; - - private volatile Message[] messages = new Message[0]; - - MailReceiverContext(Folder folder) { - this.folder = folder; - } - - Message[] getMessages() { - return this.messages; - } - - void setMessages(Message[] messages) { - Assert.noNullElements(messages, "messages cannot be null"); - this.messages = messages; - } - - Folder getFolder() { - return this.folder; - } - } + Object[] receive() throws javax.mail.MessagingException; } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java index bb41a16ccc..d98300cf8f 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java @@ -46,14 +46,14 @@ import org.springframework.util.Assert; * @author Oleg Zhurakousky * @author Artem Bilan */ -public class MailReceivingMessageSource implements MessageSource, +public class MailReceivingMessageSource implements MessageSource, BeanFactoryAware, BeanNameAware, NamedComponent { private final Log logger = LogFactory.getLog(this.getClass()); private final MailReceiver mailReceiver; - private final Queue mailQueue = new ConcurrentLinkedQueue(); + private final Queue mailQueue = new ConcurrentLinkedQueue(); private volatile BeanFactory beanFactory; @@ -103,12 +103,13 @@ public class MailReceivingMessageSource implements MessageSource receive() { + public Message receive() { try { - javax.mail.Message mailMessage = this.mailQueue.poll(); + Object mailMessage = this.mailQueue.poll(); if (mailMessage == null) { - javax.mail.Message[] messages = this.mailReceiver.receive(); + Object[] messages = this.mailReceiver.receive(); if (messages != null) { this.mailQueue.addAll(Arrays.asList(messages)); } @@ -118,7 +119,12 @@ public class MailReceivingMessageSource implements MessageSource) mailMessage; + } + else { + return getMessageBuilderFactory().withPayload(mailMessage).build(); + } } } catch (Exception e) { diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java index c1cc30cb3f..e3d7dcc700 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java @@ -78,7 +78,8 @@ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser { } else { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "java-mail-properties"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "authenticator", "javaMailAuthenticator"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "authenticator", + "javaMailAuthenticator"); } IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "max-fetch-size"); receiverBuilder.addPropertyValue("shouldDeleteMessages", element.getAttribute("should-delete-messages")); @@ -95,6 +96,8 @@ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser { expressionDef.getConstructorArgumentValues().addGenericArgumentValue(selectorExpression); receiverBuilder.addPropertyValue("selectorExpression", expressionDef); } + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "header-mapper"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes"); return receiverBuilder.getBeanDefinition(); } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java index 800dc247e8..6d4fe5582c 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java @@ -93,6 +93,8 @@ public class MailInboundChannelAdapterParser extends AbstractPollingInboundChann expressionDef.getConstructorArgumentValues().addGenericArgumentValue(selectorExpression); receiverBuilder.addPropertyValue("selectorExpression", expressionDef); } + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "header-mapper"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes"); return receiverBuilder.getBeanDefinition(); } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java index 4d86184e94..37f6e8eef5 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java @@ -21,6 +21,7 @@ import java.util.Properties; import javax.mail.Authenticator; import javax.mail.Session; import javax.mail.URLName; +import javax.mail.internet.MimeMessage; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -36,6 +37,7 @@ import org.springframework.integration.mail.ImapMailReceiver; import org.springframework.integration.mail.MailReceiver; import org.springframework.integration.mail.Pop3MailReceiver; import org.springframework.integration.mail.SearchTermStrategy; +import org.springframework.integration.mapping.HeaderMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -79,6 +81,10 @@ public class MailReceiverFactoryBean implements FactoryBean, Dispo private volatile BeanFactory beanFactory; + private volatile HeaderMapper headerMapper; + + private Boolean embeddedPartsAsBytes; + public void setStoreUri(String storeUri) { this.storeUri = storeUri; } @@ -127,6 +133,14 @@ public class MailReceiverFactoryBean implements FactoryBean, Dispo this.userFlag = userFlag; } + public void setHeaderMapper(HeaderMapper headerMapper) { + this.headerMapper = headerMapper; + } + + public void setEmbeddedPartsAsBytes(Boolean embeddedPartsAsBytes) { + this.embeddedPartsAsBytes = embeddedPartsAsBytes; + } + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; @@ -210,6 +224,12 @@ public class MailReceiverFactoryBean implements FactoryBean, Dispo if (this.beanFactory != null) { receiver.setBeanFactory(this.beanFactory); } + if (this.headerMapper != null) { + receiver.setHeaderMapper(this.headerMapper); + } + if (this.embeddedPartsAsBytes != null) { + receiver.setEmbeddedPartsAsBytes(this.embeddedPartsAsBytes); + } receiver.afterPropertiesSet(); return receiver; } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/DefaultMailHeaderMapper.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/DefaultMailHeaderMapper.java new file mode 100644 index 0000000000..fc2d7dc48a --- /dev/null +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/DefaultMailHeaderMapper.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.mail.support; + +import java.util.Date; +import java.util.Enumeration; +import java.util.Map; + +import javax.mail.Header; +import javax.mail.internet.MimeMessage; + +import org.springframework.integration.mail.MailHeaders; +import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.MessagingException; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Maps an inbound {@link MimeMessage} to a {@link Map}. + * + * @author Gary Russell + * @since 4.3 + * + */ +public class DefaultMailHeaderMapper implements HeaderMapper { + + @Override + public void fromHeaders(MessageHeaders headers, MimeMessage target) { + throw new UnsupportedOperationException("Mapping to a mail message is not currently supported"); + } + + @Override + public Map toHeaders(MimeMessage source) { + Map headers = MailUtils.extractStandardHeaders(source); + try { + Enumeration allHeaders = source.getAllHeaders(); + MultiValueMap rawHeaders = new LinkedMultiValueMap(); + while (allHeaders.hasMoreElements()) { + Object headerInstance = allHeaders.nextElement(); + if (headerInstance instanceof Header) { + Header header = (Header) headerInstance; + rawHeaders.add(header.getName(), header.getValue()); + } + } + headers.put(MailHeaders.RAW_HEADERS, rawHeaders); + headers.put(MailHeaders.FLAGS, source.getFlags()); + int lineCount = source.getLineCount(); + if (lineCount > 0) { + headers.put(MailHeaders.LINE_COUNT, lineCount); + } + Date receivedDate = source.getReceivedDate(); + if (receivedDate != null) { + headers.put(MailHeaders.RECEIVED_DATE, receivedDate); + } + int size = source.getSize(); + if (size > 0) { + headers.put(MailHeaders.SIZE, size); + } + headers.put(MailHeaders.EXPUNGED, source.isExpunged()); + headers.put(MailHeaders.CONTENT_TYPE, source.getContentType()); + } + catch (Exception e) { + throw new MessagingException("Failed to map message headers", e); + } + return headers; + } + +} diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/MailUtils.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/MailUtils.java new file mode 100644 index 0000000000..9e41dee4b3 --- /dev/null +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/MailUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.mail.support; + +import java.util.HashMap; +import java.util.Map; + +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.Message.RecipientType; + +import org.springframework.integration.mail.MailHeaders; +import org.springframework.messaging.MessagingException; +import org.springframework.util.Assert; + +/** + * Utilities for handling mail messages. + * + * @author Gary Russell + * @since 4.3 + * + */ +public final class MailUtils { + + private MailUtils() { + // empty + } + + /** + * Map the message headers to a Map using {@link MailHeaders} keys; specifically + * maps the address headers and the subject. + * @param source the message. + * @return the map. + */ + public static Map extractStandardHeaders(Message source) { + Map headers = new HashMap(); + try { + headers.put(MailHeaders.FROM, convertToString(source.getFrom())); + headers.put(MailHeaders.BCC, convertToStringArray(source.getRecipients(RecipientType.BCC))); + headers.put(MailHeaders.CC, convertToStringArray(source.getRecipients(RecipientType.CC))); + headers.put(MailHeaders.TO, convertToStringArray(source.getRecipients(RecipientType.TO))); + headers.put(MailHeaders.REPLY_TO, convertToString(source.getReplyTo())); + headers.put(MailHeaders.SUBJECT, source.getSubject()); + return headers; + } + catch (Exception e) { + throw new MessagingException("conversion of MailMessage headers failed", e); + } + } + + private static String convertToString(Address[] addresses) { + if (addresses == null || addresses.length == 0) { + return null; + } + Assert.state(addresses.length == 1, "expected a single value but received an Array"); + return addresses[0].toString(); + } + + private static String[] convertToStringArray(Address[] addresses) { + if (addresses != null) { + String[] addressStrings = new String[addresses.length]; + for (int i = 0; i < addresses.length; i++) { + addressStrings[i] = addresses[i].toString(); + } + return addressStrings; + } + return new String[0]; + } + +} diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/package-info.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/package-info.java new file mode 100644 index 0000000000..30c2844727 --- /dev/null +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides classes to support email. + */ +package org.springframework.integration.mail.support; diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/AbstractMailMessageTransformer.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/AbstractMailMessageTransformer.java index eded654b7f..492d0ff614 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/AbstractMailMessageTransformer.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/AbstractMailMessageTransformer.java @@ -16,18 +16,14 @@ package org.springframework.integration.mail.transformer; -import java.util.HashMap; import java.util.Map; -import javax.mail.Address; -import javax.mail.Message.RecipientType; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.integration.mail.MailHeaders; +import org.springframework.integration.mail.support.MailUtils; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.MessageBuilderFactory; @@ -35,8 +31,6 @@ import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.integration.transformer.MessageTransformationException; import org.springframework.integration.transformer.Transformer; import org.springframework.messaging.Message; -import org.springframework.messaging.MessagingException; -import org.springframework.util.Assert; /** * Base class for Transformers that convert from a JavaMail Message to a @@ -99,38 +93,7 @@ public abstract class AbstractMailMessageTransformer implements Transformer, private Map extractHeaderMapFromMailMessage(javax.mail.Message mailMessage) { - try { - Map headers = new HashMap(); - headers.put(MailHeaders.FROM, this.convertToString(mailMessage.getFrom())); - headers.put(MailHeaders.BCC, this.convertToStringArray(mailMessage.getRecipients(RecipientType.BCC))); - headers.put(MailHeaders.CC, this.convertToStringArray(mailMessage.getRecipients(RecipientType.CC))); - headers.put(MailHeaders.TO, this.convertToStringArray(mailMessage.getRecipients(RecipientType.TO))); - headers.put(MailHeaders.REPLY_TO, this.convertToString(mailMessage.getReplyTo())); - headers.put(MailHeaders.SUBJECT, mailMessage.getSubject()); - return headers; - } - catch (Exception e) { - throw new MessagingException("conversion of MailMessage headers failed", e); - } - } - - private String convertToString(Address[] addresses) { - if (addresses == null || addresses.length == 0) { - return null; - } - Assert.state(addresses.length == 1, "expected a single value but received an Array"); - return addresses[0].toString(); - } - - private String[] convertToStringArray(Address[] addresses) { - if (addresses != null) { - String[] addressStrings = new String[addresses.length]; - for (int i = 0; i < addresses.length; i++) { - addressStrings[i] = addresses[i].toString(); - } - return addressStrings; - } - return new String[0]; + return MailUtils.extractStandardHeaders(mailMessage); } } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/MailToStringTransformer.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/MailToStringTransformer.java index 22635984d3..75bfef56d3 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/MailToStringTransformer.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/transformer/MailToStringTransformer.java @@ -20,15 +20,16 @@ import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; import javax.mail.Multipart; +import javax.mail.Part; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.util.Assert; /** - * Transforms a Message payload of type {@link javax.mail.Message} to a String. - * If the mail message's content is a String, it will be the payload of the - * result Message. If the content is a Multipart, a String will be created from - * an output stream of bytes using the provided charset (or UTF-8 by default). + * Transforms a Message payload of type {@link javax.mail.Message} to a String. If the + * mail message's content is a String, it will be the payload of the result Message. If + * the content is a Part or Multipart, a String will be created from an output stream of + * bytes using the provided charset (or UTF-8 by default). * * @author Mark Fisher * @author Gary Russell @@ -62,6 +63,12 @@ public class MailToStringTransformer extends AbstractMailMessageTransformer + @@ -363,4 +364,38 @@ + + + + ' bean. + When not supplied, the message payload will be the raw MimeMessage with no header mapping. + When a mapper is provided, the mail headers are mapped to 'MessageHeaders' and the payload + will depend on the email contents, and the setting of 'embedded-parts-as-bytes'. The framework + provides a 'DefaultMailHeaderMapper'; see the reference manual regarding the headers mapped + and the payload types. + ]]> + + + + + + + + + + + When a 'header-mapper' is provided, and the 'MimeMessage' has embedded 'Part' (e.g. 'Message' or + 'Multipart') contents, the message payload will be a byte[] containing the raw data. Set this + boolean to 'false' for the payload to be a decoded 'Part' object. Note that 'Part's are not + 'Serializable', nor are they suitable for serialization using other technologies such as Kryo. + Default 'true' (payload is 'byte[]'). + + + + + + + + diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java index 30686d86dc..623fb2d4a7 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java @@ -16,10 +16,13 @@ package org.springframework.integration.mail; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -29,7 +32,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.io.OutputStream; +import java.io.IOException; import java.lang.reflect.Field; import java.util.Properties; import java.util.concurrent.CountDownLatch; @@ -80,15 +83,15 @@ import org.springframework.integration.history.MessageHistory; import org.springframework.integration.mail.ImapIdleChannelAdapter.ImapIdleExceptionEvent; import org.springframework.integration.mail.PoorMansMailServer.ImapServer; import org.springframework.integration.mail.config.ImapIdleChannelAdapterParserTests; +import org.springframework.integration.mail.support.DefaultMailHeaderMapper; import org.springframework.integration.test.support.LongRunningIntegrationTest; import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.PollableChannel; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.util.FileCopyUtils; import com.sun.mail.imap.IMAPFolder; -import com.sun.mail.imap.IMAPMessage; /** * @author Oleg Zhurakousky @@ -136,18 +139,26 @@ public class ImapMailReceiverTests { } } }); - testIdleWithServerGuts(receiver); + testIdleWithServerGuts(receiver, false); } @Test public void testIdleWithServerDefaultSearch() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX"); - testIdleWithServerGuts(receiver); + testIdleWithServerGuts(receiver, false); assertTrue(imapIdleServer.assertReceived("searchWithUserFlag")); } - public void testIdleWithServerGuts(ImapMailReceiver receiver) throws MessagingException { + @Test + public void testIdleWithMessageMapping() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + + "/INBOX"); + receiver.setHeaderMapper(new DefaultMailHeaderMapper()); + testIdleWithServerGuts(receiver, true); + } + + public void testIdleWithServerGuts(ImapMailReceiver receiver, boolean mapped) throws MessagingException { imapIdleServer.resetServer(); Properties mailProps = new Properties(); mailProps.put("mail.debug", "true"); @@ -168,12 +179,23 @@ public class ImapMailReceiverTests { adapter.setOutputChannel(channel); adapter.setTaskScheduler(taskScheduler); adapter.start(); - @SuppressWarnings("unchecked") - org.springframework.messaging.Message received = - (org.springframework.messaging.Message) channel.receive(10000); - assertNotNull(received); - assertNotNull(received.getPayload().getReceivedDate()); - assertTrue(received.getPayload().getLineCount() > -1); + if (!mapped) { + @SuppressWarnings("unchecked") + org.springframework.messaging.Message received = + (org.springframework.messaging.Message) channel.receive(10000); + assertNotNull(received); + assertNotNull(received.getPayload().getReceivedDate()); + assertTrue(received.getPayload().getLineCount() > -1); + } + else { + org.springframework.messaging.Message received = channel.receive(10000); + assertNotNull(received); + assertNotNull(received.getHeaders().get(MailHeaders.RAW_HEADERS)); + assertThat((String) received.getHeaders().get(MailHeaders.CONTENT_TYPE), + equalTo("TEXT/PLAIN; charset=ISO-8859-1")); + assertThat((String) received.getHeaders().get(MessageHeaders.CONTENT_TYPE), + equalTo("TEXT/PLAIN; charset=ISO-8859-1")); + } assertNotNull(channel.receive(10000)); // new message after idle assertNull(channel.receive(10000)); // no new message after second and third idle verify(logger).debug("Canceling IDLE"); @@ -838,12 +860,52 @@ public class ImapMailReceiverTests { @Test public void testAttachments() throws Exception { final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); + Folder folder = testAttachmentsGuts(receiver); + Message[] messages = (Message[]) receiver.receive(); + Object content = messages[0].getContent(); + assertEquals("bar", ((Multipart) content).getBodyPart(0).getContent().toString().trim()); + assertEquals("foo", ((Multipart) content).getBodyPart(1).getContent().toString().trim()); + + assertSame(folder, messages[0].getFolder()); + } + + @Test + public void testAttachmentsWithMappingMultiAsBytes() throws Exception { + final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); + receiver.setHeaderMapper(new DefaultMailHeaderMapper()); + testAttachmentsGuts(receiver); + org.springframework.messaging.Message[] messages = (org.springframework.messaging.Message[]) receiver + .receive(); + org.springframework.messaging.Message received = messages[0]; + Object content = received.getPayload(); + assertThat(content, instanceOf(byte[].class)); + assertThat((String) received.getHeaders().get(MailHeaders.CONTENT_TYPE), + equalTo("multipart/mixed;\r\n boundary=\"------------040903000701040401040200\"")); + assertThat((String) received.getHeaders().get(MessageHeaders.CONTENT_TYPE), + equalTo("application/octet-stream")); + } + + @Test + public void testAttachmentsWithMapping() throws Exception { + final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); + receiver.setHeaderMapper(new DefaultMailHeaderMapper()); + receiver.setEmbeddedPartsAsBytes(false); + testAttachmentsGuts(receiver); + org.springframework.messaging.Message[] messages = (org.springframework.messaging.Message[]) receiver + .receive(); + Object content = messages[0].getPayload(); + assertThat(content, instanceOf(Multipart.class)); + assertEquals("bar", ((Multipart) content).getBodyPart(0).getContent().toString().trim()); + assertEquals("foo", ((Multipart) content).getBodyPart(1).getContent().toString().trim()); + } + + private Folder testAttachmentsGuts(final ImapMailReceiver receiver) throws MessagingException, IOException { Store store = mock(Store.class); Folder folder = mock(Folder.class); when(folder.exists()).thenReturn(true); when(folder.isOpen()).thenReturn(true); - IMAPMessage message = mock(IMAPMessage.class); + Message message = new MimeMessage(null, new ClassPathResource("test.mail").getInputStream()); when(folder.search((SearchTerm) Mockito.any())).thenReturn(new Message[]{message}); when(store.getFolder(Mockito.any(URLName.class))).thenReturn(folder); when(folder.getPermanentFlags()).thenReturn(new Flags(Flags.Flag.USER)); @@ -852,20 +914,7 @@ public class ImapMailReceiverTests { receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); - doAnswer(new Answer() { - - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - OutputStream os = (OutputStream) invocation.getArguments()[0]; - FileCopyUtils.copy(new ClassPathResource("test.mail").getInputStream(), os); - return null; - } - }).when(message).writeTo(Mockito.any(OutputStream.class)); - Message[] messages = receiver.receive(); - Object content = messages[0].getContent(); - assertEquals("bar", ((Multipart) content).getBodyPart(0).getContent().toString().trim()); - - assertSame(folder, messages[0].getFolder()); + return folder; } @Test @@ -922,10 +971,10 @@ public class ImapMailReceiverTests { } ImapMailReceiver receiver = new TestReceiver(); - Message[] received = receiver.receive(); + Message[] received = (Message[]) receiver.receive(); assertEquals(1, received.length); assertSame(message1, received[0]); - received = receiver.receive(); + received = (Message[]) receiver.receive(); assertEquals(1, received.length); assertSame(messages2, received); assertSame(message2, received[0]); diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailReceivingMessageSourceTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailReceivingMessageSourceTests.java index b34c954401..8919c5ef2f 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailReceivingMessageSourceTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailReceivingMessageSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ public class MailReceivingMessageSourceTests { private final ConcurrentLinkedQueue messages = new ConcurrentLinkedQueue(); + @Override public javax.mail.Message[] receive() { return messages.poll(); } @@ -74,16 +75,6 @@ public class MailReceivingMessageSourceTests { public void stop() { } - public MailReceiverContext getTransactionContext() { - return null; - } - - public void closeContextAfterSuccess(MailReceiverContext context) { - } - - public void closeContextAfterFailure(MailReceiverContext context) { - } - } } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailTestsHelper.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailTestsHelper.java index 8ed680bfcb..ae4815202e 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailTestsHelper.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/MailTestsHelper.java @@ -16,13 +16,7 @@ package org.springframework.integration.mail; -import static org.mockito.Mockito.mock; - -import javax.mail.Folder; - -import org.springframework.integration.mail.MailReceiver.MailReceiverContext; import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.test.util.TestUtils; import org.springframework.mail.SimpleMailMessage; import org.springframework.messaging.Message; @@ -77,13 +71,4 @@ public class MailTestsHelper { .build(); } - public static MailReceiverContext setupContextHolder(AbstractMailReceiver receiver) { - @SuppressWarnings("unchecked") - ThreadLocal contextHolder = TestUtils.getPropertyValue(receiver, "contextHolder", ThreadLocal.class); - Folder folder = mock(Folder.class); - MailReceiverContext context = new MailReceiverContext(folder); - contextHolder.set(context); - return context; - } - } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml index 3ed6af317d..a80ac62ae1 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml @@ -35,8 +35,12 @@ store-uri="imap:foo" channel="channel" auto-startup="false" + header-mapper="mapper" + embedded-parts-as-bytes="false" should-delete-messages="true"/> + + - + - + - + - + + @@ -31,46 +41,75 @@ false - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + @@ -86,17 +125,28 @@ - + - + - + - + - + - + diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests.java index b6101267b6..6ae53ac732 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests.java @@ -45,6 +45,7 @@ import org.springframework.integration.mail.Pop3MailReceiver; import org.springframework.integration.mail.SearchTermStrategy; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.MessageChannel; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -56,6 +57,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext public class InboundChannelAdapterParserTests { @Autowired @@ -73,16 +75,22 @@ public class InboundChannelAdapterParserTests { public void pop3ShouldDeleteTrue() { AbstractMailReceiver receiver = this.getReceiver("pop3ShouldDeleteTrue"); assertEquals(Pop3MailReceiver.class, receiver.getClass()); - Boolean value = (Boolean) new DirectFieldAccessor(receiver).getPropertyValue("shouldDeleteMessages"); + DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver); + Boolean value = (Boolean) receiverAccessor.getPropertyValue("shouldDeleteMessages"); assertTrue(value); + assertEquals(Boolean.FALSE, receiverAccessor.getPropertyValue("embeddedPartsAsBytes")); + assertNotNull(receiverAccessor.getPropertyValue("headerMapper")); } @Test public void imapShouldMarkMessagesAsRead() { AbstractMailReceiver receiver = this.getReceiver("imapShouldMarkAsReadTrue"); assertEquals(ImapMailReceiver.class, receiver.getClass()); - Boolean value = (Boolean) new DirectFieldAccessor(receiver).getPropertyValue("shouldMarkMessagesAsRead"); + DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver); + Boolean value = (Boolean) receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead"); assertTrue(value); + assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("embeddedPartsAsBytes")); + assertNull(receiverAccessor.getPropertyValue("headerMapper")); } @Test diff --git a/src/reference/asciidoc/mail.adoc b/src/reference/asciidoc/mail.adoc index b6c39bc89f..8143eaef64 100644 --- a/src/reference/asciidoc/mail.adoc +++ b/src/reference/asciidoc/mail.adoc @@ -20,7 +20,7 @@ However, a few simple Message mapping strategies are supported out-of-the-box. For example, if the message payload is a byte array, then that will be mapped to an attachment. For simple text-based emails, you can provide a String-based Message payload. In that case, a MailMessage will be created with that String as the text content. -If you are working with a Message payload type whose `toString()`` method returns appropriate mail text content, then consider adding Spring Integration's _ObjectToStringTransformer_ prior to the outbound Mail adapter (see the example within <> for more detail). +If you are working with a Message payload type whose `toString()` method returns appropriate mail text content, then consider adding Spring Integration's _ObjectToStringTransformer_ prior to the outbound Mail adapter (see the example within <> for more detail). The outbound MailMessage may also be configured with certain values from the `MessageHeaders`. If available, values will be mapped to the outbound mail's properties, such as the recipients (TO, CC, and BCC), the from/reply-to, and the subject. @@ -55,6 +55,82 @@ Spring Integration provides the `ImapIdleChannelAdapter` which is itself a Messa It delegates to an instance of the `ImapMailReceiver` but enables asynchronous reception of Mail Messages. There are examples in the next section of configuring both types of inbound Channel Adapter with Spring Integration's namespace support in the 'mail' schema. +[[mail-mapping]] +=== Inbound Mail Message Mapping + +By default, the payload of messages produced by the inbound adapters is the raw `MimeMessage`; you can interrogate +the headers and content using that object. +Starting with _version 4.3_, you can provide a `HeaderMapper` to map the headers to `MessageHeaders`; for +convenience, a `DefaultMailHeaderMapper` is provided for this purpose. +This maps the following headers: + +- `mail_from` - A String representation of the `from` address. +- `mail_bcc` - A String array containing the `bcc` addresses. +- `mail_cc` - A String array containing the `cc` addresses. +- `mail_to` - A String array containing the `to` addresses. +- `mail_replyTo` - A String representation of the `replyTo` address. +- `mail_subject` - The mail subject. +- `mail_lineCount` - A line count (if available). +- `mail_receivedDate` - The received date (if available). +- `mail_size` - The mail size (if available). +- `mail_expunged` - A boolen indicating if the message is expunged. +- `mail_raw` - A `MultiValueMap` containing all the mail headers and their values. +- `mail_contentType` - The content type of the original mail message. +- `contentType` - The payload content type (see below). + +When message mapping is enabled, the payload depends on the mail message and its implementation. +Email contents are usually rendered by a `DataHandler` within the `MimeMessage`. + +- For a simple `text/*` email, the payload will be a String and the `contentType` header will be the same as +`mail_contentType`. + +- For a messages with embedded `javax.mail.Part` s, the `DataHandler` usually renders a `Part` object - these objects +are not `Serializable`, and are not suitable for serialization using alternative technologies such as `Kryo`. +For this reason, by default, when mapping is enabled, such payloads are rendered as a raw `byte[]` containing the +`Part` data. +Examples of `Part` are `Message` and `Multipart`. +The `contentType` header is `application/octet-stream` in this case. +To change this behavior, and receive a `Multipart` object payload, set `embeddedPartsAsBytes` to `false` on the +`MailReceiver`. +For content types that are unknown to the `DataHandler`, the contents are rendered as a `byte[]` with a `contentType` +header of `application/octet-stream`. + +When you do not provide a header mapper, the message payload is the `MimeMessage` presented by `javax.mail`. +The framework provides a `MailToStringTransformer` which can be used to convert the message using a simple strategy +to convert the mail contents to a String. +This is also available using the XML namespace: + +[source, xml] +---- + +---- + +and with Java configuration: + +[source, java] +---- +@Bean +@Transformer(inputChannel="...", outputChannel="...") +public Transformer transformer() { + return new MailToStringTransformer(); +} +---- + +and with the Java DSL: + +[source, java] +---- + ... + .transform(Transformers.fromMail()) + ... +---- + +Starting with _version 4.3_, the transformer will handle embedded `Part` as well as `Multipart` which was handled +previously. +The transformer is a subclass of `AbstractMailTransformer` which maps the address and subject headers from the list +above. +If you wish to perform some other transformation on the message, consider subclassing `AbstractMailTransformer`. + [[mail-namespace]] === Mail Namespace Support @@ -171,7 +247,7 @@ See <> regarding message flagging. ---- public interface SearchTermStrategy { - SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder); + SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder); } ---- @@ -206,11 +282,9 @@ If not specified, the previous behavior is retained (peek is `true`). When using IMAP IDLE channel adapter there might be situations where connection to the server may be lost (e.g., network failure) and since Java Mail documentation explicitly states that the actual IMAP API is EXPERIMENTAL it is important to understand the differences in the API and how to deal with them when configuring IMAP IDLE adapters. Currently Spring Integration Mail adapters was tested with Java Mail 1.4.1 and Java Mail 1.4.3 and depending on which one is used special attention must be payed to some of the java mail properties that needs to be set with regard to auto-reconnect. -_ - The following behavior was observed with GMAIL but should provide you with some tips on how to solve re-connect - issue with other providers, however feedback is always welcome. +NOTE: The following behavior was observed with GMAIL but should provide you with some tips on how to solve re-connect +issue with other providers, however feedback is always welcome. Again, below notes are based on GMAIL. - _ With Java Mail 1.4.1 if `mail.imaps.timeout` property is set for a relatively short period of time (e.g., ~ 5 min) then `IMAPFolder.idle()` will throw `FolderClosedException` after this timeout. However if this property is not set (should be indefinite) the behavior that was observed is that `IMAPFolder.idle()` method never returns nor it throws an exception. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index e57098c9f9..e4ad90e40f 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -55,10 +55,19 @@ See <> and <> for more information. ==== Mail Changes +===== Customizable User Flag + The customizable `userFlag` added in 4.2.2 to provide customization of the flag used to denote that the mail has been seen is now available using the XML namespace. See <> for more information. +===== Mail Message Mapping + +There is now an option to map inbound mail messages with the `MessageHeaders` containing the mail headers and the +payload containing the email content. +Previously, the payload was always the raw `MimeMessage`. +See <> for more information. + ==== JMS Changes ===== Header Mapper