448 lines
15 KiB
Java
448 lines
15 KiB
Java
/*
|
|
* 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 {@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.
|
|
*/
|
|
private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
|
|
|
|
|
|
/**
|
|
* The current state of the source component.
|
|
*/
|
|
private volatile LifecycleState state = LifecycleState.NEW;
|
|
|
|
|
|
private boolean throwOnFailure = true;
|
|
|
|
|
|
/**
|
|
* 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 {@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}
|
|
*/
|
|
@Override
|
|
public void addLifecycleListener(LifecycleListener listener) {
|
|
lifecycleListeners.add(listener);
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public LifecycleListener[] findLifecycleListeners() {
|
|
return lifecycleListeners.toArray(new LifecycleListener[0]);
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void removeLifecycleListener(LifecycleListener listener) {
|
|
lifecycleListeners.remove(listener);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
protected abstract void initInternal() throws LifecycleException;
|
|
|
|
|
|
/**
|
|
* {@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
|
|
* {@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}
|
|
*/
|
|
@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
|
|
* {@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
|
|
*/
|
|
protected abstract void destroyInternal() throws LifecycleException;
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public LifecycleState getState() {
|
|
return state;
|
|
}
|
|
|
|
|
|
/**
|
|
* {@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
|
|
* {@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
|
|
* {@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 {
|
|
setStateInternal(LifecycleState.FAILED, null, false);
|
|
ExceptionUtils.handleThrowable(t);
|
|
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);
|
|
}
|
|
}
|
|
}
|