View Javadoc

1   /*
2    * Copyright 2005-2011 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.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.net.URI;
22  import java.util.List;
23  import javax.xml.transform.Result;
24  import javax.xml.transform.Source;
25  import javax.xml.transform.Transformer;
26  import javax.xml.transform.TransformerConfigurationException;
27  import javax.xml.transform.TransformerException;
28  
29  import org.springframework.beans.factory.BeanInitializationException;
30  import org.springframework.oxm.Marshaller;
31  import org.springframework.oxm.Unmarshaller;
32  import org.springframework.util.Assert;
33  import org.springframework.util.ObjectUtils;
34  import org.springframework.ws.FaultAwareWebServiceMessage;
35  import org.springframework.ws.WebServiceMessage;
36  import org.springframework.ws.WebServiceMessageFactory;
37  import org.springframework.ws.client.WebServiceClientException;
38  import org.springframework.ws.client.WebServiceIOException;
39  import org.springframework.ws.client.WebServiceTransformerException;
40  import org.springframework.ws.client.WebServiceTransportException;
41  import org.springframework.ws.client.support.WebServiceAccessor;
42  import org.springframework.ws.client.support.destination.DestinationProvider;
43  import org.springframework.ws.client.support.interceptor.ClientInterceptor;
44  import org.springframework.ws.context.DefaultMessageContext;
45  import org.springframework.ws.context.MessageContext;
46  import org.springframework.ws.soap.client.core.SoapFaultMessageResolver;
47  import org.springframework.ws.support.DefaultStrategiesHelper;
48  import org.springframework.ws.support.MarshallingUtils;
49  import org.springframework.ws.transport.FaultAwareWebServiceConnection;
50  import org.springframework.ws.transport.TransportException;
51  import org.springframework.ws.transport.WebServiceConnection;
52  import org.springframework.ws.transport.WebServiceMessageSender;
53  import org.springframework.ws.transport.context.DefaultTransportContext;
54  import org.springframework.ws.transport.context.TransportContext;
55  import org.springframework.ws.transport.context.TransportContextHolder;
56  import org.springframework.ws.transport.http.HttpUrlConnectionMessageSender;
57  import org.springframework.ws.transport.support.TransportUtils;
58  
59  import org.apache.commons.logging.Log;
60  import org.apache.commons.logging.LogFactory;
61  
62  /**
63   * <strong>The central class for client-side Web services.</strong> It provides a message-driven approach to sending and
64   * receiving {@link WebServiceMessage} instances.
65   * <p/>
66   * Code using this class need only implement callback interfaces, provide {@link Source} objects to read data from, or
67   * use the pluggable {@link Marshaller} support. For invoking the {@link #marshalSendAndReceive marshalling methods},
68   * the {@link #setMarshaller(Marshaller) marshaller} and {@link #setUnmarshaller(Unmarshaller) unmarshaller} properties
69   * must be set.
70   * <p/>
71   * This template uses a {@link SoapFaultMessageResolver} to handle fault response messages. Another {@link
72   * FaultMessageResolver} can be defined with with {@link #setFaultMessageResolver(FaultMessageResolver)
73   * faultMessageResolver} property. If this property is set to <code>null</code>, no fault resolving is performed.
74   * <p/>
75   * This template uses the following algorithm for sending and receiving. <ol> <li>Call {@link #createConnection(URI)
76   * createConnection()}.</li> <li>Call {@link WebServiceMessageFactory#createWebServiceMessage()
77   * createWebServiceMessage()} on the registered message factory to create a request message.</li> <li>Invoke {@link
78   * WebServiceMessageCallback#doWithMessage(WebServiceMessage) doWithMessage()} on the request callback, if any. This
79   * step stores content in the request message, based on <code>Source</code>, marshalling, etc.</li> <li>Invoke {@link
80   * ClientInterceptor#handleRequest(MessageContext) handleRequest()} on the registered {@link
81   * #setInterceptors(ClientInterceptor[]) interceptors}. Interceptors are executed in order. If any of the interceptors
82   * creates a response message in the message context, skip to step 7.</li> <li>Call {@link
83   * WebServiceConnection#send(WebServiceMessage) send()} on the connection.</li> <li>Call {@link
84   * #hasError(WebServiceConnection,WebServiceMessage) hasError()} to check if the connection has an error. For an HTTP
85   * transport, a status code other than <code>2xx</code> indicates an error. However, since a status code of 500 can also
86   * indicate a SOAP fault, the template verifies whether the error is not a fault.</li> <ul> <li>If the connection has an
87   * error, call the {@link #handleError handleError()} method, which by default throws a {@link
88   * WebServiceTransportException}.</li> <li>If the connection has no error, continue with the next step.</li> </ul>
89   * <li>Invoke {@link WebServiceConnection#receive(WebServiceMessageFactory) receive} on the connection to read the
90   * response message, if any.</li> <ul> <li>If no response was received, return <code>null</code> or
91   * <code>false</code></li> <li>Call {@link #hasFault(WebServiceConnection,WebServiceMessage) hasFault()} to determine
92   * whether the response has a fault. If it has, call {@link ClientInterceptor#handleFault(MessageContext)} and the
93   * {@link #handleFault handleFault()} method.</li> <li>Otherwise, invoke {@link ClientInterceptor#handleResponse(MessageContext)}
94   * and {@link WebServiceMessageExtractor#extractData(WebServiceMessage) extractData()} on the response extractor, or
95   * {@link WebServiceMessageCallback#doWithMessage(WebServiceMessage) doWithMessage} on the response callback.</li> </ul>
96   * <li>Call to {@link WebServiceConnection#close() close} on the connection.</li> </ol>
97   *
98   * @author Arjen Poutsma
99   * @since 1.0.0
100  */
101 public class WebServiceTemplate extends WebServiceAccessor implements WebServiceOperations {
102 
103     /** Log category to use for message tracing. */
104     public static final String MESSAGE_TRACING_LOG_CATEGORY = "org.springframework.ws.client.MessageTracing";
105 
106     /** Additional logger to use for sent message tracing. */
107     protected static final Log sentMessageTracingLogger =
108             LogFactory.getLog(WebServiceTemplate.MESSAGE_TRACING_LOG_CATEGORY + ".sent");
109 
110     /** Additional logger to use for received message tracing. */
111     protected static final Log receivedMessageTracingLogger =
112             LogFactory.getLog(WebServiceTemplate.MESSAGE_TRACING_LOG_CATEGORY + ".received");
113 
114     private Marshaller marshaller;
115 
116     private Unmarshaller unmarshaller;
117 
118     private FaultMessageResolver faultMessageResolver;
119 
120     private boolean checkConnectionForError = true;
121 
122     private boolean checkConnectionForFault = true;
123 
124     private ClientInterceptor[] interceptors;
125 
126     private DestinationProvider destinationProvider;
127 
128     /** Creates a new <code>WebServiceTemplate</code> using default settings. */
129     public WebServiceTemplate() {
130         initDefaultStrategies();
131     }
132 
133     /**
134      * Creates a new <code>WebServiceTemplate</code> based on the given message factory.
135      *
136      * @param messageFactory the message factory to use
137      */
138     public WebServiceTemplate(WebServiceMessageFactory messageFactory) {
139         setMessageFactory(messageFactory);
140         initDefaultStrategies();
141     }
142 
143     /**
144      * Creates a new <code>WebServiceTemplate</code> with the given marshaller. If the given {@link
145      * Marshaller} also implements the {@link Unmarshaller} interface, it is used for both marshalling and
146      * unmarshalling. Otherwise, an exception is thrown.
147      * <p/>
148      * Note that all {@link Marshaller} implementations in Spring also implement the {@link Unmarshaller} interface,
149      * so that you can safely use this constructor.
150      *
151      * @param marshaller object used as marshaller and unmarshaller
152      * @throws IllegalArgumentException when <code>marshaller</code> does not implement the {@link Unmarshaller}
153      *                                  interface
154      * @since 2.0.3
155      */
156     public WebServiceTemplate(Marshaller marshaller) {
157         Assert.notNull(marshaller, "marshaller must not be null");
158         if (!(marshaller instanceof Unmarshaller)) {
159             throw new IllegalArgumentException("Marshaller [" + marshaller + "] does not implement the Unmarshaller " +
160                     "interface. Please set an Unmarshaller explicitly by using the " +
161                     "WebServiceTemplate(Marshaller, Unmarshaller) constructor.");
162         }
163         else {
164             this.setMarshaller(marshaller);
165             this.setUnmarshaller((Unmarshaller) marshaller);
166         }
167 	    initDefaultStrategies();
168     }
169 
170     /**
171      * Creates a new <code>MarshallingMethodEndpointAdapter</code> with the given marshaller and unmarshaller.
172      *
173      * @param marshaller   the marshaller to use
174      * @param unmarshaller the unmarshaller to use
175      * @since 2.0.3
176      */
177     public WebServiceTemplate(Marshaller marshaller, Unmarshaller unmarshaller) {
178         Assert.notNull(marshaller, "marshaller must not be null");
179         Assert.notNull(unmarshaller, "unmarshaller must not be null");
180         this.setMarshaller(marshaller);
181         this.setUnmarshaller(unmarshaller);
182 	    initDefaultStrategies();
183     }
184 
185     /** Returns the default URI to be used on operations that do not have a URI parameter. */
186     public String getDefaultUri() {
187         if (destinationProvider != null) {
188             URI uri = destinationProvider.getDestination();
189             return uri != null ? uri.toString() : null;
190         }
191         else {
192             return null;
193         }
194     }
195 
196     /**
197      * Set the default URI to be used on operations that do not have a URI parameter.
198      * <p/>
199      * Typically, either this property is set, or {@link #setDestinationProvider(DestinationProvider)}, but not both.
200      *
201      * @see #marshalSendAndReceive(Object)
202      * @see #marshalSendAndReceive(Object,WebServiceMessageCallback)
203      * @see #sendSourceAndReceiveToResult(Source,Result)
204      * @see #sendSourceAndReceiveToResult(Source,WebServiceMessageCallback,Result)
205      * @see #sendSourceAndReceive(Source,SourceExtractor)
206      * @see #sendSourceAndReceive(Source,WebServiceMessageCallback,SourceExtractor)
207      * @see #sendAndReceive(WebServiceMessageCallback,WebServiceMessageCallback)
208      */
209     public void setDefaultUri(final String uri) {
210         destinationProvider = new DestinationProvider() {
211 
212             public URI getDestination() {
213                 return URI.create(uri);
214             }
215         };
216     }
217 
218     /** Returns the destination provider used on operations that do not have a URI parameter. */
219     public DestinationProvider getDestinationProvider() {
220         return destinationProvider;
221     }
222 
223     /**
224      * Set the destination provider URI to be used on operations that do not have a URI parameter.
225      * <p/>
226      * Typically, either this property is set, or {@link #setDefaultUri(String)}, but not both.
227      *
228      * @see #marshalSendAndReceive(Object)
229      * @see #marshalSendAndReceive(Object,WebServiceMessageCallback)
230      * @see #sendSourceAndReceiveToResult(Source,Result)
231      * @see #sendSourceAndReceiveToResult(Source,WebServiceMessageCallback,Result)
232      * @see #sendSourceAndReceive(Source,SourceExtractor)
233      * @see #sendSourceAndReceive(Source,WebServiceMessageCallback,SourceExtractor)
234      * @see #sendAndReceive(WebServiceMessageCallback,WebServiceMessageCallback)
235      */
236     public void setDestinationProvider(DestinationProvider destinationProvider) {
237         this.destinationProvider = destinationProvider;
238     }
239 
240     /** Returns the marshaller for this template. */
241     public Marshaller getMarshaller() {
242         return marshaller;
243     }
244 
245     /** Sets the marshaller for this template. */
246     public void setMarshaller(Marshaller marshaller) {
247         this.marshaller = marshaller;
248     }
249 
250     /** Returns the unmarshaller for this template. */
251     public Unmarshaller getUnmarshaller() {
252         return unmarshaller;
253     }
254 
255     /** Sets the unmarshaller for this template. */
256     public void setUnmarshaller(Unmarshaller unmarshaller) {
257         this.unmarshaller = unmarshaller;
258     }
259 
260     /** Returns the fault message resolver for this template. */
261     public FaultMessageResolver getFaultMessageResolver() {
262         return faultMessageResolver;
263     }
264 
265     /**
266      * Sets the fault resolver for this template. Default is the
267      * {@link org.springframework.ws.soap.client.core.SoapFaultMessageResolver, SoapFaultMessageResolver}, but may be
268      * set to {@code null} to disable fault handling.
269      */
270     public void setFaultMessageResolver(FaultMessageResolver faultMessageResolver) {
271         this.faultMessageResolver = faultMessageResolver;
272     }
273 
274     /**
275      * Indicates whether the {@linkplain WebServiceConnection#hasError() connection} should be checked for error
276      * indicators (<code>true</code>), or whether these should be ignored (<code>false</code>). The default is
277      * <code>true</code>.
278      * <p/>
279      * When using an HTTP transport, this property defines whether to check the HTTP response status code is in the 2xx
280      * Successful range. Both the SOAP specification and the WS-I Basic Profile define that a Web service must return a
281      * "200 OK" or "202 Accepted" HTTP status code for a normal response. Setting this property to <code>false</code>
282      * allows this template to deal with non-conforming services.
283      *
284      * @see #hasError(WebServiceConnection, WebServiceMessage)
285      * @see <a href="http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383529">SOAP 1.1 specification</a>
286      * @see <a href="http://www.ws-i.org/Profiles/BasicProfile-1.1.html#HTTP_Success_Status_Codes">WS-I Basic
287      *      Profile</a>
288      */
289     public void setCheckConnectionForError(boolean checkConnectionForError) {
290         this.checkConnectionForError = checkConnectionForError;
291     }
292 
293     /**
294      * Indicates whether the {@linkplain FaultAwareWebServiceConnection#hasFault() connection} should be checked for
295      * fault indicators (<code>true</code>), or whether we should rely on the {@link
296      * FaultAwareWebServiceMessage#hasFault() message} only (<code>false</code>). The default is <code>true</code>.
297      * <p/>
298      * When using an HTTP transport, this property defines whether to check the HTTP response status code for fault
299      * indicators. Both the SOAP specification and the WS-I Basic Profile define that a Web service must return a "500
300      * Internal Server Error" HTTP status code if the response envelope is a Fault. Setting this property to
301      * <code>false</code> allows this template to deal with non-conforming services.
302      *
303      * @see #hasFault(WebServiceConnection,WebServiceMessage)
304      * @see <a href="http://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383529">SOAP 1.1 specification</a>
305      * @see <a href="http://www.ws-i.org/Profiles/BasicProfile-1.1.html#HTTP_Server_Error_Status_Codes">WS-I Basic
306      *      Profile</a>
307      */
308     public void setCheckConnectionForFault(boolean checkConnectionForFault) {
309         this.checkConnectionForFault = checkConnectionForFault;
310     }
311 
312     /**
313      * Returns the client interceptors to apply to all web service invocations made by this template.
314      *
315      * @return array of endpoint interceptors, or <code>null</code> if none
316      */
317     public ClientInterceptor[] getInterceptors() {
318         return interceptors;
319     }
320 
321     /**
322      * Sets the client interceptors to apply to all web service invocations made by this template.
323      *
324      * @param interceptors array of endpoint interceptors, or <code>null</code> if none
325      */
326     public final void setInterceptors(ClientInterceptor[] interceptors) {
327         this.interceptors = interceptors;
328     }
329 
330     /**
331      * Initialize the default implementations for the template's strategies: {@link SoapFaultMessageResolver}, {@link
332      * org.springframework.ws.soap.saaj.SaajSoapMessageFactory}, and {@link HttpUrlConnectionMessageSender}.
333      *
334      * @throws BeanInitializationException in case of initalization errors
335      * @see #setFaultMessageResolver(FaultMessageResolver)
336      * @see #setMessageFactory(WebServiceMessageFactory)
337      * @see #setMessageSender(WebServiceMessageSender)
338      */
339     protected void initDefaultStrategies() {
340         DefaultStrategiesHelper strategiesHelper = new DefaultStrategiesHelper(WebServiceTemplate.class);
341         if (getMessageFactory() == null) {
342             initMessageFactory(strategiesHelper);
343         }
344         if (ObjectUtils.isEmpty(getMessageSenders())) {
345             initMessageSenders(strategiesHelper);
346         }
347         if (getFaultMessageResolver() == null) {
348             initFaultMessageResolver(strategiesHelper);
349         }
350     }
351 
352     private void initMessageFactory(DefaultStrategiesHelper helper) throws BeanInitializationException {
353         WebServiceMessageFactory messageFactory = helper.getDefaultStrategy(WebServiceMessageFactory.class);
354         setMessageFactory(messageFactory);
355     }
356 
357     private void initMessageSenders(DefaultStrategiesHelper helper) {
358         List<WebServiceMessageSender> messageSenders = helper.getDefaultStrategies(WebServiceMessageSender.class);
359         setMessageSenders(messageSenders.toArray(new WebServiceMessageSender[messageSenders.size()]));
360     }
361 
362     private void initFaultMessageResolver(DefaultStrategiesHelper helper) throws BeanInitializationException {
363         FaultMessageResolver faultMessageResolver = helper.getDefaultStrategy(FaultMessageResolver.class);
364         setFaultMessageResolver(faultMessageResolver);
365     }
366 
367     //
368     // Marshalling methods
369     //
370 
371     public Object marshalSendAndReceive(final Object requestPayload) {
372         return marshalSendAndReceive(requestPayload, null);
373     }
374 
375     public Object marshalSendAndReceive(String uri, final Object requestPayload) {
376         return marshalSendAndReceive(uri, requestPayload, null);
377     }
378 
379     public Object marshalSendAndReceive(final Object requestPayload, final WebServiceMessageCallback requestCallback) {
380         return marshalSendAndReceive(getDefaultUri(), requestPayload, requestCallback);
381     }
382 
383     public Object marshalSendAndReceive(String uri,
384                                         final Object requestPayload,
385                                         final WebServiceMessageCallback requestCallback) {
386         return sendAndReceive(uri, new WebServiceMessageCallback() {
387 
388             public void doWithMessage(WebServiceMessage request) throws IOException, TransformerException {
389                 if (requestPayload != null) {
390                     Marshaller marshaller = getMarshaller();
391                     if (marshaller == null) {
392                         throw new IllegalStateException(
393                                 "No marshaller registered. Check configuration of WebServiceTemplate.");
394                     }
395                     MarshallingUtils.marshal(marshaller, requestPayload, request);
396                     if (requestCallback != null) {
397                         requestCallback.doWithMessage(request);
398                     }
399                 }
400             }
401         }, new WebServiceMessageExtractor<Object>() {
402 
403             public Object extractData(WebServiceMessage response) throws IOException {
404                 Unmarshaller unmarshaller = getUnmarshaller();
405                 if (unmarshaller == null) {
406                     throw new IllegalStateException(
407                             "No unmarshaller registered. Check configuration of WebServiceTemplate.");
408                 }
409                 return MarshallingUtils.unmarshal(unmarshaller, response);
410             }
411         });
412     }
413 
414     //
415     // Result-handling methods
416     //
417 
418     public boolean sendSourceAndReceiveToResult(Source requestPayload, Result responseResult) {
419         return sendSourceAndReceiveToResult(requestPayload, null, responseResult);
420     }
421 
422     public boolean sendSourceAndReceiveToResult(String uri, Source requestPayload, Result responseResult) {
423         return sendSourceAndReceiveToResult(uri, requestPayload, null, responseResult);
424     }
425 
426     public boolean sendSourceAndReceiveToResult(Source requestPayload,
427                                                 WebServiceMessageCallback requestCallback,
428                                                 final Result responseResult) {
429         return sendSourceAndReceiveToResult(getDefaultUri(), requestPayload, requestCallback, responseResult);
430     }
431 
432     public boolean sendSourceAndReceiveToResult(String uri,
433                                                 Source requestPayload,
434                                                 WebServiceMessageCallback requestCallback,
435                                                 final Result responseResult) {
436         try {
437             final Transformer transformer = createTransformer();
438             Boolean retVal = doSendAndReceive(uri, transformer, requestPayload, requestCallback,
439                     new SourceExtractor<Boolean>() {
440 
441                         public Boolean extractData(Source source) throws IOException, TransformerException {
442                             if (source != null) {
443                                 transformer.transform(source, responseResult);
444                             }
445                             return Boolean.TRUE;
446                         }
447                     });
448             return retVal != null && retVal;
449         }
450         catch (TransformerConfigurationException ex) {
451             throw new WebServiceTransformerException("Could not create transformer", ex);
452         }
453     }
454 
455     //
456     // Source-handling methods
457     //
458 
459     public <T> T sendSourceAndReceive(final Source requestPayload, final SourceExtractor<T> responseExtractor) {
460         return sendSourceAndReceive(requestPayload, null, responseExtractor);
461     }
462 
463     public <T> T sendSourceAndReceive(String uri,
464                                        final Source requestPayload,
465                                        final SourceExtractor<T> responseExtractor) {
466         return sendSourceAndReceive(uri, requestPayload, null, responseExtractor);
467     }
468 
469     public <T> T sendSourceAndReceive(final Source requestPayload,
470                                        final WebServiceMessageCallback requestCallback,
471                                        final SourceExtractor<T> responseExtractor) {
472         return sendSourceAndReceive(getDefaultUri(), requestPayload, requestCallback, responseExtractor);
473     }
474 
475     public <T> T sendSourceAndReceive(String uri,
476                                        final Source requestPayload,
477                                        final WebServiceMessageCallback requestCallback,
478                                        final SourceExtractor<T> responseExtractor) {
479 
480         try {
481             return doSendAndReceive(uri, createTransformer(), requestPayload, requestCallback, responseExtractor);
482         }
483         catch (TransformerConfigurationException ex) {
484             throw new WebServiceTransformerException("Could not create transformer", ex);
485         }
486     }
487 
488     private <T> T doSendAndReceive(String uri,
489                                     final Transformer transformer,
490                                     final Source requestPayload,
491                                     final WebServiceMessageCallback requestCallback,
492                                     final SourceExtractor<T> responseExtractor) {
493         Assert.notNull(responseExtractor, "responseExtractor must not be null");
494         return sendAndReceive(uri, new WebServiceMessageCallback() {
495             public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
496                 transformer.transform(requestPayload, message.getPayloadResult());
497                 if (requestCallback != null) {
498                     requestCallback.doWithMessage(message);
499                 }
500             }
501         }, new SourceExtractorMessageExtractor<T>(responseExtractor));
502     }
503 
504     //
505     // WebServiceMessage-handling methods
506     //
507 
508     public boolean sendAndReceive(WebServiceMessageCallback requestCallback,
509                                   WebServiceMessageCallback responseCallback) {
510         return sendAndReceive(getDefaultUri(), requestCallback, responseCallback);
511     }
512 
513     public boolean sendAndReceive(String uri,
514                                   WebServiceMessageCallback requestCallback,
515                                   WebServiceMessageCallback responseCallback) {
516         Assert.notNull(responseCallback, "responseCallback must not be null");
517         Boolean result = sendAndReceive(uri, requestCallback,
518                 new WebServiceMessageCallbackMessageExtractor(responseCallback));
519         return result != null && result;
520     }
521 
522     public <T> T sendAndReceive(WebServiceMessageCallback requestCallback,
523                                  WebServiceMessageExtractor<T> responseExtractor) {
524         return sendAndReceive(getDefaultUri(), requestCallback, responseExtractor);
525     }
526 
527     public <T> T sendAndReceive(String uriString,
528                                  WebServiceMessageCallback requestCallback,
529                                  WebServiceMessageExtractor<T> responseExtractor) {
530         Assert.notNull(responseExtractor, "'responseExtractor' must not be null");
531         Assert.hasLength(uriString, "'uri' must not be empty");
532         TransportContext previousTransportContext = TransportContextHolder.getTransportContext();
533         WebServiceConnection connection = null;
534         try {
535             connection = createConnection(URI.create(uriString));
536             TransportContextHolder.setTransportContext(new DefaultTransportContext(connection));
537             MessageContext messageContext = new DefaultMessageContext(getMessageFactory());
538 
539             return doSendAndReceive(messageContext, connection, requestCallback, responseExtractor);
540         }
541         catch (TransportException ex) {
542             throw new WebServiceTransportException("Could not use transport: " + ex.getMessage(), ex);
543         }
544         catch (IOException ex) {
545             throw new WebServiceIOException("I/O error: " + ex.getMessage(), ex);
546         }
547         finally {
548             TransportUtils.closeConnection(connection);
549             TransportContextHolder.setTransportContext(previousTransportContext);
550         }
551     }
552 
553     /**
554      * Sends and receives a {@link MessageContext}. Sends the {@link MessageContext#getRequest() request message}, and
555      * received to the {@link MessageContext#getResponse() repsonse message}. Invocates the defined {@link
556      * #setInterceptors(ClientInterceptor[]) interceptors} as part of the process.
557      *
558      * @param messageContext    the message context
559      * @param connection        the connection to use
560      * @param requestCallback   the requestCallback to be used for manipulating the request message
561      * @param responseExtractor object that will extract results
562      * @return an arbitrary result object, as returned by the <code>WebServiceMessageExtractor</code>
563      * @throws WebServiceClientException if there is a problem sending or receiving the message
564      * @throws IOException               in case of I/O errors
565      */
566     @SuppressWarnings("unchecked")
567     protected <T> T doSendAndReceive(MessageContext messageContext,
568                                      WebServiceConnection connection,
569                                      WebServiceMessageCallback requestCallback,
570                                      WebServiceMessageExtractor<T> responseExtractor) throws IOException {
571         try {
572             if (requestCallback != null) {
573                 requestCallback.doWithMessage(messageContext.getRequest());
574             }
575             // Apply handleRequest of registered interceptors
576             int interceptorIndex = -1;
577             if (interceptors != null) {
578                 for (int i = 0; i < interceptors.length; i++) {
579                     interceptorIndex = i;
580                     if (!interceptors[i].handleRequest(messageContext)) {
581                         break;
582                     }
583                 }
584             }
585             // if an interceptor has set a response, we don't send/receive
586             if (!messageContext.hasResponse()) {
587                 sendRequest(connection, messageContext.getRequest());
588                 if (hasError(connection, messageContext.getRequest())) {
589                     return (T)handleError(connection, messageContext.getRequest());
590                 }
591                 WebServiceMessage response = connection.receive(getMessageFactory());
592                 messageContext.setResponse(response);
593             }
594             logResponse(messageContext);
595             if (messageContext.hasResponse()) {
596                 if (!hasFault(connection, messageContext.getResponse())) {
597                     triggerHandleResponse(interceptorIndex, messageContext);
598                     return responseExtractor.extractData(messageContext.getResponse());
599                 }
600                 else {
601                     triggerHandleFault(interceptorIndex, messageContext);
602                     return (T)handleFault(connection, messageContext);
603                 }
604             }
605             else {
606                 return null;
607             }
608         }
609         catch (TransformerException ex) {
610             throw new WebServiceTransformerException("Transformation error: " + ex.getMessage(), ex);
611         }
612     }
613 
614     /** Sends the request in the given message context over the connection. */
615     private void sendRequest(WebServiceConnection connection, WebServiceMessage request) throws IOException {
616         if (sentMessageTracingLogger.isTraceEnabled()) {
617             ByteArrayOutputStream os = new ByteArrayOutputStream();
618             request.writeTo(os);
619             sentMessageTracingLogger.trace("Sent request [" + os.toString("UTF-8") + "]");
620         }
621         else if (sentMessageTracingLogger.isDebugEnabled()) {
622             sentMessageTracingLogger.debug("Sent request [" + request + "]");
623         }
624         connection.send(request);
625     }
626 
627     /**
628      * Determines whether the given connection or message context has an error.
629      * <p/>
630      * This implementation checks the {@link WebServiceConnection#hasError() connection} first. If it indicates an
631      * error, it makes sure that it is not a {@link FaultAwareWebServiceConnection#hasFault() fault}.
632      *
633      * @param connection the connection (possibly a {@link FaultAwareWebServiceConnection}
634      * @param request    the response message (possibly a {@link FaultAwareWebServiceMessage}
635      * @return <code>true</code> if the connection has an error; <code>false</code> otherwise
636      * @throws IOException in case of I/O errors
637      */
638     protected boolean hasError(WebServiceConnection connection, WebServiceMessage request) throws IOException {
639         if (checkConnectionForError && connection.hasError()) {
640             // could be a fault
641             if (checkConnectionForFault && connection instanceof FaultAwareWebServiceConnection) {
642                 FaultAwareWebServiceConnection faultConnection = (FaultAwareWebServiceConnection) connection;
643                 return !(faultConnection.hasFault() && request instanceof FaultAwareWebServiceMessage);
644             }
645             else {
646                 return true;
647             }
648         }
649         return false;
650     }
651 
652     /**
653      * Handles an error on the given connection. The default implementation throws a {@link
654      * WebServiceTransportException}.
655      *
656      * @param connection the erroneous connection
657      * @param request    the corresponding request message
658      * @return the object to be returned from {@link #sendAndReceive(String,WebServiceMessageCallback,
659      *         WebServiceMessageExtractor)}, if any
660      */
661     protected Object handleError(WebServiceConnection connection, WebServiceMessage request) throws IOException {
662         if (logger.isDebugEnabled()) {
663             logger.debug("Received error for request [" + request + "]");
664         }
665         throw new WebServiceTransportException(connection.getErrorMessage());
666     }
667 
668     private void logResponse(MessageContext messageContext) throws IOException {
669         if (messageContext.hasResponse()) {
670             if (receivedMessageTracingLogger.isTraceEnabled()) {
671                 ByteArrayOutputStream requestStream = new ByteArrayOutputStream();
672                 messageContext.getRequest().writeTo(requestStream);
673                 ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
674                 messageContext.getResponse().writeTo(responseStream);
675                 receivedMessageTracingLogger
676                         .trace("Received response [" + responseStream.toString("UTF-8") + "] for request [" +
677                                 requestStream.toString("UTF-8") + "]");
678             }
679             else if (receivedMessageTracingLogger.isDebugEnabled()) {
680                 receivedMessageTracingLogger
681                         .debug("Received response [" + messageContext.getResponse() + "] for request [" +
682                                 messageContext.getRequest() + "]");
683             }
684         }
685         else {
686             if (logger.isDebugEnabled()) {
687                 receivedMessageTracingLogger
688                         .debug("Received no response for request [" + messageContext.getRequest() + "]");
689             }
690         }
691     }
692 
693     /**
694      * Determines whether the given connection or message has a fault.
695      * <p/>
696      * This implementation checks the {@link FaultAwareWebServiceConnection#hasFault() connection} if the {@link
697      * #setCheckConnectionForFault(boolean) checkConnectionForFault} property is true, and defaults to the {@link
698      * FaultAwareWebServiceMessage#hasFault() message} otherwise.
699      *
700      * @param connection the connection (possibly a {@link FaultAwareWebServiceConnection}
701      * @param response   the response message (possibly a {@link FaultAwareWebServiceMessage}
702      * @return <code>true</code> if either the connection or the message has a fault; <code>false</code> otherwise
703      * @throws IOException in case of I/O errors
704      */
705     protected boolean hasFault(WebServiceConnection connection, WebServiceMessage response) throws IOException {
706         if (checkConnectionForFault && connection instanceof FaultAwareWebServiceConnection) {
707             // check whether the connection has a fault (i.e. status code 500 in HTTP)
708             FaultAwareWebServiceConnection faultConnection = (FaultAwareWebServiceConnection) connection;
709             if (!faultConnection.hasFault()) {
710                 return false;
711             }
712         }
713         if (response instanceof FaultAwareWebServiceMessage) {
714             // either the connection has a fault, or checkConnectionForFault is false: let's verify the fault
715             FaultAwareWebServiceMessage faultMessage = (FaultAwareWebServiceMessage) response;
716             return faultMessage.hasFault();
717         }
718         return false;
719     }
720 
721     /**
722      * Trigger handleResponse on the defined ClientInterceptors. Will just invoke said method on all interceptors whose
723      * handleRequest invocation returned <code>true</code>, in addition to the last interceptor who returned
724      * <code>false</code>.
725      *
726      * @param interceptorIndex index of last interceptor that was called
727      * @param messageContext   the message context, whose request and response are filled
728      * @see ClientInterceptor#handleResponse(MessageContext)
729      * @see ClientInterceptor#handleFault(MessageContext)
730      */
731     private void triggerHandleResponse(int interceptorIndex, MessageContext messageContext) {
732         if (messageContext.hasResponse() && interceptors != null) {
733             for (int i = interceptorIndex; i >= 0; i--) {
734                 if (!interceptors[i].handleResponse(messageContext)) {
735                     break;
736                 }
737             }
738         }
739     }
740 
741     /**
742      * Trigger handleFault on the defined ClientInterceptors. Will just invoke said method on all interceptors whose
743      * handleRequest invocation returned <code>true</code>, in addition to the last interceptor who returned
744      * <code>false</code>.
745      *
746      * @param interceptorIndex index of last interceptor that was called
747      * @param messageContext   the message context, whose request and response are filled
748      * @see ClientInterceptor#handleResponse(MessageContext)
749      * @see ClientInterceptor#handleFault(MessageContext)
750      */
751     private void triggerHandleFault(int interceptorIndex, MessageContext messageContext) {
752         if (messageContext.hasResponse() && interceptors != null) {
753             for (int i = interceptorIndex; i >= 0; i--) {
754                 if (!interceptors[i].handleFault(messageContext)) {
755                     break;
756                 }
757             }
758         }
759     }
760 
761     /**
762      * Handles an fault in the given response message. The default implementation invokes the {@link
763      * FaultMessageResolver fault resolver} if registered, or invokes {@link #handleError(WebServiceConnection,
764      * WebServiceMessage)} otherwise.
765      *
766      * @param connection     the faulty connection
767      * @param messageContext the message context
768      * @return the object to be returned from {@link #sendAndReceive(String,WebServiceMessageCallback,
769      *         WebServiceMessageExtractor)}, if any
770      */
771     protected Object handleFault(WebServiceConnection connection, MessageContext messageContext) throws IOException {
772         if (logger.isDebugEnabled()) {
773             logger.debug("Received Fault message for request [" + messageContext.getRequest() + "]");
774         }
775         if (getFaultMessageResolver() != null) {
776             getFaultMessageResolver().resolveFault(messageContext.getResponse());
777             return null;
778         }
779         else {
780             return handleError(connection, messageContext.getRequest());
781         }
782     }
783 
784     /** Adapter to enable use of a WebServiceMessageCallback inside a WebServiceMessageExtractor. */
785     private static class WebServiceMessageCallbackMessageExtractor implements WebServiceMessageExtractor<Boolean> {
786 
787         private final WebServiceMessageCallback callback;
788 
789         private WebServiceMessageCallbackMessageExtractor(WebServiceMessageCallback callback) {
790             this.callback = callback;
791         }
792 
793         public Boolean extractData(WebServiceMessage message) throws IOException, TransformerException {
794             callback.doWithMessage(message);
795             return Boolean.TRUE;
796         }
797     }
798 
799     /** Adapter to enable use of a SourceExtractor inside a WebServiceMessageExtractor. */
800     private static class SourceExtractorMessageExtractor<T> implements WebServiceMessageExtractor<T> {
801 
802         private final SourceExtractor<T> sourceExtractor;
803 
804         private SourceExtractorMessageExtractor(SourceExtractor<T> sourceExtractor) {
805             this.sourceExtractor = sourceExtractor;
806         }
807 
808         public T extractData(WebServiceMessage message) throws IOException, TransformerException {
809             return sourceExtractor.extractData(message.getPayloadSource());
810         }
811     }
812 
813 }