All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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