org.ow2.petals.binding.soap.listener.outgoing.SOAPCaller Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of petals-bc-soap Show documentation
Show all versions of petals-bc-soap Show documentation
The PEtALS SOAP JBI binding component based on Axis2 and Jetty.
The newest version!
/**
* Copyright (c) 2007-2012 EBM WebSourcing, 2012-2023 Linagora
*
* This program/library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at your
* option) any later version.
*
* This program/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program/library; If not, see http://www.gnu.org/licenses/
* for the GNU Lesser General Public License version 2.1.
*/
package org.ow2.petals.binding.soap.listener.outgoing;
import static javax.jbi.messaging.NormalizedMessageProperties.PROTOCOL_HEADERS;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.xml.namespace.QName;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPFault;
import org.apache.axiom.soap.SOAPFaultDetail;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.util.XMLUtils;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.Description;
import org.ow2.easywsdl.wsdl.api.Binding;
import org.ow2.easywsdl.wsdl.api.BindingOperation;
import org.ow2.easywsdl.wsdl.api.Endpoint;
import org.ow2.petals.binding.soap.ServiceContext;
import org.ow2.petals.binding.soap.SoapComponentContext;
import org.ow2.petals.binding.soap.SoapProvideExtFlowStepBeginLogData;
import org.ow2.petals.binding.soap.addressing.Addressing;
import org.ow2.petals.binding.soap.addressing.WSAHelper;
import org.ow2.petals.binding.soap.exception.ServiceClientPoolExhaustedException;
import org.ow2.petals.binding.soap.util.Marshaller;
import org.ow2.petals.binding.soap.util.SUPropertiesHelper;
import org.ow2.petals.commons.log.FlowAttributes;
import org.ow2.petals.commons.log.Level;
import org.ow2.petals.commons.log.PetalsExecutionContext;
import org.ow2.petals.component.framework.api.configuration.SuConfigurationParameters;
import org.ow2.petals.component.framework.api.message.Exchange;
import org.ow2.petals.component.framework.jbidescriptor.generated.Provides;
import org.ow2.petals.component.framework.logger.ProvideExtFlowStepEndLogData;
import org.ow2.petals.component.framework.logger.ProvideExtFlowStepFailureLogData;
import org.ow2.petals.component.framework.logger.StepLogHelper;
import org.ow2.petals.probes.api.enums.ExecutionStatus;
import org.ow2.petals.probes.api.exceptions.ProbeException;
import org.ow2.petals.probes.api.exceptions.ProbeKeyMissingException;
import org.ow2.petals.probes.api.exceptions.ProbeNotStartedException;
import org.ow2.petals.probes.api.exceptions.ResponseTimeCollectionStoppedException;
import org.ow2.petals.probes.api.exceptions.StartDateItemUnknownException;
import org.ow2.petals.probes.api.probes.KeyedStartDateItem;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* An external web service dispatcher.
*
* This dispatcher send the JBI message to an external web service. The service URL is specified in the Petals
* extensions.
*
*
* @author Christophe Hamerling - EBM WebSourcing
*/
public class SOAPCaller {
private final SoapComponentContext soapContext;
/**
* Creates a new instance of {@link SOAPCaller}
*
* @param soapContext
*/
public SOAPCaller(final SoapComponentContext soapContext) {
this.soapContext = soapContext;
}
public void call(final Exchange exchange, final Provides provides) {
final ServiceContext context = soapContext.getProvidersManager().getServiceContext(provides);
// Get the incoming Normalized message
final NormalizedMessage in = exchange.getInMessage();
if (in == null) {
exchange.setError(new MessagingException("Message exchange must have an In normalized message"));
} else {
final SuConfigurationParameters cdkExtensions = context.getExtensions();
// get address to send to
final Addressing addressing = WSAHelper.getAddressing(cdkExtensions);
final String address = addressing.getTo();
if (address == null) {
final String message = "Cannot resolve the Web service address to send the message to";
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, message);
}
exchange.setError(new MessagingException("BC-SOAP Exception => " + message));
return;
}
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "Calling external Web Service : " + address);
}
// Get operation
final QName jbiOperation = exchange.getOperation();
// Trying to determine the value of the soapAction parameter to set on the outgoing message
// first : the soapAction has been set in the jbi descriptor
String suSoapAction = SUPropertiesHelper.retrieveDefaultSOAPAction(cdkExtensions);
// or instead, try to retrieve it from the WSDL, based on the first element of the message
final String soapAction = suSoapAction == null ? retrieveSoapActionFromWsdl(exchange, context)
: suSoapAction;
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().fine("jbiOperation of the received exchange: " + jbiOperation);
context.getLogger().fine("soapAction of the received exchange: " + soapAction);
}
// create service client used to invoke WS
try {
// The service client options are set during its creation
final ServiceClient serviceClient = soapContext.borrowServiceClient(address, jbiOperation, soapAction,
exchange.getPattern(), context);
// add addressing information in the options
updateAddressingOptions(serviceClient.getOptions(), addressing);
try {
// create the in body
final OMElement inBodyElement = Marshaller.createSOAPBodyContent(in, serviceClient);
addHeadersToServiceClient(in, cdkExtensions, serviceClient);
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "OUTGOING Payload : " + inBodyElement);
}
final FlowAttributes prev = PetalsExecutionContext.getFlowAttributes();
final FlowAttributes fa = PetalsExecutionContext.nextFlowStepId();
this.soapContext.getComponent().logMonitTrace(exchange, new SoapProvideExtFlowStepBeginLogData(
fa.getFlowInstanceId(), prev.getFlowStepId(), fa.getFlowStepId(), address));
final ServiceClientKey probeKey = new ServiceClientKey(address,
jbiOperation == null ? soapAction : jbiOperation.toString(), exchange.getPattern());
KeyedStartDateItem startDateItem;
try {
this.soapContext.getOutgoingProbes().probeWsRequestsInvocationsCount.incPending(probeKey);
startDateItem = this.soapContext.getOutgoingProbes().probeWsClientInvocationsResponseTime
.newExecution(probeKey);
} catch (final ProbeException e) {
context.getLogger().log(Level.WARNING, "The WS probes seems to have a problem.", e);
startDateItem = null;
}
final boolean succeeded;
try {
if (exchange.isInOnlyPattern()) {
// send as InOnly message: we use sendRobust because if an error happen
// on the server side, we still want to know about it (i.e. a not Done exchange status)
// sendRobust is NOT JBI RobustInOnly. JBI RobustInOnly is more like a SOAP InOut.
// also, we use sendRobust because we want to block until the end of the send before
// returning the serviceClient!
serviceClient.sendRobust(jbiOperation, inBodyElement);
succeeded = true;
} else {
final MessageContext response = ((PetalsServiceClient) serviceClient)
.sendReceive2(jbiOperation, inBodyElement);
// in RobustInOnly, if we didn't receive a fault, then we don't care
if (exchange.isRobustInOnlyPattern() && (response == null || !response.isFault())) {
succeeded = true;
} else {
succeeded = processResponse(exchange, cdkExtensions, context, probeKey, startDateItem,
response, fa);
}
}
// if not, handled in processResponse
if (succeeded) {
this.soapContext.getComponent().logMonitTrace(exchange,
new ProvideExtFlowStepEndLogData(fa.getFlowInstanceId(), fa.getFlowStepId()));
try {
this.soapContext.getOutgoingProbes().probeWsRequestsInvocationsCount.move(probeKey,
ExecutionStatus.SUCCEEDED);
if (startDateItem != null) {
this.soapContext.getOutgoingProbes().probeWsClientInvocationsResponseTime
.endsExecution(startDateItem, ExecutionStatus.SUCCEEDED);
}
} catch (final ProbeException e) {
context.getLogger().log(Level.WARNING, "The WS probes seems to have a problem.", e);
}
}
} catch (final AxisFault e) {
this.soapContext.getComponent()
.logMonitTrace(exchange, new ProvideExtFlowStepFailureLogData(fa.getFlowInstanceId(),
fa.getFlowStepId(),
String.format(StepLogHelper.TECHNICAL_ERROR_MESSAGE_PATTERN, e.getMessage())));
try {
this.soapContext.getOutgoingProbes().probeWsRequestsInvocationsCount.move(probeKey,
ExecutionStatus.ERROR);
if (startDateItem != null) {
this.soapContext.getOutgoingProbes().probeWsClientInvocationsResponseTime
.endsExecution(startDateItem, ExecutionStatus.ERROR);
}
} catch (final ProbeException e1) {
context.getLogger().log(Level.WARNING, "The WS probes seems to have a problem.", e1);
}
throw e;
}
} finally {
try {
soapContext.returnServiceClient(serviceClient);
} catch (final MessagingException e) {
// let's not throw an exception in a finally, that's dangerous
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, "Can't return the service client to the pool", e);
}
}
}
} catch (final ServiceClientPoolExhaustedException e) {
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, e.getMessage());
}
exchange.setError(new MessagingException(e.getMessage()));
} catch (final Exception e) {
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, "Exception on the WS invocation", e);
}
exchange.setError(e);
}
}
}
/**
* Update the client properties
*
* @param options
* @param addressing
*/
protected void updateAddressingOptions(final Options options, final Addressing addressing) {
// update the WS-Addressing properties. No need to update the wsa:To
// since this value has been used to create the client from the pool
// factory.
if (addressing.getFaultTo() != null) {
options.setFaultTo(new EndpointReference(addressing.getFaultTo()));
}
if (addressing.getFrom() != null) {
options.setFrom(new EndpointReference(addressing.getFrom()));
}
if (addressing.getReplyTo() != null) {
options.setReplyTo(new EndpointReference(addressing.getReplyTo()));
}
}
private boolean processResponse(final Exchange exchange, final SuConfigurationParameters cdkExtensions,
final ServiceContext context, final ServiceClientKey probeKey,
final KeyedStartDateItem startDateItem, final MessageContext response,
final FlowAttributes fa) throws MessagingException, ProbeKeyMissingException, ProbeNotStartedException,
ResponseTimeCollectionStoppedException, StartDateItemUnknownException {
final boolean succeeded;
final SOAPBody body = response.getEnvelope().getBody();
// if msg exchange required a response, set it
if (body != null) {
if (response.isFault()) {
assert body.getFault() != null;
final SOAPFault soapFault = body.getFault();
final SOAPFaultDetail soapFaultDetails = soapFault.getDetail();
if (soapFaultDetails != null) {
final OMElement firstDetailFaultElt = soapFault.getDetail().getFirstElement();
if (firstDetailFaultElt == null) {
// Technical error
context.getLogger().log(Level.FINE, "RESPONSE is a SOAP Fault (Technical error).");
Marshaller.fillJBITechnicalError(soapFault, exchange);
this.soapContext.getComponent().logMonitTrace(exchange,
new ProvideExtFlowStepFailureLogData(fa.getFlowInstanceId(), fa.getFlowStepId(),
String.format(StepLogHelper.TECHNICAL_ERROR_MESSAGE_PATTERN,
exchange.getError().getMessage())));
try {
this.soapContext.getOutgoingProbes().probeWsRequestsInvocationsCount.move(probeKey,
ExecutionStatus.ERROR);
if (startDateItem != null) {
this.soapContext.getOutgoingProbes().probeWsClientInvocationsResponseTime
.endsExecution(startDateItem, ExecutionStatus.ERROR);
}
} catch (final ProbeException e) {
context.getLogger().log(Level.WARNING, "The WS probes seems to have a problem.", e);
}
} else if (firstDetailFaultElt.getType() == OMNode.ELEMENT_NODE) {
// Business fault
context.getLogger().log(Level.FINE, "RESPONSE is a SOAP Fault (Business error).");
this.soapContext.getComponent().logMonitTrace(exchange, new ProvideExtFlowStepFailureLogData(
fa.getFlowInstanceId(), fa.getFlowStepId(), StepLogHelper.BUSINESS_ERROR_MESSAGE));
try {
this.soapContext.getOutgoingProbes().probeWsRequestsInvocationsCount.move(probeKey,
ExecutionStatus.FAULT);
if (startDateItem != null) {
this.soapContext.getOutgoingProbes().probeWsClientInvocationsResponseTime
.endsExecution(startDateItem, ExecutionStatus.FAULT);
}
} catch (final ProbeException e) {
context.getLogger().log(Level.WARNING, "The WS probes seems to have a problem.", e);
}
Marshaller.fillJBIBusinessFault(firstDetailFaultElt, exchange);
} else {
throw new MessagingException("Unsupported fault type: " + soapFault.getDetail().getType());
}
} else {
// Technical error
context.getLogger().log(Level.FINE, "RESPONSE is a SOAP Fault (Technical error without details).");
Marshaller.fillJBITechnicalError(soapFault, exchange);
this.soapContext.getComponent().logMonitTrace(exchange,
new ProvideExtFlowStepFailureLogData(fa.getFlowInstanceId(), fa.getFlowStepId(),
String.format(StepLogHelper.TECHNICAL_ERROR_MESSAGE_PATTERN,
exchange.getError().getMessage())));
try {
this.soapContext.getOutgoingProbes().probeWsRequestsInvocationsCount.move(probeKey,
ExecutionStatus.ERROR);
if (startDateItem != null) {
this.soapContext.getOutgoingProbes().probeWsClientInvocationsResponseTime
.endsExecution(startDateItem, ExecutionStatus.ERROR);
}
} catch (final ProbeException e) {
context.getLogger().log(Level.WARNING, "The WS probes seems to have a problem.", e);
}
}
succeeded = false;
} else {
Marshaller.fillJBIMessage(response, exchange.getOutMessage(),
SUPropertiesHelper.isAxis1CompatibilityEnabled(cdkExtensions), context.getLogger());
succeeded = true;
}
} else {
// TODO check that it is an optional in out?
context.getLogger().log(Level.FINE, "RESPONSE Payload : No response.");
succeeded = true;
}
return succeeded;
}
private void silentAddHeader(final DocumentFragment header, final ServiceClient serviceClient) {
final Node node = header.getFirstChild();
if (node instanceof Element) {
try {
serviceClient.addHeader(XMLUtils.toOM((Element) node));
} catch (final Exception t) {
soapContext.getLogger().log(Level.WARNING,
"Can't parse the custom additional header and add it to be sent over SOAP", t);
}
}
}
/**
* Retrieve data from JBI properties wich will be set into the SOAP header.
*
* @param nm
* @return
*/
@SuppressWarnings("unchecked")
private void addHeadersToServiceClient(final NormalizedMessage nm, final SuConfigurationParameters cdkExtensions,
final ServiceClient serviceClient) {
final List filters = SUPropertiesHelper.retrieveHeaderList(cdkExtensions);
// 1. get the properties defined in the SU from the filter value
if (filters != null && !filters.isEmpty()) {
final Set properties = new HashSet();
final Set names = nm.getPropertyNames();
for (final Object object : names) {
if (object instanceof String) {
final String propertyName = (String) object;
if (isFilteredValue(propertyName, filters)) {
properties.add(propertyName);
}
}
}
for (final String propertyName : properties) {
final Object property = nm.getProperty(propertyName);
// TODO : Inject not only document fragments...
if (property instanceof DocumentFragment) {
silentAddHeader((DocumentFragment) property, serviceClient);
}
}
}
// 2. additional headers
if (SUPropertiesHelper.retrieveInjectHeader(cdkExtensions)) {
// add the protocol headers (the ones injected in consumer mode)
final Object protocolHeadersPropertyObject = nm.getProperty(PROTOCOL_HEADERS);
if (protocolHeadersPropertyObject != null && protocolHeadersPropertyObject instanceof Map) {
for (final DocumentFragment df : ((Map) protocolHeadersPropertyObject)
.values()) {
silentAddHeader(df, serviceClient);
}
}
for (final DocumentFragment h : SUPropertiesHelper.retrieveHeaderToInject(cdkExtensions,
soapContext.getLogger())) {
silentAddHeader(h, serviceClient);
}
}
}
/**
* @param propertyName
* @param filters
* @return
*/
protected boolean isFilteredValue(final String propertyName, final List filters) {
final boolean result = false;
for (final String filter : filters) {
if (propertyName.equalsIgnoreCase(filter)) {
return true;
}
if (filter.endsWith("*")) {
final String tmp = filter.substring(0, filter.lastIndexOf("*"));
if (propertyName.equals(tmp) || propertyName.startsWith(tmp)) {
return true;
}
}
}
return result;
}
private String retrieveSoapActionFromWsdl(final Exchange exchange, final ServiceContext context) {
String soapAction = null;
final String endpointName = exchange.getEndpointName();
final QName service = exchange.getEndpoint().getServiceName();
final Description d = context.getServiceDescription();
final Endpoint e = d.getService(service).getEndpoint(endpointName);
if (e != null) {
final Binding b = e.getBinding();
if (b != null) {
final BindingOperation bo = b.getBindingOperation(exchange.getOperationName());
if (bo != null) {
soapAction = bo.getSoapAction();
// See PETALSBCSOAP-151
if (soapAction == null || soapAction.isEmpty()) {
// it must contain the characters for an empty string, if not
// org.apache.axis2.transport.http.CommonsHTTPTransportSender.findSOAPAction(MessageContext)
// won't be happy with it!
soapAction = "\"\"";
}
}
}
}
return soapAction;
}
}