diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/AbstractMailHeaderMapper.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/AbstractMailHeaderMapper.java new file mode 100644 index 0000000000..fbbacb3e29 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/AbstractMailHeaderMapper.java @@ -0,0 +1,174 @@ +/* + * Copyright 2002-2007 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.adapter.mail; + +import java.util.HashMap; +import java.util.Map; + +import javax.mail.Address; +import javax.mail.Message.RecipientType; +import javax.mail.internet.MimeMessage; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.adapter.MessageHeaderMapper; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHeaders; +import org.springframework.integration.message.MessagingException; +import org.springframework.mail.javamail.MimeMailMessage; + +/** + * + * @author Jonas Partner + * + */ +public abstract class AbstractMailHeaderMapper implements MessageHeaderMapper { + + /** + * Retrieve the subject of an e-mail message from an integration message. + * + * @param message the integration {@link Message} + * @return the e-mail message subject + */ + protected abstract String getSubject(MessageHeaders message); + + /** + * Retrieve the recipients list from an integration message. + * + * @param message the integration {@link Message} + * @return recipients list (TO) + */ + protected abstract String[] getTo(MessageHeaders message); + + /** + * Retrieve the CC recipients list from an integration message. + * + * @param message the integration {@link Message} + * @return CC recipients list (e-mail addresses) + */ + protected abstract String[] getCc(MessageHeaders message); + + /** + * Retrieve the BCC recipients list from an integration message. + * + * @param message the integration {@link Message} + * @return BCC recipients list (e-mail addresses) + */ + protected abstract String[] getBcc(MessageHeaders message); + + /** + * Retrieve the From: e-mail address from an integration message. + * + * @param message the integration {@link Message} + * @return the From: e-mail address + */ + protected abstract String getFrom(MessageHeaders message); + + /** + * Retrieve the Reply To: e-mail address from an integration message. + * + * @param message the integration {@link Message} + * @return the ReplyTo: e-mail address + */ + protected abstract String getReplyTo(MessageHeaders message); + + private final Log logger = LogFactory.getLog(this.getClass()); + + public void mapFromMessageHeaders(MessageHeaders headers, MimeMessage mailMessage) { + MimeMailMessage message = new MimeMailMessage(mailMessage); + + final String subject = getSubject(headers); + final String[] to = getTo(headers); + final String[] cc = getCc(headers); + final String[] bcc = getBcc(headers); + final String from = getFrom(headers); + final String replyTo = getReplyTo(headers); + if (subject != null) { + message.setSubject(subject); + } + else if (logger.isWarnEnabled()) { + logger.warn("no 'SUBJECT' property available for mail message"); + } + if (to != null) { + message.setTo(to); + } + else if (logger.isWarnEnabled()) { + logger.warn("no 'TO' property available for mail message"); + } + if (cc != null) { + message.setCc(cc); + } + if (bcc != null) { + message.setBcc(bcc); + } + if (from != null) { + message.setFrom(from); + } + else if (logger.isWarnEnabled()) { + logger.warn("no 'FROM' property available for mail message"); + } + if (replyTo != null) { + message.setReplyTo(replyTo); + } + } + + public Map mapToMessageHeaders(MimeMessage mailMessage) { + try { + Map headers = new HashMap(); + headers.put(MailAttributeKeys.FROM, toStringArray(mailMessage.getFrom())); + headers.put(MailAttributeKeys.BCC, toStringArray(mailMessage.getRecipients(RecipientType.BCC))); + headers.put(MailAttributeKeys.CC, toStringArray(mailMessage.getRecipients(RecipientType.CC))); + headers.put(MailAttributeKeys.TO, toStringArray(mailMessage.getRecipients(RecipientType.TO))); + headers.put(MailAttributeKeys.REPLY_TO, toStringArray(mailMessage.getReplyTo())); + headers.put(MailAttributeKeys.SUBJECT, mailMessage.getSubject()); + return headers; + } + catch (Exception e) { + throw new MessagingException("Conversion of MailMessage headers failed", e); + } + } + + + protected String retrieveAsString(MessageHeaders headers, String key) { + Object value = headers.get(key); + return (value instanceof String) ? (String) value : null; + } + + protected String[] retrieveAsStringArray(MessageHeaders headers, String key) { + Object value = headers.get(key); + if (value instanceof String[]) { + return (String[]) value; + } + if (value instanceof String) { + return new String[] { (String) value }; + } + return null; + } + + + protected String[] toStringArray(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/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/DefaultMailMessageConverter.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/DefaultMailMessageConverter.java new file mode 100644 index 0000000000..3480f99113 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/DefaultMailMessageConverter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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.adapter.mail; + +import java.util.Map; + +import javax.mail.internet.MimeMessage; + +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; + +/** + * + * @author Jonas Partner + * + */ +public class DefaultMailMessageConverter implements MailMessageConverter { + + private DefaultMailMessageHeaderMapper headerMapper = new DefaultMailMessageHeaderMapper(); + + @SuppressWarnings("unchecked") + public Message create(MimeMessage mailMessage) { + try { + Map header = headerMapper.mapToMessageHeaders(mailMessage); + GenericMessage message = new GenericMessage(mailMessage.getContent(), header); + return message; + } + catch (Exception e) { + throw new MessagingException("Conversion of MailMessage failed", e); + } + } + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/DefaultMailMessageHeaderMapper.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/DefaultMailMessageHeaderMapper.java new file mode 100644 index 0000000000..c3c53bbe22 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/DefaultMailMessageHeaderMapper.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 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.adapter.mail; + +import org.springframework.integration.message.MessageHeaders; + +/** + * @author Jonas Partner + * + */ +public class DefaultMailMessageHeaderMapper extends AbstractMailHeaderMapper { + + @Override + protected String getSubject(MessageHeaders headers) { + return this.retrieveAsString(headers, MailAttributeKeys.SUBJECT); + } + + @Override + protected String[] getTo(MessageHeaders headers) { + return this.retrieveAsStringArray(headers, MailAttributeKeys.TO); + } + + @Override + protected String[] getCc(MessageHeaders headers) { + return this.retrieveAsStringArray(headers, MailAttributeKeys.CC); + } + + @Override + protected String[] getBcc(MessageHeaders headers) { + return this.retrieveAsStringArray(headers, MailAttributeKeys.BCC); + } + + @Override + protected String getFrom(MessageHeaders headers) { + return this.retrieveAsString(headers, MailAttributeKeys.FROM); + } + + @Override + protected String getReplyTo(MessageHeaders headers) { + return this.retrieveAsString(headers, MailAttributeKeys.REPLY_TO); + } + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/MailMessageConverter.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/MailMessageConverter.java new file mode 100644 index 0000000000..090f760a7a --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/MailMessageConverter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2007 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.adapter.mail; + +import javax.mail.internet.MimeMessage; + +import org.springframework.integration.message.Message; + +/** + * Converts a {@link MimeMessage} to a {@link Message} + * @author Jonas Partner + * + */ +public interface MailMessageConverter { + + + @SuppressWarnings("unchecked") + public Message create(MimeMessage mailMessage); + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/PollingMailSource.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/PollingMailSource.java new file mode 100644 index 0000000000..ab0642c70c --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/PollingMailSource.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2007 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.adapter.mail; + +import java.util.Properties; + +import javax.mail.Folder; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.URLName; +import javax.mail.internet.MimeMessage; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.Lifecycle; +import org.springframework.integration.adapter.mail.monitor.DefaultLocalMailMessageStore; +import org.springframework.integration.adapter.mail.monitor.LocalMailMessageStore; +import org.springframework.integration.adapter.mail.monitor.MailTransportUtils; +import org.springframework.integration.adapter.mail.monitor.MonitoringStrategy; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageSource; +import org.springframework.util.Assert; + +/** + * {@link MessageSource} implementation which delegates to a + * {@link MonitoringStrategy} to poll a mailbox Each poll of the mailbox may + * return more than one message which will then be stored locally using the + * provided {@link LocalMailMessageStore} + * @author Jonas Partner + * + */ + +@SuppressWarnings("unchecked") +public class PollingMailSource implements MessageSource, DisposableBean, Lifecycle { + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final MonitoringStrategy monitoringStrategy; + + private Session session; + + private Store store; + + private Folder folder; + + private URLName storeUri; + + private MailMessageConverter converter = new DefaultMailMessageConverter(); + + private LocalMailMessageStore mailMessageStore = new DefaultLocalMailMessageStore(); + + public PollingMailSource(MonitoringStrategy monitoringStrategy) { + this.monitoringStrategy = monitoringStrategy; + } + + @SuppressWarnings("unchecked") + public Message receive() { + Message received = null; + javax.mail.Message mailMessage = mailMessageStore.getNext(); + if (mailMessage == null) { + try { + javax.mail.Message[] messages = monitoringStrategy.receive(folder); + mailMessageStore.addLast(messages); + mailMessage = mailMessageStore.getNext(); + } + catch (Exception e) { + throw new org.springframework.integration.message.MessagingException("Excpetion receiving mail", e); + } + } + if (mailMessage != null) { + received = converter.create((MimeMessage) mailMessage); + if (logger.isDebugEnabled()) { + logger.debug("Received message " + received); + } + } + return received; + } + + public void setJavaMailProperties(Properties javaMailProperties) { + session = Session.getInstance(javaMailProperties, null); + } + + public void setJavaMailsession(Session session) { + this.session = session; + } + + public void setStoreUri(String storeUri) { + this.storeUri = new URLName(storeUri); + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(storeUri, "Property 'storeUri' is required"); + Assert.notNull(session, "Property 'JavaMailProperties' is required"); + Assert.notNull(converter, "An instantce of MailMessageConverter' is required"); + openSession(); + openFolder(); + } + + private void openFolder() throws MessagingException { + if (folder != null && folder.isOpen()) { + return; + } + folder = store.getFolder(storeUri); + if (folder == null || !folder.exists()) { + throw new IllegalStateException("No default folder to receive from"); + } + if (logger.isDebugEnabled()) { + logger.debug("Opening folder [" + MailTransportUtils.toPasswordProtectedString(storeUri) + "]"); + } + folder.open(monitoringStrategy.getFolderOpenMode()); + } + + private void openSession() throws MessagingException { + store = session.getStore(storeUri); + if (logger.isDebugEnabled()) { + logger.debug("Connecting to store [" + MailTransportUtils.toPasswordProtectedString(storeUri) + "]"); + } + store.connect(); + } + + public void setConverter(MailMessageConverter converter) { + this.converter = converter; + } + + public void destroy() throws Exception { + stop(); + } + + public boolean isRunning() { + return folder.isOpen(); + } + + public void start() { + try { + openSession(); + openFolder(); + } + catch (MessagingException messageE) { + throw new org.springframework.integration.message.MessagingException("Excpetion starting MailSource", + messageE); + } + + } + + public void stop() { + MailTransportUtils.closeFolder(folder); + MailTransportUtils.closeService(store); + } + + public void setMailMessageStore(LocalMailMessageStore mailMessageStore) { + this.mailMessageStore = mailMessageStore; + } + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/AbstractMonitoringStrategy.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/AbstractMonitoringStrategy.java new file mode 100644 index 0000000000..60574c5970 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/AbstractMonitoringStrategy.java @@ -0,0 +1,169 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + +import javax.mail.FetchProfile; +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.search.AndTerm; +import javax.mail.search.FlagTerm; +import javax.mail.search.SearchTerm; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract base class for the {@link MonitoringStrategy} interface. Exposes a {@link #setDeleteMessages(boolean) + * deleteMessages} property, and includes a basic workflow for message monitoring. + * + * @author Arjen Poutsma + */ +public abstract class AbstractMonitoringStrategy implements MonitoringStrategy { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + private boolean deleteMessages = true; + + private int maxMessagesPerReceive = -1; + + /** + * Sets whether messages should be marked as {@link javax.mail.Flags.Flag#DELETED DELETED} after they have been + * read. Default is true. + */ + public void setDeleteMessages(boolean deleteMessages) { + this.deleteMessages = deleteMessages; + } + + public int getFolderOpenMode() { + return deleteMessages ? Folder.READ_WRITE : Folder.READ_ONLY; + } + + public void setMaxMessagePerDownload(int maxMessagesPerReceive){ + this.maxMessagesPerReceive = maxMessagesPerReceive; + } + + /** + * Monitors the given folder, and returns any new messages when they arrive. This implementation calls {@link + * #waitForNewMessages(Folder)}, then searches for new messages using {@link #searchForNewMessages(Folder)}, fetches + * the messages using {@link #fetchMessages(Folder, Message[])}, and finally {@link #setDeleteMessages(boolean) + * deletes} the messages, if {@link #setDeleteMessages(boolean) deleteMessages} is true. + * + * @param folder the folder to monitor + * @return the new messages + * @throws MessagingException in case of JavaMail errors + * @throws InterruptedException when a thread is interrupted + */ + public final Message[] receive(Folder folder) throws MessagingException, InterruptedException { + logger.info("Receiving for folder" + folder.getFullName()); + if(!folder.isOpen()){ + folder.open(getFolderOpenMode()); + } + folder.getMessageCount(); + Message[] messages = searchForNewMessages(folder); + if (logger.isDebugEnabled()) { + logger.debug("Found " + messages.length + " new messages"); + } + if(maxMessagesPerReceive > 0 && messages.length > maxMessagesPerReceive){ + Message[] reducedMessages = new Message[maxMessagesPerReceive]; + System.arraycopy(messages, 0, reducedMessages, 0, maxMessagesPerReceive); + messages = reducedMessages; + } + + if (messages.length > 0) { + fetchMessages(folder, messages); + } + if (deleteMessages) { + deleteMessages(folder, messages); + } + return messages; + } + + /** + * Retrieves new messages from the given folder. This implementation creates a {@link SearchTerm} that searches for + * all messages in the folder that are {@link javax.mail.Flags.Flag#RECENT RECENT}, not {@link + * javax.mail.Flags.Flag#ANSWERED ANSWERED}, and not {@link javax.mail.Flags.Flag#DELETED DELETED}. The search term + * is used to {@link Folder#search(SearchTerm) search} for new messages. + * + * @param folder the folder to retrieve new messages from + * @return the new messages + * @throws MessagingException in case of JavaMail errors + */ + protected Message[] searchForNewMessages(Folder folder) throws MessagingException { + if (!folder.isOpen()) { + return new Message[0]; + } + Flags supportedFlags = folder.getPermanentFlags(); + SearchTerm searchTerm = null; + if (supportedFlags != null) { + if (supportedFlags.contains(Flags.Flag.RECENT)) { + searchTerm = new FlagTerm(new Flags(Flags.Flag.RECENT), true); + } + if (supportedFlags.contains(Flags.Flag.ANSWERED)) { + FlagTerm answeredTerm = new FlagTerm(new Flags(Flags.Flag.ANSWERED), false); + if (searchTerm == null) { + searchTerm = answeredTerm; + } + else { + searchTerm = new AndTerm(searchTerm, answeredTerm); + } + } + if (supportedFlags.contains(Flags.Flag.DELETED)) { + FlagTerm deletedTerm = new FlagTerm(new Flags(Flags.Flag.DELETED), false); + if (searchTerm == null) { + searchTerm = deletedTerm; + } + else { + searchTerm = new AndTerm(searchTerm, deletedTerm); + } + } + } + return searchTerm != null ? folder.search(searchTerm) : folder.getMessages(); + } + + /** + * Fetches the specified messages from the specified folder. Default implementation {@link Folder#fetch(Message[], + * FetchProfile) fetches} every {@link javax.mail.FetchProfile.Item}. + * + * @param folder the folder to fetch messages from + * @param messages the messages to fetch + * @throws MessagingException in case of JavMail errors + */ + protected void fetchMessages(Folder folder, Message[] messages) throws MessagingException { + FetchProfile contentsProfile = new FetchProfile(); + contentsProfile.add(FetchProfile.Item.ENVELOPE); + contentsProfile.add(FetchProfile.Item.CONTENT_INFO); + contentsProfile.add(FetchProfile.Item.FLAGS); + folder.fetch(messages, contentsProfile); + } + + /** + * Deletes the given messages from the given folder. Only invoked when {@link #setDeleteMessages(boolean)} is + * true. + * + * @param folder the folder to delete messages from + * @param messages the messages to delete + * @throws MessagingException in case of JavaMail errors + */ + protected void deleteMessages(Folder folder, Message[] messages) throws MessagingException { + for (int i = 0; i < messages.length; i++) { + messages[i].setFlag(Flags.Flag.DELETED, true); + } + } +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/DefaultLocalMailMessageStore.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/DefaultLocalMailMessageStore.java new file mode 100644 index 0000000000..4bf606e146 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/DefaultLocalMailMessageStore.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2007 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.adapter.mail.monitor; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.mail.Message; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * + * @author Jonas Partner + * + */ +public class DefaultLocalMailMessageStore implements LocalMailMessageStore { + + private final Log logger = LogFactory.getLog(this.getClass()); + + private ConcurrentLinkedQueue messages = new ConcurrentLinkedQueue(); + + public void addLast(Message[] newMessages) { + for (Message message : newMessages) { + messages.add(message); + } + logger.info("LocalMailMessageStore size is now" + messages.size()); + } + + public Message getNext() { + logger.info("Message store size " + messages.size()); + return messages.poll(); + } + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/ImapIdleMonitoringStrategy.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/ImapIdleMonitoringStrategy.java new file mode 100644 index 0000000000..ee95eae26a --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/ImapIdleMonitoringStrategy.java @@ -0,0 +1,74 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.event.MessageCountAdapter; +import javax.mail.event.MessageCountEvent; +import javax.mail.event.MessageCountListener; + +/** + * Implementation of the {@link MonitoringStrategy} interface that uses the IMAP IDLE command for asynchronous message + * detection. + *

+ * Note that this implementation is only suitable for use with IMAP servers which support the IDLE command. + * Additionally, this strategy requires JavaMail version 1.4.1. + * + * @author Arjen Poutsma + */ +public class ImapIdleMonitoringStrategy extends AbstractMonitoringStrategy { + + private MessageCountListener messageCountListener; + + protected void waitForNewMessages(Folder folder) throws MessagingException, InterruptedException { + // Assert.isInstanceOf(IMAPFolder.class, folder); + //IMAPFolder imapFolder = (IMAPFolder) folder; + // retrieve unseen messages before we enter the blocking idle call + if (searchForNewMessages(folder).length > 0) { + return; + } + if (messageCountListener == null) { + createMessageCountListener(); + } + folder.addMessageCountListener(messageCountListener); + try { + //TODO: add this back in when we have java mail 1.4.1 in the repository imapFolder.idle(); + } + finally { + folder.removeMessageCountListener(messageCountListener); + } + } + + private void createMessageCountListener() { + messageCountListener = new MessageCountAdapter() { + public void messagesAdded(MessageCountEvent e) { + Message[] messages = e.getMessages(); + for (int i = 0; i < messages.length; i++) { + try { + // this will return the flow to the idle call, above + messages[i].getLineCount(); + } + catch (MessagingException ex) { + // ignore + } + } + } + }; + } +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/LocalMailMessageStore.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/LocalMailMessageStore.java new file mode 100644 index 0000000000..17fde67797 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/LocalMailMessageStore.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2007 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.adapter.mail.monitor; + +import javax.mail.Message; + +/** + * Acts as a buffer for downloaded MailMessages + * @author Jonas Partner + * + */ +public interface LocalMailMessageStore { + + public Message getNext(); + + public void addLast(Message[] newMessages); + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MailTransportConstants.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MailTransportConstants.java new file mode 100644 index 0000000000..dcd7239940 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MailTransportConstants.java @@ -0,0 +1,36 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + + +/** + * Declares Mail-specific transport constants. + * + * @author Arjen Poutsma + */ +public interface MailTransportConstants { + + /** + * The "mail" URI scheme. + */ + String MAIL_URI_SCHEME = "mailto"; + + /** + * The "In-Reply-To" header. + */ + String HEADER_IN_REPLY_TO = "In-Reply-To"; +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MailTransportUtils.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MailTransportUtils.java new file mode 100644 index 0000000000..0a1020561f --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MailTransportUtils.java @@ -0,0 +1,187 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.mail.Folder; +import javax.mail.MessagingException; +import javax.mail.Service; +import javax.mail.Store; +import javax.mail.Transport; +import javax.mail.URLName; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.StringUtils; + + +/** + * Collection of utility methods to work with Mail transports. + * + * @author Arjen Poutsma + */ +public abstract class MailTransportUtils { + + private static final Pattern TO_PATTERN = Pattern.compile("^([^\\?]+)"); + + private static final Pattern SUBJECT_PATTERN = Pattern.compile("subject=([^\\&]+)"); + + private static final Log logger = LogFactory.getLog(MailTransportUtils.class); + + private MailTransportUtils() { + } + + public static InternetAddress getTo(URI uri) { + Matcher matcher = TO_PATTERN.matcher(uri.getSchemeSpecificPart()); + if (matcher.find()) { + for (int i = 1; i <= matcher.groupCount(); i++) { + String group = matcher.group(i); + if (group != null) { + try { + return new InternetAddress(group); + } + catch (AddressException e) { + // try next group + } + } + } + } + return null; + } + + public static String getSubject(URI uri) { + Matcher matcher = SUBJECT_PATTERN.matcher(uri.getSchemeSpecificPart()); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + /** + * Close the given JavaMail Service and ignore any thrown exception. This is useful for typical finally + * blocks in manual JavaMail code. + * + * @param service the JavaMail Service to close (may be null) + * @see Transport + * @see Store + */ + public static void closeService(Service service) { + if (service != null) { + try { + service.close(); + } + catch (MessagingException ex) { + logger.debug("Could not close JavaMail Service", ex); + } + } + } + + /** + * Close the given JavaMail Folder and ignore any thrown exception. This is useful for typical finally + * blocks in manual JavaMail code. + * + * @param folder the JavaMail Folder to close (may be null) + */ + + public static void closeFolder(Folder folder) { + closeFolder(folder, false); + } + + /** + * Close the given JavaMail Folder and ignore any thrown exception. This is useful for typical finally + * blocks in manual JavaMail code. + * + * @param folder the JavaMail Folder to close (may be null) + * @param expunge whether all deleted messages should be expunged from the folder + */ + public static void closeFolder(Folder folder, boolean expunge) { + if (folder != null && folder.isOpen()) { + try { + folder.close(expunge); + } + catch (MessagingException ex) { + logger.debug("Could not close JavaMail Folder", ex); + } + } + } + + /** Returns a string representation of the given {@link URLName}, where the password has been protected. */ + public static String toPasswordProtectedString(URLName name) { + String protocol = name.getProtocol(); + String username = name.getUsername(); + String password = name.getPassword(); + String host = name.getHost(); + int port = name.getPort(); + String file = name.getFile(); + String ref = name.getRef(); + StringBuffer tempURL = new StringBuffer(); + if (protocol != null) { + tempURL.append(protocol).append(':'); + } + + if (StringUtils.hasLength(username) || StringUtils.hasLength(host)) { + tempURL.append("//"); + if (StringUtils.hasLength(username)) { + tempURL.append(username); + if (StringUtils.hasLength(password)) { + tempURL.append(":*****"); + } + tempURL.append("@"); + } + if (StringUtils.hasLength(host)) { + tempURL.append(host); + } + if (port != -1) { + tempURL.append(':').append(Integer.toString(port)); + } + if (StringUtils.hasLength(file)) { + tempURL.append('/'); + } + } + if (StringUtils.hasLength(file)) { + tempURL.append(file); + } + if (StringUtils.hasLength(ref)) { + tempURL.append('#').append(ref); + } + return tempURL.toString(); + } + + /** + * Converts the given internet address into a mailto URI. + * + * @param to the To: address + * @param subject the subject, may be null + * @return a mailto URI + */ + public static URI toUri(InternetAddress to, String subject) throws URISyntaxException { + if (StringUtils.hasLength(subject)) { + return new URI(MailTransportConstants.MAIL_URI_SCHEME, to.getAddress() + "?subject=" + subject, null); + } + else { + return new URI(MailTransportConstants.MAIL_URI_SCHEME, to.getAddress(), null); + } + } + + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MonitoringStrategy.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MonitoringStrategy.java new file mode 100644 index 0000000000..08feda4b03 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/MonitoringStrategy.java @@ -0,0 +1,47 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; + +/** + * Defines the contract for objects that monitor a given folder for new messages. Allows for multiple implementation + * strategies, including polling, or event-driven techniques such as IMAP's IDLE command. + * + * @author Arjen Poutsma + */ +public interface MonitoringStrategy { + + /** + * Monitors the given folder, and returns any new messages when they arrive. + * + * @param folder the folder in which to look for new messages + * @return the new messages + * @throws MessagingException in case of JavaMail errors + * @throws InterruptedException if a thread is interrupted + */ + Message[] receive(Folder folder) throws MessagingException, InterruptedException; + + /** + * Returns the folder open mode to be used by this strategy. Can be either {@link Folder#READ_ONLY} or {@link + * Folder#READ_WRITE}. + */ + int getFolderOpenMode(); + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/PollingMonitoringStrategy.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/PollingMonitoringStrategy.java new file mode 100644 index 0000000000..05a7336cc1 --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/PollingMonitoringStrategy.java @@ -0,0 +1,33 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + + +/** + * Implementation of the {@link MonitoringStrategy} interface that uses a simple polling mechanism. Defines a {@link + * #setPollingInterval(long) polling interval} property which defines the interval in between message polls. + *

+ * Note that this implementation is not suitable for use with POP3 servers. Use the {@link + * Pop3PollingMonitoringStrategy} instead. + * + * @author Arjen Poutsma + */ +public class PollingMonitoringStrategy extends AbstractMonitoringStrategy { + + + +} diff --git a/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/Pop3PollingMonitoringStrategy.java b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/Pop3PollingMonitoringStrategy.java new file mode 100644 index 0000000000..3527b5283b --- /dev/null +++ b/org.springframework.integration.adapter/src/main/java/org/springframework/integration/adapter/mail/monitor/Pop3PollingMonitoringStrategy.java @@ -0,0 +1,66 @@ +/* + * Copyright 2007 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.adapter.mail.monitor; + +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +/** + * Implementation of the {@link MonitoringStrategy} interface that uses a simple polling mechanism suitable for POP3 + * servers. Since POP3 does not have a native mechanism to determine which messages are "new", this implementation + * simply retrieves all messages in the {@link Folder}, and delete them afterwards. All messages in the POP3 mailbox are + * therefore, by definition, new. + *

+ * Setting the {@link #setDeleteMessages(boolean) deleteMessages} property is therefore ignored: messages are always + * deleted. + * + * @author Arjen Poutsma + */ +public class Pop3PollingMonitoringStrategy extends PollingMonitoringStrategy { + + public Pop3PollingMonitoringStrategy() { + super.setDeleteMessages(true); + } + + public void setDeleteMessages(boolean deleteMessages) { + } + + /** + * Simply returns {@link Folder#getMessages()}. + */ + protected Message[] searchForNewMessages(Folder folder) throws MessagingException { + return folder.getMessages(); + } + + /** + * Deletes the given messages from the given folder, and closes it to expunge deleted messages. + * + * @param folder the folder to delete messages from + * @param messages the messages to delete + * @throws MessagingException in case of JavaMail errors + */ + protected void deleteMessages(Folder folder, Message[] messages) throws MessagingException { + super.deleteMessages(folder, messages); + // expunge deleted mails, and make sure we've retrieved them before closing the folder + for (int i = 0; i < messages.length; i++) { + new MimeMessage((MimeMessage) messages[i]); + } + MailTransportUtils.closeFolder(folder, true); + } +} \ No newline at end of file