diff --git a/pom.xml b/pom.xml index 6a83a16a..791d7add 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 org.springframework.ws spring-ws @@ -103,9 +103,10 @@ xml oxm core + support security archetype - + jdk1.5 @@ -250,8 +251,7 @@ - + @@ -733,4 +733,4 @@ test - + \ No newline at end of file diff --git a/support/pom.xml b/support/pom.xml new file mode 100644 index 00000000..06a3ecd7 --- /dev/null +++ b/support/pom.xml @@ -0,0 +1,99 @@ + + + spring-ws + org.springframework.ws + 1.1-SNAPSHOT + + 4.0.0 + org.springframework.ws + spring-ws-support + jar + Spring WS Support + Spring Web Services Support package. + + + + maven-javadoc-plugin + + ${basedir}/../src/main/javadoc/javadoc.css + + + + + + + spring-2.0 + + true + + + + org.springframework + spring-mock + + + + + spring-2.5 + + + spring.version + 2.5-rc1 + + + + + org.springframework + spring-test + + + + + + + + org.springframework.ws + spring-ws-core + + + + org.springframework + spring-jms + + + org.springframework + spring-remoting + true + + + + javax.xml.soap + saaj-api + test + + + javax.jms + jms + provided + + + javax.ejb + ejb + 2.1 + true + + + + com.sun.xml.messaging.saaj + saaj-impl + test + + + org.apache.activemq + activemq-core + 4.1.1 + test + + + diff --git a/support/src/main/java/org/springframework/ws/transport/jms/BytesMessageInputStream.java b/support/src/main/java/org/springframework/ws/transport/jms/BytesMessageInputStream.java new file mode 100644 index 00000000..8a7e929f --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/BytesMessageInputStream.java @@ -0,0 +1,73 @@ +/* + * 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.jms; + +import java.io.IOException; +import java.io.InputStream; +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.MessageEOFException; + +/** + * Input stream that wraps a {@link BytesMessage}. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +class BytesMessageInputStream extends InputStream { + + private BytesMessage message; + + BytesMessageInputStream(BytesMessage message) { + this.message = message; + } + + public int read(byte b[]) throws IOException { + try { + return message.readBytes(b); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + + public int read(byte b[], int off, int len) throws IOException { + if (off == 0) { + try { + return message.readBytes(b, len); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + else { + return super.read(b, off, len); + } + } + + public int read() throws IOException { + try { + return message.readByte(); + } + catch (MessageEOFException ex) { + return -1; + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/BytesMessageOutputStream.java b/support/src/main/java/org/springframework/ws/transport/jms/BytesMessageOutputStream.java new file mode 100644 index 00000000..a5886ffa --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/BytesMessageOutputStream.java @@ -0,0 +1,64 @@ +/* + * 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.jms; + +import java.io.IOException; +import java.io.OutputStream; +import javax.jms.BytesMessage; +import javax.jms.JMSException; + +/** + * Output stream that wraps a {@link BytesMessage}. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +class BytesMessageOutputStream extends OutputStream { + + private BytesMessage message; + + BytesMessageOutputStream(BytesMessage message) { + this.message = message; + } + + public void write(byte b[]) throws IOException { + try { + message.writeBytes(b); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + + public void write(byte b[], int off, int len) throws IOException { + try { + message.writeBytes(b, off, len); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + + public void write(int b) throws IOException { + try { + message.writeByte((byte) b); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsMessageReceiver.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsMessageReceiver.java new file mode 100644 index 00000000..258e331b --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsMessageReceiver.java @@ -0,0 +1,56 @@ +/* + * Copyright 2006 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.jms; + +import javax.jms.BytesMessage; +import javax.jms.Message; +import javax.jms.Session; + +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.WebServiceMessageReceiver; +import org.springframework.ws.transport.support.SimpleWebServiceMessageReceiverObjectSupport; + +/** + * Convenience base class for JMS server-side transport objects. Contains a {@link WebServiceMessageReceiver}, and has + * methods for handling incoming JMS {@link Message} requests. + *

+ * Used by {@link WebServiceMessageListener} and {@link WebServiceMessageDrivenBean}. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class JmsMessageReceiver extends SimpleWebServiceMessageReceiverObjectSupport { + + /** + * Handles an incoming messages. Uses the given session to create a response message. + * + * @param request the incoming message + * @param session the JMS session used to create a response + * @throws IllegalArgumentException when request is not a {@link BytesMessage} + */ + protected final void handleMessage(Message request, Session session) throws Exception { + if (request instanceof BytesMessage) { + WebServiceConnection connection = new JmsReceiverConnection((BytesMessage) request, session); + handleConnection(connection); + } + else { + throw new IllegalArgumentException( + "Wrong message type: [" + request.getClass() + "]. Only BytesMessages can be handled."); + } + + } +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsMessageSender.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsMessageSender.java new file mode 100644 index 00000000..ca72dfc7 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsMessageSender.java @@ -0,0 +1,100 @@ +/* + * 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.jms; + +import java.io.IOException; +import javax.jms.BytesMessage; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; + +import org.springframework.jms.support.destination.DestinationResolver; +import org.springframework.jms.support.destination.DynamicDestinationResolver; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.WebServiceMessageSender; + +/** + * {@link WebServiceMessageSender} implementation that uses JMS. + *

+ * This message sender sends the request message of the queue configured with either the queue or + * queueName property. It creates a temporary queue for the response message. For both request and response + * {@link BytesMessage}s are used. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class JmsMessageSender implements WebServiceMessageSender, JmsTransportConstants { + + /** + * Default timeout for receive operations: -1 indicates a blocking receive without timeout. + */ + public static final long DEFAULT_RECEIVE_TIMEOUT = -1; + + private ConnectionFactory connectionFactory; + + private DestinationResolver destinationResolver = new DynamicDestinationResolver(); + + private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT; + + public JmsMessageSender() { + } + + public JmsMessageSender(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + /** + * Set the ConnectionFactory to use for obtaining JMS {@link Connection}s. + */ + public void setConnectionFactory(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void setDestinationResolver(DestinationResolver destinationResolver) { + this.destinationResolver = destinationResolver; + } + + /** + * Set the timeout to use for receive calls. The default is 0, which means no timeout. + */ + public void setReceiveTimeout(long receiveTimeout) { + this.receiveTimeout = receiveTimeout; + } + + public WebServiceConnection createConnection(String uriString) throws IOException { + Assert.notNull(connectionFactory, "connectionFactory must not be null"); + JmsSenderConnection connection = null; + try { + JmsUri uri = new JmsUri(uriString); + connection = new JmsSenderConnection(uri, connectionFactory, destinationResolver, receiveTimeout); + return connection; + } + catch (JMSException ex) { + if (connection != null) { + connection.close(); + } + throw new JmsTransportException(ex); + } + } + + public boolean supports(String uri) { + return StringUtils.hasLength(uri) && uri.startsWith(URI_SCHEME + ":"); + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsReceiverConnection.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsReceiverConnection.java new file mode 100644 index 00000000..d5ae27dc --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsReceiverConnection.java @@ -0,0 +1,202 @@ +/* + * 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.jms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.MessageProducer; +import javax.jms.Session; + +import org.springframework.jms.support.JmsUtils; +import org.springframework.util.Assert; +import org.springframework.ws.FaultAwareWebServiceMessage; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.transport.AbstractReceiverConnection; +import org.springframework.ws.transport.FaultAwareWebServiceConnection; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.jms.support.JmsTransportUtils; + +/** + * Implementation of {@link WebServiceConnection} that is used for server-side JMS access. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class JmsReceiverConnection extends AbstractReceiverConnection + implements JmsTransportConstants, FaultAwareWebServiceConnection { + + private final BytesMessage requestMessage; + + private final Session session; + + private BytesMessage responseMessage; + + /** + * Constructs a new JMS connection with the given parameters. + */ + protected JmsReceiverConnection(BytesMessage 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 BytesMessage getRequestMessage() { + return requestMessage; + } + + /** + * Returns the response message, if any, for this connection. + */ + public BytesMessage getResponseMessage() { + return responseMessage; + } + + public String getErrorMessage() throws IOException { + return null; + } + + public boolean hasError() throws IOException { + return false; + } + + /* + * Receiving + */ + + protected Iterator getRequestHeaderNames() throws IOException { + try { + Enumeration headers = requestMessage.getPropertyNames(); + List results = new ArrayList(); + while (headers.hasMoreElements()) { + String header = (String) headers.nextElement(); + if (header.startsWith(JmsTransportConstants.PROPERTY_PREFIX)) { + results.add(header); + } + } + return results.iterator(); + } + catch (JMSException ex) { + throw new JmsTransportException("Could not get property names", ex); + } + } + + protected Iterator getRequestHeaders(String name) throws IOException { + try { + String value = requestMessage.getStringProperty(name); + return Collections.singletonList(value).iterator(); + } + catch (JMSException ex) { + throw new JmsTransportException("Could not get property value", ex); + } + } + + protected InputStream getRequestInputStream() throws IOException { + return new BytesMessageInputStream(requestMessage); + } + + /* + * Sending + */ + + protected void onSendBeforeWrite(WebServiceMessage message) throws IOException { + try { + responseMessage = session.createBytesMessage(); + responseMessage.setJMSCorrelationID(requestMessage.getJMSMessageID()); + responseMessage.setStringProperty(PROPERTY_BINDING_VERSION, "1.0"); + if (message instanceof FaultAwareWebServiceMessage) { + FaultAwareWebServiceMessage faultMessage = (FaultAwareWebServiceMessage) message; + responseMessage.setBooleanProperty(PROPERTY_IS_FAULT, faultMessage.hasFault()); + } + } + catch (JMSException ex) { + throw new JmsTransportException("Could not create response message", ex); + } + } + + protected void addResponseHeader(String name, String value) throws IOException { + try { + String property = JmsTransportUtils.headerToJmsProperty(name); + responseMessage.setStringProperty(property, value); + } + catch (JMSException ex) { + throw new JmsTransportException("Could not set property", ex); + } + } + + protected OutputStream getResponseOutputStream() throws IOException { + return new BytesMessageOutputStream(responseMessage); + } + + protected void onSendAfterWrite(WebServiceMessage message) throws IOException { + MessageProducer messageProducer = null; + try { + if (requestMessage.getJMSReplyTo() != null) { + messageProducer = session.createProducer(requestMessage.getJMSReplyTo()); + messageProducer.setDeliveryMode(requestMessage.getJMSDeliveryMode()); + messageProducer.setPriority(requestMessage.getJMSPriority()); + messageProducer.send(responseMessage); + } + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + finally { + JmsUtils.closeMessageProducer(messageProducer); + } + } + + public void close() throws IOException { + } + + /* + * Faults + */ + + public boolean hasFault() throws IOException { + try { + return requestMessage.getBooleanProperty(JmsTransportConstants.PROPERTY_IS_FAULT); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + + public void setFault(boolean fault) throws IOException { + if (responseMessage != null) { + try { + responseMessage.setBooleanProperty(JmsTransportConstants.PROPERTY_IS_FAULT, fault); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + } + + +} 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 new file mode 100644 index 00000000..8e1d8827 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsSenderConnection.java @@ -0,0 +1,276 @@ +/* + * 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.jms; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import javax.jms.BytesMessage; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TemporaryQueue; + +import org.springframework.jms.connection.ConnectionFactoryUtils; +import org.springframework.jms.support.JmsUtils; +import org.springframework.jms.support.destination.DestinationResolver; +import org.springframework.util.Assert; +import org.springframework.ws.FaultAwareWebServiceMessage; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.transport.AbstractSenderConnection; +import org.springframework.ws.transport.FaultAwareWebServiceConnection; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.jms.support.JmsTransportUtils; + +/** + * Implementation of {@link WebServiceConnection} that is used for client-side JMS access. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class JmsSenderConnection extends AbstractSenderConnection + implements FaultAwareWebServiceConnection, JmsTransportConstants { + + private final ConnectionFactory connectionFactory; + + private final DestinationResolver destinationResolver; + + private final Connection connection; + + private final Session session; + + private final Destination requestDestination; + + private final JmsUri uri; + + private final long receiveTimeout; + + private Destination responseDestination; + + private BytesMessage requestMessage; + + private BytesMessage responseMessage; + + /** + * Constructs a new JMS connection with the given parameters. + */ + protected JmsSenderConnection(JmsUri uri, + ConnectionFactory connectionFactory, + DestinationResolver destinationResolver, + long receiveTimeout) throws JMSException { + Assert.notNull(uri, "'uri' must not be null"); + Assert.notNull(connectionFactory, "'connectionFactory' must not be null"); + Assert.notNull(destinationResolver, "destinationResolver must not be null"); + this.connectionFactory = connectionFactory; + this.destinationResolver = destinationResolver; + connection = connectionFactory.createConnection(); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + requestDestination = + destinationResolver.resolveDestinationName(session, uri.getDestination(), uri.isPubSubDomain()); + this.uri = uri; + this.receiveTimeout = receiveTimeout; + } + + /** + * Returns the request message for this connection. + */ + public BytesMessage getRequestMessage() { + return requestMessage; + } + + /** + * Returns the response message, if any, for this connection. + */ + public BytesMessage getResponseMessage() { + return responseMessage; + } + + public boolean hasError() throws IOException { + return false; + } + + public String getErrorMessage() throws IOException { + return null; + } + + /* + * Sending + */ + + protected void onSendBeforeWrite(WebServiceMessage message) throws IOException { + try { + requestMessage = session.createBytesMessage(); + requestMessage.setStringProperty(PROPERTY_BINDING_VERSION, "1.0"); + if (message instanceof FaultAwareWebServiceMessage) { + FaultAwareWebServiceMessage faultMessage = (FaultAwareWebServiceMessage) message; + requestMessage.setBooleanProperty(PROPERTY_IS_FAULT, faultMessage.hasFault()); + } + requestMessage.setStringProperty(PROPERTY_REQUEST_IRI, uri.toString()); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + + protected void addRequestHeader(String name, String value) throws IOException { + try { + String property = JmsTransportUtils.headerToJmsProperty(name); + requestMessage.setStringProperty(property, value); + } + catch (JMSException ex) { + throw new JmsTransportException("Could not set property", ex); + } + } + + protected OutputStream getRequestOutputStream() throws IOException { + return new BytesMessageOutputStream(requestMessage); + } + + protected void onSendAfterWrite(WebServiceMessage message) throws IOException { + MessageProducer messageProducer = null; + try { + messageProducer = session.createProducer(requestDestination); + messageProducer.setDeliveryMode(uri.getDeliveryMode()); + messageProducer.setTimeToLive(uri.getTimeToLive()); + messageProducer.setPriority(uri.getPriority()); + if (uri.hasReplyTo()) { + responseDestination = + destinationResolver.resolveDestinationName(session, uri.getReplyTo(), uri.isPubSubDomain()); + } + else { + responseDestination = session.createTemporaryQueue(); + } + requestMessage.setJMSReplyTo(responseDestination); + connection.start(); + messageProducer.send(requestMessage); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + finally { + JmsUtils.closeMessageProducer(messageProducer); + } + } + + /* + * Receiving + */ + + protected void onReceiveBeforeRead() throws IOException { + MessageConsumer messageConsumer = null; + try { + messageConsumer = session.createConsumer(responseDestination); + responseMessage = (BytesMessage) (receiveTimeout >= 0 ? messageConsumer.receive(receiveTimeout) : + messageConsumer.receive()); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + finally { + JmsUtils.closeMessageConsumer(messageConsumer); + if (responseDestination instanceof TemporaryQueue) { + try { + ((TemporaryQueue) responseDestination).delete(); + } + catch (JMSException e) { + // ignore + } + } + } + } + + protected boolean hasResponse() throws IOException { + return responseMessage != null; + } + + protected Iterator getResponseHeaderNames() throws IOException { + try { + List headerNames = new ArrayList(); + Enumeration propertyNames = responseMessage.getPropertyNames(); + while (propertyNames.hasMoreElements()) { + String propertyName = (String) propertyNames.nextElement(); + headerNames.add(JmsTransportUtils.jmsPropertyToHeader(propertyName)); + } + return headerNames.iterator(); + } + catch (JMSException ex) { + throw new JmsTransportException("Could not get property names", ex); + } + } + + protected Iterator getResponseHeaders(String name) throws IOException { + try { + String propertyName = JmsTransportUtils.headerToJmsProperty(name); + String value = responseMessage.getStringProperty(propertyName); + if (value != null) { + return Collections.singletonList(value).iterator(); + } + else { + return Collections.EMPTY_LIST.iterator(); + } + } + catch (JMSException ex) { + throw new JmsTransportException("Could not get property value", ex); + } + } + + protected InputStream getResponseInputStream() throws IOException { + return new BytesMessageInputStream(responseMessage); + } + + public void close() throws IOException { + JmsUtils.closeSession(session); + ConnectionFactoryUtils.releaseConnection(connection, connectionFactory, true); + } + + /* + * Faults + */ + + public boolean hasFault() throws IOException { + if (responseMessage != null) { + try { + return responseMessage.getBooleanProperty(JmsTransportConstants.PROPERTY_IS_FAULT); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + else { + return false; + } + } + + public void setFault(boolean fault) throws IOException { + try { + requestMessage.setBooleanProperty(JmsTransportConstants.PROPERTY_IS_FAULT, fault); + } + catch (JMSException ex) { + throw new JmsTransportException(ex); + } + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsTransportConstants.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsTransportConstants.java new file mode 100644 index 00000000..5ce69c9a --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsTransportConstants.java @@ -0,0 +1,68 @@ +/* + * 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.jms; + +import org.springframework.ws.transport.TransportConstants; + +/** + * Declares JMS-specific transport constants. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public interface JmsTransportConstants extends TransportConstants { + + String URI_SCHEME = "jms"; + + String PARAM_DELIVERY_MODE = "deliveryMode"; + + String PARAM_CONNECTION_FACTORY_NAME = "connectionFactoryName"; + + String PARAM_INITIAL_CONTEXT_FACTORY = "initialContextFactory"; + + String PARAM_JNDI_URL = "jndiURL"; + + String PARAM_TIME_TO_LIVE = "timeToLive"; + + String PARAM_PRIORITY = "priority"; + + String PARAM_DESTINATION_TYPE = "destinationType"; + + String PARAM_REPLY_TO_NAME = "replyToName"; + + String DESTINATION_TYPE_QUEUE = "queue"; + + String DESTINATION_TYPE_TOPIC = "topic"; + + String PROPERTY_PREFIX = "SOAPJMS_"; + + String PROPERTY_IS_FAULT = PROPERTY_PREFIX + "isFault"; + + String PROPERTY_SOAP_ACTION = PROPERTY_PREFIX + "soapAction"; + + String PROPERTY_CONTENT_LENGTH = PROPERTY_PREFIX + "contentLength"; + + String PROPERTY_CONTENT_TYPE = PROPERTY_PREFIX + "contentType"; + + String PROPERTY_BINDING_VERSION = PROPERTY_PREFIX + "bindingVersion"; + + String PROPERTY_TARGET_SERVICE = PROPERTY_PREFIX + "targetService"; + + String PROPERTY_REQUEST_IRI = PROPERTY_PREFIX + "requestIRI"; + + String PROPERTY_SOAP_MEP = PROPERTY_PREFIX + "soapMEP"; +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsTransportException.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsTransportException.java new file mode 100644 index 00000000..e1ce05bd --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsTransportException.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.jms; + +import javax.jms.JMSException; + +import org.springframework.ws.transport.TransportException; + +/** + * Exception that is thrown when an error occurs in the JMS transport. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ + +public class JmsTransportException extends TransportException { + + private final JMSException jmsException; + + public JmsTransportException(String msg, JMSException ex) { + super(msg + ": " + ex.getMessage()); + jmsException = ex; + } + + public JmsTransportException(JMSException ex) { + super(ex.getMessage()); + jmsException = ex; + } + + public JMSException getJmsException() { + return jmsException; + } +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/JmsUri.java b/support/src/main/java/org/springframework/ws/transport/jms/JmsUri.java new file mode 100644 index 00000000..bb967691 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/JmsUri.java @@ -0,0 +1,134 @@ +/* + * 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.jms; + +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.Message; +import javax.jms.Queue; +import javax.jms.Topic; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.transport.support.ParameterizedUri; + +/** + * @author Arjen Poutsma + * @see RI + * Scheme for Java Message Service 1.0 RC1 + */ +public class JmsUri extends ParameterizedUri implements JmsTransportConstants { + + public JmsUri(String uri) { + super(uri); + validateParameters(); + } + + private void validateParameters() { + validateIntegerParameter(PARAM_DELIVERY_MODE); + validateIntegerParameter(PARAM_PRIORITY); + validateIntegerParameter(PARAM_TIME_TO_LIVE); + String destinationType = getDestinationType(); + if (StringUtils.hasLength(destinationType)) { + Assert.isTrue( + DESTINATION_TYPE_QUEUE.equals(destinationType) || DESTINATION_TYPE_TOPIC.equals(destinationType), + "Invalid " + PARAM_DESTINATION_TYPE + ": [" + destinationType + "]. Expected '" + + DESTINATION_TYPE_QUEUE + "' or '" + DESTINATION_TYPE_TOPIC + "'"); + } + } + + private void validateIntegerParameter(String paramName) { + String paramValue = getParameter(paramName); + if (StringUtils.hasLength(paramValue)) { + try { + Integer.parseInt(paramValue); + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException("Invalid " + paramName + ": [" + paramValue + "]. Not an integer."); + } + } + } + + /** + * Returns whether the request message is persistent or not. + * + * @see DeliveryMode#NON_PERSISTENT + * @see DeliveryMode#PERSISTENT + */ + public int getDeliveryMode() { + return getIntegerParameter(PARAM_DELIVERY_MODE, Message.DEFAULT_DELIVERY_MODE); + } + + public String getDestination() { + return super.getDestination(); + } + + /** + * Specifies whether the destination is a {@link Queue} or a {@link Topic}, with the value "queue" or + * "topic", respectively. + */ + public String getDestinationType() { + return getParameter(PARAM_DESTINATION_TYPE); + } + + /** + * Returns the JMS priority associated with the request message. + * + * @see Message#setJMSPriority(int) + */ + public int getPriority() { + return getIntegerParameter(PARAM_PRIORITY, Message.DEFAULT_PRIORITY); + } + + /** + * Returns the lifetime, in milliseconds, of the request message. + */ + public long getTimeToLive() { + String paramValue = getParameter(PARAM_TIME_TO_LIVE); + return paramValue != null ? Long.parseLong(paramValue) : Message.DEFAULT_TIME_TO_LIVE; + } + + private int getIntegerParameter(String paramName, int defaultValue) { + String paramValue = getParameter(paramName); + return paramValue != null ? Integer.parseInt(paramValue) : defaultValue; + } + + /** + * Indicates whether this URI has a reply-to name. + */ + public boolean hasReplyTo() { + return StringUtils.hasLength(getReplyTo()); + } + + /** + * Returns the reply-to name. + * + * @see Message#setJMSReplyTo(Destination) + */ + public String getReplyTo() { + return getParameter(PARAM_REPLY_TO_NAME); + } + + /** + * Return whether the Publish/Subscribe domain ({@link javax.jms.Topic Topics}) is used. Otherwise, the + * Point-to-Point domain ({@link javax.jms.Queue Queues}) is used. + */ + public boolean isPubSubDomain() { + return DESTINATION_TYPE_TOPIC.equals(getDestinationType()); + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/WebServiceMessageDrivenBean.java b/support/src/main/java/org/springframework/ws/transport/jms/WebServiceMessageDrivenBean.java new file mode 100644 index 00000000..e39982bb --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/WebServiceMessageDrivenBean.java @@ -0,0 +1,157 @@ +/* + * 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.jms; + +import javax.ejb.EJBException; +import javax.ejb.MessageDrivenBean; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Session; +import javax.naming.NamingException; + +import org.springframework.ejb.support.AbstractJmsMessageDrivenBean; +import org.springframework.jms.connection.ConnectionFactoryUtils; +import org.springframework.jms.support.JmsUtils; +import org.springframework.jndi.JndiLookupFailureException; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.transport.WebServiceMessageReceiver; + +/** + * EJB {@link MessageDrivenBean} that can be used to handleMessage incoming JMS messages. + *

+ * This class needs a JMS {@link ConnectionFactory}, and a {@link WebServiceMessageFactory} and {@link + * WebServiceMessageReceiver} to operate. By default, these are obtained by doing a bean lookup on the bean factory + * provided by {@link #getBeanFactory()} the super class. + * + * @author Arjen Poutsma + * @see #createConnectionFactory() + * @see #createMessageFactory() + * @see #createMessageReceiver() + */ +public class WebServiceMessageDrivenBean extends AbstractJmsMessageDrivenBean { + + /** Well-known name for the {@link ConnectionFactory} object in the bean factory for this bean. */ + public static final String CONNECTION_FACTORY_BEAN_NAME = "connectionFactory"; + + /** Well-known name for the {@link WebServiceMessageFactory} bean in the bean factory for this bean. */ + public static final String MESSAGE_FACTORY_BEAN_NAME = "messageFactory"; + + /** Well-known name for the {@link WebServiceMessageReceiver} object in the bean factory for this bean. */ + public static final String MESSAGE_RECEIVER_BEAN_NAME = "messageReceiver"; + + private JmsMessageReceiver delegate; + + private ConnectionFactory connectionFactory; + + /** Delegates to {@link JmsMessageReceiver#handleMessage(Message,Session)}. */ + public void onMessage(Message message) { + Connection connection = null; + Session session = null; + try { + connection = createConnection(connectionFactory); + session = createSession(connection); + delegate.handleMessage(message, session); + } + catch (JmsTransportException ex) { + throw JmsUtils.convertJmsAccessException(ex.getJmsException()); + } + catch (JMSException ex) { + throw JmsUtils.convertJmsAccessException(ex); + } + catch (Exception ex) { + throw new EJBException(ex); + } + finally { + JmsUtils.closeSession(session); + ConnectionFactoryUtils.releaseConnection(connection, connectionFactory, true); + } + } + + /** + * Creates a new {@link Connection}, {@link WebServiceMessageFactory}, and {@link WebServiceMessageReceiver}. + * + * @see #createConnectionFactory() + * @see #createMessageFactory() + * @see #createMessageReceiver() + */ + protected void onEjbCreate() { + try { + connectionFactory = createConnectionFactory(); + delegate = new JmsMessageReceiver(); + delegate.setMessageFactory(createMessageFactory()); + delegate.setMessageReceiver(createMessageReceiver()); + } + catch (NamingException ex) { + throw new JndiLookupFailureException("Could not create connection", ex); + } + catch (JMSException ex) { + throw JmsUtils.convertJmsAccessException(ex); + } + catch (Exception ex) { + throw new EJBException(ex); + } + } + + /** Creates a connection factory. Default implemantion does a bean lookup for {@link #CONNECTION_FACTORY_BEAN_NAME}. */ + protected ConnectionFactory createConnectionFactory() throws Exception { + return (ConnectionFactory) getBeanFactory().getBean(CONNECTION_FACTORY_BEAN_NAME, ConnectionFactory.class); + } + + /** Creates a message factory. Default implemantion does a bean lookup for {@link #MESSAGE_FACTORY_BEAN_NAME}. */ + protected WebServiceMessageFactory createMessageFactory() { + return (WebServiceMessageFactory) getBeanFactory() + .getBean(MESSAGE_FACTORY_BEAN_NAME, WebServiceMessageFactory.class); + } + + /** Creates a connection factory. Default implemantion does a bean lookup for {@link #MESSAGE_RECEIVER_BEAN_NAME}. */ + protected WebServiceMessageReceiver createMessageReceiver() { + return (WebServiceMessageReceiver) getBeanFactory() + .getBean(MESSAGE_RECEIVER_BEAN_NAME, WebServiceMessageReceiver.class); + } + + /** + * Create a JMS {@link Connection} using the given {@link ConnectionFactory}. + *

+ * This implementation uses JMS 1.1 API. + * + * @param connectionFactory the JMS ConnectionFactory to create a Connection with + * @return the new JMS Connection + * @throws JMSException if thrown by JMS API methods + * @see ConnectionFactory#createConnection() + */ + protected Connection createConnection(ConnectionFactory connectionFactory) throws JMSException { + return connectionFactory.createConnection(); + } + + /** + * Creates a JMS {@link Session}. Default implemantion creates a non-transactional, {@link Session#AUTO_ACKNOWLEDGE + * auto acknowledged} session. + *

+ * This implementation uses JMS 1.1 API. + * + * @param connection the JMS Connection to create a Session for + * @return the new JMS Session + * @throws JMSException if thrown by JMS API methods + * @see Connection#createSession(boolean,int) + */ + protected Session createSession(Connection connection) throws JMSException { + return connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/WebServiceMessageListener.java b/support/src/main/java/org/springframework/ws/transport/jms/WebServiceMessageListener.java new file mode 100644 index 00000000..4d94a213 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/WebServiceMessageListener.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 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.jms; + +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Session; + +import org.springframework.jms.listener.SessionAwareMessageListener; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.transport.WebServiceMessageReceiver; + +/** + * Spring-2.0 {@link SessionAwareMessageListener} that can be used to handle incoming JMS messages. + *

+ * Requires a {@link WebServiceMessageFactory} which is used to convert the incoming JMS {@link BytesMessage} into a + * {@link WebServiceMessage}, and passes that to the {@link WebServiceMessageReceiver} {@link + * #setMessageReceiver(WebServiceMessageReceiver) registered}. + * + * @author Arjen Poutsma + * @see #setMessageFactory(org.springframework.ws.WebServiceMessageFactory) + * @see #setMessageReceiver(org.springframework.ws.transport.WebServiceMessageReceiver) + * @since 1.1.0 + */ +public class WebServiceMessageListener extends JmsMessageReceiver implements SessionAwareMessageListener { + + public void onMessage(Message message, Session session) throws JMSException { + try { + handleMessage(message, session); + } + catch (JmsTransportException ex) { + throw ex.getJmsException(); + } + catch (Exception ex) { + JMSException jmsException = new JMSException(ex.getMessage()); + jmsException.setLinkedException(ex); + throw jmsException; + } + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/package.html b/support/src/main/java/org/springframework/ws/transport/jms/package.html new file mode 100644 index 00000000..2b58664e --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/package.html @@ -0,0 +1,5 @@ + + +Package providing support for handling messages via JMS. + + diff --git a/support/src/main/java/org/springframework/ws/transport/jms/support/JmsTransportUtils.java b/support/src/main/java/org/springframework/ws/transport/jms/support/JmsTransportUtils.java new file mode 100644 index 00000000..fa672f9f --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/support/JmsTransportUtils.java @@ -0,0 +1,69 @@ +/* + * 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.jms.support; + +import org.springframework.ws.transport.jms.JmsTransportConstants; + +/** + * Collection of utility methods to work with JMS transports. Includes methods to convert from transport header names to + * JMS Properties and vice-versa. + * + * @author Arjen Poutsma + * @since 1.1.0 + */ +public class JmsTransportUtils { + + private static final String[] CONVERSION_TABLE = new String[]{JmsTransportConstants.HEADER_CONTENT_TYPE, + JmsTransportConstants.PROPERTY_CONTENT_TYPE, JmsTransportConstants.HEADER_CONTENT_LENGTH, + JmsTransportConstants.PROPERTY_CONTENT_LENGTH, JmsTransportConstants.HEADER_SOAP_ACTION, + JmsTransportConstants.PROPERTY_SOAP_ACTION}; + + private JmsTransportUtils() { + } + + /** + * Converts the given transport header to a JMS property name. Returns the given header name if no match is found. + * + * @param headerName the header name to transform + * @return the JMS property name + */ + public static String headerToJmsProperty(String headerName) { + for (int i = 0; i < CONVERSION_TABLE.length; i = i + 2) { + if (CONVERSION_TABLE[i].equals(headerName)) { + return CONVERSION_TABLE[i + 1]; + } + } + return headerName; + } + + /** + * Converts the given JMS property name to a transport header name. Returns the given property name if no match is + * found. + * + * @param propertyName the JMS property name to transform + * @return the transport header name + */ + public static String jmsPropertyToHeader(String propertyName) { + for (int i = 1; i < CONVERSION_TABLE.length; i = i + 2) { + if (CONVERSION_TABLE[i].equals(propertyName)) { + return CONVERSION_TABLE[i - 1]; + } + } + return propertyName; + } + +} diff --git a/support/src/main/java/org/springframework/ws/transport/jms/support/package.html b/support/src/main/java/org/springframework/ws/transport/jms/support/package.html new file mode 100644 index 00000000..8c2ac138 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/jms/support/package.html @@ -0,0 +1,5 @@ + + +Classes supporting the org.springframework.ws.transport.jms package. + + \ No newline at end of file diff --git a/support/src/main/java/org/springframework/ws/transport/support/ParameterizedUri.java b/support/src/main/java/org/springframework/ws/transport/support/ParameterizedUri.java new file mode 100644 index 00000000..58be7494 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/support/ParameterizedUri.java @@ -0,0 +1,84 @@ +/* + * 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/support/src/main/java/org/springframework/ws/transport/support/SimpleWebServiceMessageReceiverObjectSupport.java b/support/src/main/java/org/springframework/ws/transport/support/SimpleWebServiceMessageReceiverObjectSupport.java new file mode 100644 index 00000000..eeaec2f9 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/support/SimpleWebServiceMessageReceiverObjectSupport.java @@ -0,0 +1,59 @@ +/* + * 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/support/src/test/java/org/springframework/ws/transport/SimpleTestingMessageReceiver.java b/support/src/test/java/org/springframework/ws/transport/SimpleTestingMessageReceiver.java new file mode 100644 index 00000000..aff2af56 --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/SimpleTestingMessageReceiver.java @@ -0,0 +1,34 @@ +/* + * 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/support/src/test/java/org/springframework/ws/transport/jms/JmsMessageSenderIntegrationTest.java b/support/src/test/java/org/springframework/ws/transport/jms/JmsMessageSenderIntegrationTest.java new file mode 100644 index 00000000..3d5d76fd --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/jms/JmsMessageSenderIntegrationTest.java @@ -0,0 +1,130 @@ +/* + * 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.jms; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Session; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; + +import org.springframework.jms.core.JmsTemplate; +import org.springframework.jms.core.MessageCreator; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.ws.transport.WebServiceConnection; + +public class JmsMessageSenderIntegrationTest extends AbstractDependencyInjectionSpringContextTests { + + private JmsMessageSender messageSender; + + private JmsTemplate jmsTemplate; + + private MessageFactory messageFactory; + + private static final String REQUEST_QUEUE_URI = "jms:RequestQueue"; + + private static final String SOAP_ACTION = "\"http://springframework.org/DoIt\""; + + protected void onSetUp() throws Exception { + messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + } + + protected String[] getConfigLocations() { + return new String[]{"classpath:org/springframework/ws/transport/jms/jms-sender-applicationContext.xml"}; + } + + public void setJmsTemplate(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + public void setMessageSender(JmsMessageSender messageSender) { + this.messageSender = messageSender; + } + + public void testSendAndReceiveQueue() throws Exception { + WebServiceConnection connection = null; + try { + connection = messageSender.createConnection(REQUEST_QUEUE_URI); + SoapMessage soapRequest = new SaajSoapMessage(messageFactory.createMessage()); + soapRequest.setSoapAction(SOAP_ACTION); + connection.send(soapRequest); + + BytesMessage request = (BytesMessage) jmsTemplate.receive(); + validateMessage(request); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + messageFactory.createMessage().writeTo(bos); + final byte[] buf = bos.toByteArray(); + jmsTemplate.send(request.getJMSReplyTo(), new MessageCreator() { + + public Message createMessage(Session session) throws JMSException { + BytesMessage response = session.createBytesMessage(); + response.setStringProperty(JmsTransportConstants.PROPERTY_BINDING_VERSION, "1.0"); + response.setIntProperty(JmsTransportConstants.PROPERTY_CONTENT_LENGTH, buf.length); + response.setStringProperty(JmsTransportConstants.PROPERTY_CONTENT_TYPE, "text/xml"); + response.setBooleanProperty(JmsTransportConstants.PROPERTY_IS_FAULT, false); + response.setStringProperty(JmsTransportConstants.PROPERTY_REQUEST_IRI, REQUEST_QUEUE_URI); + response.setStringProperty(JmsTransportConstants.PROPERTY_SOAP_ACTION, SOAP_ACTION); + + response.writeBytes(buf); + return response; + } + }); + SoapMessage response = (SoapMessage) connection.receive(new SaajSoapMessageFactory(messageFactory)); + assertNotNull("No response received", response); + assertEquals("Invalid SOAPAction", SOAP_ACTION, response.getSoapAction()); + assertFalse("Message is fault", response.hasFault()); + } + finally { + if (connection != null) { + connection.close(); + } + } + } + + private void validateMessage(BytesMessage message) throws JMSException, IOException { + assertEquals("Invalid SOAPAction", SOAP_ACTION, + message.getStringProperty(JmsTransportConstants.PROPERTY_SOAP_ACTION)); + assertEquals("Invalid binding version", "1.0", + message.getStringProperty(JmsTransportConstants.PROPERTY_BINDING_VERSION)); + assertEquals("Invalid service IRI", REQUEST_QUEUE_URI, + message.getStringProperty(JmsTransportConstants.PROPERTY_REQUEST_IRI)); + assertFalse("Message is Fault", message.getBooleanProperty(JmsTransportConstants.PROPERTY_IS_FAULT)); + assertTrue("Invalid Content Type", + message.getStringProperty(JmsTransportConstants.PROPERTY_CONTENT_TYPE).indexOf("text/xml") != -1); + assertTrue("No Content Length", message.getIntProperty(JmsTransportConstants.PROPERTY_CONTENT_LENGTH) > 0); + + assertTrue("Message has no contents", getMessageContents(message).length() > 0); + + } + + private String getMessageContents(BytesMessage message) throws JMSException, IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = message.readBytes(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + out.flush(); + return out.toString("UTF-8"); + } +} \ No newline at end of file diff --git a/support/src/test/java/org/springframework/ws/transport/jms/JmsUriTest.java b/support/src/test/java/org/springframework/ws/transport/jms/JmsUriTest.java new file mode 100644 index 00000000..49e6bafe --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/jms/JmsUriTest.java @@ -0,0 +1,76 @@ +/* + * 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.jms; + +import junit.framework.TestCase; + +public class JmsUriTest extends TestCase { + + public void testJmsUri() { + JmsUri uri = new JmsUri("jms:news?connectionFactoryName=SOAPJMSFactory&" + "deliveryMode=2&" + + "destinationType=topic&" + "initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory&" + + "jndiURL=theJndiURL&" + "priority=8&" + "timeToLive=10&" + "replyToName=interested&" + + "userprop=mystuff"); + assertEquals("Invalid delivery mode", 2, uri.getDeliveryMode()); + assertEquals("Invalid destination", "news", uri.getDestination()); + assertEquals("Invalid destination type", "topic", uri.getDestinationType()); + assertTrue("Invalid pub sub domain", uri.isPubSubDomain()); + assertEquals("Invalid prority", 8, uri.getPriority()); + assertEquals("Invalid time to live", 10, uri.getTimeToLive()); + assertEquals("Invalid reply to name", "interested", uri.getReplyTo()); + + } + + public void testGetDestinationNoParams() { + JmsUri uri = new JmsUri("jms:news"); + assertEquals("Invalid destination", "news", uri.getDestination()); + } + + public void testInvalidDeliveryMode() { + testIllegalArgument("jms:news?deliveryMode=abc"); + } + + public void testInvalidPriority() { + testIllegalArgument("jms:news?priority=abc"); + } + + public void testInvalidTimeToLive() { + testIllegalArgument("jms:news?timeToLive=abc"); + } + + public void testInvalidDestinationType() { + testIllegalArgument("jms:news?destinationType=abc"); + } + + public void testEmpty() { + testIllegalArgument(""); + } + + public void testIllegalParam() { + testIllegalArgument("jms:news?bla"); + } + + private void testIllegalArgument(String uri) { + try { + new JmsUri(uri); + fail("Expected IllegalArgumentException for uri [" + uri + "]"); + } + catch (IllegalArgumentException ex) { + //expected + } + } +} \ No newline at end of file diff --git a/support/src/test/java/org/springframework/ws/transport/jms/MessageEndpointMessageListenerTest.java b/support/src/test/java/org/springframework/ws/transport/jms/MessageEndpointMessageListenerTest.java new file mode 100644 index 00000000..72b7cd5e --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/jms/MessageEndpointMessageListenerTest.java @@ -0,0 +1,116 @@ +/* +*/ +/* + * Copyright 2006 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.jms; + +import javax.jms.BytesMessage; +import javax.jms.Destination; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.StreamMessage; + +import junit.framework.TestCase; +import org.codehaus.activemq.message.ActiveMQBytesMessage; +import org.codehaus.activemq.message.ActiveMQTopic; +import org.easymock.MockControl; +import org.springframework.ws.MockWebServiceMessageFactory; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.endpoint.MessageEndpoint; + +public class MessageEndpointMessageListenerTest extends TestCase { + + private static final String REQUEST = " \n" + " \n" + + " \n" + " DIS\n" + + " \n" + " \n" + ""; + + private WebServiceMessageListener messageListener; + + private BytesMessage request; + + private MockControl sessionControl; + + private Session sessionMock; + + protected void setUp() throws Exception { + messageListener = new WebServiceMessageListener(); + request = new ActiveMQBytesMessage(); + request.writeBytes(REQUEST.getBytes("UTF-8")); + messageListener.setMessageFactory(new MockWebServiceMessageFactory()); + sessionControl = MockControl.createControl(Session.class); + sessionMock = (Session) sessionControl.getMock(); + } + + public void testOnMessageInvalidMessage() throws Exception { + MockControl mockControl = MockControl.createControl(StreamMessage.class); + StreamMessage message = (StreamMessage) mockControl.getMock(); + try { + messageListener.onMessage(message, sessionMock); + fail("Expected IllegalArgumentException"); + } + catch (IllegalArgumentException ex) { + // expected + } + } + + public void testOnMessageNoResponse() throws Exception { + + MessageEndpoint endpoint = new MessageEndpoint() { + + public void invoke(MessageContext messageContext) throws Exception { + } + }; + messageListener.setMessageReceiver(endpoint); + + request.reset(); + messageListener.onMessage(request, sessionMock); + } + + public void testOnMessageResponse() throws Exception { + MockControl producerControl = MockControl.createControl(MessageProducer.class); + MessageProducer producerMock = (MessageProducer) producerControl.getMock(); + BytesMessage response = new ActiveMQBytesMessage(); + String correlationId = "correlationId"; + Destination replyTo = new ActiveMQTopic(); + request.setJMSCorrelationID(correlationId); + request.setJMSReplyTo(replyTo); + request.reset(); + sessionControl.expectAndReturn(sessionMock.createBytesMessage(), response); + sessionControl.expectAndReturn(sessionMock.createProducer(replyTo), producerMock); + producerMock.marshalSendAndReceive(response); + sessionControl.replay(); + producerControl.replay(); + + MessageEndpoint endpoint = new MessageEndpoint() { + + public void invoke(MessageContext messageContext) throws Exception { + messageContext.getResponse(); + } + }; + messageListener.setMessageReceiver(endpoint); + + messageListener.onMessage(request, sessionMock); + + sessionControl.verify(); + producerControl.verify(); + assertEquals("Invalid correlationId", correlationId, response.getJMSCorrelationID()); + } + +}*/ diff --git a/support/src/test/java/org/springframework/ws/transport/jms/WebServiceMessageListenerIntegrationTest.java b/support/src/test/java/org/springframework/ws/transport/jms/WebServiceMessageListenerIntegrationTest.java new file mode 100644 index 00000000..7aa5c214 --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/jms/WebServiceMessageListenerIntegrationTest.java @@ -0,0 +1,95 @@ +/* + * 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.jms; + +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.Topic; + +import org.springframework.jms.core.JmsTemplate; +import org.springframework.jms.core.MessageCreator; +import org.springframework.test.AbstractDependencyInjectionSpringContextTests; + +public class WebServiceMessageListenerIntegrationTest extends AbstractDependencyInjectionSpringContextTests { + + private static final String CONTENT = + "" + "\n" + + "\n" + + "DIS\n" + "\n" + ""; + + private JmsTemplate jmsTemplate; + + private Queue responseQueue; + + private Queue requestQueue; + + private Topic requestTopic; + + public WebServiceMessageListenerIntegrationTest() { + setAutowireMode(AUTOWIRE_BY_NAME); + } + + public void setJmsTemplate(JmsTemplate jmsTemplate) { + this.jmsTemplate = jmsTemplate; + } + + public void setRequestQueue(Queue requestQueue) { + this.requestQueue = requestQueue; + } + + public void setRequestTopic(Topic requestTopic) { + this.requestTopic = requestTopic; + } + + public void setResponseQueue(Queue responseQueue) { + this.responseQueue = responseQueue; + } + + protected String[] getConfigLocations() { + return new String[]{"classpath:org/springframework/ws/transport/jms/jms-receiver-applicationContext.xml"}; + } + + public void testReceiveQueue() throws Exception { + final byte[] b = CONTENT.getBytes("UTF-8"); + jmsTemplate.send(requestQueue, new MessageCreator() { + public Message createMessage(Session session) throws JMSException { + BytesMessage request = session.createBytesMessage(); + request.setJMSReplyTo(responseQueue); + request.writeBytes(b); + return request; + } + }); + BytesMessage response = (BytesMessage) jmsTemplate.receive(responseQueue); + assertNotNull("No response received", response); + } + + public void testReceiveTopic() throws Exception { + final byte[] b = CONTENT.getBytes("UTF-8"); + jmsTemplate.send(requestTopic, new MessageCreator() { + public Message createMessage(Session session) throws JMSException { + BytesMessage request = session.createBytesMessage(); + request.writeBytes(b); + return request; + } + }); + Thread.sleep(100); + } + +} diff --git a/support/src/test/java/org/springframework/ws/transport/jms/support/JmsTransportUtilsTest.java b/support/src/test/java/org/springframework/ws/transport/jms/support/JmsTransportUtilsTest.java new file mode 100644 index 00000000..350665eb --- /dev/null +++ b/support/src/test/java/org/springframework/ws/transport/jms/support/JmsTransportUtilsTest.java @@ -0,0 +1,28 @@ +/* + * 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.jms.support; + +import junit.framework.TestCase; +import org.springframework.ws.transport.jms.support.JmsTransportUtils; + +public class JmsTransportUtilsTest extends TestCase { + + public void testHeaderToJmsProperty() throws Exception { + String result = JmsTransportUtils.headerToJmsProperty("SOAPAction"); + assertEquals("Invalid result", "SOAPJMS_soapAction", result); + } +} \ No newline at end of file diff --git a/support/src/test/resources/log4j.properties b/support/src/test/resources/log4j.properties new file mode 100644 index 00000000..8e32525b --- /dev/null +++ b/support/src/test/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootCategory=WARN, stdout +log4j.logger.org.springframework.ws=DEBUG +log4j.logger.org.springframework.jms=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/support/src/test/resources/org/springframework/ws/transport/jms/jms-receiver-applicationContext.xml b/support/src/test/resources/org/springframework/ws/transport/jms/jms-receiver-applicationContext.xml new file mode 100644 index 00000000..bacd4ae0 --- /dev/null +++ b/support/src/test/resources/org/springframework/ws/transport/jms/jms-receiver-applicationContext.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/support/src/test/resources/org/springframework/ws/transport/jms/jms-sender-applicationContext.xml b/support/src/test/resources/org/springframework/ws/transport/jms/jms-sender-applicationContext.xml new file mode 100644 index 00000000..f91a61b9 --- /dev/null +++ b/support/src/test/resources/org/springframework/ws/transport/jms/jms-sender-applicationContext.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file