| /* |
| * 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 subclass 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 subclass 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; |
| } |
| |
| |
| @Override |
| public void addLifecycleListener(LifecycleListener listener) { |
| lifecycleListeners.add(listener); |
| } |
| |
| |
| @Override |
| public LifecycleListener[] findLifecycleListeners() { |
| return lifecycleListeners.toArray(new LifecycleListener[0]); |
| } |
| |
| |
| @Override |
| public void removeLifecycleListener(LifecycleListener listener) { |
| lifecycleListeners.remove(listener); |
| } |
| |
| |
| /** |
| * Allow subclasses 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(BEFORE_INIT_EVENT); |
| } |
| |
| try { |
| setStateInternal(LifecycleState.INITIALIZING, null, false); |
| initInternal(); |
| setStateInternal(LifecycleState.INITIALIZED, null, false); |
| } catch (Throwable t) { |
| handleSubClassException(t, "lifecycleBase.initFail", toString()); |
| } |
| } |
| |
| |
| /** |
| * Subclasses implement this method to perform any instance initialisation required. |
| * |
| * @throws LifecycleException If the initialisation fails |
| */ |
| protected abstract void initInternal() throws LifecycleException; |
| |
| |
| @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(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 subclasses are |
| // doing what they are supposed to. |
| invalidTransition(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()); |
| } |
| } |
| |
| |
| /** |
| * Subclasses 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. |
| * |
| * @exception LifecycleException if this component detects an error that prevents this component from being started |
| */ |
| protected abstract void startInternal() throws LifecycleException; |
| |
| |
| @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.INITIALIZED)) { |
| return; |
| } |
| |
| if (state.equals(LifecycleState.NEW)) { |
| state = LifecycleState.STOPPED; |
| return; |
| } |
| |
| if (!state.equals(LifecycleState.STARTED) && !state.equals(LifecycleState.FAILED)) { |
| invalidTransition(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 subclasses are |
| // doing what they are supposed to. |
| if (!state.equals(LifecycleState.STOPPING) && !state.equals(LifecycleState.FAILED)) { |
| invalidTransition(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(); |
| } |
| } |
| } |
| |
| |
| /** |
| * Subclasses 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. |
| * |
| * @exception LifecycleException if this component detects an error that prevents this component from being stopped |
| * cleanly |
| */ |
| 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(BEFORE_DESTROY_EVENT); |
| } |
| |
| try { |
| setStateInternal(LifecycleState.DESTROYING, null, false); |
| destroyInternal(); |
| setStateInternal(LifecycleState.DESTROYED, null, false); |
| } catch (Throwable t) { |
| handleSubClassException(t, "lifecycleBase.destroyFail", toString()); |
| } |
| } |
| |
| |
| /** |
| * Subclasses implement this method to perform any instance destruction required. |
| * |
| * @throws LifecycleException If the destruction fails |
| */ |
| protected abstract void destroyInternal() throws LifecycleException; |
| |
| |
| @Override |
| public LifecycleState getState() { |
| return state; |
| } |
| |
| |
| @Override |
| public String getStateName() { |
| return getState().toString(); |
| } |
| |
| |
| /** |
| * Provides a mechanism for subclasses 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 |
| * subclass. |
| * |
| * @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 subclasses 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 |
| * subclass. |
| * |
| * @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 { |
| throw new LifecycleException(sm.getString("lifecycleBase.invalidTransition", type, toString(), state)); |
| } |
| |
| |
| private void handleSubClassException(Throwable t, String key, Object... args) throws LifecycleException { |
| setStateInternal(LifecycleState.FAILED, null, false); |
| ExceptionUtils.handleThrowable(t); |
| if (getThrowOnFailure()) { |
| if (!(t instanceof LifecycleException)) { |
| t = new LifecycleException(sm.getString(key, args), t); |
| } |
| throw (LifecycleException) t; |
| } else { |
| log.error(sm.getString(key, args), t); |
| } |
| } |
| } |