blob: 8bd2fc0a7fe8b44ecac374d8a6875bf3b1abdf6d [file] [log] [blame]
/*
* 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 org.apache.catalina.Lifecycle;
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("org.apache.catalina.util");
/**
* Used to handle firing lifecycle events.
* TODO: Consider merging LifecycleSupport into this class.
*/
private final LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* The current state of the source component.
*/
private volatile LifecycleState state = LifecycleState.NEW;
/**
* {@inheritDoc}
*/
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
/**
* {@inheritDoc}
*/
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
/**
* {@inheritDoc}
*/
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(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) {
lifecycle.fireLifecycleEvent(type, data);
}
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
setStateInternal(LifecycleState.INITIALIZING, null, false);
try {
initInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
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);
}
setStateInternal(LifecycleState.STARTING_PREP, null, false);
try {
startInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.startFail",toString()), t);
}
if (state.equals(LifecycleState.FAILED) ||
state.equals(LifecycleState.MUST_STOP)) {
stop();
} else {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STARTING)) {
invalidTransition(Lifecycle.AFTER_START_EVENT);
}
setStateInternal(LifecycleState.STARTED, null, false);
}
}
/**
* 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
*/
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) &&
!state.equals(LifecycleState.MUST_STOP)) {
invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
}
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);
}
try {
stopInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.stopFail",toString()), t);
}
if (state.equals(LifecycleState.MUST_DESTROY)) {
// Complete stop process first
setStateInternal(LifecycleState.STOPPED, null, false);
destroy();
} else if (!state.equals(LifecycleState.FAILED)){
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STOPPING)) {
invalidTransition(Lifecycle.AFTER_STOP_EVENT);
}
setStateInternal(LifecycleState.STOPPED, null, false);
}
}
/**
* 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
*/
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.warn(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()) {
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);
}
setStateInternal(LifecycleState.DESTROYING, null, false);
try {
destroyInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.destroyFail",toString()), t);
}
setStateInternal(LifecycleState.DESTROYED, null, false);
}
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
*/
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
*/
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);
}
}