org.mule.service.soap.client.SoapCxfClient Maven / Gradle / Ivy
/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.service.soap.client;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.builder.ToStringBuilder.reflectionToString;
import static org.apache.cxf.message.Message.ENCODING;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.disposeIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.startIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.stopIfNeeded;
import static org.mule.runtime.core.api.util.IOUtils.toDataHandler;
import static org.mule.service.soap.util.XmlTransformationUtils.stringToDomElement;
import org.mule.metadata.api.TypeLoader;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.util.Preconditions;
import org.mule.runtime.extension.api.soap.SoapAttachment;
import org.mule.runtime.extension.api.soap.message.MessageDispatcher;
import org.mule.runtime.soap.api.SoapVersion;
import org.mule.runtime.soap.api.client.SoapClient;
import org.mule.runtime.soap.api.client.metadata.SoapMetadataResolver;
import org.mule.runtime.soap.api.exception.BadRequestException;
import org.mule.runtime.soap.api.exception.DispatchingException;
import org.mule.runtime.soap.api.exception.SoapFaultException;
import org.mule.runtime.soap.api.exception.SoapServiceException;
import org.mule.runtime.soap.api.message.SoapRequest;
import org.mule.runtime.soap.api.message.SoapResponse;
import org.mule.service.soap.generator.SoapRequestGenerator;
import org.mule.service.soap.generator.SoapResponseGenerator;
import org.mule.service.soap.generator.attachment.AttachmentRequestEnricher;
import org.mule.service.soap.generator.attachment.AttachmentResponseEnricher;
import org.mule.service.soap.generator.attachment.MtomRequestEnricher;
import org.mule.service.soap.generator.attachment.MtomResponseEnricher;
import org.mule.service.soap.generator.attachment.SoapAttachmentRequestEnricher;
import org.mule.service.soap.generator.attachment.SoapAttachmentResponseEnricher;
import org.mule.service.soap.metadata.DefaultSoapMetadataResolver;
import org.mule.service.soap.util.XmlTransformationException;
import org.mule.service.soap.util.XmlTransformationUtils;
import org.mule.wsdl.parser.exception.OperationNotFoundException;
import org.mule.wsdl.parser.model.PortModel;
import org.mule.wsdl.parser.model.WsdlModel;
import org.mule.wsdl.parser.model.operation.OperationModel;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.attachment.AttachmentImpl;
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Attachment;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
/**
* a {@link SoapClient} implementation based on CXF.
*
* @since 1.0
*/
public class SoapCxfClient implements SoapClient {
private static final Logger LOGGER = LoggerFactory.getLogger(SoapCxfClient.class);
public static final String MESSAGE_DISPATCHER = "mule.soap.dispatcher";
public static final String MULE_ATTACHMENTS_KEY = "mule.soap.attachments";
public static final String MULE_WSC_ADDRESS = "mule.soap.address";
public static final String MULE_HEADERS_KEY = "mule.soap.headers";
public static final String MULE_TRANSPORT_HEADERS_KEY = "mule.soap.transport.headers";
public static final String MULE_SOAP_ACTION = "mule.soap.action";
public static final String MULE_SOAP_OPERATION_STYLE = "mule.soap.operation.type";
private final SoapRequestGenerator requestGenerator;
private final SoapResponseGenerator responseGenerator;
private final Client client;
private final WsdlModel wsdlModel;
private final PortModel port;
private final TypeLoader loader;
private final String address;
private final MessageDispatcher defaultDispatcher;
private final SoapVersion version;
private final String encoding;
private final boolean isMtom;
SoapCxfClient(Client client,
WsdlModel wsdlModel,
PortModel portModel,
String address,
MessageDispatcher dispatcher,
SoapVersion version,
String encoding,
boolean isMtom) {
this.client = client;
this.wsdlModel = wsdlModel;
this.port = portModel;
this.loader = wsdlModel.getLoader().getValue();
this.address = address;
this.defaultDispatcher = dispatcher;
this.version = version;
this.isMtom = isMtom;
this.encoding = encoding;
// TODO: MULE-10889 -> instead of creating this enrichers, interceptors that works with the live stream would be ideal
this.requestGenerator = new SoapRequestGenerator(getRequestEnricher(isMtom), portModel, loader);
this.responseGenerator = new SoapResponseGenerator(getResponseEnricher(isMtom));
}
@Override
public SoapResponse consume(SoapRequest request) {
return consume(request, defaultDispatcher);
}
@Override
public SoapResponse consume(SoapRequest request, MessageDispatcher dispatcher) {
requireNonNull(dispatcher, "Message Dispatcher cannot be null");
String operation = request.getOperation();
Exchange exchange = new ExchangeImpl();
Object[] response = invoke(request, exchange, dispatcher);
return responseGenerator.generate(operation, response, exchange);
}
@Override
public void stop() throws MuleException {
disposeIfNeeded(defaultDispatcher, LOGGER);
stopIfNeeded(defaultDispatcher);
client.destroy();
}
@Override
public void start() throws MuleException {
initialiseIfNeeded(defaultDispatcher);
startIfNeeded(defaultDispatcher);
}
@Override
public SoapMetadataResolver getMetadataResolver() {
return new DefaultSoapMetadataResolver(wsdlModel, port, loader);
}
private Object[] invoke(SoapRequest request, Exchange exchange, MessageDispatcher dispatcher) {
String operation = request.getOperation();
XMLStreamReader xmlBody = getXmlBody(request);
try {
Map ctx = getInvocationContext(request, dispatcher);
return client.invoke(getInvocationOperation(), new Object[] {xmlBody}, ctx, exchange);
} catch (SoapFault sf) {
throw new SoapFaultException(sf.getFaultCode(), sf.getSubCode(), parseExceptionDetail(sf.getDetail()).orElse(null),
sf.getReason(), sf.getNode(), sf.getRole(), sf);
} catch (Fault f) {
if (f.getMessage().contains("COULD_NOT_READ_XML")) {
throw new BadRequestException("Error consuming the operation [" + operation + "], the request body is not a valid XML");
}
throw new SoapFaultException(f.getFaultCode(), parseExceptionDetail(f.getDetail()).orElse(null), f);
} catch (DispatchingException e) {
throw e;
} catch (OperationNotFoundException e) {
String location = wsdlModel.getLocation();
throw new BadRequestException("The provided [" + operation + "] does not exist in the WSDL file [" + location + "]", e);
} catch (Exception e) {
throw new SoapServiceException("Unexpected error while consuming the web service operation [" + operation + "]", e);
}
}
private XMLStreamReader getXmlBody(SoapRequest request) {
try {
String xml = request.getContent() != null ? IOUtils.toString(request.getContent()) : null;
return requestGenerator.generate(request.getOperation(), xml, request.getAttachments());
} catch (IOException e) {
throw new BadRequestException("an error occurred while parsing the provided request");
}
}
private BindingOperationInfo getInvocationOperation() throws Exception {
// Normally its not this hard to invoke the CXF Client, but we're
// sending along some exchange properties, so we need to use a more advanced
// method
Endpoint ep = client.getEndpoint();
// The operation is always named invoke because hits our ProxyService implementation.
QName q = new QName(ep.getService().getName().getNamespaceURI(), "invoke");
BindingOperationInfo bop = ep.getBinding().getBindingInfo().getOperation(q);
if (bop.isUnwrappedCapable()) {
bop = bop.getUnwrappedOperation();
}
return bop;
}
private Map getInvocationContext(SoapRequest request,
MessageDispatcher dispatcher) {
Map props = new HashMap<>();
OperationModel operation = port.getOperation(request.getOperation());
// is NOT mtom the attachments must not be touched by cxf, we create a custom request embedding the attachment in the xml
props.put(MULE_ATTACHMENTS_KEY, isMtom ? transformToCxfAttachments(request.getAttachments()) : emptyMap());
props.put(MULE_WSC_ADDRESS, address);
props.put(ENCODING, encoding == null ? "UTF-8" : encoding);
props.put(MULE_HEADERS_KEY, transformToCxfHeaders(request.getSoapHeaders()));
props.put(MULE_TRANSPORT_HEADERS_KEY, request.getTransportHeaders());
props.put(MESSAGE_DISPATCHER, dispatcher);
props.put(MULE_SOAP_OPERATION_STYLE, port.getOperation(request.getOperation()).getType());
operation.getSoapAction().ifPresent(action -> props.put(MULE_SOAP_ACTION, action));
Map ctx = new HashMap<>();
ctx.put(Client.REQUEST_CONTEXT, props);
return ctx;
}
private List transformToCxfHeaders(Map headers) {
if (headers == null) {
return emptyList();
}
return headers.entrySet().stream()
.map(header -> {
try {
return new SoapHeader(new QName(null, header.getKey()), stringToDomElement(header.getValue()));
} catch (Exception e) {
throw new BadRequestException("Cannot parse input header [" + header.getKey() + "]", e);
}
})
.collect(toList());
}
private Map transformToCxfAttachments(Map attachments) {
ImmutableMap.Builder builder = ImmutableMap.builder();
attachments.forEach((name, value) -> {
try {
builder.put(name, new AttachmentImpl(name, toDataHandler(name, value.getContent(), value.getContentType())));
} catch (IOException e) {
throw new BadRequestException(format("Error while preparing attachment [%s] for upload", name), e);
}
});
return builder.build();
}
private AttachmentRequestEnricher getRequestEnricher(boolean isMtom) {
return isMtom ? new MtomRequestEnricher(loader) : new SoapAttachmentRequestEnricher(loader);
}
private AttachmentResponseEnricher getResponseEnricher(boolean isMtom) {
return isMtom ? new MtomResponseEnricher(loader, port.getOperationsMap())
: new SoapAttachmentResponseEnricher(loader, port.getOperationsMap());
}
private Optional parseExceptionDetail(Element detail) {
try {
return ofNullable(XmlTransformationUtils.nodeToString(detail));
} catch (XmlTransformationException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Error while parsing Soap Exception detail: " + detail.toString(), e);
}
return empty();
}
}
@Override
public String toString() {
return reflectionToString(this);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy