/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.util;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;
Base implementation of the Lifecycle
interface that implements the state transition rules for Lifecycle.start()
and Lifecycle.stop()
/**
* Base implementation of the {@link Lifecycle} interface that implements the
* state transition rules for {@link Lifecycle#start()} and
* {@link Lifecycle#stop()}
*/
public abstract class LifecycleBase implements Lifecycle {
private static final Log log = LogFactory.getLog(LifecycleBase.class);
private static final StringManager sm = StringManager.getManager(LifecycleBase.class);
The list of registered LifecycleListeners for event notifications.
/**
* The list of registered LifecycleListeners for event notifications.
*/
private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
The current state of the source component.
/**
* The current state of the source component.
*/
private volatile LifecycleState state = LifecycleState.NEW;
private boolean throwOnFailure = true;
Will a LifecycleException
thrown by a sub-class during initInternal()
, startInternal()
, stopInternal()
or destroyInternal()
be re-thrown for the caller to handle or will it be logged instead? Returns: true
if the exception will be re-thrown, otherwise false
/**
* Will a {@link LifecycleException} thrown by a sub-class during
* {@link #initInternal()}, {@link #startInternal()},
* {@link #stopInternal()} or {@link #destroyInternal()} be re-thrown for
* the caller to handle or will it be logged instead?
*
* @return {@code true} if the exception will be re-thrown, otherwise
* {@code false}
*/
public boolean getThrowOnFailure() {
return throwOnFailure;
}
Configure if a LifecycleException
thrown by a sub-class during initInternal()
, startInternal()
, stopInternal()
or destroyInternal()
will be re-thrown for the caller to handle or if it will be logged instead. Params: - throwOnFailure –
true
if the exception should be re-thrown, otherwise false
/**
* Configure if a {@link LifecycleException} thrown by a sub-class during
* {@link #initInternal()}, {@link #startInternal()},
* {@link #stopInternal()} or {@link #destroyInternal()} will be re-thrown
* for the caller to handle or if it will be logged instead.
*
* @param throwOnFailure {@code true} if the exception should be re-thrown,
* otherwise {@code false}
*/
public void setThrowOnFailure(boolean throwOnFailure) {
this.throwOnFailure = throwOnFailure;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycleListeners.add(listener);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycleListeners.toArray(new LifecycleListener[0]);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycleListeners.remove(listener);
}
Allow sub classes to fire Lifecycle
events. Params: - type – Event type
- data – Data associated with event.
/**
* Allow sub classes to fire {@link Lifecycle} events.
*
* @param type Event type
* @param data Data associated with event.
*/
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
Sub-classes implement this method to perform any instance initialisation
required.
Throws: - LifecycleException – If the initialisation fails
/**
* Sub-classes implement this method to perform any instance initialisation
* required.
*
* @throws LifecycleException If the initialisation fails
*/
protected abstract void initInternal() throws LifecycleException;
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
Sub-classes must ensure that the state is changed to LifecycleState.STARTING
during the execution of this method. Changing state will trigger the Lifecycle.START_EVENT
event. If a component fails to start it may either throw a LifecycleException
which will cause it's parent to fail to start or it can place itself in the error state in which case stop()
will be called on the failed component but the parent component will continue to start normally. Throws: - LifecycleException – Start error occurred
/**
* Sub-classes must ensure that the state is changed to
* {@link LifecycleState#STARTING} during the execution of this method.
* Changing state will trigger the {@link Lifecycle#START_EVENT} event.
*
* If a component fails to start it may either throw a
* {@link LifecycleException} which will cause it's parent to fail to start
* or it can place itself in the error state in which case {@link #stop()}
* will be called on the failed component but the parent component will
* continue to start normally.
*
* @throws LifecycleException Start error occurred
*/
protected abstract void startInternal() throws LifecycleException;
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public final synchronized void stop() throws LifecycleException {
if (LifecycleState.STOPPING_PREP.equals(state) || LifecycleState.STOPPING.equals(state) ||
LifecycleState.STOPPED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStopped", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStopped", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
state = LifecycleState.STOPPED;
return;
}
if (!state.equals(LifecycleState.STARTED) && !state.equals(LifecycleState.FAILED)) {
invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
}
try {
if (state.equals(LifecycleState.FAILED)) {
// Don't transition to STOPPING_PREP as that would briefly mark the
// component as available but do ensure the BEFORE_STOP_EVENT is
// fired
fireLifecycleEvent(BEFORE_STOP_EVENT, null);
} else {
setStateInternal(LifecycleState.STOPPING_PREP, null, false);
}
stopInternal();
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STOPPING) && !state.equals(LifecycleState.FAILED)) {
invalidTransition(Lifecycle.AFTER_STOP_EVENT);
}
setStateInternal(LifecycleState.STOPPED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.stopFail", toString());
} finally {
if (this instanceof Lifecycle.SingleUse) {
// Complete stop process first
setStateInternal(LifecycleState.STOPPED, null, false);
destroy();
}
}
}
Sub-classes must ensure that the state is changed to LifecycleState.STOPPING
during the execution of this method. Changing state will trigger the Lifecycle.STOP_EVENT
event. Throws: - LifecycleException – Stop error occurred
/**
* Sub-classes must ensure that the state is changed to
* {@link LifecycleState#STOPPING} during the execution of this method.
* Changing state will trigger the {@link Lifecycle#STOP_EVENT} event.
*
* @throws LifecycleException Stop error occurred
*/
protected abstract void stopInternal() throws LifecycleException;
@Override
public final synchronized void destroy() throws LifecycleException {
if (LifecycleState.FAILED.equals(state)) {
try {
// Triggers clean-up
stop();
} catch (LifecycleException e) {
// Just log. Still want to destroy.
log.error(sm.getString("lifecycleBase.destroyStopFail", toString()), e);
}
}
if (LifecycleState.DESTROYING.equals(state) || LifecycleState.DESTROYED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyDestroyed", toString()), e);
} else if (log.isInfoEnabled() && !(this instanceof Lifecycle.SingleUse)) {
// Rather than have every component that might need to call
// destroy() check for SingleUse, don't log an info message if
// multiple calls are made to destroy()
log.info(sm.getString("lifecycleBase.alreadyDestroyed", toString()));
}
return;
}
if (!state.equals(LifecycleState.STOPPED) && !state.equals(LifecycleState.FAILED) &&
!state.equals(LifecycleState.NEW) && !state.equals(LifecycleState.INITIALIZED)) {
invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
}
try {
setStateInternal(LifecycleState.DESTROYING, null, false);
destroyInternal();
setStateInternal(LifecycleState.DESTROYED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.destroyFail", toString());
}
}
Sub-classes implement this method to perform any instance destruction
required.
Throws: - LifecycleException – If the destruction fails
/**
* Sub-classes implement this method to perform any instance destruction
* required.
*
* @throws LifecycleException If the destruction fails
*/
protected abstract void destroyInternal() throws LifecycleException;
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public LifecycleState getState() {
return state;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public String getStateName() {
return getState().toString();
}
Provides a mechanism for sub-classes to update the component state. Calling this method will automatically fire any associated Lifecycle
event. It will also check that any attempted state transition is valid for a sub-class. Params: - state – The new state for this component
Throws: - LifecycleException – when attempting to set an invalid state
/**
* Provides a mechanism for sub-classes to update the component state.
* Calling this method will automatically fire any associated
* {@link Lifecycle} event. It will also check that any attempted state
* transition is valid for a sub-class.
*
* @param state The new state for this component
* @throws LifecycleException when attempting to set an invalid state
*/
protected synchronized void setState(LifecycleState state) throws LifecycleException {
setStateInternal(state, null, true);
}
Provides a mechanism for sub-classes to update the component state. Calling this method will automatically fire any associated Lifecycle
event. It will also check that any attempted state transition is valid for a sub-class. Params: - state – The new state for this component
- data – The data to pass to the associated
Lifecycle
event
Throws: - LifecycleException – when attempting to set an invalid state
/**
* Provides a mechanism for sub-classes to update the component state.
* Calling this method will automatically fire any associated
* {@link Lifecycle} event. It will also check that any attempted state
* transition is valid for a sub-class.
*
* @param state The new state for this component
* @param data The data to pass to the associated {@link Lifecycle} event
* @throws LifecycleException when attempting to set an invalid state
*/
protected synchronized void setState(LifecycleState state, Object data)
throws LifecycleException {
setStateInternal(state, data, true);
}
private synchronized void setStateInternal(LifecycleState state, Object data, boolean check)
throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("lifecycleBase.setState", this, state));
}
if (check) {
// Must have been triggered by one of the abstract methods (assume
// code in this class is correct)
// null is never a valid state
if (state == null) {
invalidTransition("null");
// Unreachable code - here to stop eclipse complaining about
// a possible NPE further down the method
return;
}
// Any method can transition to failed
// startInternal() permits STARTING_PREP to STARTING
// stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
// STOPPING
if (!(state == LifecycleState.FAILED ||
(this.state == LifecycleState.STARTING_PREP &&
state == LifecycleState.STARTING) ||
(this.state == LifecycleState.STOPPING_PREP &&
state == LifecycleState.STOPPING) ||
(this.state == LifecycleState.FAILED &&
state == LifecycleState.STOPPING))) {
// No other transition permitted
invalidTransition(state.name());
}
}
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
fireLifecycleEvent(lifecycleEvent, data);
}
}
private void invalidTransition(String type) throws LifecycleException {
String msg = sm.getString("lifecycleBase.invalidTransition", type, toString(), state);
throw new LifecycleException(msg);
}
private void handleSubClassException(Throwable t, String key, Object... args) throws LifecycleException {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
String msg = sm.getString(key, args);
if (getThrowOnFailure()) {
if (!(t instanceof LifecycleException)) {
t = new LifecycleException(msg, t);
}
throw (LifecycleException) t;
} else {
log.error(msg, t);
}
}
}