/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.soa.esb.listeners.message;
import java.net.URISyntaxException;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.crypto.SealedObject;
import javax.security.auth.Subject;
import javax.xml.validation.Schema;
import org.apache.log4j.Logger;
import org.jboss.internal.soa.esb.services.security.SecurityContextPropagator;
import org.jboss.internal.soa.esb.services.security.SecurityContextPropagatorFactory;
import org.jboss.internal.soa.esb.util.XMLHelper;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.actions.ActionLifecycle;
import org.jboss.soa.esb.actions.ActionPipelineProcessor;
import org.jboss.soa.esb.actions.ActionProcessingFaultException;
import org.jboss.soa.esb.actions.BeanConfiguredAction;
import org.jboss.soa.esb.addressing.Call;
import org.jboss.soa.esb.addressing.EPR;
import org.jboss.soa.esb.addressing.MalformedEPRException;
import org.jboss.soa.esb.addressing.eprs.LogicalEPR;
import org.jboss.soa.esb.addressing.util.DefaultFaultTo;
import org.jboss.soa.esb.addressing.util.DefaultReplyTo;
import org.jboss.soa.esb.client.ServiceInvoker;
import org.jboss.soa.esb.common.Environment;
import org.jboss.soa.esb.couriers.Courier;
import org.jboss.soa.esb.couriers.CourierException;
import org.jboss.soa.esb.couriers.CourierFactory;
import org.jboss.soa.esb.couriers.CourierUtil;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.lifecycle.LifecycleResourceException;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.listeners.message.errors.Factory;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.MessagePayloadProxy;
import org.jboss.soa.esb.message.Properties;
import org.jboss.soa.esb.services.persistence.MessageStore;
import org.jboss.soa.esb.services.security.PublicCryptoUtil;
import org.jboss.soa.esb.services.security.SecurityConfig;
import org.jboss.soa.esb.services.security.SecurityConfigUtil;
import org.jboss.soa.esb.services.security.SecurityContext;
import org.jboss.soa.esb.services.security.SecurityService;
import org.jboss.soa.esb.services.security.SecurityServiceException;
import org.jboss.soa.esb.services.security.SecurityServiceFactory;
import org.jboss.soa.esb.services.security.auth.AuthenticationRequest;
import org.jboss.soa.esb.services.security.auth.AuthenticationRequestImpl;
import org.jboss.soa.esb.util.ClassUtil;
import org.xml.sax.SAXException;
/**
* Action Processing Pipeline. <p/> Runs a list of action classes on a message
*
* @author <a
* href="mailto:schifest@heuristica.com.ar">schifest@heuristica.com.ar</a>
* @author kevin
* @author <a href="mailto:dbevenius@jboss.com">Daniel Bevenius</a>
* @since Version 4.0
*/
public class ActionProcessingPipeline
{
/**
* The logger instance.
*/
private final static Logger LOGGER = Logger .getLogger(ActionProcessingPipeline.class);
/**
* The processors.
*/
private final ActionPipelineProcessor[] processors;
/**
* The active flag.
*/
private final AtomicBoolean active = new AtomicBoolean(false);
/**
* The request XSD.
*/
private final Schema requestSchema ;
/**
* The response XSD.
*/
private final Schema responseSchema ;
/**
* The request payload proxy.
*/
private MessagePayloadProxy requestPayloadProxy ;
/**
* The response payload proxy.
*/
private MessagePayloadProxy responsePayloadProxy ;
/**
*
*/
private final ServiceMessageCounter serviceMessageCounter;
/**
* The transactional flag.
*/
private boolean transactional ;
/**
* The flag indicating an action pipeline for a one way MEP.
*/
private final boolean oneWay ;
/**
* The flag indicating whether we are using implicit or explicit processing.
*/
private final boolean defaultProcessing ;
/**
* ESB Service Security configuration information.
*/
private SecurityConfig securityConf;
/**
* The {@link SecurityContextPropagator} is use.
* This can be configured either globally in jbossesb-properites.xml or
* per-service in jboss-esb.xml.
*/
private SecurityContextPropagator securityContextPropagator;
private String serviceName;
/**
* public constructor
*
* @param config The pipeline configuration.
*/
public ActionProcessingPipeline(final ConfigTree config) throws ConfigurationException
{
if (config == null)
{
throw new IllegalArgumentException( "Configuration needed for action classes");
}
final String mep = config.getAttribute(ListenerTagNames.MEP_ATTRIBUTE_TAG) ;
final boolean oneWay ;
final boolean defaultProcessing ;
if (mep == null)
{
oneWay = false ;
defaultProcessing = true ;
}
else if (ListenerTagNames.MEP_ONE_WAY.equals(mep))
{
oneWay = true ;
defaultProcessing = false ;
}
else if (ListenerTagNames.MEP_REQUEST_RESPONSE.equals(mep))
{
oneWay = false ;
defaultProcessing = false ;
}
else
{
throw new ConfigurationException("Unrecognised action MEP: " + mep) ;
}
final boolean validate = config.getBooleanAttribute(ListenerTagNames.VALIDATE_ATTRIBUTE_TAG, false) ;
if (validate)
{
final String inXsd = config.getAttribute(ListenerTagNames.IN_XSD_ATTRIBUTE_TAG) ;
try
{
requestSchema = (inXsd == null ? null : XMLHelper.getSchema(inXsd)) ;
}
catch (final SAXException saxe)
{
throw new ConfigurationException("Failed to parse the request schema: " + inXsd, saxe) ;
}
final String outXsd = config.getAttribute(ListenerTagNames.OUT_XSD_ATTRIBUTE_TAG) ;
try
{
responseSchema = (outXsd == null ? null : XMLHelper.getSchema(outXsd));
}
catch (final SAXException saxe)
{
throw new ConfigurationException("Failed to parse the response schema: " + outXsd, saxe) ;
}
requestPayloadProxy = new MessagePayloadProxy(config.getAttribute(ListenerTagNames.REQUEST_LOCATION_TAG), null) ;
responsePayloadProxy = new MessagePayloadProxy(config.getAttribute(ListenerTagNames.RESPONSE_LOCATION_TAG), null) ;
}
else
{
requestSchema = null ;
responseSchema = null ;
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Using mep: " + mep + ", oneWay: " + oneWay + ", defaultProcessing: " + defaultProcessing) ;
}
this.oneWay = oneWay ;
this.defaultProcessing = defaultProcessing ;
final ConfigTree[] actionList = config
.getChildren(ListenerTagNames.ACTION_ELEMENT_TAG);
if ((actionList == null) || (actionList.length == 0))
{
throw new ConfigurationException("No actions in list");
}
final ArrayList<ActionPipelineProcessor> processorList = new ArrayList<ActionPipelineProcessor>();
try
{
serviceMessageCounter = ServiceMessageCounterLifecycleResource.getServiceMessageCounter(config);
}
catch (final LifecycleResourceException lre)
{
throw new ConfigurationException("Failed to obtain the service message counter", lre);
}
for (final ConfigTree actionConfig : actionList)
{
final String actionClassTag = actionConfig
.getAttribute(ListenerTagNames.ACTION_CLASS_TAG);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Registering action class " + actionClassTag);
}
final Class<?> actionClass;
try
{
actionClass = ClassUtil.forName(actionClassTag, getClass());
}
catch (final ClassNotFoundException cnfe)
{
throw new ConfigurationException("Could not load action class "
+ actionClassTag);
}
final ActionPipelineProcessor processor;
if (BeanConfiguredAction.class.isAssignableFrom(actionClass))
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Using bean configured action processor for "
+ actionClassTag);
}
processor = new BeanConfigActionProcessor(actionConfig,
actionClass);
}
else if (ActionPipelineProcessor.class
.isAssignableFrom(actionClass))
{
final ActionPipelineProcessor currentProcessor = (ActionPipelineProcessor) ActionProcessorMethodInfo
.getActionClassInstance(actionConfig, actionClass);
if (ActionProcessorMethodInfo.checkOverridden(actionConfig))
{
if (LOGGER.isDebugEnabled())
{
LOGGER
.debug("Using overridden action pipeline processor for "
+ actionClassTag);
}
processor = new OverriddenActionPipelineProcessor(
actionConfig, currentProcessor);
}
else
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Using normal action pipeline processor for " + actionClassTag);
}
processor = currentProcessor;
}
}
else if (ActionLifecycle.class.isAssignableFrom(actionClass))
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Using overridden action lifecycle processor for " + actionClassTag);
}
final ActionLifecycle currentLifecycle = (ActionLifecycle) ActionProcessorMethodInfo
.getActionClassInstance(actionConfig, actionClass);
processor = new OverriddenActionLifecycleProcessor(
actionConfig, currentLifecycle);
}
else if (BeanContainerAction.isAnnotatedActionClass(actionClass))
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Using BeanContainerAction for Annotated Action processor for " + actionClassTag);
}
try {
processor = new BeanContainerAction(actionClass.newInstance(), actionConfig);
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to create an instance of Annotated ESB Action class '" + actionClass.getName() + "'. Class must contain a default public constructor.", e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to create an instance of Annotated ESB Action class '" + actionClass.getName() + "'. Class must contain a default public constructor.", e);
}
}
else
{
LOGGER.warn("Action class " + actionClassTag + " does not: a) implement the ActionLifecycle interface, or b) have any public method annotated with the @ProcessMethod annotation.");
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Using overridden actions processor for " + actionClassTag);
}
processor = new OverriddenActionProcessor(actionConfig, actionClass);
}
processorList.add(processor);
}
processors = processorList.toArray(new ActionPipelineProcessor[processorList.size()]);
ConfigTree[] securityConfigs = config.getChildren( ListenerTagNames.SECURITY_TAG );
String securityPropagatorClass = null;
if (securityConfigs.length > 0)
{
securityConf = SecurityConfigUtil.createSecurityConfig(securityConfigs[0]);
// Check if a security context propagator was specified in the security element.
securityPropagatorClass = securityConf.getProperties().get(Environment.SECURITY_SERVICE_CONTEXT_PROPAGATOR_CLASS);
}
serviceName = config.getAttribute(ListenerTagNames.SERVICE_NAME_TAG);
try
{
securityContextPropagator = securityPropagatorClass != null ?
SecurityContextPropagatorFactory.create(securityPropagatorClass):
SecurityContextPropagatorFactory.createFromConfig();
}
catch (final SecurityServiceException e)
{
final String errorMsg;
if (securityPropagatorClass != null )
{
errorMsg = "Could not create an instance of class '" + securityPropagatorClass + "' which was configured for service '" +
serviceName + "'. Please check the value of '" + Environment.SECURITY_SERVICE_CONTEXT_PROPAGATOR_CLASS + "'" +
" which is a property element of the security element declared in jboss-esb.xml.";
}
else
{
errorMsg = "Could not create an instance of class the security context propagator configured in jbossesb-properties.xml" +
".Please check the value of '" + Environment.SECURITY_SERVICE_CONTEXT_PROPAGATOR_CLASS + "' in jbossesb-properties.xml";
}
throw new ConfigurationException(errorMsg, e);
}
if (LOGGER.isDebugEnabled())
{
if (securityContextPropagator != null)
{
LOGGER.debug("SecurityContextPropagator in use for service '" + serviceName + "' is '" + securityContextPropagator.getClass().getName() + "'" );
}
}
}
/**
* Handle the initialisation of the pipeline
*
* @throws ConfigurationException
* For errors during initialisation.
*/
public void initialise() throws ConfigurationException
{
final int numLifecycles = processors.length;
for (int count = 0; count < numLifecycles; count++)
{
final ActionLifecycle lifecycle = processors[count];
try
{
lifecycle.initialise();
}
catch (final Exception ex)
{
handleDestroy(count - 1);
throw new ConfigurationException(
"Unexpected exception during lifecycle initialisation",
ex);
}
}
active.set(true);
}
/**
* Handle the destruction of the pipeline
*/
public void destroy()
{
active.set(false);
handleDestroy(processors.length - 1);
}
/**
* Process the specified message.
*
* @param message
* The current message.
* @return true if the processing was successful, false otherwise.
*/
public boolean process(final Message message)
{
long start = System.nanoTime();
serviceMessageCounter.incrementTotalCount();
if (active.get())
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("pipeline process for message: "+message.getHeader());
}
return processPipeline(message, getSecurityContext(message));
}
else
{
final Call callDetails = createCallDetails(message);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("pipeline process disabled for message: "+message.getHeader());
}
faultTo(callDetails, Factory.createErrorMessage(Factory.NOT_ENABLED, message, null));
long procTime = System.nanoTime() - start;
MessageCounterStatistics.getMessageCounterStatistics().update(new MessageStatusBean(procTime, message,
MessageStatusBean.MESSAGE_FAILED));
return false;
}
}
/**
* Processes the action pipeline.
* <p/>
* If the service has been enable for security authentication, it has the security element
* in the configuration xml that is, this method will authenitcate the caller.
* If security has not been enabled this method will not perform any authentication but
* it will pass through a pre-existing SecurityContext.
* <p>
*
* <h1>SecurityContext</h1>
* After the caller has been successfully authenticated a SecurityContext will be created.
* This context is valid on the current ESB node(VM) only.
* When a SecurityContext is created a timeout is specified. This value can either be the globally
* value specified in jbossesb-properties.xml or it can be overridden per service by specifiying the
* same property in the security element in jboss-esb.xml:
* The value is specified in milliseconds.
* <pre>
* <security module="someModuleName">
* <property name="org.jboss.soa.esb.services.security.contextTimeout" value="50000"/>
* </security>
* </pre>
*
* <h1>AuthenticationRequest</h1>
* If a call is routed to a different ESB node then that node will re-authenticate the caller.
* The node will descrypt the authentication request passed with the Message object
* and try to authenticate the caller.
*
* <h1>SecurityContext Propagation</h1>
* Regardless of whether the service has been configured with security or not, the security
* context information might need to be propagated using the configured {@link SecurityContextPropagator}.
*
* @param message The ESB message object.
* @param sealedSecurityContext The sealed SecurityContext which will exist if the caller has already been authenticated.
* @return true If the action pipeline was processed successfully.
*/
private boolean processPipeline(final Message message, final SealedObject sealedSecurityContext)
{
SecurityContext securityContext = null;
if (sealedSecurityContext != null )
{
// Store the security context. Will be re-attached to outgoing messages regardless whether the service is secured or not.
SecurityContext.setSecurityContext(sealedSecurityContext);
}
AuthenticationRequest authRequest;
try
{
/*
* Get the authentication reqeust if one exists. Note that this is needed even if
* the current service does not require authentication. A service later down the line might
* need to access this information, it might call an EJB that is secured for example, and
* this way pass it through.
*/
authRequest = getAutenticationRequest(message);
if (isServiceSecured())
{
try
{
// Try to decrypt the security context.
securityContext = SecurityContext.decryptContext(sealedSecurityContext);
}
catch (final SecurityServiceException ignored)
{
LOGGER.info("Could not decrypt the security context in Service '" + serviceName + "'. The call might have come from a different VM. Will re-authenticate if security is enabled for this service.", ignored);
securityContext = null;
}
if (LOGGER.isDebugEnabled())
{
if (securityContext != null)
{
/*
* Might be interesting to know if a security context is often invalid as this
* will cause re-authentication. This might be avoidable by setting a longer
* timeout by overriding the default timeout(jbossesb-properties.xml) and specifying
* one on the security element in jboss-esb.xml.
*/
LOGGER.debug(securityContext);
}
}
final SecurityService securityService = SecurityServiceFactory.getSecurityService();
final String moduleName = securityConf.getModuleName() ;
if (securityContext == null || !securityContext.isValid() || ((moduleName != null) && !moduleName.equals(securityContext.getDomain())))
{
if (authRequest == null)
{
throw new SecurityServiceException("Service '" + serviceName + "' has been configured for security but no AuthenticationRequest could be located in the Message Context. Cannot authenticate without an AuthenticationRequest.");
}
// No existing security context exist or it had expired. Create a new one to drive the autentication.
securityContext = new SecurityContext(new Subject(), getSecurityContextTimeout(securityConf), moduleName);
// Authenticate the caller
securityService.authenticate(securityConf, securityContext, authRequest);
// Store the encrypted security context. Will be re-attached to outgoing messages.
SecurityContext.setSecurityContext(SecurityContext.encryptContext(securityContext));
}
// Check that the caller is a member of atleast one of the declared roles.
if (!securityService.checkRolesAllowed(securityConf.getRolesAllowed(), securityContext))
{
throw new SecurityServiceException("Caller did not belong to any of the rolesAllowed " + securityConf.getRolesAllowed());
}
}
}
catch (final SecurityServiceException e)
{
LOGGER.error( "SecurityService exception : ", e);
faultTo(createCallDetails(message), Factory.createErrorMessage(Factory.UNEXPECTED_ERROR, message, e));
return false;
}
catch (final ConfigurationException e)
{
LOGGER.error( "SecurityService exception : ", e);
faultTo(createCallDetails(message), Factory.createErrorMessage(Factory.UNEXPECTED_ERROR, message, e));
return false;
}
finally
{
// Always remove the security context.
message.getContext().removeContext(SecurityService.CONTEXT);
message.getContext().removeContext(SecurityService.AUTH_REQUEST);
}
if (securityContext != null)
{
try
{
// Need to propagate the security context regardless if security was enabled for this service or not.
propagateSecurityContext(message, securityContext, authRequest);
return (Boolean) Subject.doAsPrivileged(securityContext.getSubject(), getPrivilegedAction(message), null);
}
catch (final SecurityServiceException e)
{
LOGGER.error( "SecurityService exception : ", e);
faultTo(createCallDetails(message), Factory.createErrorMessage(Factory.UNEXPECTED_ERROR, message, e));
return false;
}
finally
{
popSecurityContext(securityContext);
}
}
else
{
return processPipeline(message);
}
}
private boolean processPipeline(final Message message)
{
final long start = System.nanoTime();
final Call callDetails = createCallDetails(message);
boolean result = false ;
String validationFailure = null ;
if (requestSchema != null)
{
try
{
final Object input = requestPayloadProxy.getPayload(message) ;
if ((input == null) || !XMLHelper.validate(requestSchema, input.toString()))
{
validationFailure = "Request validation failure: " + input ;
}
}
catch (final MessageDeliverException mde)
{
validationFailure = mde.getMessage() ;
}
}
if (validationFailure == null)
{
final int numProcessors = processors.length;
final Message[] messages = new Message[numProcessors];
Message currentMessage = message;
for (int count = 0; count < numProcessors; count++)
{
final ActionPipelineProcessor processor = processors[count];
messages[count] = currentMessage;
final long actionStart = System.nanoTime();
try
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("executing processor " + count+ " "+processor);
}
currentMessage = processor.process(currentMessage);
}
catch (final Exception ex)
{
final long procTime = System.nanoTime() - actionStart;
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Unexpected exception caught while processing the action pipeline",ex);
}
notifyException(count, ex, messages);
/*
* Is this an application specific error? If so, try to return
* the error message to the identified recipient.
*/
final boolean throwRuntime = transactional && (ex instanceof RuntimeException) ;
if (ex instanceof ActionProcessingFaultException)
{
ActionProcessingFaultException fault = (ActionProcessingFaultException) ex;
if (fault.getFaultMessage() == null)
{
faultTo(callDetails, Factory.createErrorMessage(Factory.PROCESSING_ERROR, message, ex));
}
else
faultTo(callDetails, fault.getFaultMessage());
}
else if (!throwRuntime)
{
faultTo(callDetails, Factory.createErrorMessage(Factory.UNEXPECTED_ERROR, message, ex));
}
final long totalProcTime = System.nanoTime() - start;
serviceMessageCounter.update(new ActionStatusBean(procTime, count, message,
ActionStatusBean.ACTION_FAILED, serviceMessageCounter.getObjectName().toString()));
MessageCounterStatistics.getMessageCounterStatistics().update(new MessageStatusBean(totalProcTime, message,
MessageStatusBean.MESSAGE_FAILED));
if (throwRuntime)
{
throw (RuntimeException)ex ;
}
return false;
}
final long procTime = System.nanoTime() - actionStart;
serviceMessageCounter.update(new ActionStatusBean(procTime, count, message,
ActionStatusBean.ACTION_SENT, serviceMessageCounter.getObjectName().toString()));
if (currentMessage == null)
{
break;
}
}
// Reply...
if (!oneWay)
{
if (currentMessage != null)
{
if (responseSchema != null)
{
try
{
final Object output = responsePayloadProxy.getPayload(message) ;
if ((output == null) || !XMLHelper.validate(responseSchema, output.toString()))
{
validationFailure = "Response validation failure: " + output ;
}
}
catch (final MessageDeliverException mde)
{
validationFailure = mde.getMessage() ;
}
}
if (validationFailure == null)
{
replyTo(callDetails, currentMessage);
}
}
else if (!defaultProcessing)
{
LOGGER.warn("No response message for RequestResponse mep! " + callDetails);
}
}
if (validationFailure == null)
{
notifySuccess(messages);
long procTime = System.nanoTime() - start;
MessageCounterStatistics.getMessageCounterStatistics().update(new MessageStatusBean(procTime, message,
MessageStatusBean.MESSAGE_SENT));
result = true;
}
}
if (validationFailure != null)
{
final MessageValidationException mve = new MessageValidationException(validationFailure) ;
faultTo(callDetails, Factory.createErrorMessage(Factory.VALIDATION_FAILURE, message, mve));
long procTime = System.nanoTime() - start;
MessageCounterStatistics.getMessageCounterStatistics().update(new MessageStatusBean(procTime, message,
MessageStatusBean.MESSAGE_FAILED));
}
return result ;
}
/**
* Set the transactional flag for this pipeline.
* @param transactional true if running within a transaction, false otherwise.
*/
public void setTransactional(final boolean transactional)
{
this.transactional = transactional ;
}
/**
* Get the transactional flag for this pipeline.
* @return true if running within a transaction, false otherwise.
*/
public boolean isTransactional()
{
return transactional ;
}
/**
* Send the reply.
*
* @param callDetails
* the call details for the original request.
* @param message
* the message.
*/
private void replyTo(final Call callDetails, final Message message)
{
if (!DefaultReplyTo.initialiseReply(message, callDetails))
{
if (defaultProcessing)
{
LOGGER.warn("No reply to address defined for reply message! " + callDetails);
sendToDLQ(callDetails, message, MessageType.reply) ;
}
}
else
{
final EPR replyToEPR = message.getHeader().getCall().getTo() ;
messageTo(replyToEPR, message, MessageType.reply);
}
}
/**
* Send the fault message to the EPR.
*
* @param callDetails
* the call details for the original request.
* @param faultToAddress
* the EPR to target if one is not set in the message.
* @param message
* the message.
*/
private void faultTo(final Call callDetails, final Message message)
{
if (!DefaultFaultTo.initialiseReply(message, callDetails, oneWay))
{
if (defaultProcessing || oneWay)
{
LOGGER.warn("No fault address defined for fault message! " + callDetails);
sendToDLQ(callDetails, message, MessageType.fault) ;
}
}
else
{
final EPR faultToEPR = message.getHeader().getCall().getTo() ;
messageTo(faultToEPR, message, MessageType.fault);
}
}
/**
* Sent the message to the DLQ service.
* @param callDetails The original call details.
* @param message The response message.
* @param messageType The response type.
*/
private void sendToDLQ(final Call callDetails, final Message message,
final MessageType messageType)
{
final Properties properties = message.getProperties() ;
properties.setProperty(MessageStore.CLASSIFICATION, MessageStore.CLASSIFICATION_DLQ);
properties.setProperty(ActionProcessingConstants.PROPERTY_FAILURE_CALL_DETAILS, callDetails.toString()) ;
properties.setProperty(ActionProcessingConstants.PROPERTY_FAILURE_RESPONSE_TYPE, messageType.name()) ;
try
{
final ServiceInvoker serviceInvoker = new ServiceInvoker(ServiceInvoker.dlqService) ;
serviceInvoker.deliverAsync(message) ;
}
catch (final MessageDeliverException mde)
{
LOGGER.warn("Failed to send response failure to DLQ service") ;
LOGGER.debug("Failed to send response failure to DLQ service", mde) ;
}
}
private static enum MessageType {
reply,
fault,
}
private void messageTo(EPR epr, Message message, MessageType messageType) {
if(epr instanceof LogicalEPR) {
try {
ServiceInvoker invoker = ((LogicalEPR)epr).getServiceInvoker();
invoker.deliverAsync(message);
} catch (MessageDeliverException e) {
LOGGER.error("Failed to send " + messageType + " to address " + epr
+ " for message "+message.getHeader(), e);
}
} else {
Courier courier = null;
try {
courier = CourierFactory.getCourier(epr);
courier.deliver(message);
} catch (final CourierException e) {
LOGGER.error("Failed to send " + messageType + " to address " + epr
+ " for message " + message.getHeader(), e);
} catch (final MalformedEPRException e) {
LOGGER.error("Failed to send " + messageType + " to address " + epr
+ " for message " + message.getHeader(), e);
} catch (final Throwable e) {
LOGGER.error("Failed to send " + messageType + " to address " + epr
+ " for message " + message.getHeader(), e);
} finally {
if (courier != null) {
CourierUtil.cleanCourier(courier);
}
}
}
}
/**
* Handle the destruction of the pipeline from the specified position.
*
* @param initialPosition
* The initial position to begin destruction.
*/
private void handleDestroy(final int initialPosition)
{
for (int count = initialPosition; count >= 0; count--)
{
final ActionLifecycle lifecycle = processors[count];
try
{
lifecycle.destroy();
}
catch (final Exception ex)
{
LOGGER
.warn(
"Unexpected exception during lifecycle destruction",
ex);
}
}
}
/**
* Notify the processors of an error during processing.
*
* @param initialPosition
* The position of the first processor to notify.
* @param ex
* The exception which caused the failure.
* @param messages
* The messages associated with successful processors.
*/
private void notifyException(final int initialPosition, final Exception ex,
final Message[] messages)
{
for (int count = initialPosition; count >= 0; count--)
{
final ActionPipelineProcessor processor = processors[count];
try
{
processor.processException(messages[count], ex);
}
catch (final Exception ex2)
{
LOGGER
.warn(
"Unexpected exception notifying processor of pipeline failure",
ex2);
}
}
}
/**
* Notify the processors of a successful pipeline process.
*
* @param messages
* The messages associated with the processors.
*/
private void notifySuccess(final Message[] messages)
{
for (int count = messages.length - 1; count >= 0; count--)
{
final Message message = messages[count];
if (message != null)
{
final ActionPipelineProcessor processor = processors[count];
try
{
processor.processSuccess(messages[count]);
}
catch (final Exception ex)
{
LOGGER
.warn(
"Unexpected exception notifying processor of pipeline success",
ex);
}
}
}
}
private boolean isServiceSecured()
{
return securityConf != null;
}
/**
* Retrieves the authentication reqeust from the Message context using
* {@link SecurityService#AUTH_REQUEST} as the key.
* <p/>
* This location may contain an encrypted AuthenticationRequest and if one
* exists it will be decryped and return. If one does not exist this
* method will return null.
*
* @param message The ESB Message object.
* @return {@link AuthenticationRequest} The decrypted AuthenticationRequest or null if one did not exist.
*
* @throws SecurityServiceException If a problem occurs during decryption.
*/
private AuthenticationRequest getAutenticationRequest(final Message message) throws SecurityServiceException
{
final byte[] encryptedAuthRequest = (byte[]) message.getContext().getContext(SecurityService.AUTH_REQUEST);
if (encryptedAuthRequest != null)
{
// store the encrypted auth request. Will be re-attached to outgoing messages.
AuthenticationRequestImpl.setEncryptedAuthRequest(encryptedAuthRequest);
return (AuthenticationRequest) PublicCryptoUtil.INSTANCE.decrypt(encryptedAuthRequest);
}
return null;
}
private SealedObject getSecurityContext(final Message message)
{
return (SealedObject) message.getContext().getContext(SecurityService.CONTEXT);
}
private PrivilegedAction<Boolean> getPrivilegedAction(final Message message)
{
// the work to be performed in the context of the authenticated caller
return new PrivilegedAction<Boolean>()
{
public Boolean run()
{
return processPipeline(message);
}
};
}
@SuppressWarnings("deprecation")
private Call createCallDetails(final Message message)
{
Call callDetails;
try
{
callDetails = new Call(message.getHeader().getCall());
}
catch (final URISyntaxException e)
{
LOGGER.error("Caught an URISyntaxException while calling Call's copy constructor. Will revert to using the old way using the copy method.", e);
callDetails = new Call();
callDetails.copy(message.getHeader().getCall()) ;
}
return callDetails;
}
/**
* Checks if the security config has a property named 'org.jboss.soa.esb.services.security.contextTimeout'
* specified. This will in that case be used as the SecurityContext timeout for this pipeline.
* If not specified the value will fallback to the value specified in jbossesb-properties.xml
*
* @param securityConfig The security configuration for this pipeline. This maps to the security element in jboss-esb.xml
* @return long Either the timeout value specified in the security element in jboss-esb.xml otherwise the value in jbossesb-properties.xml
* @throws SecurityServiceException If the value specified in jbossesb-properties.xml cannot be parsed. If the value in jboss-esb.xml cannot be parsed, only a warning will be issued.
*/
long getSecurityContextTimeout(final SecurityConfig securityConfig) throws SecurityServiceException
{
String timeoutStr = securityConfig.getProperties().get(Environment.SECURITY_SERVICE_CONTEXT_TIMEOUT);
if (timeoutStr != null)
{
try
{
return Long.parseLong(timeoutStr);
}
catch (final NumberFormatException ignore)
{
LOGGER.warn("Could not parse '" + timeoutStr +"' to a long. Please make sure the the value of the property '" + Environment.SECURITY_SERVICE_CONTEXT_TIMEOUT + "' in jbossesb-xml is a valid long(ms)");
// fallback to global configuration.
}
}
return SecurityContext.getConfigurationTimeout();
}
/**
* Propagates the security context by delegating to the current {@link SecurityContextPropagator}.
* This method returns silently if a SecurityContextPropagator has not been configured to avoid
* the overhead of decrypting the AuthenticationRequest (is needed).
*
* @param message The ESB message object.
* @param context The SecurityContext.
* @param authRequest The {@link AuthenticationRequest}.
* @throws SecurityServiceException
*/
private void propagateSecurityContext(final Message message, final SecurityContext context, final AuthenticationRequest authRequest) throws SecurityServiceException
{
if (securityContextPropagator == null)
{
// No need to do anything if a security context propagator was not configured.
return;
}
final AuthenticationRequest request;
if (authRequest == null)
{
final byte[] encryptedAuthRequest = (byte[]) message.getContext().getContext(SecurityService.AUTH_REQUEST);
if (encryptedAuthRequest == null)
{
// there might not be a authentication reqeust. Just return.
return;
}
request = (AuthenticationRequest) PublicCryptoUtil.INSTANCE.decrypt(encryptedAuthRequest);
}
else
{
// use the passed in authentication request.
request = authRequest;
}
securityContextPropagator.pushSecurityContext(context, request.getCredentials(), securityConf);
}
private void popSecurityContext(final SecurityContext securityContext)
{
if (securityContextPropagator != null)
{
securityContextPropagator.popSecurityContext(securityContext, securityConf);
}
}
}