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