/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ws;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.TransformerException;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.FaultMessageResolver;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.WebServiceMessageExtractor;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.client.support.destination.DestinationProvider;
import org.springframework.ws.client.support.interceptor.ClientInterceptor;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.transport.WebServiceMessageSender;
import org.springframework.xml.transform.TransformerObjectSupport;
/**
* Base class for outbound Web Service-invoking Messaging Gateways.
*
* @author Mark Fisher
* @author Jonas Partner
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public abstract class AbstractWebServiceOutboundGateway extends AbstractReplyProducingMessageHandler {
private final String uri;
private final DestinationProvider destinationProvider;
private final Map<String, Expression> uriVariableExpressions = new HashMap<String, Expression>();
private volatile StandardEvaluationContext evaluationContext;
private volatile WebServiceMessageCallback requestCallback;
private WebServiceTemplate webServiceTemplate;
private volatile boolean ignoreEmptyResponses = true;
private volatile boolean encodeUri = true;
protected volatile SoapHeaderMapper headerMapper = new DefaultSoapHeaderMapper();
private boolean webServiceTemplateExplicitlySet;
public AbstractWebServiceOutboundGateway(final String uri, WebServiceMessageFactory messageFactory) {
Assert.hasText(uri, "URI must not be empty");
this.webServiceTemplate = new WebServiceTemplate(messageFactory);
this.destinationProvider = null;
this.uri = uri;
}
public AbstractWebServiceOutboundGateway(DestinationProvider destinationProvider,
WebServiceMessageFactory messageFactory) {
Assert.notNull(destinationProvider, "DestinationProvider must not be null");
this.webServiceTemplate = new WebServiceTemplate(messageFactory);
this.destinationProvider = destinationProvider;
// we always call WebServiceTemplate methods with an explicit URI argument,
// but in case the WebServiceTemplate is accessed directly we'll set this:
this.webServiceTemplate.setDestinationProvider(destinationProvider);
this.uri = null;
}
public void setHeaderMapper(SoapHeaderMapper headerMapper) {
this.headerMapper = headerMapper;
}
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
* @param uriVariableExpressions The URI variable expressions.
*/
public void setUriVariableExpressions(Map<String, Expression> uriVariableExpressions) {
synchronized (this.uriVariableExpressions) {
this.uriVariableExpressions.clear();
this.uriVariableExpressions.putAll(uriVariableExpressions);
}
}
/**
* Specify whether the URI should be encoded after any <code>uriVariables</code>
* are expanded and before sending the request. The default value is <code>true</code>.
* @param encodeUri true if the URI should be encoded.
* @see org.springframework.web.util.UriComponentsBuilder
* @since 4.1
*/
public void setEncodeUri(boolean encodeUri) {
this.encodeUri = encodeUri;
}
public void setReplyChannel(MessageChannel replyChannel) {
this.setOutputChannel(replyChannel);
}
/**
* Specify whether empty String response payloads should be ignored.
* The default is <code>true</code>. Set this to <code>false</code> if
* you want to send empty String responses in reply Messages.
* @param ignoreEmptyResponses true if empty responses should be ignored.
*/
public void setIgnoreEmptyResponses(boolean ignoreEmptyResponses) {
this.ignoreEmptyResponses = ignoreEmptyResponses;
}
public void setWebServiceTemplate(WebServiceTemplate webServiceTemplate) {
doSetWebServiceTemplate(webServiceTemplate);
}
protected final void doSetWebServiceTemplate(WebServiceTemplate webServiceTemplate) {
Assert.notNull(webServiceTemplate, "'webServiceTemplate' must not be null");
this.webServiceTemplate = webServiceTemplate;
this.webServiceTemplateExplicitlySet = true;
}
public void setMessageFactory(WebServiceMessageFactory messageFactory) {
Assert.state(!this.webServiceTemplateExplicitlySet,
() -> "'messageFactory' must be specified on the provided: " + this.webServiceTemplate);
this.webServiceTemplate.setMessageFactory(messageFactory);
}
public void setRequestCallback(WebServiceMessageCallback requestCallback) {
this.requestCallback = requestCallback;
}
public void setFaultMessageResolver(FaultMessageResolver faultMessageResolver) {
Assert.state(!this.webServiceTemplateExplicitlySet,
() -> "'faultMessageResolver' must be specified on the provided: " + this.webServiceTemplate);
this.webServiceTemplate.setFaultMessageResolver(faultMessageResolver);
}
public void setMessageSender(WebServiceMessageSender messageSender) {
Assert.state(!this.webServiceTemplateExplicitlySet,
() -> "'messageSender' must be specified on the provided: " + this.webServiceTemplate);
this.webServiceTemplate.setMessageSender(messageSender);
}
public void setMessageSenders(WebServiceMessageSender... messageSenders) {
Assert.state(!this.webServiceTemplateExplicitlySet,
() -> "'messageSenders' must be specified on the provided: " + this.webServiceTemplate);
this.webServiceTemplate.setMessageSenders(messageSenders);
}
public void setInterceptors(ClientInterceptor... interceptors) {
Assert.state(!this.webServiceTemplateExplicitlySet,
() -> "'interceptors' must be specified on the provided: " + this.webServiceTemplate);
this.webServiceTemplate.setInterceptors(interceptors);
}
@Override
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
Assert.state(this.destinationProvider == null || CollectionUtils.isEmpty(this.uriVariableExpressions),
"uri variables are not supported when a DestinationProvider is supplied.");
}
protected WebServiceTemplate getWebServiceTemplate() {
return this.webServiceTemplate;
}
@Override
public final Object handleRequestMessage(Message<?> requestMessage) {
URI uri = null;
try {
uri = this.prepareUri(requestMessage);
}
catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
if (uri == null) {
throw new MessageDeliveryException(requestMessage, "Failed to determine URI for " +
"Web Service request in outbound gateway: " + this.getComponentName());
}
Object responsePayload = this.doHandle(uri.toString(), requestMessage, this.requestCallback);
if (responsePayload != null) {
boolean shouldIgnore = (this.ignoreEmptyResponses
&& responsePayload instanceof String && !StringUtils.hasText((String) responsePayload));
if (!shouldIgnore) {
return responsePayload;
}
}
return null;
}
private URI prepareUri(Message<?> requestMessage) throws URISyntaxException {
if (this.destinationProvider != null) {
return this.destinationProvider.getDestination();
}
Map<String, Object> uriVariables = ExpressionEvalMap.from(this.uriVariableExpressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(this.uri).buildAndExpand(uriVariables);
return this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
}
protected abstract Object doHandle(String uri, Message<?> requestMessage,
WebServiceMessageCallback requestCallback);
protected abstract class RequestMessageCallback extends TransformerObjectSupport
implements WebServiceMessageCallback {
private final WebServiceMessageCallback requestCallback;
private final Message<?> requestMessage;
public RequestMessageCallback(WebServiceMessageCallback requestCallback, Message<?> requestMessage) {
this.requestCallback = requestCallback;
this.requestMessage = requestMessage;
}
@Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
Object payload = this.requestMessage.getPayload();
if (message instanceof SoapMessage) {
this.doWithMessageInternal(message, payload);
AbstractWebServiceOutboundGateway.this.headerMapper
.fromHeadersToRequest(this.requestMessage.getHeaders(), (SoapMessage) message);
if (this.requestCallback != null) {
this.requestCallback.doWithMessage(message);
}
}
}
public abstract void doWithMessageInternal(WebServiceMessage message, Object payload)
throws IOException, TransformerException;
}
protected abstract class ResponseMessageExtractor extends TransformerObjectSupport
implements WebServiceMessageExtractor<Object> {
@Override
public Object extractData(WebServiceMessage message)
throws IOException, TransformerException {
Object resultObject = this.doExtractData(message);
if (resultObject != null && message instanceof SoapMessage) {
Map<String, Object> mappedMessageHeaders =
AbstractWebServiceOutboundGateway.this.headerMapper.toHeadersFromReply((SoapMessage) message);
return AbstractWebServiceOutboundGateway.this.getMessageBuilderFactory()
.withPayload(resultObject)
.copyHeaders(mappedMessageHeaders)
.build();
}
else {
return resultObject;
}
}
public abstract Object doExtractData(WebServiceMessage message) throws IOException, TransformerException;
}
}