org.ow2.petals.binding.soap.listener.incoming.PetalsReceiver 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) 2010-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.incoming;
import static org.ow2.petals.binding.soap.SoapConstants.Axis2.SOAP_EXTERNAL_LISTENER_SERVICE_PARAM;
import static org.ow2.petals.binding.soap.SoapConstants.SOAP.FAULT_SERVER;
import java.util.Optional;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.xml.namespace.QName;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.engine.AxisEngine;
import org.apache.axis2.receivers.AbstractInOutMessageReceiver;
import org.apache.axis2.receivers.AbstractMessageReceiver;
import org.apache.axis2.util.MessageContextBuilder;
import org.ow2.easywsdl.wsdl.api.abstractItf.AbsItfOperation.MEPPatternConstants;
import org.ow2.petals.binding.soap.ServiceContext;
import org.ow2.petals.binding.soap.SoapComponentContext;
import org.ow2.petals.binding.soap.SoapConstants;
import org.ow2.petals.binding.soap.listener.incoming.jetty.IncomingServiceKey;
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.Message.MEPConstants;
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.Consumes;
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.ProbeNotStartedException;
import org.ow2.petals.probes.api.exceptions.ResponseTimeCollectionStoppedException;
import org.ow2.petals.probes.api.exceptions.StartDateItemLostException;
import org.ow2.petals.probes.api.probes.KeyedStartDateItem;
/**
* The message receiver used by the binding component.
*
* The SOAP message is forwarded to the JBI endpoint.
*
*
* We cannot simply extend {@link AbstractInOutMessageReceiver} because we need to catch send errors from Axis and
* sometimes we don't even need to send back an answer (for InOnly)
*
* @author Nicolas Oddoux - EBM WebSourcing
*/
public class PetalsReceiver extends AbstractMessageReceiver {
// this is not really used as an exception for knowing where it happened
// we can thus reuse it and avoid the overhead of creating the exception
public static final AxisFault TIMEOUT_EXCEPTION = new AxisFault(
"A timeout happened while the BC SOAP sent an exchange to a JBI service", FAULT_SERVER);
static {
TIMEOUT_EXCEPTION.setStackTrace(new StackTraceElement[0]);
}
private final SoapComponentContext soapContext;
/**
* @param soapContext
*/
public PetalsReceiver(final SoapComponentContext soapContext) {
super();
this.soapContext = soapContext;
}
@Override
public final void invokeBusinessLogic(final MessageContext msgContext) throws AxisFault {
@SuppressWarnings("unchecked")
final ServiceContext context = (ServiceContext) msgContext.getAxisService()
.getParameter(SoapConstants.Axis2.CONSUMES_SERVICE_CONTEXT_PARAM).getValue();
try {
// send message to JBI endpoint
process(msgContext, context);
} catch (final Exception af) {
// Update service invocation probe
this.updateIncomingProbes(msgContext, context, ExecutionStatus.ERROR);
final Optional flowTracingActivationState = (Optional) msgContext
.getProperty(SoapConstants.MESSAGE_CONTEXT_FLOW_TRACING_ACTIVATION_STATE);
assert flowTracingActivationState != null;
this.soapContext.getComponent().logMonitTrace(flowTracingActivationState, context
.getConfig(),
StepLogHelper.getMonitExtFailureTrace(PetalsExecutionContext.getFlowAttributes(), af, true));
throw af;
}
}
/**
* Process the request. Sends the SOAP Envelop of the request message directly to a JBI service and operation on the
* JBI container. The response message from the JBI service invocation is transmitted as an outgoing message.
*
* @param context2
*
* @param the
* in message context
* @return the out message context
* @throws AxisFault
* the axis fault
*/
private void process(final MessageContext messageContext, final ServiceContext context)
throws AxisFault {
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "Processing the incoming SOAP message");
}
// Type of service has been checked before
final AxisService axisService = messageContext.getAxisService();
final AxisOperation axisOperation = messageContext.getAxisOperation();
if (context.getLogger().isLoggable(Level.FINEST)) {
context.getLogger().log(Level.FINEST,
"soapAction found in the incoming message : "
+ axisOperation.getSoapAction());
}
// JBI operation = Axis operation name
final QName jbiOperation = axisOperation.getName();
try {
final String wsPath = (String) messageContext
.getProperty(SoapConstants.MESSAGE_CONTEXT_INCOMINGSERVICEKEY_WS_PATH);
final String clientIpAddress = (String) messageContext
.getProperty(SoapConstants.MESSAGE_CONTEXT_INCOMINGSERVICEKEY_CLIENT_IP_ADDRESS);
final IncomingServiceKey serviceInvocationKey = new IncomingServiceKey(wsPath,
axisOperation.getName().toString(), clientIpAddress);
messageContext
.setProperty(
org.ow2.petals.binding.soap.SoapConstants.MESSAGE_CONTEXT_INCOMINGSERVICEKEY_NAME,
serviceInvocationKey);
soapContext.getIncomingProbes().probeHttpRequestsInvocationsCount.incPending(serviceInvocationKey);
try {
final KeyedStartDateItem responseTime = soapContext
.getIncomingProbes().probeHttpRequestsInvocationsResponseTime
.newExecution(serviceInvocationKey);
messageContext.setProperty(org.ow2.petals.binding.soap.SoapConstants.MESSAGE_CONTEXT_RESPONSETIME_NAME,
responseTime);
} catch (final ResponseTimeCollectionStoppedException e) {
// This exception should not occur because the HTTP server
// should be stopped in the same time than the probes
context.getLogger().warning("A response times collection is stopped.");
} catch (final StartDateItemLostException e) {
context.getLogger()
.warning("A response time is lost because it can't be added to the internal list.");
}
} catch (final ProbeNotStartedException e) {
context.getLogger()
.warning("HTTP probes are not started. Values of probes could be incorrect.");
}
// JBI MEP compute from the Axis MEP defined at AxisServicesHelper.getWSDL2Mep(...)
final int axisMep = axisOperation.getAxisSpecificMEPConstant();
final MEPPatternConstants mep;
if (axisMep == org.apache.axis2.wsdl.WSDLConstants.MEP_CONSTANT_IN_OUT) {
mep = MEPConstants.IN_OUT_PATTERN;
} else if (axisMep == org.apache.axis2.wsdl.WSDLConstants.MEP_CONSTANT_ROBUST_IN_ONLY) {
mep = MEPConstants.ROBUST_IN_ONLY_PATTERN;
} else if (axisMep == org.apache.axis2.wsdl.WSDLConstants.MEP_CONSTANT_IN_ONLY) {
mep = MEPConstants.IN_ONLY_PATTERN;
} else {
context.getLogger().warning(String.format("Axis MEP not managed: [%d]. We use the default MEP.", axisMep));
mep = null;
}
final SoapExternalListener soapExternalListener = (SoapExternalListener) axisService
.getParameter(SOAP_EXTERNAL_LISTENER_SERVICE_PARAM).getValue();
final Consumes consumes = context.getConfig();
final SuConfigurationParameters consumesExtensions = context.getExtensions();
final Exchange exchange;
try {
// create the message exchange
exchange = this.createMessageExchange(messageContext, jbiOperation, mep, soapExternalListener,
consumes, consumesExtensions);
} catch (final MessagingException me) {
final String errorMsg = "Error while transforming SOAP request to JBI MessageExchange";
context.getLogger().log(Level.SEVERE, errorMsg, me);
throw new AxisFault(errorMsg, FAULT_SERVER, me);
}
// send the message exchange through JBI
// As the SOAP/HTTP request may wait for a response,
final SOAPFactory soapFactory = this.getSOAPFactory(messageContext);
sendJBIMessage(messageContext, soapFactory, exchange, soapExternalListener, context);
}
/**
* Create the JBI message exchange
*
* @param inContext
* the SOAP message
* @param operation
* the operation
* @param mep
* the MEP
* @param soapExternalListener
* the SOAP external listener
* @param consumes
* the consumes
* @param consumesExtensions
* the consume extensions
*
* @return the JBI message exchange
*
* @throws MessagingException
*/
private final Exchange createMessageExchange(final MessageContext inContext,
final QName operation, final MEPPatternConstants mep,
final SoapExternalListener soapExternalListener, final Consumes consumes,
final SuConfigurationParameters consumesExtensions) throws MessagingException {
// create the message exchange from the extensions.
final Optional flowTracingActivationState = (Optional) inContext
.getProperty(SoapConstants.MESSAGE_CONTEXT_FLOW_TRACING_ACTIVATION_STATE);
assert flowTracingActivationState != null;
final Exchange msgExchange = soapExternalListener.createExchange(consumes, mep, flowTracingActivationState);
msgExchange.setOperation(operation);
Marshaller.fillJBIMessage(inContext, msgExchange.getInMessage(),
SUPropertiesHelper.isAxis1CompatibilityEnabled(consumesExtensions), soapExternalListener.getLogger());
return msgExchange;
}
/**
* Put the {@link NormalizedMessage} attachments in the output Axis2 {@link MessageContext}.
*
* @param soapFactory
* the SOAP factory
* @param nm
* the normalized message
* @param outMessage
* the SOAP message
* @param context
* @throws AxisFault
*/
private final void handleResponseAttachments(final SOAPFactory soapFactory, final NormalizedMessage nm,
final MessageContext outMessage, final ServiceContext context) throws AxisFault {
// process the output message with its attachments
Marshaller.fillSOAPBodyWithAttachments(nm, soapFactory, outMessage);
final SOAPEnvelope env = outMessage.getEnvelope();
if (env != null && context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "SOAPENVELOPE AFTER Attachment handling");
context.getLogger().log(Level.FINE, outMessage.getEnvelope().toString());
}
}
/**
* Process the JBI response.
* According to the response status :
*
* - set outContext to null for DONE
* - throws an AxisFault for ERROR
* - set outContext body to a FAULT
* - set outContext body to a response
*
*
* @param exchange
* JBI response
* @param inMessage
* the SOAP message
* @param factory
* soap factory used to create body response
* @param soapExternalListener
* the SOAP external listener
* @param context
* @return a FAULT or OUT JBI response or null if no response
* @throws AxisFault
* with ERROR JBI status, or problem while creating response
*/
private void processJBIResponse(final Exchange exchange, final MessageContext inMessage,
final SOAPFactory factory, SoapExternalListener soapExternalListener,
final ServiceContext context) throws AxisFault {
if (exchange.isDoneStatus()) {
// exchange DONE, nothing to do, no soap response
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "Receive a Done status response");
}
final FlowAttributes flowAttributes = PetalsExecutionContext.getFlowAttributes();
this.soapContext.getComponent().logMonitTrace(context.getConfig(),
StepLogHelper.getMonitExtEndOrFailureTrace(exchange.getMessageExchange(), flowAttributes, true));
// Update service invocation probe
this.updateIncomingProbes(inMessage, context, ExecutionStatus.SUCCEEDED);
} else if (exchange.isErrorStatus()) {
// exchange ERROR: throws an axis fault
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "Receive an Error status response");
}
// we can't just use the exception message because it could contain the whole stacktrace (because of how
// MessagingException are transformed to something classpath-safe).
// the probe will be updated in the catch clause of invokeBusinessLogic
throw new AxisFault("Error in the JBI exchange", FAULT_SERVER, exchange.getError());
} else if (exchange.isActiveStatus()) {
if (context.getLogger().isLoggable(Level.FINE)) {
context.getLogger().log(Level.FINE, "Receive an Active status response");
}
final MessageContext outMessage = MessageContextBuilder.createOutMessageContext(inMessage);
outMessage.getOperationContext().addMessageContext(outMessage);
if (exchange.getFault() != null) {
final SOAPEnvelope envelope = Marshaller.createSOAPEnvelope(factory, exchange.getFault(),
context.getLogger());
outMessage.setEnvelope(envelope);
} else {
// Get the output message if the message is an instance of out
final NormalizedMessage nm = exchange.getOutMessage();
if (nm == null) {
final String errorMsg = "The MEP '" + exchange.getPattern()
+ "' does not accept a null response";
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, errorMsg);
}
// the probe will be updated in the catch clause of invokeBusinessLogic
throw new AxisFault(errorMsg, FAULT_SERVER);
} else {
final SOAPEnvelope envelope = Marshaller.createSOAPEnvelope(factory, nm,
context.getLogger());
outMessage.setEnvelope(envelope);
handleResponseAttachments(factory, nm, outMessage, context);
}
}
try {
// this is what AbstractInOutMessageReceiver does!
replicateState(outMessage);
// send back the response to the WS consumer
AxisEngine.send(outMessage);
} catch (final AxisFault e) {
// no need to throw it, the answer won't be returned to the client anyway if this failed
exchange.setError(e);
}
final ExecutionStatus status;
if (exchange.isErrorStatus()) {
status = ExecutionStatus.ERROR;
} else if (exchange.getFault() != null) {
status = ExecutionStatus.FAULT;
} else {
status = ExecutionStatus.SUCCEEDED;
}
this.updateIncomingProbes(inMessage, context, status);
// let's log the end or failure trace (after we successfully, or not, sent back the answer)
final FlowAttributes flowAttributes = PetalsExecutionContext.getFlowAttributes();
final Optional flowTracingActivationState = (Optional) inMessage
.getProperty(SoapConstants.MESSAGE_CONTEXT_FLOW_TRACING_ACTIVATION_STATE);
assert flowTracingActivationState != null;
this.soapContext.getComponent().logMonitTrace(flowTracingActivationState, context
.getConfig(),
StepLogHelper.getMonitExtEndOrFailureTrace(exchange.getMessageExchange(), flowAttributes, true));
// the rest of the method should not throw AxisFault
// (because it will result in incoming probes be updated again in invokeBusinessLogic)
try {
// let's acknowledge the JBI message
// test in case it was set to error by the axis send before!
if (exchange.isActiveStatus()) {
exchange.setStatus(ExchangeStatus.DONE);
}
soapExternalListener.send(exchange);
} catch (final MessagingException e) {
context.getLogger().log(Level.WARNING, "Error while acknowledging the JBI MessageExchange.", e);
}
} else {
throw new AxisFault(
SoapConstants.SOAP.ERROR_WRONG_MESSAGE_STATUS + " " + exchange.getStatus().toString(),
FAULT_SERVER);
}
}
private void updateIncomingProbes(final MessageContext inMessage, final ServiceContext context,
final ExecutionStatus executionStatus) {
try {
final IncomingServiceKey currentServiceKey = (IncomingServiceKey) inMessage
.getProperty(SoapConstants.MESSAGE_CONTEXT_INCOMINGSERVICEKEY_NAME);
soapContext.getIncomingProbes().probeHttpRequestsInvocationsCount.move(currentServiceKey, executionStatus);
@SuppressWarnings("unchecked")
final KeyedStartDateItem responseTime = (KeyedStartDateItem) inMessage
.getProperty(SoapConstants.MESSAGE_CONTEXT_RESPONSETIME_NAME);
soapContext.getIncomingProbes().probeHttpRequestsInvocationsResponseTime.endsExecution(responseTime,
executionStatus);
} catch (final ProbeException e) {
// The request processing should not be interrupted if an error occurs during the probe update.
context.getLogger().log(Level.WARNING, "Error updating probes", e);
}
}
/**
* Send synchronously the JBI message
*
* @param inContext
* the message context
* @param factory
* the SOAP factory
* @param exchange
* the message exchange
* @param soapExternalListener
* the SOAP external listener
* @param context
* @return the message context with the response
*
* @throws AxisFault
*/
private void sendJBIMessage(final MessageContext inContext, final SOAPFactory factory, Exchange exchange,
final SoapExternalListener soapExternalListener, final ServiceContext context) throws AxisFault {
boolean noTimeout = true;
try {
// TODO move to async sending...
noTimeout = soapExternalListener.sendSync(exchange);
} catch (final MessagingException e) {
final String errorMsg = "Error while sending JBI exchange with id '"
+ exchange.getExchangeId() + "'";
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, errorMsg, e);
}
throw new AxisFault(errorMsg, FAULT_SERVER, e);
}
if (!noTimeout) {
final String errorMsg = "A timeout occurs on JBI exchange with id '"
+ exchange.getExchangeId() + "'";
if (context.getLogger().isLoggable(Level.WARNING)) {
context.getLogger().log(Level.WARNING, errorMsg);
}
throw TIMEOUT_EXCEPTION;
}
// process the response received from JBI NMR. The out context is
// returned
processJBIResponse(exchange, inContext, factory, soapExternalListener, context);
}
}