/*
* 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.runtime.core.internal.construct;
import static org.mule.runtime.core.api.construct.Flow.INITIAL_STATE_STARTED;
import static org.mule.runtime.core.api.construct.Flow.INITIAL_STATE_STOPPED;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.disposeIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.startIfNeeded;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.stopIfNeeded;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.lifecycle.Lifecycle;
import org.mule.runtime.api.meta.AbstractAnnotatedObject;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.construct.FlowConstructAware;
import org.mule.runtime.core.api.construct.FlowConstructInvalidException;
import org.mule.runtime.core.api.context.MuleContextAware;
import org.mule.runtime.core.api.exception.MessagingExceptionHandler;
import org.mule.runtime.core.api.exception.MessagingExceptionHandlerAcceptor;
import org.mule.runtime.core.api.exception.MessagingExceptionHandlerAware;
import org.mule.runtime.core.api.lifecycle.LifecycleState;
import org.mule.runtime.core.api.processor.Processor;
import org.mule.runtime.core.api.source.MessageSource;
import org.mule.runtime.core.config.i18n.CoreMessages;
import org.mule.runtime.core.lifecycle.EmptyLifecycleCallback;
import org.mule.runtime.core.management.stats.FlowConstructStatistics;
import org.mule.runtime.core.util.ClassUtils;
import java.beans.ExceptionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract implementation of {@link FlowConstruct} that:
* <ul>
* <li>Is constructed with unique name and {@link MuleContext}.
* <li>Uses a {@link MessageSource} as the source of messages.
* <li>Uses a chain of {@link Processor}s to process messages.
* <li>Has lifecycle and propagates this lifecycle to both {@link MessageSource} and {@link Processor}s in the correct order
* depending on the lifecycle phase.
* <li>Allows an {@link ExceptionListener} to be set.
* </ul>
* Implementations of <code>AbstractFlowConstuct</code> should implement {@link #validateConstruct()} validate the resulting
* construct. Validation may include validation of the type of attributes of the {@link MessageSource}.
* <p/>
* Implementations may also implement {@link #doInitialise()}, {@link #doStart()}, {@link #doStop()} and {@link #doDispose()} if
* they need to perform any action on lifecycle transitions.
*/
public abstract class AbstractFlowConstruct extends AbstractAnnotatedObject implements FlowConstruct, Lifecycle {
protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractFlowConstruct.class);
private final FlowConstructLifecycleManager lifecycleManager;
protected String name;
protected MessagingExceptionHandler exceptionListener;
protected final MuleContext muleContext;
protected FlowConstructStatistics statistics;
/**
* Determines the initial state of this flow when the mule starts. Can be 'stopped' or 'started' (default)
*/
protected String initialState = INITIAL_STATE_STARTED;
public AbstractFlowConstruct(String name, MuleContext muleContext) {
this.muleContext = muleContext;
this.name = name;
this.lifecycleManager = new FlowConstructLifecycleManager(this, muleContext);
}
@Override
public final void initialise() throws InitialisationException {
try {
if (exceptionListener == null) {
this.exceptionListener = muleContext.getDefaultErrorHandler();
}
lifecycleManager.fireInitialisePhase((phaseName, object) -> {
injectFlowConstructMuleContext(exceptionListener);
initialiseIfInitialisable(exceptionListener);
validateConstruct();
doInitialise();
});
} catch (InitialisationException e) {
throw e;
} catch (MuleException e) {
throw new InitialisationException(e, this);
}
}
@Override
public final void start() throws MuleException {
// Check if Initial State is Stopped
if (!isStopped() && initialState.equals(INITIAL_STATE_STOPPED)) {
lifecycleManager.fireStartPhase(new EmptyLifecycleCallback<>());
lifecycleManager.fireStopPhase(new EmptyLifecycleCallback<>());
LOGGER.info("Flow " + name + " has not been started (initial state = 'stopped')");
return;
}
lifecycleManager.fireStartPhase((phaseName, object) -> {
startIfStartable(exceptionListener);
doStart();
});
}
@Override
public final void stop() throws MuleException {
lifecycleManager.fireStopPhase((phaseName, object) -> {
doStop();
stopIfStoppable(exceptionListener);
});
}
@Override
public final void dispose() {
try {
if (isStarted()) {
stop();
}
lifecycleManager.fireDisposePhase((phaseName, object) -> {
doDispose();
disposeIfDisposable(exceptionListener);
});
} catch (MuleException e) {
LOGGER.error("Failed to stop service: " + name, e);
}
}
public boolean isStarted() {
return lifecycleManager.getState().isStarted();
}
public boolean isStopped() {
return lifecycleManager.getState().isStopped();
}
public boolean isStopping() {
return lifecycleManager.getState().isStopping();
}
@Override
public String getName() {
return name;
}
@Override
public MessagingExceptionHandler getExceptionListener() {
return exceptionListener;
}
public void setExceptionListener(MessagingExceptionHandler exceptionListener) {
this.exceptionListener = exceptionListener;
}
public String getInitialState() {
return initialState;
}
public void setInitialState(String initialState) {
this.initialState = initialState;
}
@Override
public LifecycleState getLifecycleState() {
return lifecycleManager.getState();
}
@Override
public MuleContext getMuleContext() {
return muleContext;
}
@Override
public FlowConstructStatistics getStatistics() {
return statistics;
}
protected void doInitialise() throws MuleException {
configureStatistics();
}
protected void configureStatistics() {
statistics = new FlowConstructStatistics(getConstructType(), name);
statistics.setEnabled(muleContext.getStatistics().isEnabled());
muleContext.getStatistics().add(statistics);
}
protected void doStart() throws MuleException {
// Empty template method
}
protected void doStop() throws MuleException {
// Empty template method
}
protected void doDispose() {
muleContext.getStatistics().remove(statistics);
}
/**
* Validates configured flow construct
*
* @throws FlowConstructInvalidException if the flow construct does not pass validation
*/
protected void validateConstruct() throws FlowConstructInvalidException {
if (exceptionListener instanceof MessagingExceptionHandlerAcceptor) {
if (!((MessagingExceptionHandlerAcceptor) exceptionListener).acceptsAll()) {
throw new FlowConstructInvalidException(CoreMessages
.createStaticMessage("Flow exception listener contains an exception strategy that doesn't handle all request,"
+ " Perhaps there's an exception strategy with a when attribute set but it's not part of a catch exception strategy"),
this);
}
}
}
protected void injectFlowConstructMuleContext(Object candidate) {
if (candidate instanceof FlowConstructAware) {
((FlowConstructAware) candidate).setFlowConstruct(this);
}
if (candidate instanceof MuleContextAware) {
((MuleContextAware) candidate).setMuleContext(muleContext);
}
}
protected void injectExceptionHandler(Object candidate) {
if (candidate instanceof MessagingExceptionHandlerAware) {
((MessagingExceptionHandlerAware) candidate).setMessagingExceptionHandler(this.getExceptionListener());
}
}
@Override
public String getUniqueIdString() {
return muleContext.getUniqueIdString();
}
@Override
public String getServerId() {
return muleContext.getId();
}
@Override
public String toString() {
return String.format("%s{%s}", ClassUtils.getSimpleName(this.getClass()), getName());
}
protected void initialiseIfInitialisable(Object candidate) throws InitialisationException {
initialiseIfNeeded(candidate, true, muleContext);
}
protected void startIfStartable(Object candidate) throws MuleException {
startIfNeeded(candidate);
}
protected void stopIfStoppable(Object candidate) throws MuleException {
stopIfNeeded(candidate);
}
protected void disposeIfDisposable(Object candidate) {
disposeIfNeeded(candidate, LOGGER);
}
/**
* @return the type of construct being created, e.g. "Flow"
*/
public abstract String getConstructType();
}