From d6fd075203ada51591738f5a8c362de1f4b646ed Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Sun, 26 Nov 2006 22:20:19 +0000 Subject: [PATCH] Added JMS support to airline, including client. --- samples/airline/client/jms/build.xml | 55 +++++ samples/airline/client/jms/readme.txt | 13 ++ .../airline/client/jms/GetFlights.java | 195 ++++++++++++++++++ samples/airline/pom.xml | 40 +++- samples/airline/readme.txt | 18 +- .../StubFrequentFlyerSecurityService.java | 53 +++++ .../service/impl/AirlineServiceImpl.java | 6 +- .../applicationContext-hibernate.xml | 15 +- .../airline/service/applicationContext.xml | 6 +- .../airline/ws/applicationContext-ws-jms.xml | 42 ++++ .../airline/ws/applicationContext-ws.xml | 17 +- .../main/webapp/WEB-INF/airline-servlet.xml | 3 - .../airline/src/main/webapp/WEB-INF/web.xml | 2 + samples/airline/src/main/webapp/airline.wsdl | 2 +- 14 files changed, 429 insertions(+), 38 deletions(-) create mode 100644 samples/airline/client/jms/build.xml create mode 100644 samples/airline/client/jms/readme.txt create mode 100644 samples/airline/client/jms/src/org/springframework/ws/samples/airline/client/jms/GetFlights.java create mode 100644 samples/airline/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java create mode 100644 samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws-jms.xml diff --git a/samples/airline/client/jms/build.xml b/samples/airline/client/jms/build.xml new file mode 100644 index 00000000..dd4415f0 --- /dev/null +++ b/samples/airline/client/jms/build.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/airline/client/jms/readme.txt b/samples/airline/client/jms/readme.txt new file mode 100644 index 00000000..b05cc94a --- /dev/null +++ b/samples/airline/client/jms/readme.txt @@ -0,0 +1,13 @@ +SPRING WEB SERVICES + +This directory contains a client for the Airline Web Service that uses JMS: Java Message Service. The client can be run +from the provided ant file, by calling "ant run". + +NOTE that the client uses ActiveMQ 2.1, and needs to be changed for other versions of ActiveMQ, or other JMS providers. +Also note that ActiveMQ needs to be running before this sample is started. + +SAJA Client Sample table of contents +--------------------------------------------------- +* src - The source files for the client +* build.xml - Ant build file with a 'build' and a 'run' target + diff --git a/samples/airline/client/jms/src/org/springframework/ws/samples/airline/client/jms/GetFlights.java b/samples/airline/client/jms/src/org/springframework/ws/samples/airline/client/jms/GetFlights.java new file mode 100644 index 00000000..2e884c7f --- /dev/null +++ b/samples/airline/client/jms/src/org/springframework/ws/samples/airline/client/jms/GetFlights.java @@ -0,0 +1,195 @@ +/* + * 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.samples.airline.client.jms; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; +import javax.jms.BytesMessage; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import javax.jms.Session; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicConnectionFactory; +import javax.jms.TopicPublisher; +import javax.jms.TopicSession; +import javax.jms.TopicSubscriber; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPBodyElement; +import javax.xml.soap.SOAPElement; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.codehaus.activemq.ActiveMQConnection; +import org.codehaus.activemq.ActiveMQConnectionFactory; + +/** + * @author Arjen Poutsma + */ +public class GetFlights implements MessageListener { + + public static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/airline/schemas"; + + public static final String PREFIX = "airline"; + + private static final String CORRELATION_ID = "correlationId"; + + private static final String REQUEST_TOPIC = "org.springframework.ws.samples.airline.RequestTopic"; + + private static final String RESPONSE_TOPIC = "org.springframework.ws.samples.airline.ResponseTopic"; + + private TopicConnection connection; + + private MessageFactory messageFactory; + + private Topic responseTopic; + + private TopicSession session; + + private TransformerFactory transfomerFactory; + + public GetFlights(TopicConnectionFactory connectionFactory) throws SOAPException, JMSException { + messageFactory = MessageFactory.newInstance(); + transfomerFactory = TransformerFactory.newInstance(); + connection = connectionFactory.createTopicConnection(); + session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + responseTopic = session.createTopic(RESPONSE_TOPIC); + TopicSubscriber subscriber = session.createSubscriber(responseTopic); + subscriber.setMessageListener(this); + connection.start(); + } + + public void onMessage(Message message) { + try { + System.out.println("Received message"); + BytesMessage bytesMessage = (BytesMessage) message; + byte[] buf = new byte[(int) bytesMessage.getBodyLength()]; + bytesMessage.readBytes(buf); + ByteArrayInputStream is = new ByteArrayInputStream(buf); + SOAPMessage saajMessage = messageFactory.createMessage(new MimeHeaders(), is); + writeGetFlightsResponse(saajMessage); + System.exit(0); + } + catch (Exception e) { + e.printStackTrace(System.err); + } + } + + public void close() { + if (session != null) { + try { + session.close(); + } + catch (JMSException ex) { + ex.printStackTrace(System.err); + } + } + if (connection != null) { + try { + connection.close(); + } + catch (JMSException ex) { + ex.printStackTrace(System.err); + } + } + } + + private SOAPMessage createGetFlightsRequest() throws SOAPException { + SOAPMessage message = messageFactory.createMessage(); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name getFlightsRequestName = envelope.createName("GetFlightsRequest", PREFIX, NAMESPACE_URI); + SOAPBodyElement getFlightsRequestElement = message.getSOAPBody().addBodyElement(getFlightsRequestName); + Name fromName = envelope.createName("from", PREFIX, NAMESPACE_URI); + SOAPElement fromElement = getFlightsRequestElement.addChildElement(fromName); + fromElement.setValue("AMS"); + Name toName = envelope.createName("to", PREFIX, NAMESPACE_URI); + SOAPElement toElement = getFlightsRequestElement.addChildElement(toName); + toElement.setValue("VCE"); + Name departureDateName = envelope.createName("departureDate", PREFIX, NAMESPACE_URI); + SOAPElement departureDateElement = getFlightsRequestElement.addChildElement(departureDateName); + departureDateElement.setValue("2006-01-31"); + return message; + } + + public void getFlights() throws SOAPException, IOException, TransformerException, JMSException { + SOAPMessage request = createGetFlightsRequest(); + Topic requestTopic = session.createTopic(REQUEST_TOPIC); + TopicPublisher publisher = session.createPublisher(requestTopic); + BytesMessage message = session.createBytesMessage(); + message.setJMSCorrelationID(CORRELATION_ID); + message.setJMSReplyTo(responseTopic); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + request.writeTo(os); + os.flush(); + message.writeBytes(os.toByteArray()); + publisher.publish(message); + System.out.println("Written GetFlights request to " + requestTopic); + } + + private void writeGetFlightsResponse(SOAPMessage message) throws SOAPException, TransformerException { + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name getFlightsResponseName = envelope.createName("GetFlightsResponse", PREFIX, NAMESPACE_URI); + SOAPBodyElement getFlightsResponseElement = + (SOAPBodyElement) message.getSOAPBody().getChildElements(getFlightsResponseName).next(); + Name flightName = envelope.createName("flight", PREFIX, NAMESPACE_URI); + Iterator iterator = getFlightsResponseElement.getChildElements(flightName); + Transformer transformer = transfomerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + int count = 1; + while (iterator.hasNext()) { + System.out.println("Flight " + count); + System.out.println("--------"); + SOAPElement flightElement = (SOAPElement) iterator.next(); + DOMSource source = new DOMSource(flightElement); + transformer.transform(source, new StreamResult(System.out)); + } + } + + public static void main(String[] args) throws Exception { + String url = ActiveMQConnection.DEFAULT_URL; + if (args.length > 0) { + url = args[0]; + } + TopicConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url); + GetFlights getFlights = null; + try { + getFlights = new GetFlights(connectionFactory); + getFlights.getFlights(); + while (true) { + // keep running until we receive a response message in onMessage + } + } + finally { + if (getFlights != null) { + getFlights.close(); + } + } + } +} \ No newline at end of file diff --git a/samples/airline/pom.xml b/samples/airline/pom.xml index 3e275aaa..2f429696 100644 --- a/samples/airline/pom.xml +++ b/samples/airline/pom.xml @@ -1,4 +1,5 @@ - + spring-ws-samples org.springframework.ws @@ -19,7 +20,7 @@ - + ${project.build.directory}/generated-sources/main/java @@ -46,10 +47,15 @@ org.springframework.ws spring-ws-security + + org.springframework.ws + spring-ws-sandbox + ${project.version} + org.springframework - spring-hibernate + spring-hibernate3 org.springframework @@ -59,6 +65,10 @@ org.springframework spring-mock + + org.springframework + spring-jms + jdom @@ -150,11 +160,26 @@ mysql-connector-java 3.1.11 + - hsqldb - hsqldb - 1.8.0.4 - test + javax.jms + jms + 1.1 + runtime + + + geronimo-spec + geronimo-spec-j2ee-management + 1.0-rc4 + + + activemq + activemq + + + concurrent + concurrent + 1.3.4 @@ -167,6 +192,5 @@ joda-time-hibernate 0.8 - \ No newline at end of file diff --git a/samples/airline/readme.txt b/samples/airline/readme.txt index daffa7cf..1699a78e 100644 --- a/samples/airline/readme.txt +++ b/samples/airline/readme.txt @@ -6,27 +6,31 @@ Features a web service on top of an airline reservation system, backed by a database. The web service works by using XML Marshalling techniques (JAXB 1), and JDOM in combination with XPath queries to pull information from a message. All -messages follow following the airline.xsd schema in src/webapp. +messages follow the airline.xsd schema in src/main/webapp. -A C# client is available in the client directory. +Multiple clients are available, showing interoperability with Axis 1, SAAJ, C#, JMS and more. 2. INSTALLATION The Airline sample is a normal web application that connects to a database of your choice. 1. Create a database using one of the scripts in src/etc/db. First, initialize the database using either the MySQL or - PostgreSQL initDB.sql script, and after that run populateDb.sql. + PostgreSQL initDB.sql script, and after that run src/etc/db/populateDb.sql. 2. Adjust the jdbc.properties in src/main/resources/org/springframework/ws/samples/airline/dao - to reflect your database connection settings -3. Adjust the hibernate.properties in src/main/resources/org/springframework/ws/samples/airline/dao/hibernate + to reflect your database connection settings. By default MySQL is used. +3. Adjust the hibernate.properties in src/main/resources/org/springframework/ws/samples/airline/dao/hibernate. By + default MySQL is used. +4. (Optional) Adjust the applicationContext-ws-jms.xml in src/main/resources/org/springframework/ws/samples/airline/ws/ + to reflect your JMS connections settings. By default ActiveMQ 2.1 is used. Make sure to start ActiveMQ before 4. run "mvn package" and deploy the war file generated in target; or run "mvn jetty:run" to run the sample using the Jetty Web container built into Maven 2. +5. Note that both MySQL drivers are linked in using Maven so you don't have include these in your server if you're using this database. 3. THE CLIENTS -The client directory contains two sample clients: one in C# and one using SAAJ. More instructions are provided in the -readme files in the directories. +The client directory contains a number of sample clients. More instructions are provided in the readme files in the +directories. diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java new file mode 100644 index 00000000..e9c5b029 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/StubFrequentFlyerSecurityService.java @@ -0,0 +1,53 @@ +/* + * 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.samples.airline.security; + +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +/** + * Stub implementation of FrequentFlyerSecurityService. This implementation is used by default by {@link + * org.springframework.ws.samples.airline.service.impl.AirlineServiceImpl}, to allow it to run without depending on + * Acegi Security. + * + * @author Arjen Poutsma + */ +public class StubFrequentFlyerSecurityService implements FrequentFlyerSecurityService { + + private FrequentFlyer john; + + public StubFrequentFlyerSecurityService() { + john = new FrequentFlyer(); + john.setUsername("john"); + john.setFirstName("John"); + john.setLastName("Doe"); + john.setPassword("changeme"); + john.setMiles(10); + } + + public FrequentFlyer getFrequentFlyer(String username) { + if (john.getUsername().equals(username)) { + return john; + } + else { + return null; + } + } + + public FrequentFlyer getCurrentlyAuthenticatedFrequentFlyer() { + return john; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java index 0a0bf6ce..0964634e 100644 --- a/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java @@ -22,7 +22,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.joda.time.DateTime; import org.joda.time.YearMonthDay; - import org.springframework.util.Assert; import org.springframework.ws.samples.airline.dao.FlightDao; import org.springframework.ws.samples.airline.dao.TicketDao; @@ -32,6 +31,7 @@ import org.springframework.ws.samples.airline.domain.Passenger; import org.springframework.ws.samples.airline.domain.ServiceClass; import org.springframework.ws.samples.airline.domain.Ticket; import org.springframework.ws.samples.airline.security.FrequentFlyerSecurityService; +import org.springframework.ws.samples.airline.security.StubFrequentFlyerSecurityService; import org.springframework.ws.samples.airline.service.AirlineService; import org.springframework.ws.samples.airline.service.NoSeatAvailableException; import org.springframework.ws.samples.airline.service.NoSuchFlightException; @@ -43,13 +43,13 @@ import org.springframework.ws.samples.airline.service.NoSuchFlightException; */ public class AirlineServiceImpl implements AirlineService { - private final static Log logger = LogFactory.getLog(AirlineServiceImpl.class); + private static final Log logger = LogFactory.getLog(AirlineServiceImpl.class); private FlightDao flightDao; private TicketDao ticketDao; - private FrequentFlyerSecurityService frequentFlyerSecurityService; + private FrequentFlyerSecurityService frequentFlyerSecurityService = new StubFrequentFlyerSecurityService(); public void setFlightDao(FlightDao flightDao) { this.flightDao = flightDao; diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml index fd5e3993..e19014c2 100644 --- a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml @@ -1,8 +1,9 @@ - - - + @@ -24,10 +25,8 @@ - - - + diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml index a287297b..4849e80d 100644 --- a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml @@ -1,8 +1,8 @@ - + - diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws-jms.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws-jms.xml new file mode 100644 index 00000000..c856d3a9 --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws-jms.xml @@ -0,0 +1,42 @@ + + + + This application context contains a Spring-WS JMS transport. + + + + + The JMS connection factory to use for receiving request JMS message, and sending response messages. Replace + this bean with a JNDI Pooled connection factory, or change the wrapped "targetConnectionFactory" bean. + + + + + + + + + + + A Spring 2.0 MessageListenerContainer which listens for incoming messages on the Request Topic. When a + message is received, the messageListener defined below is invoked. + + + + + + + + + + Spring 2.0 SessionAwareMessageListener that creates a SOAP message from the invoming JMS message using + a messageFactory, and forwards it to the message to the messageDispatcher. Both of these beans are defined + in applicationContext-ws.xml. + + + + + + diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml index 5f6a416d..1f7708be 100644 --- a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml @@ -1,16 +1,23 @@ - - - + This application context contains the Spring-WS beans. + + + The MessageFactory is used to create new SOAP messages from incoming requests. It is referenced in + airline-servlet.xml and applicationContext-ws-jms.xml. + + + The MessageDispatcher is responsible for routing messages to endpoints. It uses three endpoint mappings to - determine the endpoint suitable for handling a particular incoming request. This is not a recommended - approach, it's done here only for illustration purposes. + determine the endpoint suitable for handling a particular incoming request. Having three mappings is not a + recommended approach, it's done here only for illustration purposes. diff --git a/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml b/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml index c312b673..b5586cf2 100644 --- a/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml +++ b/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml @@ -7,13 +7,10 @@ The handlerAdapter makes sure that Spring's DispatcherServlet supports MessageEndpoints instances as handlers. - It uses a SAAJ to construct SoapMessageContexts (and SoapMessages). - - This handler adapter adds support for WsdlDefinitions to Spring's DispatcherServlet. It transforms location diff --git a/samples/airline/src/main/webapp/WEB-INF/web.xml b/samples/airline/src/main/webapp/WEB-INF/web.xml index cbb3f63d..51173c38 100644 --- a/samples/airline/src/main/webapp/WEB-INF/web.xml +++ b/samples/airline/src/main/webapp/WEB-INF/web.xml @@ -20,6 +20,8 @@ classpath:org/springframework/ws/samples/airline/service/applicationContext.xml classpath:org/springframework/ws/samples/airline/security/applicationContext-security.xml + + classpath:org/springframework/ws/samples/airline/ws/applicationContext-ws-jms.xml classpath:org/springframework/ws/samples/airline/ws/applicationContext-ws.xml diff --git a/samples/airline/src/main/webapp/airline.wsdl b/samples/airline/src/main/webapp/airline.wsdl index 38a5897f..25dc1d43 100644 --- a/samples/airline/src/main/webapp/airline.wsdl +++ b/samples/airline/src/main/webapp/airline.wsdl @@ -1,8 +1,8 @@