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