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(); + } + +}