View Javadoc

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