1   /*
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.client.core;
18  
19  import java.io.IOException;
20  import java.util.Enumeration;
21  import java.util.Iterator;
22  import java.util.StringTokenizer;
23  import javax.activation.CommandMap;
24  import javax.activation.DataHandler;
25  import javax.activation.MailcapCommandMap;
26  import javax.mail.util.ByteArrayDataSource;
27  import javax.servlet.ServletConfig;
28  import javax.servlet.ServletException;
29  import javax.servlet.http.HttpServlet;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.xml.namespace.QName;
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  import javax.xml.soap.MessageFactory;
36  import javax.xml.soap.MimeHeader;
37  import javax.xml.soap.MimeHeaders;
38  import javax.xml.soap.SOAPBody;
39  import javax.xml.soap.SOAPException;
40  import javax.xml.soap.SOAPMessage;
41  import javax.xml.transform.Result;
42  import javax.xml.transform.Source;
43  import javax.xml.transform.Transformer;
44  import javax.xml.transform.TransformerConfigurationException;
45  import javax.xml.transform.TransformerException;
46  import javax.xml.transform.TransformerFactory;
47  import javax.xml.transform.dom.DOMSource;
48  import javax.xml.transform.stream.StreamResult;
49  
50  import junit.extensions.TestSetup;
51  import junit.framework.Test;
52  import junit.framework.TestSuite;
53  import org.custommonkey.xmlunit.XMLTestCase;
54  import org.mortbay.jetty.Server;
55  import org.mortbay.jetty.servlet.Context;
56  import org.mortbay.jetty.servlet.ServletHolder;
57  import org.w3c.dom.Document;
58  import org.xml.sax.SAXException;
59  
60  import org.springframework.oxm.Marshaller;
61  import org.springframework.oxm.Unmarshaller;
62  import org.springframework.oxm.XmlMappingException;
63  import org.springframework.util.StringUtils;
64  import org.springframework.ws.WebServiceMessage;
65  import org.springframework.ws.client.WebServiceTransportException;
66  import org.springframework.ws.pox.dom.DomPoxMessageFactory;
67  import org.springframework.ws.soap.SoapMessage;
68  import org.springframework.ws.soap.SoapMessageFactory;
69  import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory;
70  import org.springframework.ws.soap.client.SoapFaultClientException;
71  import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
72  import org.springframework.ws.transport.http.CommonsHttpMessageSender;
73  import org.springframework.xml.transform.StringResult;
74  import org.springframework.xml.transform.StringSource;
75  
76  public class WebServiceTemplateIntegrationTest extends XMLTestCase {
77  
78      private WebServiceTemplate template;
79  
80      private static Server jettyServer;
81  
82      private String messagePayload = "<root xmlns='http://springframework.org/spring-ws'><child/></root>";
83  
84      public static Test suite() {
85          return new ServerTestSetup(new TestSuite(WebServiceTemplateIntegrationTest.class));
86      }
87  
88      public void testSaaj() throws Exception {
89          doSoap(new SaajSoapMessageFactory(MessageFactory.newInstance()));
90      }
91  
92      public void testAxiom() throws Exception {
93          doSoap(new AxiomSoapMessageFactory());
94      }
95  
96      public void testAxiomNonCaching() throws Exception {
97          AxiomSoapMessageFactory axiomFactory = new AxiomSoapMessageFactory();
98          axiomFactory.setPayloadCaching(false);
99          doSoap(axiomFactory);
100     }
101 
102     public void testPox() throws Exception {
103         template = new WebServiceTemplate(new DomPoxMessageFactory());
104         template.setMessageSender(new CommonsHttpMessageSender());
105         String content = "<root xmlns='http://springframework.org/spring-ws'><child/></root>";
106         StringResult result = new StringResult();
107         template.sendSourceAndReceiveToResult("http://localhost:8888/pox", new StringSource(content), result);
108         assertXMLEqual(content, result.toString());
109         try {
110             template.sendSourceAndReceiveToResult("http://localhost:8888/errors/notfound", new StringSource(content),
111                     new StringResult());
112             fail("WebServiceTransportException expected");
113         }
114         catch (WebServiceTransportException ex) {
115             //expected
116         }
117         try {
118             template.sendSourceAndReceiveToResult("http://localhost:8888/errors/server", new StringSource(content),
119                     result);
120             fail("WebServiceTransportException expected");
121         }
122         catch (WebServiceTransportException ex) {
123             //expected
124         }
125     }
126 
127     private void doSoap(SoapMessageFactory messageFactory) throws Exception {
128         template = new WebServiceTemplate(messageFactory);
129         template.setMessageSender(new CommonsHttpMessageSender());
130         sendSourceAndReceiveToResult();
131         sendSourceAndReceiveToResultNoResponse();
132         marshalSendAndReceiveResponse();
133         marshalSendAndReceiveNoResponse();
134         notFound();
135         fault();
136         faultNonCompliant();
137         attachment();
138     }
139 
140     private void sendSourceAndReceiveToResult() throws SAXException, IOException {
141         StringResult result = new StringResult();
142         boolean b = template.sendSourceAndReceiveToResult("http://localhost:8888/soap/echo",
143                 new StringSource(messagePayload), result);
144         assertTrue("Invalid result", b);
145         assertXMLEqual(messagePayload, result.toString());
146     }
147 
148     private void sendSourceAndReceiveToResultNoResponse() {
149         boolean b = template.sendSourceAndReceiveToResult("http://localhost:8888/soap/noResponse",
150                 new StringSource(messagePayload), new StringResult());
151         assertFalse("Invalid result", b);
152     }
153 
154     private void marshalSendAndReceiveResponse() throws TransformerConfigurationException {
155         final Transformer transformer = TransformerFactory.newInstance().newTransformer();
156         final Object requestObject = new Object();
157         Marshaller marshaller = new Marshaller() {
158 
159             public void marshal(Object graph, Result result) throws XmlMappingException, IOException {
160                 assertEquals("Invalid object", graph, requestObject);
161                 try {
162                     transformer.transform(new StringSource(messagePayload), result);
163                 }
164                 catch (TransformerException e) {
165                     fail(e.getMessage());
166                 }
167             }
168 
169             public boolean supports(Class clazz) {
170                 assertEquals("Invalid class", Object.class, clazz);
171                 return true;
172             }
173         };
174         final Object responseObject = new Object();
175         Unmarshaller unmarshaller = new Unmarshaller() {
176 
177             public Object unmarshal(Source source) throws XmlMappingException, IOException {
178                 return responseObject;
179             }
180 
181             public boolean supports(Class clazz) {
182                 assertEquals("Invalid class", Object.class, clazz);
183                 return true;
184             }
185         };
186         template.setMarshaller(marshaller);
187         template.setUnmarshaller(unmarshaller);
188         Object result = template.marshalSendAndReceive("http://localhost:8888/soap/echo", requestObject);
189         assertEquals("Invalid response object", responseObject, result);
190     }
191 
192     private void marshalSendAndReceiveNoResponse() throws TransformerConfigurationException {
193         final Transformer transformer = TransformerFactory.newInstance().newTransformer();
194         final Object requestObject = new Object();
195         Marshaller marshaller = new Marshaller() {
196 
197             public void marshal(Object graph, Result result) throws XmlMappingException, IOException {
198                 assertEquals("Invalid object", graph, requestObject);
199                 try {
200                     transformer.transform(new StringSource(messagePayload), result);
201                 }
202                 catch (TransformerException e) {
203                     fail(e.getMessage());
204                 }
205             }
206 
207             public boolean supports(Class clazz) {
208                 assertEquals("Invalid class", Object.class, clazz);
209                 return true;
210             }
211         };
212         template.setMarshaller(marshaller);
213         Object result = template.marshalSendAndReceive("http://localhost:8888/soap/noResponse", requestObject);
214         assertNull("Invalid response object", result);
215     }
216 
217     private void notFound() {
218         try {
219             template.sendSourceAndReceiveToResult("http://localhost:8888/errors/notfound",
220                     new StringSource(messagePayload), new StringResult());
221             fail("WebServiceTransportException expected");
222         }
223         catch (WebServiceTransportException ex) {
224             //expected
225         }
226     }
227 
228     private void fault() {
229         Result result = new StringResult();
230         try {
231             template.sendSourceAndReceiveToResult("http://localhost:8888/soap/fault", new StringSource(messagePayload),
232                     result);
233             fail("SoapFaultClientException expected");
234         }
235         catch (SoapFaultClientException ex) {
236             //expected
237         }
238     }
239 
240     private void faultNonCompliant() {
241         Result result = new StringResult();
242         template.setCheckConnectionForFault(false);
243         template.setCheckConnectionForError(false);
244         try {
245             template.sendSourceAndReceiveToResult("http://localhost:8888/soap/badRequestFault",
246                     new StringSource(messagePayload), result);
247             fail("SoapFaultClientException expected");
248         }
249         catch (SoapFaultClientException ex) {
250             //expected
251         }
252     }
253 
254     private void attachment() {
255         template.sendSourceAndReceiveToResult("http://localhost:8888/soap/attachment", new StringSource(messagePayload),
256                 new WebServiceMessageCallback() {
257 
258                     public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
259                         SoapMessage soapMessage = (SoapMessage) message;
260                         final String attachmentContent = "content";
261                         soapMessage.addAttachment("attachment-1",
262                                 new DataHandler(new ByteArrayDataSource(attachmentContent, "text/plain")));
263                     }
264                 }, new StringResult());
265     }
266 
267     /** Servlet that returns and error message for a given status code. */
268     private static class ErrorServlet extends HttpServlet {
269 
270         private int sc;
271 
272         private ErrorServlet(int sc) {
273             this.sc = sc;
274         }
275 
276         protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
277             resp.sendError(sc);
278         }
279     }
280 
281     /** Simple POX Servlet. */
282     private static class PoxServlet extends HttpServlet {
283 
284         private DocumentBuilderFactory documentBuilderFactory;
285 
286         private TransformerFactory transformerFactory;
287 
288         public void init(ServletConfig servletConfig) throws ServletException {
289             super.init(servletConfig);
290             documentBuilderFactory = DocumentBuilderFactory.newInstance();
291             documentBuilderFactory.setNamespaceAware(true);
292             transformerFactory = TransformerFactory.newInstance();
293         }
294 
295         public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
296             try {
297                 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
298                 Document message = documentBuilder.parse(req.getInputStream());
299                 Transformer transformer = transformerFactory.newTransformer();
300                 transformer.transform(new DOMSource(message), new StreamResult(resp.getOutputStream()));
301             }
302             catch (Exception ex) {
303                 throw new ServletException("POX POST failed" + ex.getMessage());
304             }
305         }
306     }
307 
308     /** Abstract SOAP Servlet */
309     private abstract static class AbstractSoapServlet extends HttpServlet {
310 
311         protected MessageFactory messageFactory = null;
312 
313         private int sc = -1;
314 
315         public void setSc(int sc) {
316             this.sc = sc;
317         }
318 
319         public void init(ServletConfig servletConfig) throws ServletException {
320             super.init(servletConfig);
321             try {
322                 messageFactory = MessageFactory.newInstance();
323             }
324             catch (SOAPException ex) {
325                 throw new ServletException("Unable to create message factory" + ex.getMessage());
326             }
327         }
328 
329         public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
330             try {
331                 MimeHeaders headers = getHeaders(req);
332                 SOAPMessage request = messageFactory.createMessage(headers, req.getInputStream());
333                 SOAPMessage reply = onMessage(request);
334                 if (sc != -1) {
335                     resp.setStatus(sc);
336                 }
337                 if (reply != null) {
338                     if (reply.saveRequired()) {
339                         reply.saveChanges();
340                     }
341                     if (sc == -1) {
342                         resp.setStatus(!reply.getSOAPBody().hasFault() ? HttpServletResponse.SC_OK :
343                                 HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
344                     }
345                     putHeaders(reply.getMimeHeaders(), resp);
346                     reply.writeTo(resp.getOutputStream());
347                 }
348                 else if (sc == -1) {
349                     resp.setStatus(HttpServletResponse.SC_ACCEPTED);
350                 }
351             }
352             catch (Exception ex) {
353                 throw new ServletException("SAAJ POST failed " + ex.getMessage(), ex);
354             }
355         }
356 
357         private MimeHeaders getHeaders(HttpServletRequest httpServletRequest) {
358             Enumeration enumeration = httpServletRequest.getHeaderNames();
359             MimeHeaders headers = new MimeHeaders();
360             while (enumeration.hasMoreElements()) {
361                 String headerName = (String) enumeration.nextElement();
362                 String headerValue = httpServletRequest.getHeader(headerName);
363                 StringTokenizer values = new StringTokenizer(headerValue, ",");
364                 while (values.hasMoreTokens()) {
365                     headers.addHeader(headerName, values.nextToken().trim());
366                 }
367             }
368             return headers;
369         }
370 
371         private void putHeaders(MimeHeaders headers, HttpServletResponse res) {
372             Iterator it = headers.getAllHeaders();
373             while (it.hasNext()) {
374                 MimeHeader header = (MimeHeader) it.next();
375                 String[] values = headers.getHeader(header.getName());
376                 res.setHeader(header.getName(), StringUtils.arrayToCommaDelimitedString(values));
377             }
378         }
379 
380         protected abstract SOAPMessage onMessage(SOAPMessage message) throws SOAPException;
381     }
382 
383     private static class EchoSoapServlet extends AbstractSoapServlet {
384 
385         protected SOAPMessage onMessage(SOAPMessage message) throws SOAPException {
386             return message;
387         }
388     }
389 
390     private static class NoResponseSoapServlet extends AbstractSoapServlet {
391 
392         protected SOAPMessage onMessage(SOAPMessage message) throws SOAPException {
393             return null;
394         }
395     }
396 
397     private static class SoapFaultServlet extends AbstractSoapServlet {
398 
399         protected SOAPMessage onMessage(SOAPMessage message) throws SOAPException {
400             SOAPMessage response = messageFactory.createMessage();
401             SOAPBody body = response.getSOAPBody();
402             body.addFault(new QName("http://schemas.xmlsoap.org/soap/envelope/", "Server"), "Server fault");
403             return response;
404         }
405     }
406 
407     private static class AttachmentsServlet extends AbstractSoapServlet {
408 
409         protected SOAPMessage onMessage(SOAPMessage message) throws SOAPException {
410             assertEquals("No attachments found", 1, message.countAttachments());
411             return null;
412         }
413     }
414 
415     /** A custom TestSetup to make sure that setUp() and tearDown() are only called once. */
416     private static class ServerTestSetup extends TestSetup {
417 
418         private ServerTestSetup(Test test) {
419             super(test);
420         }
421 
422         protected void setUp() throws Exception {
423             removeXmlDataContentHandler();
424 
425             jettyServer = new Server(8888);
426             Context jettyContext = new Context(jettyServer, "/");
427             jettyContext.addServlet(new ServletHolder(new EchoSoapServlet()), "/soap/echo");
428             jettyContext.addServlet(new ServletHolder(new SoapFaultServlet()), "/soap/fault");
429             SoapFaultServlet badRequestFault = new SoapFaultServlet();
430             badRequestFault.setSc(400);
431             jettyContext.addServlet(new ServletHolder(badRequestFault), "/soap/badRequestFault");
432             jettyContext.addServlet(new ServletHolder(new NoResponseSoapServlet()), "/soap/noResponse");
433             jettyContext.addServlet(new ServletHolder(new AttachmentsServlet()), "/soap/attachment");
434             jettyContext.addServlet(new ServletHolder(new PoxServlet()), "/pox");
435             jettyContext.addServlet(new ServletHolder(new ErrorServlet(404)), "/errors/notfound");
436             jettyContext.addServlet(new ServletHolder(new ErrorServlet(500)), "/errors/server");
437             jettyServer.start();
438         }
439 
440         /**
441          * A workaround for the faulty XmlDataContentHandler in the SAAJ RI, which cannot handle mime types such as
442          * "text/xml; charset=UTF-8", causing issues with Axiom. We basically reset the command map
443          */
444         private void removeXmlDataContentHandler() throws SOAPException {
445             MessageFactory messageFactory = MessageFactory.newInstance();
446             SOAPMessage message = messageFactory.createMessage();
447             message.createAttachmentPart();
448             CommandMap.setDefaultCommandMap(new MailcapCommandMap());
449         }
450 
451         protected void tearDown() throws Exception {
452             jettyServer.stop();
453         }
454     }
455 
456 }