diff --git a/sandbox/src/main/java/org/springframework/ws/transport/mail/DefaultMonitoringStrategy.java b/sandbox/src/main/java/org/springframework/ws/transport/mail/DefaultMonitoringStrategy.java deleted file mode 100644 index e98d6ac8..00000000 --- a/sandbox/src/main/java/org/springframework/ws/transport/mail/DefaultMonitoringStrategy.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.ws.transport.mail; - -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; - -/** - * Default implementation of the {@link MonitoringStrategy}. Polls for new messages using a defined {@link - * #setPollingInterval(int) interval}. - * - * @author Arjen Poutsma - */ -public class DefaultMonitoringStrategy extends AbstractPollingMonitoringStrategy { - - private boolean deleteMessages = true; - - /** - * Sets whether messages should be marked as {@link Flags.Flag#DELETED DELETED} after they have been read. Default - * is true. - */ - public void setDeleteMessages(boolean deleteMessages) { - this.deleteMessages = deleteMessages; - } - - /** - * Polls for new messages in the given folder. Calls {@link #createSearchTerm(Folder)}, and uses that created term - * to search for messages in the given folder. Marks the messages as {@link Flags.Flag#DELETED DELETED} if the - * {@link #setDeleteMessages(boolean) deleteMessages} property is set. - */ - protected final Message[] pollForNewMessages(Folder folder) throws MessagingException { - SearchTerm searchTerm = createSearchTerm(folder); - Message[] messages; - if (searchTerm == null) { - messages = folder.getMessages(); - } - else { - messages = folder.search(searchTerm); - } - if (messages.length > 0) { - FetchProfile contentsProfile = new FetchProfile(); - contentsProfile.add(FetchProfile.Item.ENVELOPE); - contentsProfile.add(FetchProfile.Item.CONTENT_INFO); - folder.fetch(messages, contentsProfile); - if (deleteMessages) { - for (int i = 0; i < messages.length; i++) { - messages[i].setFlag(Flags.Flag.DELETED, true); - } - } - } - return messages; - } - - /** - * Creates the search term that defines the messages to look for. Default implementation returns a term that - * searches for all messages in the folder that are {@link Flags.Flag#RECENT RECENT}, not {@link Flags.Flag#ANSWERED - * ANSWERED}, and not {@link Flags.Flag#DELETED DELETED}. - *

- * Return null if all messages should be returned from {@link #pollForNewMessages(Folder)}. - */ - protected SearchTerm createSearchTerm(Folder folder) { - Flags supportedFlags = folder.getPermanentFlags(); - SearchTerm searchTerm = 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; - } - -} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/mail/MailtoUri.java b/sandbox/src/main/java/org/springframework/ws/transport/mail/MailtoUri.java deleted file mode 100644 index e997aae8..00000000 --- a/sandbox/src/main/java/org/springframework/ws/transport/mail/MailtoUri.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.ws.transport.mail; - -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; - -import org.springframework.util.Assert; -import org.springframework.ws.transport.support.ParameterizedUri; - -/** - * @author Arjen Poutsma - */ -public class MailtoUri extends ParameterizedUri { - - public MailtoUri(String uri) { - super(uri); - Assert.isTrue(uri.startsWith(MailTransportConstants.URI_SCHEME), "Invalid uri: " + uri); - try { - InternetAddress.parse(getDestination(), false); - } - catch (AddressException ex) { - throw new IllegalArgumentException(ex); - } - } - - public InternetAddress getTo() throws AddressException { - return new InternetAddress(getDestination()); - } - - public String getSubject() { - return getParameter("subject"); - } - - public boolean hasSubject() { - return hasParameter("subject"); - } - - public boolean hasCc() { - return hasParameter("cc"); - } - - public InternetAddress getCc() throws AddressException { - return new InternetAddress(getParameter("cc")); - } -} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/support/ParameterizedUri.java b/sandbox/src/main/java/org/springframework/ws/transport/support/ParameterizedUri.java deleted file mode 100644 index 58be7494..00000000 --- a/sandbox/src/main/java/org/springframework/ws/transport/support/ParameterizedUri.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.ws.transport.support; - -import java.util.Map; -import java.util.StringTokenizer; - -import org.springframework.core.CollectionFactory; -import org.springframework.util.Assert; - -/** @author Arjen Poutsma */ -public class ParameterizedUri { - - private final String uri; - - private final String scheme; - - // keys are string parameter names; values are string parameter values - private final Map parameters = CollectionFactory.createLinkedCaseInsensitiveMapIfPossible(5); - - private final String destination; - - public ParameterizedUri(String uri) { - Assert.hasLength(uri, "'uri' must not be empty"); - this.uri = uri; - int scIdx = uri.indexOf(':'); - Assert.isTrue(scIdx != -1, uri + " does contain scheme"); - scheme = uri.substring(0, scIdx); - Assert.isTrue(uri.length() > scheme.length(), uri + " does not have a destination"); - int paramStart = uri.indexOf('?'); - if (paramStart == -1) { - destination = uri.substring(scIdx + 1); - } - else { - destination = uri.substring(scIdx + 1, paramStart); - parseParameters(uri.substring(paramStart + 1)); - } - } - - private void parseParameters(String parametersString) { - StringTokenizer params = new StringTokenizer(parametersString, "&"); - while (params.hasMoreTokens()) { - String param = params.nextToken(); - int paramSep = param.indexOf('='); - if (paramSep == -1) { - throw new IllegalArgumentException(param + " is not a valid parameter: it has no '='"); - } - String paramName = param.substring(0, paramSep); - String paramValue = param.substring(paramSep + 1); - parameters.put(paramName, paramValue); - } - } - - /** Returns the destination of the uri. */ - protected String getDestination() { - return destination; - } - - public String toString() { - return uri; - } - - protected String getParameter(String paramName) { - return (String) parameters.get(paramName); - } - - protected boolean hasParameter(String paramName) { - return parameters.containsKey(paramName); - } -} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/support/SimpleWebServiceMessageReceiverObjectSupport.java b/sandbox/src/main/java/org/springframework/ws/transport/support/SimpleWebServiceMessageReceiverObjectSupport.java deleted file mode 100644 index eeaec2f9..00000000 --- a/sandbox/src/main/java/org/springframework/ws/transport/support/SimpleWebServiceMessageReceiverObjectSupport.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.ws.transport.support; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.ws.transport.WebServiceConnection; -import org.springframework.ws.transport.WebServiceMessageReceiver; - -/** - * Base class for server-side transport objects which have a predefined {@link WebServiceMessageReceiver}. - * - * @author Arjen Poutsma - * @see #handleConnection(WebServiceConnection) - * @since 1.1.0 - */ -public abstract class SimpleWebServiceMessageReceiverObjectSupport extends WebServiceMessageReceiverObjectSupport - implements InitializingBean { - - private WebServiceMessageReceiver messageReceiver; - - /** - * Returns the WebServiceMessageReceiver used by this listener. - */ - public WebServiceMessageReceiver getMessageReceiver() { - return messageReceiver; - } - - /** - * Sets the WebServiceMessageReceiver used by this listener. - */ - public void setMessageReceiver(WebServiceMessageReceiver messageReceiver) { - this.messageReceiver = messageReceiver; - } - - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - Assert.notNull(getMessageReceiver(), "messageReceiver must not be null"); - } - - protected final void handleConnection(WebServiceConnection connection) throws Exception { - handleConnection(connection, getMessageReceiver()); - } - -} diff --git a/sandbox/src/test/java/org/springframework/ws/transport/SimpleTestingMessageReceiver.java b/sandbox/src/test/java/org/springframework/ws/transport/SimpleTestingMessageReceiver.java deleted file mode 100644 index aff2af56..00000000 --- a/sandbox/src/test/java/org/springframework/ws/transport/SimpleTestingMessageReceiver.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.ws.transport; - -import javax.xml.transform.Transformer; - -import org.springframework.ws.context.MessageContext; -import org.springframework.xml.transform.TransformerObjectSupport; -import org.springframework.util.Assert; - -public class SimpleTestingMessageReceiver extends TransformerObjectSupport implements WebServiceMessageReceiver { - - public void receive(MessageContext messageContext) throws Exception { - Assert.notNull(messageContext, "MessageContext is null"); - logger.info("Received message"); - Transformer transformer = createTransformer(); - transformer.transform(messageContext.getRequest().getPayloadSource(), - messageContext.getResponse().getPayloadResult()); - } -} diff --git a/sandbox/src/test/java/org/springframework/ws/transport/mail/Driver.java b/sandbox/src/test/java/org/springframework/ws/transport/mail/Driver.java deleted file mode 100644 index 36aa86e2..00000000 --- a/sandbox/src/test/java/org/springframework/ws/transport/mail/Driver.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.ws.transport.mail; - -import java.io.IOException; - -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - * @author Arjen Poutsma - */ -public class Driver { - - public static void main(String[] args) throws IOException { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("applicationContext.xml", Driver.class); - context.registerShutdownHook(); - System.out.println("Started...."); - System.in.read(); - } - -} diff --git a/sandbox/src/test/java/org/springframework/ws/transport/mail/MailMessageSenderIntegrationTest.java b/sandbox/src/test/java/org/springframework/ws/transport/mail/MailMessageSenderIntegrationTest.java deleted file mode 100644 index e841a249..00000000 --- a/sandbox/src/test/java/org/springframework/ws/transport/mail/MailMessageSenderIntegrationTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.ws.transport.mail; - -import javax.xml.namespace.QName; -import javax.xml.soap.MessageFactory; -import javax.xml.soap.SOAPConstants; -import javax.xml.soap.SOAPMessage; - -import junit.framework.TestCase; -import org.springframework.ws.soap.SoapMessage; -import org.springframework.ws.soap.saaj.SaajSoapMessage; -import org.springframework.ws.transport.WebServiceConnection; - -public class MailMessageSenderIntegrationTest extends TestCase { - - private MailMessageSender messageSender; - - private MessageFactory messageFactory; - - private static final String URI = "mailto:ajwpi21@xs4all.nl?subject=SOAP Test"; -// private static final String URI = "mailto:revans@interface21.com?subject=Believe me now?"; - - private static final String SOAP_ACTION = "http://springframework.org/DoIt"; - - protected void setUp() throws Exception { - messageSender = new MailMessageSender(); - messageSender.setFrom("Arjen Poutsma "); - messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); - } - - public void testSendAndReceiveQueueNoResponse() throws Exception { - WebServiceConnection connection = null; - try { - connection = messageSender.createConnection(URI); - SOAPMessage saajMessage = messageFactory.createMessage(); - saajMessage.getSOAPBody().addBodyElement(new QName("http://springframework.org", "test")); - SoapMessage soapRequest = new SaajSoapMessage(saajMessage); - soapRequest.setSoapAction(SOAP_ACTION); - connection.send(soapRequest); -// SoapMessage response = (SoapMessage) connection.receive(new SaajSoapMessageFactory(messageFactory)); - } - finally { - if (connection != null) { - connection.close(); - } - } - } - -} \ No newline at end of file diff --git a/sandbox/src/test/resources/org/springframework/ws/transport/mail/applicationContext.xml b/sandbox/src/test/resources/org/springframework/ws/transport/mail/applicationContext.xml deleted file mode 100644 index ecfcdabb..00000000 --- a/sandbox/src/test/resources/org/springframework/ws/transport/mail/applicationContext.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.springframework.context.Lifecycle - - - - - - - - diff --git a/support/pom.xml b/support/pom.xml index 53e07380..72c2dec5 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -98,12 +98,16 @@ javax.jms jms - provided + true javax.ejb ejb - 2.1 + true + + + javax.mail + mail true diff --git a/support/src/main/java/org/springframework/ws/transport/mail/MailMessageReceiver.java b/support/src/main/java/org/springframework/ws/transport/mail/MailMessageReceiver.java new file mode 100644 index 00000000..49cb2664 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/MailMessageReceiver.java @@ -0,0 +1,280 @@ +/* + * 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.ws.transport.mail; + +import java.util.Properties; +import javax.mail.Folder; +import javax.mail.FolderClosedException; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.URLName; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.springframework.scheduling.SchedulingAwareRunnable; +import org.springframework.util.Assert; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.transport.WebServiceMessageReceiver; +import org.springframework.ws.transport.mail.monitor.MonitoringStrategy; +import org.springframework.ws.transport.mail.monitor.PollingMonitoringStrategy; +import org.springframework.ws.transport.mail.monitor.Pop3PollingMonitoringStrategy; +import org.springframework.ws.transport.mail.support.MailTransportUtils; +import org.springframework.ws.transport.support.AbstractAsyncStandaloneMessageReceiver; + +/** + * Server-side component for receiving email messages using JavaMail. Requires a {@link #setTransportUri(String) + * transport} URI, {@link #setStoreUri(String) store} URI, and {@link #setMonitoringStrategy(MonitoringStrategy) + * monitoringStrategy} to be set, in addition to the {@link #setMessageFactory(WebServiceMessageFactory) messageFactory} + * and {@link #setMessageReceiver(WebServiceMessageReceiver) messageReceiver} required by the base class. + *

+ * The {@link MonitoringStrategy} is used to detect new incoming email request. If the monitoringStrategy + * is not explicitly set, this receiver will use the {@link Pop3PollingMonitoringStrategy} for POP3 servers, and the + * {@link PollingMonitoringStrategy} for IMAP servers. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class MailMessageReceiver extends AbstractAsyncStandaloneMessageReceiver { + + private Session session = Session.getInstance(new Properties(), null); + + private URLName storeUri; + + private URLName transportUri; + + private Folder folder; + + private Store store; + + private InternetAddress from; + + private MonitoringStrategy monitoringStrategy; + + /** Sets the from address to use when sending reponse messages. */ + public void setFrom(String from) throws AddressException { + this.from = new InternetAddress(from); + } + + /** + * Set JavaMail properties for the {@link Session}. + *

+ * A new {@link Session} will be created with those properties. Use either this method or {@link #setSession}, but + * not both. + *

+ * Non-default properties in this instance will override given JavaMail properties. + */ + public void setJavaMailProperties(Properties javaMailProperties) { + session = Session.getInstance(javaMailProperties, null); + } + + /** + * Set the JavaMail Session, possibly pulled from JNDI. + *

+ * Default is a new Session without defaults, that is completely configured via this instance's + * properties. + *

+ * If using a pre-configured Session, non-default properties in this instance will override the + * settings in the Session. + * + * @see #setJavaMailProperties + */ + public void setSession(Session session) { + Assert.notNull(session, "Session must not be null"); + this.session = session; + } + + /** + * Sets the JavaMail Store URI to be used for retrieving request messages. Typically takes the form of + * [imap|pop3]://user:password@host:port/INBOX. Setting this property is required. + *

+ * For example, imap://john:secret@imap.example.com/INBOX + * + * @see Session#getStore(URLName) + */ + public void setStoreUri(String storeUri) { + this.storeUri = new URLName(storeUri); + } + + /** + * Sets the JavaMail Transport URI to be used for sending response messages. Typically takes the form of + * smtp://user:password@host:port. Setting this property is required. + *

+ * For example, smtp://john:secret@smtp.example.com + * + * @see Session#getTransport(URLName) + */ + public void setTransportUri(String transportUri) { + this.transportUri = new URLName(transportUri); + } + + /** + * Sets the monitoring strategy to use for retrieving new requests. Default is the {@link + * PollingMonitoringStrategy}. + */ + public void setMonitoringStrategy(MonitoringStrategy monitoringStrategy) { + this.monitoringStrategy = monitoringStrategy; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(storeUri, "Property 'storeUri' is required"); + Assert.notNull(transportUri, "Property 'transportUri' is required"); + if (monitoringStrategy == null) { + String protocol = storeUri.getProtocol(); + if ("pop3".equals(protocol)) { + monitoringStrategy = new Pop3PollingMonitoringStrategy(); + } + else if ("imap".equals(protocol)) { + monitoringStrategy = new PollingMonitoringStrategy(); + } + else { + throw new IllegalArgumentException("Cannot determine monitoring strategy for \"" + protocol + "\". " + + "Set the 'monitoringStrategy' explicitly."); + } + } + super.afterPropertiesSet(); + } + + protected void onActivate() throws MessagingException { + openSession(); + openFolder(); + } + + protected void onStart() { + if (logger.isInfoEnabled()) { + logger.info("Starting mail receiver [" + MailTransportUtils.toPasswordProtectedString(storeUri) + "]"); + } + execute(new MonitoringRunnable()); + } + + protected void onStop() { + if (logger.isInfoEnabled()) { + logger.info("Stopping mail receiver [" + MailTransportUtils.toPasswordProtectedString(storeUri) + "]"); + } + closeFolder(); + } + + protected void onShutdown() { + if (logger.isInfoEnabled()) { + logger.info("Shutting down mail receiver [" + MailTransportUtils.toPasswordProtectedString(storeUri) + "]"); + } + closeFolder(); + closeSession(); + } + + private void openSession() throws MessagingException { + store = session.getStore(storeUri); + if (logger.isDebugEnabled()) { + logger.debug("Connecting to store [" + MailTransportUtils.toPasswordProtectedString(storeUri) + "]"); + } + store.connect(); + } + + 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 closeFolder() { + MailTransportUtils.closeFolder(folder, true); + } + + private void closeSession() { + MailTransportUtils.closeService(store); + } + + private class MonitoringRunnable implements SchedulingAwareRunnable { + + public void run() { + try { + openFolder(); + while (isRunning()) { + try { + Message[] messages = monitoringStrategy.monitor(folder); + for (int i = 0; i < messages.length; i++) { + if (logger.isDebugEnabled()) { + if (messages[i] instanceof MimeMessage) { + MimeMessage mimeMessage = (MimeMessage) messages[i]; + logger.debug("Received email message with MessageID " + mimeMessage.getMessageID()); + } + } + MessageHandler handler = new MessageHandler(messages[i]); + execute(handler); + } + } + catch (FolderClosedException ex) { + logger.debug("Folder closed, reopening"); + if (isRunning()) { + openFolder(); + } + } + catch (MessagingException ex) { + logger.warn(ex); + } + } + } + catch (InterruptedException ex) { + // Restore the interrupted status + Thread.currentThread().interrupt(); + } + catch (MessagingException ex) { + logger.error(ex); + } + } + + public boolean isLongLived() { + return true; + } + } + + private class MessageHandler implements SchedulingAwareRunnable { + + private final Message message; + + public MessageHandler(Message message) { + this.message = message; + } + + public void run() { + MailReceiverConnection connection = new MailReceiverConnection(message, session); + connection.setTransportUri(transportUri); + connection.setFrom(from); + try { + handleConnection(connection); + } + catch (Exception ex) { + logger.error("Could not handle incoming mail connection", ex); + } + } + + public boolean isLongLived() { + return false; + } + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/mail/MailMessageSender.java b/support/src/main/java/org/springframework/ws/transport/mail/MailMessageSender.java new file mode 100644 index 00000000..3506c256 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/MailMessageSender.java @@ -0,0 +1,164 @@ +/* + * 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.ws.transport.mail; + +import java.io.IOException; +import java.net.URI; +import java.util.Properties; +import javax.mail.Session; +import javax.mail.URLName; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.WebServiceMessageSender; +import org.springframework.ws.transport.mail.monitor.PollingMonitoringStrategy; +import org.springframework.ws.transport.mail.support.MailTransportUtils; + +/** + * {@link WebServiceMessageSender} implementation that uses Mail {@link MimeMessage}s. Requires a {@link + * #setTransportUri(String) transport} and {@link #setStoreUri(String) store} URI to be set. + *

+ * Calling {@link WebServiceConnection#receive(WebServiceMessageFactory)} on connections created by this message sender + * will result in a blocking call, for the amount of milliseconds specified by the {@link #setReceiveSleepTime(long) + * receiveSleepTime} property. This will give the server time to formulate a response message. By default, this propery + * is set to 1 minute. For a proper request-response conversation to work, this property value must not be smaller the + * {@link PollingMonitoringStrategy#setPollingInterval(long) pollingInterval} property of the server-side message + * receiver polling strategy. + *

+ * This message sender supports URI's of the following format:

mailto:to[?param-name=param-value][&param-name=param-value]* + *
where the characters :, ?, and & stand for + * themselves. The to represents a RFC 822 mailbox. Valid param-name include: + *

+ *

+ *
param-nameDescription
subjectThe subject of the request message.
+ *

+ * Some examples of email URIs are: + *

+ *

mailto:john@example.com
mailto:john@example.com@?subject=SOAP%20Test
+ * + * @author Arjen Poutsma + * @see The mailto URL scheme + * @since 1.1.0 + */ +public class MailMessageSender implements WebServiceMessageSender, InitializingBean { + + /** Default timeout for receive operations. Set to 1000 * 60 milliseconds (i.e. 1 minute). */ + public static final long DEFAULT_RECEIVE_TIMEOUT = 1000 * 60; + + private long receiveSleepTime = DEFAULT_RECEIVE_TIMEOUT; + + private Session session = Session.getInstance(new Properties(), null); + + private URLName storeUri; + + private URLName transportUri; + + private InternetAddress from; + + /** Sets the from address to use when sending request messages. */ + public void setFrom(String from) throws AddressException { + this.from = new InternetAddress(from); + } + + /** + * Set JavaMail properties for the {@link Session}. + *

+ * A new {@link Session} will be created with those properties. Use either this method or {@link #setSession}, but + * not both. + *

+ * Non-default properties in this instance will override given JavaMail properties. + */ + public void setJavaMailProperties(Properties javaMailProperties) { + session = Session.getInstance(javaMailProperties, null); + } + + /** + * Set the sleep time to use for receive calls, in milliseconds. The default is 1000 * 60 ms, that + * is 1 minute. + */ + public void setReceiveSleepTime(long receiveSleepTime) { + this.receiveSleepTime = receiveSleepTime; + } + + /** + * Set the JavaMail Session, possibly pulled from JNDI. + *

+ * Default is a new Session without defaults, that is completely configured via this instance's + * properties. + *

+ * If using a pre-configured Session, non-default properties in this instance will override the + * settings in the Session. + * + * @see #setJavaMailProperties + */ + public void setSession(Session session) { + Assert.notNull(session, "Session must not be null"); + this.session = session; + } + + /** + * Sets the JavaMail Store URI to be used for retrieving response messages. Typically takes the form of + * [imap|pop3]://user:password@host:port/INBOX. Setting this property is required. + *

+ * For example, imap://john:secret@imap.example.com/INBOX + * + * @see Session#getStore(URLName) + */ + public void setStoreUri(String storeUri) { + this.storeUri = new URLName(storeUri); + } + + /** + * Sets the JavaMail Transport URI to be used for sending response messages. Typically takes the form of + * smtp://user:password@host:port. Setting this property is required. + *

+ * For example, smtp://john:secret@smtp.example.com + * + * @see Session#getTransport(URLName) + */ + public void setTransportUri(String transportUri) { + this.transportUri = new URLName(transportUri); + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(transportUri, "'transportUri' is required"); + Assert.notNull(storeUri, "'storeUri' is required"); + } + + public WebServiceConnection createConnection(URI uri) throws IOException { + InternetAddress to = MailTransportUtils.getTo(uri); + MailSenderConnection connection = + new MailSenderConnection(session, transportUri, storeUri, to, receiveSleepTime); + if (from != null) { + connection.setFrom(from); + } + String subject = MailTransportUtils.getSubject(uri); + if (subject != null) { + connection.setSubject(subject); + } + return connection; + } + + public boolean supports(URI uri) { + return uri.getScheme().equals(MailTransportConstants.MAIL_URI_SCHEME); + } +} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/mail/MailReceiverConnection.java b/support/src/main/java/org/springframework/ws/transport/mail/MailReceiverConnection.java similarity index 85% rename from sandbox/src/main/java/org/springframework/ws/transport/mail/MailReceiverConnection.java rename to support/src/main/java/org/springframework/ws/transport/mail/MailReceiverConnection.java index e4a63947..619ddab7 100644 --- a/sandbox/src/main/java/org/springframework/ws/transport/mail/MailReceiverConnection.java +++ b/support/src/main/java/org/springframework/ws/transport/mail/MailReceiverConnection.java @@ -40,10 +40,15 @@ import org.springframework.util.Assert; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.transport.AbstractReceiverConnection; import org.springframework.ws.transport.TransportConstants; -import org.springframework.ws.transport.mail.support.MailUtils; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.mail.support.MailTransportUtils; /** + * Implementation of {@link WebServiceConnection} that is used for server-side Mail access. Exposes a {@link Message} + * request and response message. + * * @author Arjen Poutsma + * @since 1.1.0 */ public class MailReceiverConnection extends AbstractReceiverConnection { @@ -61,13 +66,39 @@ public class MailReceiverConnection extends AbstractReceiverConnection { private InternetAddress from; - public MailReceiverConnection(Message requestMessage, Session session) { + /** Constructs a new Mail connection with the given parameters. */ + protected MailReceiverConnection(Message requestMessage, Session session) { Assert.notNull(requestMessage, "'requestMessage' must not be null"); Assert.notNull(session, "'session' must not be null"); this.requestMessage = requestMessage; this.session = session; } + /** Returns the request message for this connection. */ + public Message getRequestMessage() { + return requestMessage; + } + + /** Returns the response message, if any, for this connection. */ + public Message getResponseMessage() { + return responseMessage; + } + + /* + * Package-friendly setters + */ + void setTransportUri(URLName transportUri) { + this.transportUri = transportUri; + } + + void setFrom(InternetAddress from) { + this.from = from; + } + + /* + * Errors + */ + public String getErrorMessage() throws IOException { return null; } @@ -76,10 +107,6 @@ public class MailReceiverConnection extends AbstractReceiverConnection { return false; } - public void setTransportUri(URLName transportUri) { - this.transportUri = transportUri; - } - public void close() throws IOException { } @@ -167,14 +194,10 @@ public class MailReceiverConnection extends AbstractReceiverConnection { throw new MailTransportException(ex); } finally { - MailUtils.closeService(transport); + MailTransportUtils.closeService(transport); } } - public void setFrom(InternetAddress from) { - this.from = from; - } - private class ByteArrayDataSource implements DataSource { private byte[] data; diff --git a/support/src/main/java/org/springframework/ws/transport/mail/MailSenderConnection.java b/support/src/main/java/org/springframework/ws/transport/mail/MailSenderConnection.java new file mode 100644 index 00000000..85c1d8a9 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/MailSenderConnection.java @@ -0,0 +1,318 @@ +/* + * 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.ws.transport.mail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.Header; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.Transport; +import javax.mail.URLName; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.mail.search.HeaderTerm; +import javax.mail.search.SearchTerm; + +import org.springframework.util.Assert; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.transport.AbstractSenderConnection; +import org.springframework.ws.transport.TransportConstants; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.mail.support.MailTransportUtils; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Implementation of {@link WebServiceConnection} that is used for client-side Mail access. Exposes a {@link Message} + * request and response message. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ + +public class MailSenderConnection extends AbstractSenderConnection { + + private static final Log logger = LogFactory.getLog(MailSenderConnection.class); + + private final Session session; + + private MimeMessage requestMessage; + + private Message responseMessage; + + private String requestContentType; + + private boolean deleteAfterReceive = false; + + private final URLName storeUri; + + private final URLName transportUri; + + private ByteArrayOutputStream requestBuffer; + + private InternetAddress from; + + private final InternetAddress to; + + private String subject; + + private final long receiveTimeout; + + private Store store; + + private Folder folder; + + /** Constructs a new Mail connection with the given parameters. */ + protected MailSenderConnection(Session session, + URLName transportUri, + URLName storeUri, + InternetAddress to, + long receiveTimeout) { + Assert.notNull(session, "'session' must not be null"); + Assert.notNull(transportUri, "'transportUri' must not be null"); + Assert.notNull(storeUri, "'storeUri' must not be null"); + Assert.notNull(to, "'to' must not be null"); + this.session = session; + this.transportUri = transportUri; + this.storeUri = storeUri; + this.to = to; + this.receiveTimeout = receiveTimeout; + } + + /** Returns the request message for this connection. */ + public Message getRequestMessage() { + return requestMessage; + } + + /** Returns the response message, if any, for this connection. */ + public Message getResponseMessage() { + return responseMessage; + } + + /* + * Package-friendly setters + */ + + void setFrom(InternetAddress from) { + this.from = from; + } + + void setSubject(String subject) { + this.subject = subject; + } + + /* + * Sending + */ + protected void onSendBeforeWrite(WebServiceMessage message) throws IOException { + try { + requestMessage = new MimeMessage(session); + requestMessage.setRecipient(Message.RecipientType.TO, to); + requestMessage.setSentDate(new Date()); + if (from != null) { + requestMessage.setFrom(from); + } + if (subject != null) { + requestMessage.setSubject(subject); + } + requestBuffer = new ByteArrayOutputStream(); + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + } + } + + protected void addRequestHeader(String name, String value) throws IOException { + try { + requestMessage.addHeader(name, value); + if (TransportConstants.HEADER_CONTENT_TYPE.equals(name)) { + requestContentType = value; + } + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + } + } + + protected OutputStream getRequestOutputStream() throws IOException { + return requestBuffer; + } + + protected void onSendAfterWrite(WebServiceMessage message) throws IOException { + Transport transport = null; + try { + requestMessage.setDataHandler( + new DataHandler(new ByteArrayDataSource(requestContentType, requestBuffer.toByteArray()))); + transport = session.getTransport(transportUri); + transport.connect(); + requestMessage.saveChanges(); + transport.sendMessage(requestMessage, requestMessage.getAllRecipients()); + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + } + finally { + MailTransportUtils.closeService(transport); + } + } + + /* + * Receiving + */ + + protected void onReceiveBeforeRead() throws IOException { + try { + String requestMessageId = requestMessage.getMessageID(); + Assert.hasLength(requestMessageId, "No Message-ID found on request message [" + requestMessage + "]"); + try { + Thread.sleep(receiveTimeout); + } + catch (InterruptedException e) { + // Re-interrupt current thread, to allow other threads to react. + Thread.currentThread().interrupt(); + } + openFolder(); + SearchTerm searchTerm = new HeaderTerm(MailTransportConstants.HEADER_IN_REPLY_TO, requestMessageId); + Message[] responses = folder.search(searchTerm); + if (responses.length > 0) { + if (responses.length > 1) { + logger.warn("Received more than one response for request with ID [" + requestMessageId + "]"); + } + responseMessage = responses[0]; + } + if (deleteAfterReceive) { + responseMessage.setFlag(Flags.Flag.DELETED, true); + } + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + } + } + + private void openFolder() throws MessagingException { + store = session.getStore(storeUri); + store.connect(); + folder = store.getFolder(storeUri); + if (folder == null || !folder.exists()) { + throw new IllegalStateException("No default folder to receive from"); + } + if (deleteAfterReceive) { + folder.open(Folder.READ_WRITE); + } + else { + folder.open(Folder.READ_ONLY); + } + } + + protected boolean hasResponse() throws IOException { + return responseMessage != null; + } + + protected Iterator getResponseHeaderNames() throws IOException { + try { + List headers = new ArrayList(); + Enumeration enumeration = responseMessage.getAllHeaders(); + while (enumeration.hasMoreElements()) { + Header header = (Header) enumeration.nextElement(); + headers.add(header.getName()); + } + return headers.iterator(); + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + } + } + + protected Iterator getResponseHeaders(String name) throws IOException { + try { + String[] headers = responseMessage.getHeader(name); + return Arrays.asList(headers).iterator(); + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + + } + } + + protected InputStream getResponseInputStream() throws IOException { + try { + return responseMessage.getDataHandler().getInputStream(); + } + catch (MessagingException ex) { + throw new MailTransportException(ex); + } + } + + public boolean hasError() throws IOException { + return false; + } + + public String getErrorMessage() throws IOException { + return null; + } + + public void close() throws IOException { + MailTransportUtils.closeFolder(folder, deleteAfterReceive); + MailTransportUtils.closeService(store); + } + + private class ByteArrayDataSource implements DataSource { + + private byte[] data; + + private String contentType; + + public ByteArrayDataSource(String contentType, byte[] data) { + this.data = data; + this.contentType = contentType; + } + + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(data); + } + + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } + + public String getContentType() { + return contentType; + } + + public String getName() { + return "ByteArrayDataSource"; + } + } + +} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/mail/MailTransportConstants.java b/support/src/main/java/org/springframework/ws/transport/mail/MailTransportConstants.java similarity index 83% rename from sandbox/src/main/java/org/springframework/ws/transport/mail/MailTransportConstants.java rename to support/src/main/java/org/springframework/ws/transport/mail/MailTransportConstants.java index 6d2c61b3..aa413ece 100644 --- a/sandbox/src/main/java/org/springframework/ws/transport/mail/MailTransportConstants.java +++ b/support/src/main/java/org/springframework/ws/transport/mail/MailTransportConstants.java @@ -19,14 +19,16 @@ package org.springframework.ws.transport.mail; import org.springframework.ws.transport.TransportConstants; /** + * Declares Mail-specific transport constants. + * * @author Arjen Poutsma + * @since 1.1.0 */ public interface MailTransportConstants extends TransportConstants { - /** - * The "In-Reply-To" header. - */ - String HEADER_IN_REPLY_TO = "In-Reply-To"; + /** The "mail" URI scheme. */ + String MAIL_URI_SCHEME = "mailto"; - String URI_SCHEME = "mailto"; + /** The "In-Reply-To" header. */ + String HEADER_IN_REPLY_TO = "In-Reply-To"; } diff --git a/support/src/main/java/org/springframework/ws/transport/mail/MailTransportException.java b/support/src/main/java/org/springframework/ws/transport/mail/MailTransportException.java new file mode 100644 index 00000000..b166387d --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/MailTransportException.java @@ -0,0 +1,48 @@ +/* + * 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.ws.transport.mail; + +import javax.mail.MessagingException; + +import org.springframework.ws.transport.TransportException; + +/** + * Exception that is thrown when an error occurs in the Mail transport. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class MailTransportException extends TransportException { + + private final MessagingException messagingException; + + public MailTransportException(String msg, MessagingException ex) { + super(msg + ": " + ex.getMessage()); + initCause(ex); + messagingException = ex; + } + + public MailTransportException(MessagingException ex) { + super(ex.getMessage()); + initCause(ex); + messagingException = ex; + } + + public MessagingException getMessagingException() { + return messagingException; + } +} diff --git a/support/src/main/java/org/springframework/ws/transport/mail/monitor/AbstractMonitoringStrategy.java b/support/src/main/java/org/springframework/ws/transport/mail/monitor/AbstractMonitoringStrategy.java new file mode 100644 index 00000000..96b8e22b --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/monitor/AbstractMonitoringStrategy.java @@ -0,0 +1,162 @@ +/* + * 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.ws.transport.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 + * @since 1.1.0 + */ +public abstract class AbstractMonitoringStrategy implements MonitoringStrategy { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + private boolean deleteMessages = true; + + /** + * Sets whether messages should be marked as {@link 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; + } + + /** + * 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[] monitor(Folder folder) throws MessagingException, InterruptedException { + waitForNewMessages(folder); + Message[] messages = searchForNewMessages(folder); + if (logger.isDebugEnabled()) { + logger.debug("Found " + messages.length + " new messages"); + } + if (messages.length > 0) { + fetchMessages(folder, messages); + } + if (deleteMessages) { + deleteMessages(folder, messages); + } + return messages; + } + + /** + * Template method that blocks until new messages arrive in the given folder. Typical implementations use {@link + * Thread#sleep(long)} or the IMAP IDLE command. + * + * @param folder the folder to monitor + * @throws MessagingException in case of JavaMail errors + * @throws InterruptedException when a thread is interrupted + */ + protected abstract void waitForNewMessages(Folder folder) throws MessagingException, InterruptedException; + + /** + * Retrieves new messages from the given folder. This implementation creates a {@link SearchTerm} that searches for + * all messages in the folder that are {@link Flags.Flag#RECENT RECENT}, not {@link Flags.Flag#ANSWERED ANSWERED}, + * and not {@link 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.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 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/support/src/main/java/org/springframework/ws/transport/mail/monitor/ImapIdleMonitoringStrategy.java b/support/src/main/java/org/springframework/ws/transport/mail/monitor/ImapIdleMonitoringStrategy.java new file mode 100644 index 00000000..b56c3d41 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/monitor/ImapIdleMonitoringStrategy.java @@ -0,0 +1,79 @@ +/* + * 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.ws.transport.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; + +import org.springframework.util.Assert; + +import com.sun.mail.imap.IMAPFolder; + +/** + * 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 + * @since 1.1.0 + */ +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 { + 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/support/src/main/java/org/springframework/ws/transport/mail/monitor/MonitoringStrategy.java b/support/src/main/java/org/springframework/ws/transport/mail/monitor/MonitoringStrategy.java new file mode 100644 index 00000000..0de64e54 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/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.ws.transport.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[] monitor(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/support/src/main/java/org/springframework/ws/transport/mail/monitor/PollingMonitoringStrategy.java b/support/src/main/java/org/springframework/ws/transport/mail/monitor/PollingMonitoringStrategy.java new file mode 100644 index 00000000..130300ae --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/monitor/PollingMonitoringStrategy.java @@ -0,0 +1,63 @@ +/* + * 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.ws.transport.mail.monitor; + +import javax.mail.Folder; +import javax.mail.MessagingException; + +/** + * 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 + * @since 1.1.0 + */ +public class PollingMonitoringStrategy extends AbstractMonitoringStrategy { + + /** Defines the default polling frequency. Set to 1000 * 60 milliseconds (i.e. 1 minute). */ + public static final long DEFAULT_POLLING_FREQUENCY = 1000 * 60; + + private long pollingInterval = DEFAULT_POLLING_FREQUENCY; + + /** + * Sets the interval used in between message polls, in milliseconds. The default is 1000 * 60 ms, + * that is 1 minute. + */ + public void setPollingInterval(long pollingInterval) { + this.pollingInterval = pollingInterval; + } + + protected void waitForNewMessages(Folder folder) throws MessagingException, InterruptedException { + Thread.sleep(pollingInterval); + afterSleep(folder); + } + + /** + * Invoked after the {@link Thread#sleep(long)} method has been invoked. This implementation calls {@link + * Folder#getMessageCount(), to force new messages to be seen. + * + * @param folder the folder to check for new messages + * @throws MessagingException in case of JavaMail errors + */ + protected void afterSleep(Folder folder) throws MessagingException { + folder.getMessageCount(); + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/mail/monitor/Pop3PollingMonitoringStrategy.java b/support/src/main/java/org/springframework/ws/transport/mail/monitor/Pop3PollingMonitoringStrategy.java new file mode 100644 index 00000000..1272e667 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/monitor/Pop3PollingMonitoringStrategy.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.ws.transport.mail.monitor; + +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import org.springframework.ws.transport.mail.support.MailTransportUtils; + +/** + * 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 + * @since 1.1.0 + */ +public class Pop3PollingMonitoringStrategy extends PollingMonitoringStrategy { + + public Pop3PollingMonitoringStrategy() { + super.setDeleteMessages(true); + } + + public void setDeleteMessages(boolean deleteMessages) { + } + + /** Re-opens the folder, if it closed. */ + protected void afterSleep(Folder folder) throws MessagingException { + if (!folder.isOpen()) { + folder.open(Folder.READ_WRITE); + } + } + + /** 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 diff --git a/support/src/main/java/org/springframework/ws/transport/mail/monitor/package.html b/support/src/main/java/org/springframework/ws/transport/mail/monitor/package.html new file mode 100644 index 00000000..43f8e7c7 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/monitor/package.html @@ -0,0 +1,6 @@ + + +Provides the MonitoringStrategy interface and implementations. Used for monitoring a JavaMail Folder for new email +messages. + + \ No newline at end of file diff --git a/support/src/main/java/org/springframework/ws/transport/mail/package.html b/support/src/main/java/org/springframework/ws/transport/mail/package.html new file mode 100644 index 00000000..18e796fc --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/package.html @@ -0,0 +1,5 @@ + + +Package providing support for handling messages via email. + + diff --git a/support/src/main/java/org/springframework/ws/transport/mail/support/MailTransportUtils.java b/support/src/main/java/org/springframework/ws/transport/mail/support/MailTransportUtils.java new file mode 100644 index 00000000..b356ac2e --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/support/MailTransportUtils.java @@ -0,0 +1,170 @@ +/* + * 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.ws.transport.mail.support; + +import java.net.URI; +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.springframework.util.StringUtils; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Collection of utility methods to work with Mail transports. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +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(null)) { + 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(); + } + + +} diff --git a/support/src/main/java/org/springframework/ws/transport/mail/support/package.html b/support/src/main/java/org/springframework/ws/transport/mail/support/package.html new file mode 100644 index 00000000..d409f6cb --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/mail/support/package.html @@ -0,0 +1,5 @@ + + +Classes supporting the org.springframework.ws.transport.mail package. + + \ No newline at end of file diff --git a/sandbox/src/main/java/org/springframework/ws/transport/support/AbstractMultiThreadedMessageReceiver.java b/support/src/main/java/org/springframework/ws/transport/support/AbstractAsyncStandaloneMessageReceiver.java similarity index 84% rename from sandbox/src/main/java/org/springframework/ws/transport/support/AbstractMultiThreadedMessageReceiver.java rename to support/src/main/java/org/springframework/ws/transport/support/AbstractAsyncStandaloneMessageReceiver.java index 517fe756..c42076cc 100644 --- a/sandbox/src/main/java/org/springframework/ws/transport/support/AbstractMultiThreadedMessageReceiver.java +++ b/support/src/main/java/org/springframework/ws/transport/support/AbstractAsyncStandaloneMessageReceiver.java @@ -19,16 +19,16 @@ package org.springframework.ws.transport.support; import org.springframework.beans.factory.BeanNameAware; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; -import org.springframework.util.ClassUtils; import org.springframework.scheduling.commonj.WorkManagerTaskExecutor; +import org.springframework.util.ClassUtils; /** - * Abstract base class for standalone, server-side transport objects. Contains a Spring {@link TaskExecutor}, and - * various lifecycle callbacks. + * Abstract base class for asynchronous standalone, server-side transport objects. Contains a Spring {@link + * TaskExecutor}, and various lifecycle callbacks. * * @author Arjen Poutsma */ -public abstract class AbstractMultiThreadedMessageReceiver extends AbstractStandaloneMessagingReceiver +public abstract class AbstractAsyncStandaloneMessageReceiver extends AbstractStandaloneMessageReceiver implements BeanNameAware { /** Default thread name prefix. */ @@ -38,11 +38,6 @@ public abstract class AbstractMultiThreadedMessageReceiver extends AbstractStand private String beanName; - /** Returns the task executor. */ - public TaskExecutor getTaskExecutor() { - return taskExecutor; - } - /** * Set the Spring {@link TaskExecutor} to use for running the listener threads. Default is {@link * SimpleAsyncTaskExecutor}, starting up a number of new threads. @@ -77,4 +72,13 @@ public abstract class AbstractMultiThreadedMessageReceiver extends AbstractStand String threadNamePrefix = beanName != null ? beanName + "-" : DEFAULT_THREAD_NAME_PREFIX; return new SimpleAsyncTaskExecutor(threadNamePrefix); } + + /** + * Executes the given {@link Runnable} via this receiver's {@link TaskExecutor}. + * + * @see #setTaskExecutor(TaskExecutor) + */ + protected void execute(Runnable runnable) { + taskExecutor.execute(runnable); + } } diff --git a/sandbox/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessagingReceiver.java b/support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java similarity index 73% rename from sandbox/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessagingReceiver.java rename to support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java index fb3676f9..701e0b16 100644 --- a/sandbox/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessagingReceiver.java +++ b/support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java @@ -19,8 +19,14 @@ package org.springframework.ws.transport.support; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.Lifecycle; -/** @author Arjen Poutsma */ -public abstract class AbstractStandaloneMessagingReceiver extends SimpleWebServiceMessageReceiverObjectSupport +/** + * Abstract base class for standalone, server-side transport objects. Provides a basic, thread-safe implementation of + * the {@link Lifecycle} interface, and various template methods to be implemented by concrete sub classes. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public abstract class AbstractStandaloneMessageReceiver extends SimpleWebServiceMessageReceiverObjectSupport implements Lifecycle, DisposableBean { private volatile boolean active = false; @@ -54,20 +60,20 @@ public abstract class AbstractStandaloneMessagingReceiver extends SimpleWebServi this.autoStartup = autoStartup; } + /** Calls {@link #activate()} when the BeanFactory initializes the receiver instance. */ public void afterPropertiesSet() throws Exception { activate(); } - /** - * Calls shutdown when the BeanFactory destroys the server instance. - * - * @see #shutdown() - */ + /** Calls {@link #shutdown()} when the BeanFactory destroys the receiver instance. */ public void destroy() { shutdown(); } - /** Initialize this server. Starts the server if autoStartup hasn't been turned off. */ + /** + * Initialize this server. Starts the server if {@link #setAutoStartup(boolean) autoStartup} hasn't been turned + * off. + */ public final void activate() throws Exception { synchronized (lifecycleMonitor) { active = true; @@ -97,7 +103,7 @@ public abstract class AbstractStandaloneMessagingReceiver extends SimpleWebServi onStop(); } - /** Shut down the registered listeners and close this listener container. */ + /** Shut down this server. */ public final void shutdown() { synchronized (lifecycleMonitor) { running = false; @@ -107,11 +113,19 @@ public abstract class AbstractStandaloneMessagingReceiver extends SimpleWebServi onShutdown(); } + /** + * Template method invoked when {@link #activate()} is invoked. + * + * @throws Exception in case of errors + */ protected abstract void onActivate() throws Exception; + /** Template method invoked when {@link #start()} is invoked. */ protected abstract void onStart(); + /** Template method invoked when {@link #stop()} is invoked. */ protected abstract void onStop(); + /** Template method invoked when {@link #shutdown()} is invoked. */ protected abstract void onShutdown(); } diff --git a/support/src/test/java/org/springframework/ws/transport/mail/support/MailTransportUtilsTest.java b/support/src/test/java/org/springframework/ws/transport/mail/support/MailTransportUtilsTest.java new file mode 100644 index 00000000..2dbef069 --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/mail/support/MailTransportUtilsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright ${YEAR} 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.ws.transport.mail.support; + +import java.net.URI; +import javax.mail.URLName; +import javax.mail.internet.InternetAddress; + +import junit.framework.TestCase; + +public class MailTransportUtilsTest extends TestCase { + + public void testToPasswordProtectedString() throws Exception { + URLName name = new URLName("imap://john:secret@imap.example.com/INBOX"); + String result = MailTransportUtils.toPasswordProtectedString(name); + assertEquals("Password found in string", -1, result.indexOf("secret")); + } + + public void testGetTo() throws Exception { + URI uri = new URI("mailto:infobot@example.com?subject=current-issue"); + InternetAddress to = MailTransportUtils.getTo(uri); + assertEquals("Invalid destination", new InternetAddress("infobot@example.com"), to); + + uri = new URI("mailto:infobot@example.com"); + to = MailTransportUtils.getTo(uri); + assertEquals("Invalid destination", new InternetAddress("infobot@example.com"), to); + } + + public void testGetSubject() throws Exception { + URI uri = new URI("mailto:infobot@example.com?subject=current-issue"); + String subject = MailTransportUtils.getSubject(uri); + assertEquals("Invalid destination", "current-issue", subject); + + uri = new URI("mailto:infobot@example.com"); + subject = MailTransportUtils.getSubject(uri); + assertNull("Invalid destination", subject); + } +} \ No newline at end of file