/*
* $Id: AbstractConnector.java 22156 2011-06-08 21:36:30Z dfeist $
* --------------------------------------------------------------------------------------
* 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.transport;
import org.mule.MessageExchangePattern;
import org.mule.api.MessagingException;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.MuleRuntimeException;
import org.mule.api.config.MuleProperties;
import org.mule.api.config.ThreadingProfile;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.context.WorkManager;
import org.mule.api.context.WorkManagerSource;
import org.mule.api.context.notification.ServerNotification;
import org.mule.api.context.notification.ServerNotificationHandler;
import org.mule.api.endpoint.EndpointURI;
import org.mule.api.endpoint.ImmutableEndpoint;
import org.mule.api.endpoint.InboundEndpoint;
import org.mule.api.endpoint.OutboundEndpoint;
import org.mule.api.lifecycle.CreateException;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.lifecycle.LifecycleCallback;
import org.mule.api.lifecycle.LifecycleException;
import org.mule.api.lifecycle.LifecycleState;
import org.mule.api.processor.MessageProcessor;
import org.mule.api.registry.ServiceException;
import org.mule.api.registry.ServiceType;
import org.mule.api.retry.RetryCallback;
import org.mule.api.retry.RetryContext;
import org.mule.api.retry.RetryPolicyTemplate;
import org.mule.api.transformer.Transformer;
import org.mule.api.transport.Connectable;
import org.mule.api.transport.Connector;
import org.mule.api.transport.ConnectorException;
import org.mule.api.transport.DispatchException;
import org.mule.api.transport.MessageDispatcher;
import org.mule.api.transport.MessageDispatcherFactory;
import org.mule.api.transport.MessageReceiver;
import org.mule.api.transport.MessageRequester;
import org.mule.api.transport.MessageRequesterFactory;
import org.mule.api.transport.MuleMessageFactory;
import org.mule.api.transport.ReplyToHandler;
import org.mule.api.transport.SessionHandler;
import org.mule.config.i18n.CoreMessages;
import org.mule.config.i18n.MessageFactory;
import org.mule.context.notification.ConnectionNotification;
import org.mule.context.notification.EndpointMessageNotification;
import org.mule.context.notification.OptimisedNotificationHandler;
import org.mule.endpoint.EndpointAware;
import org.mule.endpoint.outbound.OutboundNotificationMessageProcessor;
import org.mule.model.streaming.DelegatingInputStream;
import org.mule.processor.OptionalAsyncInterceptingMessageProcessor;
import org.mule.processor.chain.DefaultMessageProcessorChainBuilder;
import org.mule.routing.filters.WildcardFilter;
import org.mule.session.SerializeAndEncodeSessionHandler;
import org.mule.transformer.TransformerUtils;
import org.mule.transport.service.TransportFactory;
import org.mule.transport.service.TransportServiceDescriptor;
import org.mule.transport.service.TransportServiceException;
import org.mule.util.ClassUtils;
import org.mule.util.CollectionUtils;
import org.mule.util.ObjectNameHelper;
import org.mule.util.ObjectUtils;
import org.mule.util.StringUtils;
import org.mule.util.concurrent.NamedThreadFactory;
import org.mule.util.concurrent.ThreadNameHelper;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.resource.spi.work.WorkEvent;
import javax.resource.spi.work.WorkListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
/**
* <code>AbstractConnector</code> provides base functionality for all connectors
* provided with Mule. Connectors are the mechanism used to connect to external
* systems and protocols in order to send and receive data.
* <p/>
* The <code>AbstractConnector</code> provides getter and setter methods for endpoint
* name, transport name and protocol. It also provides methods to stop and start
* connectors and sets up a dispatcher threadpool which allows deriving connectors
* the possibility to dispatch work to separate threads. This functionality is
* controlled with the <i> doThreading</i> property on the threadingProfiles for
* dispatchers and receivers. The lifecycle for a connector is -
* <ol>
* <li>Create
* <li>Initialise
* <li>Connect
* <li>Connect receivers
* <li>Start
* <li>Start Receivers
* <li>Stop
* <li>Stop Receivers
* <li>Disconnect
* <li>Disconnect Receivers
* <li>Dispose
* <li>Dispose Receivers
* </ol>
*/
public abstract class AbstractConnector implements Connector, WorkListener
{
/**
* Default number of concurrent transactional receivers.
*/
public static final int DEFAULT_NUM_CONCURRENT_TX_RECEIVERS = 4;
private static final long SCHEDULER_FORCED_SHUTDOWN_TIMEOUT = 5000l;
public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
/**
* logger used by this class
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* The name that identifies the endpoint
*/
protected volatile String name;
/**
* Factory used to create dispatchers for this connector
*/
protected volatile MessageDispatcherFactory dispatcherFactory;
/**
* Factory used to create requesters for this connector
*/
protected volatile MessageRequesterFactory requesterFactory;
/**
* Factory used to create new {@link MuleMessage} instances
*/
protected MuleMessageFactory muleMessageFactory;
/**
* A pool of dispatchers for this connector, keyed by endpoint
*/
protected volatile ConfigurableKeyedObjectPool dispatchers;
/**
* A factory for creating the pool of dispatchers for this connector.
*/
protected volatile ConfigurableKeyedObjectPoolFactory dispatcherPoolFactory;
/**
* A pool of requesters for this connector, keyed by endpoint
*/
protected final GenericKeyedObjectPool requesters = new GenericKeyedObjectPool();
/**
* The collection of listeners on this connector. Keyed by entrypoint
*/
@SuppressWarnings("unchecked")
protected final Map<Object, MessageReceiver> receivers = new ConcurrentHashMap/* <Object, MessageReceiver> */();
/**
* Defines the dispatcher threading profile
*/
private volatile ThreadingProfile dispatcherThreadingProfile;
/**
* Defines the requester threading profile
*/
private volatile ThreadingProfile requesterThreadingProfile;
/**
* Defines the receiver threading profile
*/
private volatile ThreadingProfile receiverThreadingProfile;
/**
* @see #isCreateMultipleTransactedReceivers()
*/
protected volatile boolean createMultipleTransactedReceivers = true;
/**
* @see #getNumberOfConcurrentTransactedReceivers()
*/
protected volatile int numberOfConcurrentTransactedReceivers = DEFAULT_NUM_CONCURRENT_TX_RECEIVERS;
private RetryPolicyTemplate retryPolicyTemplate;
/**
* Optimise the handling of message notifications. If dynamic is set to false
* then the cached notification handler implements a shortcut for message
* notifications.
*/
private boolean dynamicNotification = false;
private ServerNotificationHandler cachedNotificationHandler;
private final List<String> supportedProtocols;
/**
* A shared work manager for all receivers registered with this connector.
*/
private final AtomicReference/* <WorkManager> */receiverWorkManager = new AtomicReference();
/**
* A shared work manager for all requesters created for this connector.
*/
private final AtomicReference/* <WorkManager> */dispatcherWorkManager = new AtomicReference();
/**
* A shared work manager for all requesters created for this connector.
*/
private final AtomicReference/* <WorkManager> */requesterWorkManager = new AtomicReference();
/**
* A generic scheduling service for tasks that need to be performed periodically.
*/
private ScheduledExecutorService scheduler;
/**
* Holds the service configuration for this connector
*/
protected volatile TransportServiceDescriptor serviceDescriptor;
/**
* The map of service overrides that can be used to extend the capabilities of
* the connector
*/
protected volatile Properties serviceOverrides;
/**
* The strategy used for reading and writing session information to and from the
* transport
*/
protected volatile SessionHandler sessionHandler = new SerializeAndEncodeSessionHandler();
protected MuleContext muleContext;
protected ConnectorLifecycleManager lifecycleManager;
// TODO connect and disconnect are not part of lifecycle management right now
private AtomicBoolean connected = new AtomicBoolean(false);
private AtomicBoolean connecting = new AtomicBoolean(false);
/**
* Indicates whether the connector should start upon connecting. This is
* necessary to support asynchronous retry policies, otherwise the start() method
* would block until connection is successful.
*/
protected boolean startOnConnect = false;
/**
* The will cause the connector not to start when {@link #start()} is called. The
* only way to start the connector is to call
* {@link #setInitialStateStopped(boolean)} with 'false' and then calling
* {@link #start()}. This flag is used internally since some connectors that rely
* on external servers may need to wait for that server to become available
* before starting
*/
protected boolean initialStateStopped = false;
/**
* Whether to test a connection on each take.
*/
private boolean validateConnections = true;
public AbstractConnector(MuleContext context)
{
muleContext = context;
lifecycleManager = new ConnectorLifecycleManager(this);
setDynamicNotification(false);
updateCachedNotificationHandler();
// always add at least the default protocol
supportedProtocols = new ArrayList<String>();
supportedProtocols.add(getProtocol().toLowerCase());
// TODO dispatcher pool configuration should be extracted, maybe even
// moved into the factory?
// NOTE: testOnBorrow MUST be FALSE. this is a bit of a design bug in
// commons-pool since validate is used for both activation and passivation,
// but has no way of knowing which way it is going.
requesters.setTestOnBorrow(false);
requesters.setTestOnReturn(true);
}
public String getName()
{
return name;
}
public void setName(String newName)
{
if (newName == null)
{
throw new IllegalArgumentException(CoreMessages.objectIsNull("Connector name").toString());
}
if (logger.isDebugEnabled())
{
logger.debug("Set Connector name to: " + newName);
}
name = newName;
}
//-----------------------------------------------------------------------------------------------//
//- LIFECYCLE METHODS
//-----------------------------------------------------------------------------------------------//
ConnectorLifecycleManager getLifecycleManager()
{
return lifecycleManager;
}
public LifecycleState getLifecycleState()
{
return lifecycleManager.getState();
}
public final synchronized void initialise() throws InitialisationException
{
try
{
lifecycleManager.fireInitialisePhase(new LifecycleCallback<Connector>()
{
public void onTransition(String phaseName, Connector object) throws MuleException
{
if (retryPolicyTemplate == null)
{
retryPolicyTemplate = (RetryPolicyTemplate) muleContext.getRegistry().lookupObject(
MuleProperties.OBJECT_DEFAULT_RETRY_POLICY_TEMPLATE);
}
if (dispatcherPoolFactory == null) {
dispatcherPoolFactory = new DefaultConfigurableKeyedObjectPoolFactory();
}
dispatchers = dispatcherPoolFactory.createObjectPool();
if (dispatcherFactory != null) {
dispatchers.setFactory(getWrappedDispatcherFactory(dispatcherFactory));
}
// Initialise the structure of this connector
initFromServiceDescriptor();
configureDispatcherPool();
setMaxRequestersActive(getRequesterThreadingProfile().getMaxThreadsActive());
doInitialise();
try
{
initWorkManagers();
}
catch (MuleException e)
{
throw new LifecycleException(e, this);
}
}
});
}
catch (InitialisationException e)
{
throw e;
}
catch (LifecycleException e)
{
throw new InitialisationException(e, this);
}
catch (MuleException e)
{
e.printStackTrace();
}
}
// Start (but we might not be connected yet).
public final synchronized void start() throws MuleException
{
if (isInitialStateStopped())
{
logger.info("Connector not started because 'initialStateStopped' is true");
return;
}
if (!isConnected())
{
try
{
// startAfterConnect() will get called from the connect() method once connected.
// This is necessary for reconnection strategies.
startOnConnect = true;
connect();
}
catch (MuleException me)
{
throw me;
}
catch (Exception e)
{
throw new ConnectException(e, this);
}
}
else
{
startAfterConnect();
}
}
// Start now that we're sure we're connected.
protected synchronized void startAfterConnect() throws MuleException
{
// Reset this flag if it was set
startOnConnect = false;
// This breaks ConnectorLifecycleTestCase.testDoubleStartConnector()
//if (isStarted())
//{
// return;
//}
if (logger.isInfoEnabled())
{
logger.info("Starting: " + this);
}
lifecycleManager.fireStartPhase(new LifecycleCallback<Connector>()
{
public void onTransition(String phaseName, Connector object) throws MuleException
{
initWorkManagers();
scheduler = createScheduler();
doStart();
if (receivers != null)
{
for (MessageReceiver receiver : receivers.values())
{
final List<MuleException> errors = new ArrayList<MuleException>();
try
{
if (logger.isDebugEnabled())
{
logger.debug("Starting receiver on endpoint: "
+ receiver.getEndpoint().getEndpointURI());
}
if (receiver.getFlowConstruct().getLifecycleState().isStarted())
{
receiver.start();
}
}
catch (MuleException e)
{
logger.error(e);
errors.add(e);
}
if (!errors.isEmpty())
{
// throw the first one in order not to break the reconnection
// strategy logic,
// every exception has been logged above already
// api needs refactoring to support the multi-cause exception
// here
throw errors.get(0);
}
}
}
}
});
}
public final synchronized void stop() throws MuleException
{
// This breaks ConnectorLifecycleTestCase.testDoubleStopConnector()
//if (isStopped() || isStopping())
//{
// return;
//}
lifecycleManager.fireStopPhase(new LifecycleCallback<Connector>()
{
public void onTransition(String phaseName, Connector object) throws MuleException
{
// shutdown our scheduler service
shutdownScheduler();
doStop();
// Stop all the receivers on this connector
if (receivers != null)
{
for (MessageReceiver receiver : receivers.values())
{
if (logger.isDebugEnabled())
{
logger.debug("Stopping receiver on endpoint: " + receiver.getEndpoint().getEndpointURI());
}
receiver.stop();
}
}
// Now that dispatchers are borrowed/returned in worker thread we need to
// dispose workManager before clearing object pools
disposeWorkManagers();
// Workaround for MULE-4553
clearDispatchers();
clearRequesters();
// make sure the scheduler is gone
scheduler = null;
}
});
}
public final synchronized void dispose()
{
try
{
if (isStarted())
{
stop();
}
if (isConnected())
{
disconnect();
}
}
catch (Exception e)
{
logger.warn(e.getMessage(), e);
}
try
{
lifecycleManager.fireDisposePhase(new LifecycleCallback<Connector>()
{
public void onTransition(String phaseName, Connector object) throws MuleException
{
doDispose();
disposeReceivers();
}
});
}
catch (MuleException e)
{
logger.warn(e.getMessage(), e);
}
}
public final boolean isStarted()
{
return lifecycleManager.getState().isStarted();
}
public final boolean isStarting()
{
return lifecycleManager.getState().isStarting();
}
public boolean isInitialised()
{
return lifecycleManager.getState().isInitialised();
}
public boolean isStopped()
{
return lifecycleManager.getState().isStopped();
}
public boolean isStopping()
{
return lifecycleManager.getState().isStopping();
}
//-----------------------------------------------------------------------------------------------//
//- END LIFECYCLE METHODS
//-----------------------------------------------------------------------------------------------//
protected void configureDispatcherPool()
{
// Normally having a the same maximum number of dispatcher objects as threads
// is ok.
int maxDispatchersActive = getDispatcherThreadingProfile().getMaxThreadsActive();
// BUT if the WHEN_EXHAUSTED_RUN threading profile exhausted action is
// configured then a single
// additional dispatcher is required that can be used by the caller thread
// when it executes job's itself.
// Also See: MULE-4752
if (ThreadingProfile.WHEN_EXHAUSTED_RUN == getDispatcherThreadingProfile().getPoolExhaustedAction())
{
maxDispatchersActive++;
}
setMaxDispatchersActive(maxDispatchersActive);
}
/**
* <p>
* Create a {@link MuleMessageFactory} from this connector's configuration,
* typically through the transport descriptor.
* </p>
* <p/>
* <b>Attention!</b> This method is not meant to be used by client code directly.
* It is only publicly available to service message receivers which should be
* used as <em>real</em> factories to create {@link MuleMessage} instances.
*
* @see MessageReceiver#createMuleMessage(Object)
* @see MessageReceiver#createMuleMessage(Object, String)
*/
public MuleMessageFactory createMuleMessageFactory() throws CreateException
{
try
{
return serviceDescriptor.createMuleMessageFactory();
}
catch (TransportServiceException tse)
{
throw new CreateException(CoreMessages.failedToCreate("MuleMessageFactory"), tse, this);
}
}
protected void shutdownScheduler()
{
if (scheduler != null)
{
// Disable new tasks from being submitted
scheduler.shutdown();
try
{
// Wait a while for existing tasks to terminate
if (!scheduler.awaitTermination(muleContext.getConfiguration().getShutdownTimeout(),
TimeUnit.MILLISECONDS))
{
// Cancel currently executing tasks and return list of pending
// tasks
List outstanding = scheduler.shutdownNow();
// Wait a while for tasks to respond to being cancelled
if (!scheduler.awaitTermination(SCHEDULER_FORCED_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS))
{
logger.warn(MessageFormat.format(
"Pool {0} did not terminate in time; {1} work items were cancelled.", name,
outstanding.isEmpty() ? "No" : Integer.toString(outstanding.size())));
}
else
{
if (!outstanding.isEmpty())
{
logger.warn(MessageFormat.format(
"Pool {0} terminated; {1} work items were cancelled.", name,
Integer.toString(outstanding.size())));
}
}
}
}
catch (InterruptedException ie)
{
// (Re-)Cancel if current thread also interrupted
scheduler.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
finally
{
scheduler = null;
}
}
}
protected void initWorkManagers() throws MuleException
{
if (receiverWorkManager.get() == null)
{
final String threadPrefix = ThreadNameHelper.receiver(muleContext, getName());
WorkManager newWorkManager = this.getReceiverThreadingProfile().createWorkManager(
threadPrefix, muleContext.getConfiguration().getShutdownTimeout());
if (receiverWorkManager.compareAndSet(null, newWorkManager))
{
newWorkManager.start();
}
}
if (dispatcherWorkManager.get() == null)
{
ThreadingProfile dispatcherThreadingProfile = this.getDispatcherThreadingProfile();
if (dispatcherThreadingProfile.getMuleContext() == null)
{
dispatcherThreadingProfile.setMuleContext(muleContext);
}
final String threadPrefix = ThreadNameHelper.dispatcher(muleContext, getName());
WorkManager newWorkManager = dispatcherThreadingProfile.createWorkManager(
threadPrefix, muleContext.getConfiguration().getShutdownTimeout());
if (dispatcherWorkManager.compareAndSet(null, newWorkManager))
{
newWorkManager.start();
}
}
if (requesterWorkManager.get() == null)
{
final String threadPrefix = ThreadNameHelper.requester(muleContext, getName());
WorkManager newWorkManager = this.getRequesterThreadingProfile().createWorkManager(
threadPrefix, muleContext.getConfiguration().getShutdownTimeout());
if (requesterWorkManager.compareAndSet(null, newWorkManager))
{
newWorkManager.start();
}
}
}
protected void disposeWorkManagers()
{
WorkManager workManager;
logger.debug("Disposing dispatcher work manager");
workManager = (WorkManager) dispatcherWorkManager.get();
if (workManager != null)
{
workManager.dispose();
}
dispatcherWorkManager.set(null);
logger.debug("Disposing requester work manager");
workManager = (WorkManager) requesterWorkManager.get();
if (workManager != null)
{
workManager.dispose();
}
requesterWorkManager.set(null);
logger.debug("Disposing receiver work manager");
workManager = (WorkManager) receiverWorkManager.get();
if (workManager != null)
{
workManager.dispose();
}
receiverWorkManager.set(null);
}
protected void disposeReceivers()
{
if (receivers != null)
{
logger.debug("Disposing Receivers");
for (MessageReceiver receiver : receivers.values())
{
try
{
this.destroyReceiver(receiver, receiver.getEndpoint());
}
catch (Throwable e)
{
// Just log when disposing
logger.error("Failed to destroy receiver: " + receiver, e);
}
}
receivers.clear();
logger.debug("Receivers Disposed");
}
}
protected void clearDispatchers()
{
if (dispatchers != null)
{
logger.debug("Clearing Dispatcher pool");
synchronized (dispatchers)
{
dispatchers.clear();
}
logger.debug("Dispatcher pool cleared");
}
}
protected void clearRequesters()
{
if (requesters != null)
{
logger.debug("Clearing Requester pool");
requesters.clear();
logger.debug("Requester pool cleared");
}
}
public boolean isDisposed()
{
return lifecycleManager.getState().isDisposed();
}
/**
* @return Returns the dispatcherFactory.
*/
public MessageDispatcherFactory getDispatcherFactory()
{
return dispatcherFactory;
}
/**
* @param dispatcherFactory The dispatcherFactory to set.
*/
public void setDispatcherFactory(MessageDispatcherFactory dispatcherFactory)
{
KeyedPoolableObjectFactory poolFactory = getWrappedDispatcherFactory(dispatcherFactory);
if (dispatchers != null) {
this.dispatchers.setFactory(poolFactory);
}
// we keep a reference to the unadapted factory, otherwise people might end
// up with ClassCastExceptions on downcast to their implementation (sigh)
this.dispatcherFactory = dispatcherFactory;
}
private KeyedPoolableObjectFactory getWrappedDispatcherFactory(MessageDispatcherFactory dispatcherFactory)
{
KeyedPoolableObjectFactory poolFactory;
if (dispatcherFactory instanceof KeyedPoolableObjectFactory)
{
poolFactory = (KeyedPoolableObjectFactory) dispatcherFactory;
}
else
{
// need to adapt the factory for use by commons-pool
poolFactory = new KeyedPoolMessageDispatcherFactoryAdapter(dispatcherFactory);
}
return poolFactory;
}
/**
* @return Returns the requesterFactory.
*/
public MessageRequesterFactory getRequesterFactory()
{
return requesterFactory;
}
/**
* @param requesterFactory The requesterFactory to set.
*/
public void setRequesterFactory(MessageRequesterFactory requesterFactory)
{
KeyedPoolableObjectFactory poolFactory;
if (requesterFactory instanceof KeyedPoolableObjectFactory)
{
poolFactory = (KeyedPoolableObjectFactory) requesterFactory;
}
else
{
// need to adapt the factory for use by commons-pool
poolFactory = new KeyedPoolMessageRequesterFactoryAdapter(requesterFactory);
}
requesters.setFactory(poolFactory);
// we keep a reference to the unadapted factory, otherwise people might end
// up with ClassCastExceptions on downcast to their implementation (sigh)
this.requesterFactory = requesterFactory;
}
/**
* <p>The connector creates a {@link MuleMessageFactory} lazily and holds a reference to it for
* others to use.</p>
* <p>The typical use case is to share a single {@link MuleMessageFactory} between all
* {@link MessageDispatcher}, {@link MessageReceiver} and {@link MessageRequester} instances
* belonging to this connector.</p>
*/
public MuleMessageFactory getMuleMessageFactory() throws CreateException
{
if (muleMessageFactory == null)
{
muleMessageFactory = createMuleMessageFactory();
}
return muleMessageFactory;
}
/**
* The will cause the connector not to start when {@link #start()} is called. The
* only way to start the connector is to call
* {@link #setInitialStateStopped(boolean)} with 'false' and then calling
* {@link #start()}. This flag is used internally since some connectors that rely
* on external servers may need to wait for that server to become available
* before starting.
*
* @return true if the connector is not to be started with normal lifecycle,
* flase otherwise
* @since 3.0.0
*/
public boolean isInitialStateStopped()
{
return initialStateStopped;
}
/**
* The will cause the connector not to start when {@link #start()} is called. The
* only way to start the connector is to call
* {@link #setInitialStateStopped(boolean)} with 'false' and then calling
* {@link #start()}. This flag is used internally since some connectors that rely
* on external servers may need to wait for that server to become available
* before starting. The only time this method should be used is when a
* subclassing connector needs to delay the start lifecycle due to a dependence
* on an external system. Most users can ignore this.
*
* @param initialStateStopped true to stop the connector starting through normal lifecycle. It will
* be the responsibility of the code that sets this property to start the
* connector
* @since 3.0.0
*/
public void setInitialStateStopped(boolean initialStateStopped)
{
this.initialStateStopped = initialStateStopped;
}
/**
* Returns the maximum number of dispatchers that can be concurrently active per
* endpoint.
*
* @return max. number of active dispatchers
*/
public int getMaxDispatchersActive()
{
checkDispatchersInitialised();
return this.dispatchers.getMaxActive();
}
/**
* Checks if the connector was initialised or not. Some connectors fields are created
* during connector's initialization so this check should be used before accessing them.
*/
private void checkDispatchersInitialised()
{
if (dispatchers == null)
{
throw new IllegalStateException("Dispatchers pool was not initialised");
}
}
/**
* Returns the maximum number of dispatchers that can be concurrently active for
* all endpoints.
*
* @return max. total number of active dispatchers
*/
public int getMaxTotalDispatchers()
{
checkDispatchersInitialised();
return this.dispatchers.getMaxTotal();
}
/**
* Configures the maximum number of dispatchers that can be concurrently active
* per endpoint
*
* @param maxActive max. number of active dispatchers
*/
public void setMaxDispatchersActive(int maxActive)
{
checkDispatchersInitialised();
this.dispatchers.setMaxActive(maxActive);
// adjust maxIdle in tandem to avoid thrashing
this.dispatchers.setMaxIdle(maxActive);
// this tells the pool to expire some objects eventually if we start
// running out. This happens if one is using a lot of dynamic endpoints.
this.dispatchers.setMaxTotal(20 * maxActive);
}
private MessageDispatcher getDispatcher(OutboundEndpoint endpoint) throws MuleException
{
if (!isStarted())
{
throw new LifecycleException(CoreMessages.lifecycleErrorCannotUseConnector(getName(),
lifecycleManager.getCurrentPhase()), this);
}
if (endpoint == null)
{
throw new IllegalArgumentException("Endpoint must not be null");
}
if (!supportsProtocol(endpoint.getConnector().getProtocol()))
{
throw new IllegalArgumentException(CoreMessages.connectorSchemeIncompatibleWithEndpointScheme(
this.getProtocol(), endpoint.getEndpointURI().toString()).getMessage());
}
MessageDispatcher dispatcher = null;
try
{
if (logger.isDebugEnabled())
{
logger.debug("Borrowing a dispatcher for endpoint: " + endpoint.getEndpointURI());
}
dispatcher = (MessageDispatcher) dispatchers.borrowObject(endpoint);
if (logger.isDebugEnabled())
{
logger.debug("Borrowed a dispatcher for endpoint: " + endpoint.getEndpointURI() + " = "
+ dispatcher.toString());
}
return dispatcher;
}
catch (Exception ex)
{
throw new ConnectorException(CoreMessages.connectorCausedError(), this, ex);
}
finally
{
try
{
if (logger.isDebugEnabled())
{
logger.debug("Borrowed dispatcher: " + ObjectUtils.toString(dispatcher, "null"));
}
}
catch (Exception ex)
{
throw new ConnectorException(CoreMessages.connectorCausedError(), this, ex);
}
}
}
private void returnDispatcher(OutboundEndpoint endpoint, MessageDispatcher dispatcher)
{
if (endpoint != null && dispatcher != null)
{
if (logger.isDebugEnabled())
{
logger.debug("Returning dispatcher for endpoint: " + endpoint.getEndpointURI() + " = "
+ dispatcher.toString());
}
try
{
dispatchers.returnObject(endpoint, dispatcher);
}
catch (Exception e)
{
// ignore - if the dispatcher is broken, it will likely get cleaned
// up by the factory
logger.error("Failed to dispose dispatcher for endpoint: " + endpoint
+ ". This will cause a memory leak. Please report to", e);
}
}
}
/**
* Returns the maximum number of requesters that can be concurrently active per
* endpoint.
*
* @return max. number of active requesters
*/
public int getMaxRequestersActive()
{
return this.requesters.getMaxActive();
}
/**
* Configures the maximum number of requesters that can be concurrently active
* per endpoint
*
* @param maxActive max. number of active requesters
*/
public void setMaxRequestersActive(int maxActive)
{
this.requesters.setMaxActive(maxActive);
// adjust maxIdle in tandem to avoid thrashing
this.requesters.setMaxIdle(maxActive);
// this tells the pool to expire some objects eventually if we start
// running out. This happens if one is using a lot of dynamic endpoints.
this.requesters.setMaxTotal(20 * maxActive);
}
private MessageRequester getRequester(InboundEndpoint endpoint) throws MuleException
{
if (!isStarted())
{
throw new LifecycleException(CoreMessages.lifecycleErrorCannotUseConnector(getName(),
lifecycleManager.getCurrentPhase()), this);
}
if (endpoint == null)
{
throw new IllegalArgumentException("Endpoint must not be null");
}
if (!supportsProtocol(endpoint.getConnector().getProtocol()))
{
throw new IllegalArgumentException(CoreMessages.connectorSchemeIncompatibleWithEndpointScheme(
this.getProtocol(), endpoint.getEndpointURI().toString()).getMessage());
}
MessageRequester requester = null;
try
{
if (logger.isDebugEnabled())
{
logger.debug("Borrowing a requester for endpoint: " + endpoint.getEndpointURI());
}
requester = (MessageRequester) requesters.borrowObject(endpoint);
if (logger.isDebugEnabled())
{
logger.debug("Borrowed a requester for endpoint: " + endpoint.getEndpointURI() + " = "
+ requester.toString());
}
return requester;
}
catch (Exception ex)
{
throw new ConnectorException(CoreMessages.connectorCausedError(), this, ex);
}
finally
{
try
{
if (logger.isDebugEnabled())
{
logger.debug("Borrowed requester: " + ObjectUtils.toString(requester, "null"));
}
}
catch (Exception ex)
{
throw new ConnectorException(CoreMessages.connectorCausedError(), this, ex);
}
}
}
private void returnRequester(InboundEndpoint endpoint, MessageRequester requester)
{
if (endpoint != null && requester != null)
{
if (logger.isDebugEnabled())
{
logger.debug("Returning requester for endpoint: " + endpoint.getEndpointURI() + " = "
+ requester.toString());
}
try
{
requesters.returnObject(endpoint, requester);
}
catch (Exception e)
{
// ignore - if the requester is broken, it will likely get cleaned
// up by the factory
logger.error("Failed to dispose requester for endpoint: " + endpoint
+ ". This will cause a memory leak. Please report to", e);
}
}
}
public void registerListener(InboundEndpoint endpoint,
MessageProcessor messageProcessorChain,
FlowConstruct flowConstruct) throws Exception
{
if (endpoint == null)
{
throw new IllegalArgumentException("The endpoint cannot be null when registering a listener");
}
if (messageProcessorChain == null)
{
throw new IllegalArgumentException("The messageProcessorChain cannot be null when registering a listener");
}
EndpointURI endpointUri = endpoint.getEndpointURI();
if (endpointUri == null)
{
throw new ConnectorException(CoreMessages.endpointIsNullForListener(), this);
}
logger.info("Registering listener: " + flowConstruct.getName() + " on endpointUri: "
+ endpointUri.toString());
if (getReceiver(flowConstruct, endpoint) != null)
{
throw new ConnectorException(CoreMessages.listenerAlreadyRegistered(endpointUri), this);
}
MessageReceiver receiver = createReceiver(flowConstruct, endpoint);
receiver.setListener(messageProcessorChain);
Object receiverKey = getReceiverKey(flowConstruct, endpoint);
receiver.setReceiverKey(receiverKey.toString());
// Since we're managing the creation we also need to initialise
receiver.initialise();
receivers.put(receiverKey, receiver);
if (isConnected())
{
receiver.connect();
}
if (isStarted())
{
receiver.start();
}
}
/**
* The method determines the key used to store the receiver against.
*
* @param flowConstruct the service for which the endpoint is being registered
* @param endpoint the endpoint being registered for the service
* @return the key to store the newly created receiver against
*/
protected Object getReceiverKey(FlowConstruct flowConstruct, InboundEndpoint endpoint)
{
return StringUtils.defaultIfEmpty(endpoint.getEndpointURI().getFilterAddress(),
endpoint.getEndpointURI().getAddress());
}
public final void unregisterListener(InboundEndpoint endpoint, FlowConstruct flowConstruct) throws Exception
{
if (endpoint == null)
{
throw new IllegalArgumentException("The endpoint must not be null when you unregister a listener");
}
EndpointURI endpointUri = endpoint.getEndpointURI();
if (endpointUri == null)
{
throw new IllegalArgumentException(
"The endpointUri must not be null when you unregister a listener");
}
if (logger.isInfoEnabled())
{
logger.info("Removing listener on endpointUri: " + endpointUri);
}
if (receivers != null && !receivers.isEmpty())
{
MessageReceiver receiver = receivers.remove(getReceiverKey(flowConstruct, endpoint));
if (receiver != null)
{
// This will automatically stop and disconnect before disposing.
destroyReceiver(receiver, endpoint);
doUnregisterListener(flowConstruct, endpoint, receiver);
}
}
}
protected void doUnregisterListener(FlowConstruct flowConstruct, InboundEndpoint endpoint, MessageReceiver receiver)
{
// Template method
}
/**
* Getter for property 'dispatcherThreadingProfile'.
*
* @return Value for property 'dispatcherThreadingProfile'.
*/
public ThreadingProfile getDispatcherThreadingProfile()
{
if (dispatcherThreadingProfile == null && muleContext != null)
{
dispatcherThreadingProfile = muleContext.getDefaultMessageDispatcherThreadingProfile();
}
return dispatcherThreadingProfile;
}
/**
* Setter for property 'dispatcherThreadingProfile'.
*
* @param dispatcherThreadingProfile Value to set for property
* 'dispatcherThreadingProfile'.
*/
public void setDispatcherThreadingProfile(ThreadingProfile dispatcherThreadingProfile)
{
this.dispatcherThreadingProfile = dispatcherThreadingProfile;
}
/**
* Getter for property 'requesterThreadingProfile'.
*
* @return Value for property 'requesterThreadingProfile'.
*/
public ThreadingProfile getRequesterThreadingProfile()
{
if (requesterThreadingProfile == null && muleContext != null)
{
requesterThreadingProfile = muleContext.getDefaultMessageRequesterThreadingProfile();
}
return requesterThreadingProfile;
}
/**
* Setter for property 'requesterThreadingProfile'.
*
* @param requesterThreadingProfile Value to set for property
* 'requesterThreadingProfile'.
*/
public void setRequesterThreadingProfile(ThreadingProfile requesterThreadingProfile)
{
this.requesterThreadingProfile = requesterThreadingProfile;
}
/**
* Getter for property 'receiverThreadingProfile'.
*
* @return Value for property 'receiverThreadingProfile'.
*/
public ThreadingProfile getReceiverThreadingProfile()
{
if (receiverThreadingProfile == null && muleContext != null)
{
receiverThreadingProfile = muleContext.getDefaultMessageReceiverThreadingProfile();
}
return receiverThreadingProfile;
}
/**
* Setter for property 'receiverThreadingProfile'.
*
* @param receiverThreadingProfile Value to set for property
* 'receiverThreadingProfile'.
*/
public void setReceiverThreadingProfile(ThreadingProfile receiverThreadingProfile)
{
this.receiverThreadingProfile = receiverThreadingProfile;
}
public void destroyReceiver(MessageReceiver receiver, ImmutableEndpoint endpoint) throws Exception
{
receiver.dispose();
}
protected abstract void doInitialise() throws InitialisationException;
/**
* Template method to perform any work when destroying the connectoe
*/
protected abstract void doDispose();
/**
* Template method to perform any work when starting the connectoe
*
* @throws MuleException if the method fails
*/
protected abstract void doStart() throws MuleException;
/**
* Template method to perform any work when stopping the connectoe
*
* @throws MuleException if the method fails
*/
protected abstract void doStop() throws MuleException;
public List<Transformer> getDefaultInboundTransformers(ImmutableEndpoint endpoint)
{
if (serviceDescriptor == null)
{
throw new RuntimeException("serviceDescriptor not initialized");
}
return TransformerUtils.getDefaultInboundTransformers(serviceDescriptor, endpoint);
}
public List<Transformer> getDefaultResponseTransformers(ImmutableEndpoint endpoint)
{
if (serviceDescriptor == null)
{
throw new RuntimeException("serviceDescriptor not initialized");
}
return TransformerUtils.getDefaultResponseTransformers(serviceDescriptor, endpoint);
}
public List<Transformer> getDefaultOutboundTransformers(ImmutableEndpoint endpoint)
{
if (serviceDescriptor == null)
{
throw new RuntimeException("serviceDescriptor not initialized");
}
return TransformerUtils.getDefaultOutboundTransformers(serviceDescriptor, endpoint);
}
/**
* Getter for property 'replyToHandler'.
*
* @return Value for property 'replyToHandler'.
*/
public ReplyToHandler getReplyToHandler(ImmutableEndpoint endpoint)
{
return new DefaultReplyToHandler(getDefaultResponseTransformers(endpoint), muleContext);
}
/**
* Fires a server notification to all registered listeners
*
* @param notification the notification to fire.
*/
public void fireNotification(ServerNotification notification)
{
cachedNotificationHandler.fireNotification(notification);
}
public boolean isResponseEnabled()
{
return false;
}
public MessageReceiver getReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint)
{
if (receivers != null)
{
Object key = getReceiverKey(flowConstruct, endpoint);
if (key != null)
{
return receivers.get(key);
}
else
{
throw new RuntimeException("getReceiverKey() returned a null key");
}
}
else
{
throw new RuntimeException("Connector has not been initialized.");
}
}
/**
* Getter for property 'receivers'.
*
* @return Value for property 'receivers'.
*/
public Map<Object, MessageReceiver> getReceivers()
{
return Collections.unmodifiableMap(receivers);
}
public MessageReceiver lookupReceiver(String key)
{
if (key != null)
{
return receivers.get(key);
}
else
{
throw new IllegalArgumentException("Receiver key must not be null");
}
}
public MessageReceiver[] getReceivers(String wildcardExpression)
{
WildcardFilter filter = new WildcardFilter(wildcardExpression);
filter.setCaseSensitive(false);
List<MessageReceiver> found = new ArrayList<MessageReceiver>();
for (Map.Entry<Object, MessageReceiver> e : receivers.entrySet())
{
if (filter.accept(e.getKey()))
{
found.add(e.getValue());
}
}
return CollectionUtils.toArrayOfComponentType(found, MessageReceiver.class);
}
public void connect() throws Exception
{
if (lifecycleManager.getState().isDisposed())
{
throw new LifecycleException(CoreMessages.lifecycleErrorCannotUseConnector(getName(),
lifecycleManager.getCurrentPhase()), this);
}
if (isConnected())
{
return;
}
RetryCallback callback = new RetryCallback()
{
public void doWork(RetryContext context) throws Exception
{
// Try validateConnection() rather than connect() which may be a less expensive operation while we're retrying.
if (validateConnections && context.getLastFailure() instanceof ConnectException)
{
Connectable failed = ((ConnectException) context.getLastFailure()).getFailed();
if (!failed.validateConnection(context).isOk())
{
throw new ConnectException(
MessageFactory.createStaticMessage("Still unable to connect to resource " + failed.getClass().getName()),
context.getLastFailure(), failed);
}
}
doConnect();
if (receivers != null)
{
for (MessageReceiver receiver : receivers.values())
{
final List<MuleException> errors = new ArrayList<MuleException>();
try
{
if (logger.isDebugEnabled())
{
logger.debug("Connecting receiver on endpoint: " + receiver.getEndpoint().getEndpointURI());
}
receiver.connect();
}
catch (MuleException e)
{
logger.error(e);
errors.add(e);
}
if (!errors.isEmpty())
{
// throw the first one in order not to break the reconnection
// strategy logic,
// every exception has been logged above already
// api needs refactoring to support the multi-cause exception
// here
throw errors.get(0);
}
}
}
setConnected(true);
setConnecting(false);
logger.info("Connected: " + getWorkDescription());
if (startOnConnect && !isStarted() && !isStarting())
{
startAfterConnect();
}
}
public String getWorkDescription()
{
return getConnectionDescription();
}
};
if (connecting.compareAndSet(false, true))
{
if (logger.isDebugEnabled())
{
logger.debug("Connecting: " + this);
}
retryPolicyTemplate.execute(callback, muleContext.getWorkManager());
}
}
/**
* Override this method to test whether the connector is able to connect to its
* resource(s). This will allow a retry policy to go into effect in the case of
* failure.
*
* @return retry context with a success flag or failure details
* @see RetryContext#isOk()
* @see RetryContext#getLastFailure()
*/
public RetryContext validateConnection(RetryContext retryContext)
{
retryContext.setOk();
return retryContext;
}
public void disconnect() throws Exception
{
if (isStarted())
{
startOnConnect = true;
stop();
}
if (receivers != null)
{
for (MessageReceiver receiver : receivers.values())
{
if (logger.isDebugEnabled())
{
logger.debug("Disconnecting receiver on endpoint: "
+ receiver.getEndpoint().getEndpointURI());
}
try
{
receiver.disconnect();
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
}
}
}
try
{
if (isStarted() && !isStopping())
{
stop();
}
this.doDisconnect();
if (logger.isInfoEnabled())
{
logger.info("Disconnected: " + this.getConnectionDescription());
}
this.fireNotification(new ConnectionNotification(this, getConnectEventId(),
ConnectionNotification.CONNECTION_DISCONNECTED));
}
catch (Exception e)
{
logger.error(e.getMessage());
}
finally
{
connected.set(false);
}
}
public String getConnectionDescription()
{
return this.toString();
}
public final boolean isConnected()
{
return connected.get();
}
public final void setConnected(boolean flag)
{
connected.set(flag);
}
public final void setConnecting(boolean flag)
{
connecting.set(flag);
}
public final boolean isConnecting()
{
return connecting.get();
}
/**
* Template method where any connections should be made for the connector
*
* @throws Exception
*/
protected abstract void doConnect() throws Exception;
/**
* Template method where any connected resources used by the connector should be
* disconnected
*
* @throws Exception
*/
protected abstract void doDisconnect() throws Exception;
/**
* The resource id used when firing ConnectEvents from this connector
*
* @return the resource id used when firing ConnectEvents from this connector
*/
protected String getConnectEventId()
{
return getName();
}
/**
* For better throughput when using TransactedMessageReceivers this will enable a
* number of concurrent receivers, based on the value returned by
* {@link #getNumberOfConcurrentTransactedReceivers()}. This property is used by
* transports that support transactions, specifically receivers that extend the
* TransactedPollingMessageReceiver.
*
* @return true if multiple receivers will be enabled for this connection
*/
public boolean isCreateMultipleTransactedReceivers()
{
return createMultipleTransactedReceivers;
}
/**
* @param createMultipleTransactedReceivers
* if true, multiple receivers will be
* created for this connection
* @see #isCreateMultipleTransactedReceivers()
*/
public void setCreateMultipleTransactedReceivers(boolean createMultipleTransactedReceivers)
{
this.createMultipleTransactedReceivers = createMultipleTransactedReceivers;
}
/**
* Returns the number of concurrent receivers that will be launched when
* {@link #isCreateMultipleTransactedReceivers()} returns <code>true</code>.
*
* @see #DEFAULT_NUM_CONCURRENT_TX_RECEIVERS
*/
public int getNumberOfConcurrentTransactedReceivers()
{
return this.numberOfConcurrentTransactedReceivers;
}
/**
* @param count the number of concurrent transacted receivers to start
* @see #getNumberOfConcurrentTransactedReceivers()
*/
public void setNumberOfConcurrentTransactedReceivers(int count)
{
this.numberOfConcurrentTransactedReceivers = count;
}
public void setDynamicNotification(boolean dynamic)
{
dynamicNotification = dynamic;
}
protected void updateCachedNotificationHandler()
{
if (null != muleContext)
{
if (dynamicNotification)
{
cachedNotificationHandler = muleContext.getNotificationManager();
}
else
{
cachedNotificationHandler = new OptimisedNotificationHandler(
muleContext.getNotificationManager(), EndpointMessageNotification.class);
}
}
}
public boolean isEnableMessageEvents()
{
return cachedNotificationHandler.isNotificationEnabled(EndpointMessageNotification.class);
}
/**
* Registers other protocols 'understood' by this connector. These must contain
* scheme meta info. Any protocol registered must begin with the protocol of this
* connector, i.e. If the connector is axis the protocol for jms over axis will
* be axis:jms. Here, 'axis' is the scheme meta info and 'jms' is the protocol.
* If the protocol argument does not start with the connector's protocol, it will
* be appended.
*
* @param protocol the supported protocol to register
*/
public void registerSupportedProtocol(String protocol)
{
protocol = protocol.toLowerCase();
if (protocol.startsWith(getProtocol().toLowerCase()))
{
registerSupportedProtocolWithoutPrefix(protocol);
}
else
{
supportedProtocols.add(getProtocol().toLowerCase() + ":" + protocol);
}
}
/**
* Used by Meta endpoint descriptors to register support for endpoint of the meta
* endpoint type. For example an RSS endpoint uses the Http connector. By
* registering 'rss' as a supported meta protocol, this connector can be used
* when creating RSS endpoints.
*
* @param protocol the meta protocol that can be used with this connector
* @since 3.0.0
*/
public void registerSupportedMetaProtocol(String protocol)
{
supportedProtocols.add(protocol.toLowerCase() + ":" + getProtocol().toLowerCase());
}
/**
* Registers other protocols 'understood' by this connector. These must contain
* scheme meta info. Unlike the {@link #registerSupportedProtocol(String)}
* method, this allows you to register protocols that are not prefixed with the
* connector protocol. This is useful where you use a Service Finder to discover
* which Transport implementation to use. For example the 'wsdl' transport is a
* generic 'finder' transport that will use Axis or CXF to create the WSDL
* client. These transport protocols would be wsdl-axis and wsdl-cxf, but they
* can all support 'wsdl' protocol too.
*
* @param protocol the supported protocol to register
*/
protected void registerSupportedProtocolWithoutPrefix(String protocol)
{
supportedProtocols.add(protocol.toLowerCase());
}
public void unregisterSupportedProtocol(String protocol)
{
protocol = protocol.toLowerCase();
if (protocol.startsWith(getProtocol().toLowerCase()))
{
supportedProtocols.remove(protocol);
}
else
{
supportedProtocols.remove(getProtocol().toLowerCase() + ":" + protocol);
}
}
/**
* @return true if the protocol is supported by this connector.
*/
public boolean supportsProtocol(String protocol)
{
return supportedProtocols.contains(protocol.toLowerCase());
}
/**
* Returns an unmodifiable list of the protocols supported by this connector
*
* @return an unmodifiable list of the protocols supported by this connector
*/
public List getSupportedProtocols()
{
return Collections.unmodifiableList(supportedProtocols);
}
/**
* Sets A list of protocols that the connector can accept
*
* @param supportedProtocols
*/
public void setSupportedProtocols(List supportedProtocols)
{
for (Iterator iterator = supportedProtocols.iterator(); iterator.hasNext();)
{
String s = (String) iterator.next();
registerSupportedProtocol(s);
}
}
/**
* Returns a work manager for message receivers.
*/
protected WorkManager getReceiverWorkManager() throws MuleException
{
return (WorkManager) receiverWorkManager.get();
}
/**
* Returns a work manager for message dispatchers.
*
* @throws MuleException in case of error
*/
protected WorkManager getDispatcherWorkManager() throws MuleException
{
return (WorkManager) dispatcherWorkManager.get();
}
/**
* Returns a work manager for message requesters.
*
* @throws MuleException in case of error
*/
protected WorkManager getRequesterWorkManager() throws MuleException
{
return (WorkManager) requesterWorkManager.get();
}
/**
* Returns a Scheduler service for periodic tasks, currently limited to internal
* use. Note: getScheduler() currently conflicts with the same method in the
* Quartz transport
*/
public ScheduledExecutorService getScheduler()
{
return scheduler;
}
protected ScheduledExecutorService createScheduler()
{
// Use connector's classloader so that other temporary classloaders
// aren't used when things are started lazily or from elsewhere.
ThreadFactory threadFactory = new NamedThreadFactory(this.getName() + ".scheduler", muleContext.getExecutionClassLoader());
ScheduledThreadPoolExecutor newExecutor = new ScheduledThreadPoolExecutor(4, threadFactory);
newExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
newExecutor.setKeepAliveTime(this.getReceiverThreadingProfile().getThreadTTL(), TimeUnit.MILLISECONDS);
newExecutor.allowCoreThreadTimeOut(true);
return newExecutor;
}
/**
* Getter for property 'sessionHandler'.
*
* @return Value for property 'sessionHandler'.
*/
public SessionHandler getSessionHandler()
{
return sessionHandler;
}
/**
* Setter for property 'sessionHandler'.
*
* @param sessionHandler Value to set for property 'sessionHandler'.
*/
public void setSessionHandler(SessionHandler sessionHandler)
{
this.sessionHandler = sessionHandler;
}
public void workAccepted(WorkEvent event)
{
handleWorkException(event, "workAccepted");
}
public void workRejected(WorkEvent event)
{
handleWorkException(event, "workRejected");
}
public void workStarted(WorkEvent event)
{
handleWorkException(event, "workStarted");
}
public void workCompleted(WorkEvent event)
{
handleWorkException(event, "workCompleted");
}
protected void handleWorkException(WorkEvent event, String type)
{
if (event == null)
{
return;
}
Throwable e = event.getException();
if (e == null)
{
return;
}
if (e.getCause() != null)
{
e = e.getCause();
}
logger.error("Work caused exception on '" + type + "'. Work being executed was: "
+ event.getWork().toString());
if (e instanceof MessagingException)
{
MuleEvent failedEvent = ((MessagingException) e).getEvent();
failedEvent.getFlowConstruct().getExceptionListener().handleException((MessagingException) e, failedEvent);
}
else if (e instanceof Exception)
{
muleContext.getExceptionListener().handleException((Exception) e);
}
else
{
muleContext.getExceptionListener().handleException(
new MuleRuntimeException(CoreMessages.connectorCausedError(this.getName()), e));
}
}
/**
* This method will return the dispatcher to the pool or, if the payload is an
* inputstream, replace the payload with a new DelegatingInputStream which
* returns the dispatcher to the pool when the stream is closed.
*
* @param endpoint
* @param dispatcher
* @param result
*/
protected void setupDispatchReturn(final OutboundEndpoint endpoint,
final MessageDispatcher dispatcher,
MuleMessage result)
{
if (result != null && result.getPayload() instanceof InputStream)
{
DelegatingInputStream is = new DelegatingInputStream((InputStream) result.getPayload())
{
@Override
public void close() throws IOException
{
try
{
super.close();
}
finally
{
returnDispatcher(endpoint, dispatcher);
}
}
};
result.setPayload(is);
}
else
{
this.returnDispatcher(endpoint, dispatcher);
}
}
public MuleMessage request(String uri, long timeout) throws Exception
{
return request(getMuleContext().getEndpointFactory().getInboundEndpoint(uri),
timeout);
}
public MuleMessage request(InboundEndpoint endpoint, long timeout) throws Exception
{
MessageRequester requester = null;
MuleMessage result = null;
try
{
requester = this.getRequester(endpoint);
result = requester.request(timeout);
return result;
}
finally
{
setupRequestReturn(endpoint, requester, result);
}
}
/**
* This method will return the requester to the pool or, if the payload is an
* inputstream, replace the payload with a new DelegatingInputStream which
* returns the requester to the pool when the stream is closed.
*
* @param endpoint
* @param requester
* @param result
*/
protected void setupRequestReturn(final InboundEndpoint endpoint,
final MessageRequester requester,
MuleMessage result)
{
if (result != null && result.getPayload() instanceof InputStream)
{
DelegatingInputStream is = new DelegatingInputStream((InputStream) result.getPayload())
{
@Override
public void close() throws IOException
{
try
{
super.close();
}
finally
{
returnRequester(endpoint, requester);
}
}
};
result.setPayload(is);
}
else
{
this.returnRequester(endpoint, requester);
}
}
// -------- Methods from the removed AbstractServiceEnabled Connector
/**
* When this connector is created via the
* {@link org.mule.transport.service.TransportFactory} the endpoint used to
* determine the connector type is passed to this method so that any properties
* set on the endpoint that can be used to initialise the connector are made
* available.
*
* @param endpointUri the {@link EndpointURI} use to create this connector
* @throws InitialisationException If there are any problems with the
* configuration set on the Endpoint or if another exception is
* thrown it is wrapped in an InitialisationException.
*/
public void initialiseFromUrl(EndpointURI endpointUri) throws InitialisationException
{
if (!supportsProtocol(endpointUri.getFullScheme()))
{
throw new InitialisationException(CoreMessages.schemeNotCompatibleWithConnector(
endpointUri.getFullScheme(), this.getClass()), this);
}
Properties props = new Properties();
props.putAll(endpointUri.getParams());
// auto set username and password
if (endpointUri.getUserInfo() != null)
{
props.setProperty("username", endpointUri.getUser());
String passwd = endpointUri.getPassword();
if (passwd != null)
{
props.setProperty("password", passwd);
}
}
String host = endpointUri.getHost();
if (host != null)
{
props.setProperty("hostname", host);
props.setProperty("host", host);
}
if (endpointUri.getPort() > -1)
{
props.setProperty("port", String.valueOf(endpointUri.getPort()));
}
org.mule.util.BeanUtils.populateWithoutFail(this, props, true);
setName(new ObjectNameHelper(muleContext).getConnectorName(this));
// initialise();
}
/**
* Initialises this connector from its {@link TransportServiceDescriptor} This
* will be called before the {@link #doInitialise()} method is called.
*
* @throws InitialisationException InitialisationException If there are any
* problems with the configuration or if another exception is thrown
* it is wrapped in an InitialisationException.
*/
protected synchronized void initFromServiceDescriptor() throws InitialisationException
{
try
{
serviceDescriptor = (TransportServiceDescriptor) muleContext.getRegistry()
.lookupServiceDescriptor(ServiceType.TRANSPORT, getProtocol().toLowerCase(), serviceOverrides);
if (serviceDescriptor == null)
{
throw new ServiceException(CoreMessages.noServiceTransportDescriptor(getProtocol()));
}
if (logger.isDebugEnabled())
{
logger.debug("Loading DispatcherFactory for connector: " + getName() + " ("
+ getClass().getName() + ")");
}
MessageDispatcherFactory df = serviceDescriptor.createDispatcherFactory();
if (df != null)
{
this.setDispatcherFactory(df);
}
else if (logger.isDebugEnabled())
{
logger.debug("Transport '" + getProtocol() + "' will not support outbound endpoints: ");
}
if (logger.isDebugEnabled())
{
logger.debug("Loading RequesterFactory for connector: " + getName() + " ("
+ getClass().getName() + ")");
}
MessageRequesterFactory rf = serviceDescriptor.createRequesterFactory();
if (rf != null)
{
this.setRequesterFactory(rf);
}
else if (logger.isDebugEnabled())
{
logger.debug("Transport '" + getProtocol() + "' will not support requests: ");
}
sessionHandler = serviceDescriptor.createSessionHandler();
}
catch (Exception e)
{
throw new InitialisationException(e, this);
}
}
/**
* Get the {@link TransportServiceDescriptor} for this connector. This will be
* null if the connector was created by the developer. To create a connector the
* proper way the developer should use the {@link TransportFactory} and pass in
* an endpoint.
*
* @return the {@link TransportServiceDescriptor} for this connector
*/
protected TransportServiceDescriptor getServiceDescriptor()
{
if (serviceDescriptor == null)
{
throw new IllegalStateException("This connector has not yet been initialised: " + name);
}
return serviceDescriptor;
}
/**
* Create a Message receiver for this connector
*
* @param flowConstruct the service that will receive events from this receiver, the
* listener
* @param endpoint the endpoint that defies this inbound communication
* @return an instance of the message receiver defined in this connectors'
* {@link org.mule.transport.service.TransportServiceDescriptor}
* initialised using the service and endpoint.
* @throws Exception if there is a problem creating the receiver. This exception
* really depends on the underlying transport, thus any exception
* could be thrown
*/
protected MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
{
return getServiceDescriptor().createMessageReceiver(this, flowConstruct, endpoint);
}
/**
* A map of fully qualified class names that should override those in the
* connectors' service descriptor This map will be null if there are no overrides
*
* @return a map of override values or null
*/
public Map getServiceOverrides()
{
return serviceOverrides;
}
/**
* Set the Service overrides on this connector.
*
* @param serviceOverrides the override values to use
*/
public void setServiceOverrides(Map serviceOverrides)
{
this.serviceOverrides = new Properties();
this.serviceOverrides.putAll(serviceOverrides);
}
/**
* Will get the output stream for this type of transport. Typically this will be
* called only when Streaming is being used on an outbound endpoint. If Streaming
* is not supported by this transport an {@link UnsupportedOperationException} is
* thrown. Note that the stream MUST release resources on close. For help doing
* so, see {@link org.mule.model.streaming.CallbackOutputStream}.
*
* @param endpoint the endpoint that releates to this Dispatcher
* @param event the current event being processed
* @return the output stream to use for this request
* @throws MuleException in case of any error
*/
public OutputStream getOutputStream(OutboundEndpoint endpoint, MuleEvent event) throws MuleException
{
throw new UnsupportedOperationException(CoreMessages.streamingNotSupported(this.getProtocol()).toString());
}
public MuleContext getMuleContext()
{
return muleContext;
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer(120);
final String nl = System.getProperty("line.separator");
sb.append(ClassUtils.getSimpleName(this.getClass()));
// format message for multi-line output, single-line is not readable
sb.append(nl);
sb.append("{");
sb.append(nl);
sb.append(" name=").append(name);
sb.append(nl);
sb.append(" lifecycle=").append(
lifecycleManager == null ? "<not in lifecycle>" : lifecycleManager.getCurrentPhase());
sb.append(nl);
sb.append(" this=").append(Integer.toHexString(System.identityHashCode(this)));
sb.append(nl);
sb.append(" numberOfConcurrentTransactedReceivers=").append(numberOfConcurrentTransactedReceivers);
sb.append(nl);
sb.append(" createMultipleTransactedReceivers=").append(createMultipleTransactedReceivers);
sb.append(nl);
sb.append(" connected=").append(connected);
sb.append(nl);
sb.append(" supportedProtocols=").append(supportedProtocols);
sb.append(nl);
sb.append(" serviceOverrides=");
if (serviceOverrides != null)
{
for (Map.Entry<Object, Object> entry : serviceOverrides.entrySet())
{
sb.append(nl);
sb.append(" ").append(String.format("%s=%s", entry.getKey(), entry.getValue()));
}
}
else
{
sb.append("<none>");
}
sb.append(nl);
sb.append('}');
sb.append(nl);
return sb.toString();
}
public RetryPolicyTemplate getRetryPolicyTemplate()
{
return retryPolicyTemplate;
}
public void setRetryPolicyTemplate(RetryPolicyTemplate retryPolicyTemplate)
{
this.retryPolicyTemplate = retryPolicyTemplate;
}
/**
* Whether to test a connection on each take from pool.
*/
public boolean isValidateConnections()
{
return validateConnections;
}
/**
* Whether to test a connection on each take. A result is higher availability at
* the expense of a potential slight performance hit (when a test connection is
* made) or be very lightweight in other cases (like sending a hearbeat ping to
* the server).
* <p/>
* Disable to obtain slight performance gain or if you are absolutely sure of the
* server availability.
* <p/>
* It is up to the transport implementatin to support such validation, thus it
* should be considered a hint only.
* <p/>
* The default value is <code>true</code>
*/
public void setValidateConnections(final boolean validateConnections)
{
this.validateConnections = validateConnections;
}
// MULE-4751 Expose some dispatcher and requester object pool configuration
/**
* Allows an ExhaustedAction to be configured on the dispatcher object pool See:
* {@link GenericKeyedObjectPool#setWhenExhaustedAction(byte)}
*/
public void setDispatcherPoolWhenExhaustedAction(byte whenExhaustedAction)
{
checkDispatchersInitialised();
dispatchers.setWhenExhaustedAction(whenExhaustedAction);
}
/**
* Allows a maxWait timeout to be configured on the dispatcher object pool See:
* {@link GenericKeyedObjectPool#setMaxWait(long)}
*/
public void setDispatcherPoolMaxWait(int maxWait)
{
checkDispatchersInitialised();
dispatchers.setMaxWait(maxWait);
}
/**
* Allows to define a factory to create the dispatchers pool that will be used in the connector
*/
public void setDispatcherPoolFactory(ConfigurableKeyedObjectPoolFactory dispatcherPoolFactory)
{
this.dispatcherPoolFactory = dispatcherPoolFactory;
}
public ConfigurableKeyedObjectPoolFactory getDispatcherPoolFactory()
{
return dispatcherPoolFactory;
}
/**
* Allows an ExhaustedAction to be configured on the requester object pool See:
* {@link GenericKeyedObjectPool#setWhenExhaustedAction(byte)}
*/
public void setRequesterPoolWhenExhaustedAction(byte whenExhaustedAction)
{
requesters.setWhenExhaustedAction(whenExhaustedAction);
}
/**
* Allows a maxWait timeout to be configured on the requester object pool See:
* {@link GenericKeyedObjectPool#setMaxWait(long)}
*/
public void setRequesterPoolMaxWait(int maxWait)
{
requesters.setMaxWait(maxWait);
}
public MessageProcessor createDispatcherMessageProcessor(OutboundEndpoint endpoint) throws MuleException
{
if (endpoint.getExchangePattern().hasResponse() || !getDispatcherThreadingProfile().isDoThreading())
{
return new DispatcherMessageProcessor(endpoint);
}
else
{
DefaultMessageProcessorChainBuilder builder = new DefaultMessageProcessorChainBuilder();
builder.setName("dispatcher processor chain for '" + endpoint.getAddress() + "'");
OptionalAsyncInterceptingMessageProcessor async = new OptionalAsyncInterceptingMessageProcessor(new WorkManagerSource()
{
public WorkManager getWorkManager() throws MuleException
{
return getDispatcherWorkManager();
}
});
((EndpointAware)async).setEndpoint(endpoint);
builder.chain(async);
builder.chain(new DispatcherMessageProcessor(endpoint));
return builder.build();
}
}
public MessageExchangePattern getDefaultExchangePattern()
{
try
{
return serviceDescriptor.getDefaultExchangePattern();
}
catch (TransportServiceException tse)
{
throw new MuleRuntimeException(tse);
}
}
public List<MessageExchangePattern> getInboundExchangePatterns()
{
try
{
return serviceDescriptor.getInboundExchangePatterns();
}
catch (TransportServiceException tse)
{
throw new MuleRuntimeException(tse);
}
}
public List<MessageExchangePattern> getOutboundExchangePatterns()
{
try
{
return serviceDescriptor.getOutboundExchangePatterns();
}
catch (TransportServiceException tse)
{
throw new MuleRuntimeException(tse);
}
}
class DispatcherMessageProcessor implements MessageProcessor
{
private MessageProcessor notificationMessageProcessor;
private OutboundEndpoint endpoint;
public DispatcherMessageProcessor(OutboundEndpoint endpoint)
{
this.endpoint = endpoint;
}
public MuleEvent process(MuleEvent event) throws MuleException
{
MessageDispatcher dispatcher = null;
try
{
dispatcher = getDispatcher(endpoint);
MuleEvent result = dispatcher.process(event);
// We need to invoke notification message processor with request
// message only after successful send/dispatch
if (notificationMessageProcessor == null)
{
notificationMessageProcessor = new OutboundNotificationMessageProcessor(endpoint);
}
notificationMessageProcessor.process(event);
return result;
}
catch (DispatchException dex)
{
throw dex;
}
catch (MuleException ex)
{
throw new DispatchException(event, endpoint, ex);
}
finally
{
returnDispatcher(endpoint, dispatcher);
}
}
@Override
public String toString()
{
return ObjectUtils.toString(this);
}
};
}