diff --git a/core/src/main/java/org/springframework/ws/transport/support/WebServiceMessageReceiverObjectSupport.java b/core/src/main/java/org/springframework/ws/transport/support/WebServiceMessageReceiverObjectSupport.java
index e20b6782..d0a859ac 100644
--- a/core/src/main/java/org/springframework/ws/transport/support/WebServiceMessageReceiverObjectSupport.java
+++ b/core/src/main/java/org/springframework/ws/transport/support/WebServiceMessageReceiverObjectSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 the original author or authors.
+ * Copyright 2005-2010 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.
@@ -18,9 +18,6 @@ package org.springframework.ws.transport.support;
import java.net.URISyntaxException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.ws.FaultAwareWebServiceMessage;
@@ -37,6 +34,9 @@ import org.springframework.ws.transport.context.DefaultTransportContext;
import org.springframework.ws.transport.context.TransportContext;
import org.springframework.ws.transport.context.TransportContextHolder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
/**
* Convenience base class for server-side transport objects. Contains a {@link WebServiceMessageFactory}, and has
* methods for handling incoming {@link WebServiceConnection}s.
@@ -111,7 +111,7 @@ public abstract class WebServiceMessageReceiverObjectSupport implements Initiali
private void logUri(WebServiceConnection connection) {
if (logger.isDebugEnabled()) {
try {
- logger.debug("Accepting incoming [" + connection + "] to [" + connection.getUri() + "]");
+ logger.debug("Accepting incoming [" + connection + "] at [" + connection.getUri() + "]");
}
catch (URISyntaxException e) {
// ignore
diff --git a/parent/pom.xml b/parent/pom.xml
index 206a4e56..8e5071ed 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -634,6 +634,11 @@
activemq-core
4.1.1
+
+ jivesoftware
+ smack
+ 3.1.0
+
commons-logging
diff --git a/src/docbkx/client.xml b/src/docbkx/client.xml
index fe416e94..48e13a54 100644
--- a/src/docbkx/client.xml
+++ b/src/docbkx/client.xml
@@ -182,6 +182,47 @@
+]]>
+
+
+
+ XMPP transport
+
+ Spring Web Services 2.0 introduced an XMPP (Jabber) transport, which can be used to send and
+ receive web service messages via XMPP. The client-side XMPP
+ functionality is contained in the XmppMessageSender class.
+ This class creates an XMPP message from the request
+ WebServiceMessage, and sends it via XMPP. It then listens for a
+ response message to arrive.
+
+
+ To use the XmppMessageSender, set the defaultUri or
+ uri parameter to a xmpp URI, for example
+ xmpp:johndoe@jabber.org. The sender also requires an
+ XMPPConnection to work, which can be conveniently created using the
+ org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean.
+
+
+ The following example shows how to use the xmpp transport:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
]]>
diff --git a/src/docbkx/server.xml b/src/docbkx/server.xml
index 6dd5c938..5101dd18 100644
--- a/src/docbkx/server.xml
+++ b/src/docbkx/server.xml
@@ -523,6 +523,50 @@
Javadoc.
+
+ XMPP transport
+
+ Finally, Spring Web Services 2.0 introduced support for XMPP, otherwise known as Jabber. The support
+ is based on the Smack
+ library.
+
+
+ Spring Web Services support for XMPP is very similar to the other transports: there is a a
+ XmppMessageSender for the WebServiceTemplate and
+ and a XmppMessageReceiver to use with the
+ MessageDispatcher.
+
+ The following example shows how to set up the server-side XMPP components:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
Endpoints
diff --git a/support/pom.xml b/support/pom.xml
index a6be77d4..24d87afa 100644
--- a/support/pom.xml
+++ b/support/pom.xml
@@ -11,20 +11,6 @@
jar
Spring WS Support
Spring Web Services Support package.
-
jdk15
@@ -103,6 +89,10 @@
mail
true
+
+ jivesoftware
+ smack
+
org.apache.activemq
diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsSenderConnection.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsSenderConnection.java
index b32af74a..54f60824 100644
--- a/support/src/main/java/org/springframework/ws/transport/jms/JmsSenderConnection.java
+++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsSenderConnection.java
@@ -257,7 +257,8 @@ public class JmsSenderConnection extends AbstractSenderConnection {
}
else if (message != null) {
throw new IllegalArgumentException(
- "Wrong message type: [" + message.getClass() + "]. Only BytesMessages can be handled.");
+ "Wrong message type: [" + message.getClass() + "]. " +
+ "Only BytesMessages or TextMessages can be handled.");
}
}
catch (JMSException ex) {
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
index c4e84ba7..8a2e7475 100644
--- a/support/src/main/java/org/springframework/ws/transport/mail/MailMessageReceiver.java
+++ b/support/src/main/java/org/springframework/ws/transport/mail/MailMessageReceiver.java
@@ -66,7 +66,7 @@ public class MailMessageReceiver extends AbstractAsyncStandaloneMessageReceiver
private MonitoringStrategy monitoringStrategy;
- /** Sets the from address to use when sending reponse messages. */
+ /** Sets the from address to use when sending response messages. */
public void setFrom(String from) throws AddressException {
this.from = new InternetAddress(from);
}
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/MessageInputStream.java b/support/src/main/java/org/springframework/ws/transport/xmpp/MessageInputStream.java
new file mode 100644
index 00000000..a22ff4f1
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/MessageInputStream.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.util.Assert;
+
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Input stream that wraps a {@link Message}.
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+class MessageInputStream extends FilterInputStream {
+
+ MessageInputStream(Message message, String encoding) throws IOException {
+ super(createInputStream(message, encoding));
+ }
+
+ private static InputStream createInputStream(Message message, String encoding) throws IOException {
+ Assert.notNull(message, "'message' must not be null");
+ Assert.notNull(encoding, "'encoding' must not be null");
+ String text = message.getBody();
+ byte[] contents = text != null ? text.getBytes(encoding) : new byte[0];
+ return new ByteArrayInputStream(contents);
+ }
+
+
+}
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/MessageOutputStream.java b/support/src/main/java/org/springframework/ws/transport/xmpp/MessageOutputStream.java
new file mode 100644
index 00000000..d6379cc5
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/MessageOutputStream.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+
+import org.springframework.util.Assert;
+
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Output stream that wraps a {@link Message}.
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+class MessageOutputStream extends FilterOutputStream {
+
+ private final Message message;
+
+ private final String encoding;
+
+ MessageOutputStream(Message message, String encoding) {
+ super(new ByteArrayOutputStream());
+ Assert.notNull(message, "'message' must not be null");
+ Assert.notNull(encoding, "'encoding' must not be null");
+ this.message = message;
+ this.encoding = encoding;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ super.flush();
+ ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
+ String text = new String(bos.toByteArray(), encoding);
+ message.setBody(text);
+ }
+}
\ No newline at end of file
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/XmppMessageReceiver.java b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppMessageReceiver.java
new file mode 100644
index 00000000..f29be219
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppMessageReceiver.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import org.springframework.ws.transport.support.AbstractStandaloneMessageReceiver;
+
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Server-side component for receiving XMPP (Jabber) messages. Requires a {@linkplain #setConnection(XMPPConnection)
+ * connection} to be set, in addition to the {@link #setMessageFactory(org.springframework.ws.WebServiceMessageFactory)
+ * messageFactory} and {@link #setMessageReceiver(org.springframework.ws.transport.WebServiceMessageReceiver)
+ * messageReceiver} required by the base class.
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @see org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean
+ * @since 2.0
+ */
+public class XmppMessageReceiver extends AbstractStandaloneMessageReceiver {
+
+ /** Default encoding used to read from and write to {@link org.jivesoftware.smack.packet.Message} messages. */
+ public static final String DEFAULT_MESSAGE_ENCODING = "UTF-8";
+
+ private XMPPConnection connection;
+
+ private WebServicePacketListener packetListener;
+
+ private String messageEncoding = DEFAULT_MESSAGE_ENCODING;
+
+ public XmppMessageReceiver() {
+ }
+
+ /** Sets the {@code XMPPConnection} to use. Setting this property is required. */
+ public void setConnection(XMPPConnection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ protected void onActivate() throws XMPPException {
+ if (!connection.isConnected()) {
+ connection.connect();
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ if (logger.isInfoEnabled()) {
+ logger.info("Starting XMPP receiver [" + connection.getUser() + "]");
+ }
+ packetListener = new WebServicePacketListener();
+ PacketFilter packetFilter = new PacketTypeFilter(Message.class);
+ connection.addPacketListener(packetListener, packetFilter);
+ }
+
+ @Override
+ protected void onStop() {
+ if (logger.isInfoEnabled()) {
+ logger.info("Stopping XMPP receiver [" + connection.getUser() + "]");
+ }
+ connection.removePacketListener(packetListener);
+ packetListener = null;
+ }
+
+ @Override
+ protected void onShutdown() {
+ if (logger.isInfoEnabled()) {
+ logger.info("Shutting down XMPP receiver [" + connection.getUser() + "]");
+ }
+ if (connection.isConnected()) {
+ connection.disconnect();
+ }
+ }
+
+ private class WebServicePacketListener implements PacketListener {
+
+ public void processPacket(Packet packet) {
+ logger.info("Received " + packet);
+ if (packet instanceof Message) {
+ Message message = (Message) packet;
+ try {
+ XmppReceiverConnection wsConnection = new XmppReceiverConnection(connection, message);
+ wsConnection.setMessageEncoding(messageEncoding);
+ handleConnection(wsConnection);
+ }
+ catch (Exception ex) {
+ logger.error(ex);
+ }
+ }
+ }
+ }
+
+}
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/XmppMessageSender.java b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppMessageSender.java
new file mode 100644
index 00000000..fa65b888
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppMessageSender.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.UUID;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+import org.springframework.ws.transport.WebServiceConnection;
+import org.springframework.ws.transport.WebServiceMessageSender;
+import org.springframework.ws.transport.xmpp.support.XmppTransportUtils;
+
+import org.jivesoftware.smack.XMPPConnection;
+
+/**
+ * {@link WebServiceMessageSender} implementation that uses XMPP {@link org.jivesoftware.smack.packet.Message}s.
+ * Requires a {@link #setConnection(org.jivesoftware.smack.XMPPConnection) connection}to be set.
+ *
+ * This message sender supports URI's of the following format: xmpp:to
+ * The to represents a Jabber ID.
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+public class XmppMessageSender implements WebServiceMessageSender, InitializingBean {
+
+ /** Default timeout for receive operations: -1 indicates a blocking receive without timeout. */
+ public static final long DEFAULT_RECEIVE_TIMEOUT = -1;
+
+ /** Default encoding used to read from and write to {@link org.jivesoftware.smack.packet.Message} messages. */
+ public static final String DEFAULT_MESSAGE_ENCODING = "UTF-8";
+
+ private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
+
+ private String messageEncoding = DEFAULT_MESSAGE_ENCODING;
+
+ private XMPPConnection connection;
+
+ /** Sets the {@code XMPPConnection}. Setting this property is required. */
+ public void setConnection(XMPPConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Set the timeout to use for receive calls. The default is -1, which means no timeout.
+ *
+ * @see org.jivesoftware.smack.PacketCollector#nextResult(long)
+ */
+ public void setReceiveTimeout(long receiveTimeout) {
+ this.receiveTimeout = receiveTimeout;
+ }
+
+ /**
+ * Sets the encoding used to read from {@link org.jivesoftware.smack.packet.Message} object. Defaults to
+ * UTF-8.
+ */
+ public void setMessageEncoding(String messageEncoding) {
+ this.messageEncoding = messageEncoding;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(connection, "'connection' is required");
+ }
+
+ public WebServiceConnection createConnection(URI uri) throws IOException {
+ String to = XmppTransportUtils.getTo(uri);
+ String thread = createThread();
+ XmppSenderConnection connection = new XmppSenderConnection(this.connection, to, thread);
+ connection.setReceiveTimeout(receiveTimeout);
+ connection.setMessageEncoding(messageEncoding);
+ return connection;
+ }
+
+ public boolean supports(URI uri) {
+ return uri.getScheme().equals(XmppTransportConstants.XMPP_URI_SCHEME);
+ }
+
+ protected String createThread() {
+ return UUID.randomUUID().toString();
+ }
+}
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/XmppReceiverConnection.java b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppReceiverConnection.java
new file mode 100644
index 00000000..cf622035
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppReceiverConnection.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+
+import org.springframework.util.Assert;
+import org.springframework.ws.WebServiceMessage;
+import org.springframework.ws.transport.AbstractReceiverConnection;
+import org.springframework.ws.transport.xmpp.support.XmppTransportUtils;
+
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Implementation of {@link org.springframework.ws.transport.WebServiceConnection} that is used for server-side XMPP
+ * access. Exposes a {@link Message} request and response message.
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+public class XmppReceiverConnection extends AbstractReceiverConnection {
+
+ private final XMPPConnection connection;
+
+ private final Message requestMessage;
+
+ private Message responseMessage;
+
+ private String messageEncoding;
+
+ public XmppReceiverConnection(XMPPConnection connection, Message requestMessage) {
+ Assert.notNull(connection, "'connection' must not be null");
+ Assert.notNull(requestMessage, "'requestMessage' must not be null");
+ this.connection = connection;
+ this.requestMessage = requestMessage;
+ }
+
+ /** 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 setMessageEncoding(String messageEncoding) {
+ this.messageEncoding = messageEncoding;
+ }
+
+ /*
+ * URI
+ */
+
+ public URI getUri() throws URISyntaxException {
+ return XmppTransportUtils.toUri(requestMessage);
+ }
+
+ /*
+ * Errors
+ */
+
+ public boolean hasError() {
+ return XmppTransportUtils.hasError(responseMessage);
+ }
+
+ public String getErrorMessage() {
+ return XmppTransportUtils.getErrorMessage(responseMessage);
+ }
+
+ /*
+ * Receiving
+ */
+
+ @Override
+ protected Iterator getRequestHeaderNames() throws IOException {
+ return XmppTransportUtils.getHeaderNames(requestMessage);
+ }
+
+ @Override
+ protected Iterator getRequestHeaders(String name) throws IOException {
+ return XmppTransportUtils.getHeaders(requestMessage, name);
+ }
+
+ @Override
+ protected InputStream getRequestInputStream() throws IOException {
+ return new MessageInputStream(requestMessage, messageEncoding);
+ }
+
+ /*
+ * Sending
+ */
+
+ @Override
+ protected void onSendBeforeWrite(WebServiceMessage message) throws IOException {
+ responseMessage = new Message(requestMessage.getFrom(), Message.Type.chat);
+ responseMessage.setFrom(connection.getUser());
+ responseMessage.setThread(requestMessage.getThread());
+ }
+
+ @Override
+ protected void addResponseHeader(String name, String value) throws IOException {
+ XmppTransportUtils.addHeader(responseMessage, name, value);
+ }
+
+ @Override
+ protected OutputStream getResponseOutputStream() throws IOException {
+ return new MessageOutputStream(responseMessage, messageEncoding);
+ }
+
+ @Override
+ protected void onSendAfterWrite(WebServiceMessage message) throws IOException {
+ connection.sendPacket(responseMessage);
+ }
+}
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/XmppSenderConnection.java b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppSenderConnection.java
new file mode 100644
index 00000000..d7c75ae0
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppSenderConnection.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+
+import org.springframework.util.Assert;
+import org.springframework.ws.WebServiceMessage;
+import org.springframework.ws.transport.AbstractSenderConnection;
+import org.springframework.ws.transport.xmpp.support.XmppTransportUtils;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.filter.ThreadFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Implementation of {@link org.springframework.ws.transport.WebServiceConnection} that is used for client-side XMPP
+ * access. Exposes a {@link Message} request and response message.
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+public class XmppSenderConnection extends AbstractSenderConnection {
+
+ private final Message requestMessage;
+
+ private final XMPPConnection connection;
+
+ private Message responseMessage;
+
+ private String messageEncoding;
+
+ private long receiveTimeout;
+
+ protected XmppSenderConnection(XMPPConnection connection, String to, String thread) {
+ Assert.notNull(connection, "'connection' must not be null");
+ Assert.hasLength(to, "'to' must not be empty");
+ Assert.hasLength(thread, "'thread' must not be empty");
+ this.connection = connection;
+ this.requestMessage = new Message(to, Message.Type.chat);
+ this.requestMessage.setThread(thread);
+ }
+
+ /** 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 setMessageEncoding(String messageEncoding) {
+ this.messageEncoding = messageEncoding;
+ }
+
+ void setReceiveTimeout(long receiveTimeout) {
+ this.receiveTimeout = receiveTimeout;
+ }
+
+ /*
+ * URI
+ */
+
+ public URI getUri() throws URISyntaxException {
+ return XmppTransportUtils.toUri(requestMessage);
+ }
+
+ /*
+ * Errors
+ */
+
+ public boolean hasError() {
+ return XmppTransportUtils.hasError(responseMessage);
+ }
+
+ public String getErrorMessage() {
+ return XmppTransportUtils.getErrorMessage(responseMessage);
+ }
+
+ /*
+ * Sending
+ */
+
+ @Override
+ protected void addRequestHeader(String name, String value) {
+ XmppTransportUtils.addHeader(requestMessage, name, value);
+ }
+
+ @Override
+ protected OutputStream getRequestOutputStream() throws IOException {
+ return new MessageOutputStream(requestMessage, messageEncoding);
+ }
+
+ @Override
+ protected void onSendAfterWrite(WebServiceMessage message) throws IOException {
+ requestMessage.setFrom(connection.getUser());
+ connection.sendPacket(requestMessage);
+ }
+
+ /*
+ * Receiving
+ */
+
+ @Override
+ protected void onReceiveBeforeRead() throws IOException {
+ PacketFilter packetFilter = createPacketFilter();
+
+ PacketCollector collector = connection.createPacketCollector(packetFilter);
+ Packet packet = receiveTimeout >= 0 ? collector.nextResult(receiveTimeout) : collector.nextResult();
+ if (packet instanceof Message) {
+ responseMessage = (Message) packet;
+ }
+ else if (packet != null) {
+ throw new IllegalArgumentException(
+ "Wrong packet type: [" + packet.getClass() + "]. Only Messages can be handled.");
+ }
+ }
+
+ private PacketFilter createPacketFilter() {
+ AndFilter andFilter = new AndFilter();
+ andFilter.addFilter(new PacketTypeFilter(Message.class));
+ andFilter.addFilter(new ThreadFilter(requestMessage.getThread()));
+ return andFilter;
+ }
+
+ @Override
+ protected boolean hasResponse() throws IOException {
+ return responseMessage != null;
+ }
+
+ @Override
+ protected Iterator getResponseHeaderNames() {
+ return XmppTransportUtils.getHeaderNames(responseMessage);
+ }
+
+ @Override
+ protected Iterator getResponseHeaders(String name) throws IOException {
+ return XmppTransportUtils.getHeaders(responseMessage, name);
+ }
+
+ @Override
+ protected InputStream getResponseInputStream() throws IOException {
+ return new MessageInputStream(responseMessage, messageEncoding);
+ }
+
+}
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/XmppTransportConstants.java b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppTransportConstants.java
new file mode 100644
index 00000000..4bf74a79
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/XmppTransportConstants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005-2010 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.xmpp;
+
+import org.springframework.ws.transport.TransportConstants;
+
+/**
+ * Declares XMPP-specific transport constants.
+ *
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+public interface XmppTransportConstants extends TransportConstants {
+
+ /**
+ * The "xmpp" URI scheme.
+ */
+ String XMPP_URI_SCHEME = "xmpp";
+
+}
\ No newline at end of file
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/package.html b/support/src/main/java/org/springframework/ws/transport/xmpp/package.html
new file mode 100644
index 00000000..2333921a
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/package.html
@@ -0,0 +1,5 @@
+
+
+Package providing support for handling messages via xmpp.
+
+
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/support/XmppConnectionFactoryBean.java b/support/src/main/java/org/springframework/ws/transport/xmpp/support/XmppConnectionFactoryBean.java
new file mode 100644
index 00000000..14e98573
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/support/XmppConnectionFactoryBean.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2005-2010 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.xmpp.support;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import org.jivesoftware.smack.ConnectionConfiguration;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+
+/**
+ * Factory to make {@link org.jivesoftware.smack.XMPPConnection} and perform connection and login on the XMPP server
+ *
+ * @author Gildas Cuisinier
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+public class XmppConnectionFactoryBean implements FactoryBean, InitializingBean, DisposableBean {
+
+ private static final int DEFAULT_PORT = 5222;
+
+ private XMPPConnection connection;
+
+ private String host;
+
+ private int port = DEFAULT_PORT;
+
+ private String serviceName;
+
+ private String username;
+
+ private String password;
+
+ private String resource;
+
+ /** Sets the server host to connect to. */
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ /**
+ * Sets the the server port to connect to.
+ *
+ * Defaults to {@code 5222}.
+ */
+ public void setPort(int port) {
+ Assert.isTrue(port > 0, "'port' must be larger than 0");
+ this.port = port;
+ }
+
+ /** Sets the service name to connect to. */
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public void afterPropertiesSet() throws XMPPException {
+ ConnectionConfiguration configuration = createConnectionConfiguration(host, port, serviceName);
+ Assert.notNull(configuration, "'configuration' must not be null");
+ Assert.hasText(username, "'username' must not be empty");
+ Assert.hasText(password, "'password' must not be empty");
+
+ connection = new XMPPConnection(configuration);
+ connection.connect();
+ if (StringUtils.hasText(resource)) {
+ connection.login(username, password, resource);
+ }
+ else {
+ connection.login(username, password);
+ }
+ }
+
+ public void destroy() {
+ connection.disconnect();
+ }
+
+ public XMPPConnection getObject() {
+ return connection;
+ }
+
+ public Class getObjectType() {
+ return XMPPConnection.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * Creates the {@code ConnectionConfiguration} from the given parameters.
+ *
+ * @param host the host to connect to
+ * @param port the port to connect to
+ * @param serviceName the name of the service to connect to. May be {@code null}
+ */
+ protected ConnectionConfiguration createConnectionConfiguration(String host, int port, String serviceName) {
+ Assert.hasText(host, "'host' must not be empty");
+ if (StringUtils.hasText(serviceName)) {
+ return new ConnectionConfiguration(host, port, serviceName);
+ }
+ else {
+ return new ConnectionConfiguration(host, port);
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/support/src/main/java/org/springframework/ws/transport/xmpp/support/XmppTransportUtils.java b/support/src/main/java/org/springframework/ws/transport/xmpp/support/XmppTransportUtils.java
new file mode 100644
index 00000000..8a0f0d27
--- /dev/null
+++ b/support/src/main/java/org/springframework/ws/transport/xmpp/support/XmppTransportUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2005-2010 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.xmpp.support;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.springframework.util.Assert;
+import org.springframework.ws.transport.xmpp.XmppTransportConstants;
+
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Collection of utility methods to work with Mail transports.
+ *
+ * @author Arjen Poutsma
+ * @since 2.0
+ */
+public abstract class XmppTransportUtils {
+
+ private XmppTransportUtils() {
+ }
+
+ /**
+ * Converts the given XMPP destination into a xmpp URI.
+ */
+ public static URI toUri(Message requestMessage) throws URISyntaxException {
+ return new URI(XmppTransportConstants.XMPP_URI_SCHEME, requestMessage.getTo(), null);
+ }
+
+ public static String getTo(URI uri) {
+ return uri.getSchemeSpecificPart();
+ }
+
+ public static boolean hasError(Message message) {
+ return message != null && Message.Type.error.equals(message.getType());
+ }
+
+ public static String getErrorMessage(Message message) {
+ if (message == null || !Message.Type.error.equals(message.getType())) {
+ return null;
+ }
+ else {
+ return message.getBody();
+ }
+ }
+
+ public static void addHeader(Message message, String name, String value) {
+ message.setProperty(name, value);
+ }
+
+ public static Iterator getHeaderNames(Message message) {
+ Assert.notNull(message, "'message' must not be null");
+ return message.getPropertyNames().iterator();
+ }
+
+ public static Iterator getHeaders(Message message, String name) {
+ Assert.notNull(message, "'message' must not be null");
+ String value = message.getProperty(name).toString();
+ if (value != null) {
+ return Collections.singletonList(value).iterator();
+ }
+ else {
+ return Collections.emptyList().iterator();
+ }
+ }
+
+}
diff --git a/support/src/test/java/org/springframework/ws/transport/xmpp/support/XmppConnectionFactoryBeanTest.java b/support/src/test/java/org/springframework/ws/transport/xmpp/support/XmppConnectionFactoryBeanTest.java
new file mode 100644
index 00000000..9702bf16
--- /dev/null
+++ b/support/src/test/java/org/springframework/ws/transport/xmpp/support/XmppConnectionFactoryBeanTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005-2010 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.xmpp.support;
+
+import org.jivesoftware.smack.XMPPException;
+import org.junit.Before;
+import org.junit.Test;
+
+/** @author Arjen Poutsma */
+public class XmppConnectionFactoryBeanTest {
+
+ private XmppConnectionFactoryBean factoryBean;
+
+ @Before
+ public void createFactoryBean() {
+ factoryBean = new XmppConnectionFactoryBean();
+ }
+ @Test(expected = IllegalArgumentException.class)
+ public void noHost() throws XMPPException {
+ factoryBean.afterPropertiesSet();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void noUsername() throws XMPPException {
+ factoryBean.setHost("jabber.org");
+ factoryBean.afterPropertiesSet();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void wrongPort() throws XMPPException {
+ factoryBean.setPort(-10);
+ }
+
+ @Test
+ public void xs4all() throws XMPPException {
+ factoryBean.setHost("jabber.xs4all.nl");
+ factoryBean.setUsername("poutsma");
+ factoryBean.setPassword("tusega");
+ factoryBean.afterPropertiesSet();
+ factoryBean.destroy();
+ }
+
+}