org.jvnet.hk2.component.internal.runlevel.DefaultRunLevelService Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.jvnet.hk2.component.internal.runlevel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import org.jvnet.hk2.annotations.RunLevel;
import org.jvnet.hk2.component.ComponentException;
import org.jvnet.hk2.component.Enableable;
import org.jvnet.hk2.component.Habitat;
import org.jvnet.hk2.component.HabitatListener;
import org.jvnet.hk2.component.Inhabitant;
import org.jvnet.hk2.component.InhabitantActivator;
import org.jvnet.hk2.component.InhabitantListener;
import org.jvnet.hk2.component.InhabitantSorter;
import org.jvnet.hk2.component.RunLevelListener;
import org.jvnet.hk2.component.RunLevelService;
import org.jvnet.hk2.component.RunLevelState;
import org.jvnet.hk2.component.ServiceContext;
import com.sun.hk2.component.AbstractInhabitantImpl;
import com.sun.hk2.component.ClassLoaderHolder;
import com.sun.hk2.component.RunLevelInhabitant;
/**
* The default {@link RunLevelService} implementation for Hk2. See the
* {@link RunLevelService} javadoc for general details regarding this service.
*
* Here is a brief example of the behavior of this service:
*
* Imagine services ServiceA, ServiceB, and ServiceC are all in the same RunLevel X and the
* dependencies are ServiceA -> ServiceB -> ServiceC:
*
*
* @RunLevel(X)
* @Service
* public class ServiceA {
* @Inject ServiceB b;
* }
*
* @RunLevel(X)
* @Service
* public class ServiceB {
* @Inject ServiceC c;
* }
*
* @RunLevel(X)
* @Service
* public class ServiceC {
* }
*
*
* When the DefaultRunLevelService is asked to proceedTo(X), the
* expected start order is: ServiceC, ServiceB, ServiceA, and the expected shutdown
* order is: ServiceA, ServiceB, ServiceC
*
* RunLevel-annotated services correspond to {@link RunLevelInhabitant}'s and they
* hook into the {@link PostConstruct} activation sequence to record the activation order
* of inhabitants within each RunLevel.
*
* Note that no model of dependencies between services are kept in the habitat to
* make the implementation work. Any inhabitant in RunLevel X is arbitrarily picked
* to start with upon activation, and {@link Inhabitant#get()} is issued.
*
* Consider the cases of possible activation orderings:
*
* Case 1: A, B, then C by RLS. get ServiceA (called by RLS) Start ServiceA: get
* ServiceB Start ServiceB: get ServiceC Start ServiceC wire ServiceC
* PostConstruct ServiceC wire ServiceB PostConstruct ServiceB wire ServiceA
* PostConstruct ServiceA get ServiceB (called by RLS) get ServiceC (called by
* RLS)
*
* Case 2: B, C, then A by RLS. get ServiceB (called by RLS) Start ServiceB: get
* ServiceC Start ServiceC wire ServiceC PostConstruct ServiceC wire ServiceB
* PostConstruct ServiceB get ServiceC (called by RLS) get ServiceA (called by
* RLS) Start ServiceA: get ServiceB wire ServiceA PostConstruct ServiceA
*
* Case 3: B, A, then C by RLS. get ServiceB (called by RLS) Start ServiceB: get
* ServiceC Start ServiceC wire ServiceC PostConstruct ServiceC wire ServiceB
* PostConstruct ServiceB get ServiceA (called by RLS) Start ServiceA: get
* ServiceB wire ServiceA PostConstruct ServiceA get ServiceC (called by RLS)
*
* Case 4: C, B, then A by RLS. get ServiceC (called by RLS) Start ServiceC:
* wire ServiceC PostConstruct ServiceC get ServiceB (called by RLS) Start
* ServiceB: get ServiceC wire ServiceB PostConstruct ServiceB get ServiceA
* (called by RLS) Start ServiceA: get ServiceB wire ServiceA PostConstruct
* ServiceA get ServiceA (called by RLS)
*
* You can see that the order is always correct without needing to keep the model
* of dependencies.
*
* ~~~
*
* Note that the implementation performs some level of constraint checking
* during injection. For example,
*
* - It is an error to have a RunLevel-annotated service at RunLevel X to depend
* on (i.e., be injected with) a RunLevel-annotated service at RunLevel Y when Y
* > X.
*
* - It is an error to have a non-RunLevel-annotated service to depend on a
* RunLevel-annotated service at any RunLevel.
*
* Note that the implementation does not handle Holder and Collection injection
* constraint validations.
*
* ~~~
*
* The implementation will automatically proceedTo(-1) after the habitat has
* been initialized. The value of "-1" is symbolic of the kernel run level.
*
* Note that all RunLevel values less than -1 will be ignored.
*
* ~~~
*
* The implementation is written to support two modes of operation, asynchronous
* / threaded, and synchronous / single threaded. The DefaultRunLevelService
* implementation mode is pre-configured to be synchronous. The
* DefaultRunLevelService is thread safe.
*
* In the synchronous mode, calls can be made to proceedTo() to interrupt
* processing of any currently executing proceedTo() operation. This might occur:
* in another thread, in the {@link RunLevelListener} handlers, or in a
* {@link RunLevel} annotated service's {@link PostConstruct} method call.
*
* Note, however, that even in synchronous mode the proceedTo() operation may exhibit
* asynchronous behavior. This is the case when the caller has two threads calling
* proceedTo(), where the second thread is canceling the operation of the first (perhaps
* due to timeout of a service's PostConstruct, etc.). In this case, an interrupt will
* be sent to the first running thread to cancel the previous operation, and proceedTo
* the run level from the second thread's request. This presumes that the first thread
* is capable of being interrupted. In such a situation, the second proceedTo() call
* returns immediately and the first proceedTo() is interrupted to continue to the
* new runLevel requested from the second thread's interrupt.
*
* For this reason, it is strongly advised that {@link InterruptedException} is not
* swallowed by services that can be driven by the DefaultRunLevelService in
* synchronous mode.
*
* proceedTo invocations from a {@link PostConstruct} callback are discouraged. Consider
* using {@link RunLevelListener} instead.
*
* Important Note:
* The proceedTo() method will throw unchecked exceptions of type
* {@link DefaultRunLevelService$Interrupt} if it detects that it is being called
* reentrantly in synchronous mode. Callers should be careful NOT to swallow exceptions
* of this type as shown in the following example:
*
*
* try {
* rls.proceedTo(x);
* } catch (Exception e) {
* // swallow exception
* }
*
*
* ~~~
*
* All calls to the {@Link RunLevelListener} happens synchronously on the
* same thread that caused the Inhabitant to be activated. Therefore, implementors
* of this interface should be careful and avoid calling long operations.
*
* ~~~
*
* This service implements contracts {@link InhabitantSorter} as well as
* {@link InhabitantActivator}. The implementation will first attempt to find a
* habitat resident singleton for each of these contracts respectively, and failing to find an
* implementation will handle each itself. This lookup occurs iteratively just prior to a
* run level progression event. Note, however, that {@link InhabitantSorter} is only used
* as part of activations since the {@link Recorder} is chiefly responsible for ensuring
* release of the inhabitants occur in a consistent order. See earlier notes on this subject.
*
* @see RunLevelService
*
* @author Jeff Trent
*
* @since 3.1
*/
public class DefaultRunLevelService implements RunLevelService, Enableable,
RunLevelState, InhabitantListener, HabitatListener, InhabitantSorter, InhabitantActivator {
// the initial run level
public static final int INITIAL_RUNLEVEL = -2;
// the "kernel" run level
public static final int KERNEL_RUNLEVEL = -1;
// the default name
public static final String NAME = "default";
// the default mode - sync or async.
static final boolean ASYNC_ENABLED = false;
private static final Logger logger = Logger.getLogger(DefaultRunLevelService.class.getName());
private final Object lock = new Object();
// the async mode for this instance.
// if enabled, then all work is performed using a private
// executor. If disabled, then almost all of the work
// occurs on the calling thread to proceedTo(). Almost all
// because in the event a thread calls proceedTo() while
// another thread is already executing a proceedTo() operation,
// the the interrupting thread will return immediately and
// the pre-existing executing thread is interrupted to go to
// the new run level.
private final boolean asyncMode;
// the private executor service if this instance is using
// async mode.
private final ExecutorService exec;
// the name for this instance, defaulting to NAME
protected final String name;
// the target environment, defaulting to Void.class
private final Class> targetEnv;
// the habitat for this instance of the RunLevelService
private final Habitat habitat;
// used primarily for testing purposes - can ignore
private RunLevelState delegate;
// the current run level (the last one successfully achieved)
private Integer current;
// the set of recorders, one per runlevel (used as necessary, cleared when shutdown)
private final HashMap recorders;
// the "active" proceedTo worker
private Worker worker;
// @see Enableable
private boolean enabled = true;
// used for eventing an {@link RunLevelListener}s
private enum ListenerEvent {
PROGRESS, CANCEL, ERROR,
}
public DefaultRunLevelService(Habitat habitat) {
this(habitat, ASYNC_ENABLED, null, null, new LinkedHashMap());
}
protected DefaultRunLevelService(Habitat habitat, boolean async,
String name, Class> targetEnv,
HashMap recorders) {
this.habitat = habitat;
assert (null != habitat);
this.asyncMode = async;
if (asyncMode) {
// we can't use a singleThreadExecutor because a thread could become "stuck"
exec = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread activeThread = new RunLevelServiceThread(runnable);
synchronized (lock) {
logger.log(Level.FINE, "new thread: {0}", activeThread);
}
return activeThread;
}
});
} else {
this.exec = null;
}
this.name = (null == name) ? NAME : name;
this.targetEnv = (null == targetEnv) ? Void.class : targetEnv;
this.recorders = recorders;
// subscribe to events in the habitat since we cannot rely on PostConstruct.
// This is because the complete habitat may not be initialized at the time of
// our initialization.
habitat.addHabitatListener(this);
}
@Override
public String toString() {
return getClass().getSimpleName() + "-" + System.identityHashCode(this)
+ "(" + getDescription(false) + ", del: " + delegate + ")";
}
public String getDescription(boolean extended) {
StringBuilder b = new StringBuilder();
b.append("curr=").append(getCurrentRunLevel()).append(", ");
b.append("act=").append(getActivatingRunLevel()).append(", ");
b.append("plan=").append(getPlannedRunLevel()).append(", ");
b.append("env=").append(getEnvironment()).append(", ");
if (extended) {
b.append("thrd=").append(Thread.currentThread());
}
return b.toString();
}
@SuppressWarnings("unused")
private void setDelegate(RunLevelState stateProvider) {
assert (this != stateProvider);
assert (getEnvironment() == stateProvider.getEnvironment());
this.delegate = stateProvider;
}
public String getName() {
return name;
}
@Override
public RunLevelState getState() {
return (null == delegate) ? this : delegate;
}
@SuppressWarnings("unchecked")
@Override
public Class getEnvironment() {
return (null == delegate) ? targetEnv : delegate.getEnvironment();
}
@Override
public Integer getCurrentRunLevel() {
return (null == delegate) ? current : delegate.getCurrentRunLevel();
}
@Override
public Integer getPlannedRunLevel() {
if (null == delegate) {
synchronized (lock) {
return (null == worker) ? null : worker.getPlannedRunLevel();
}
} else {
return delegate.getPlannedRunLevel();
}
}
public Integer getActivatingRunLevel() {
synchronized (lock) {
return (null == worker) ? null : worker.getActivatingRunLevel();
}
}
@Override
public void enable(boolean enabled) throws IllegalStateException {
this.enabled = enabled;
}
@Override
public boolean isEnabled() {
return enabled;
}
/**
* Returns true if the RunLevel for the given inhabitant in question
* should be processed by this RunLevelService instance.
*
* @param i the inhabitant
* @param rl the inhabitan'ts runLevel
* @param activeRunLevel the current runLevel
*
* @return
*/
protected boolean accept(Inhabitant> i, RunLevel rl, int activeRunLevel) {
return (rl.value() == activeRunLevel && rl.environment() == targetEnv);
}
/**
* This is needed in the scenario where the habitat initially didn't
* have an instance of this RunLevelService (or derivative) and then
* later on in time, after all {@link RunLevelInhabitant}'s became defined,
* this instance was introduced. In the event that the RunLevelInhabitant's
* have not yet been bound, this will latently bind them now.
*/
private void checkBinding(RunLevelInhabitant,?> rli) {
RunLevelState> state = rli.getState();
if (state != this && RunLevelServiceStub.class.isInstance(state)) {
RunLevelService> delegate = ((RunLevelServiceStub)state).getDelegate();
if (null != delegate) {
assert(this == delegate);
} else {
((RunLevelServiceStub)state).activate(this);
}
}
}
private boolean isCancelled(Worker worker) {
synchronized (lock) {
return (this.worker != worker);
}
}
/**
* Called after the proceedTo work is finished. This is
* akin to a suicide act.
*
* @param worker the worker that was performing the work
*/
private void finished(Worker worker) {
synchronized (lock) {
// ensure that the worker is the "active" one and not some zombie back to life
if (!isCancelled(worker)) {
// it was the "trusted" worker
this.worker = null;
}
}
synchronized (this) {
notifyAll();
}
}
private void setCurrent(Worker worker, Integer current) {
synchronized (lock) {
// ensure that the worker is the "active" one and not some zombie back to life
if (isCancelled(worker)) {
return;
} else {
this.current = current;
}
}
// notify listeners that we progressed
event(worker, ListenerEvent.PROGRESS, null, null);
}
protected List getRecordersToRelease(
HashMap list, int runLevel) {
List qualifying = new ArrayList();
synchronized (lock) {
for (Entry entry : recorders.entrySet()) {
int entryKey = entry.getKey();
if (entryKey >= runLevel) {
qualifying.add(entry.getKey());
}
}
}
// return in order of highest to lowest
Collections.sort(qualifying);
Collections.reverse(qualifying);
return qualifying;
}
protected void event(Worker worker,
ListenerEvent event,
ServiceContext context,
Throwable error) {
event(worker, event, context, error, false);
}
protected void event(Worker worker,
ListenerEvent event,
ServiceContext context,
Throwable error,
boolean isHardInterrupt) {
logger.log(Level.FINE, "event {0} - " + getDescription(true), event);
if (isCancelled(worker)) {
logger.log(Level.FINE, "Ignoring this notification!");
} else {
Interrupt lastInterrupt = null;
Collection activeListeners =
habitat.getAllByContract(RunLevelListener.class);
for (RunLevelListener listener : activeListeners) {
try {
if (ListenerEvent.PROGRESS == event) {
listener.onProgress(this);
} else if (ListenerEvent.CANCEL == event) {
listener.onCancelled(this, context, current, isHardInterrupt);
} else {
listener.onError(this, context, error, true);
}
} catch (Interrupt interrupt) {
lastInterrupt = interrupt;
} catch (Exception e) {
// don't percolate the exception since it may negatively impact processing
logger.log(Level.WARNING, "swallowing exception - " + getDescription(true),
new ComponentException(e));
}
}
if (null != lastInterrupt) {
throw lastInterrupt;
} else {
if (null != error) {
logger.log(Level.FINE, "swallowing exception - " + context, new ComponentException(error));
}
}
}
}
@Override
public boolean inhabitantChanged(InhabitantListener.EventType eventType,
Inhabitant> inhabitant) {
if (InhabitantListener.class.isInstance(delegate)) {
return InhabitantListener.class.cast(delegate).inhabitantChanged(
eventType, inhabitant);
}
AbstractInhabitantImpl> ai = AbstractInhabitantImpl.class.cast(inhabitant);
RunLevel rl = ai.getAnnotation(RunLevel.class);
Integer activeRunLevel = (null == rl) ? null : rl.value();
if (null != activeRunLevel) {
// forward to the active recorder?
if (InhabitantListener.EventType.INHABITANT_ACTIVATED == eventType) {
Recorder activeRecorder;
synchronized (lock) {
activeRecorder = recorders.get(activeRunLevel);
if (null == activeRecorder) {
activeRecorder = new Recorder(activeRunLevel, getEnvironment());
recorders.put(activeRunLevel, activeRecorder);
}
}
if (null != activeRecorder) {
activeRecorder.inhabitantChanged(eventType, inhabitant);
}
}
}
// we always want to maintain our subscription
return true;
}
/**
* Once habitat is initialized we can proceed to boot through to kernel level (-1)
*/
@Override
public boolean inhabitantChanged(HabitatListener.EventType eventType,
Habitat habitat, Inhabitant> inhabitant) {
if (org.jvnet.hk2.component.HabitatListener.EventType.HABITAT_INITIALIZED == eventType) {
if (isEnabled()) {
proceedTo(KERNEL_RUNLEVEL);
}
}
return !habitat.isInitialized();
}
@Override
public boolean inhabitantIndexChanged(HabitatListener.EventType eventType,
Habitat habitat, Inhabitant> inhabitant, String index, String name,
Object service) {
return true;
}
@Override
public void proceedTo(int runLevel) {
proceedTo(runLevel, false);
}
@Override
public void interrupt() {
proceedTo(null, true);
}
@Override
public void interrupt(int runLevel) {
proceedTo(runLevel, true);
}
protected void proceedTo(Integer runLevel, boolean isHardInterrupt) {
if (!isEnabled()) {
throw new IllegalStateException("disabled state");
}
if (null != runLevel && runLevel < KERNEL_RUNLEVEL) {
throw new IllegalArgumentException();
}
// see if we can interrupt first
Worker worker = this.worker;
if (null != worker) {
if (worker.interrupt(isHardInterrupt, runLevel)) {
return;
}
}
if (null != runLevel) {
// if we are here then we interrupt isn't enough and we must create a new worker
synchronized (lock) {
this.worker = worker = (asyncMode) ?
new AsyncProceedToOp(runLevel) :
new SyncProceedToOp(runLevel);
}
worker.proceedTo(runLevel);
}
}
/**
* Called when we are responsible for handling the {@link InhabitantSorter} work.
*
* The implementation returns the inhabitants argument as-is .
*/
@Override
public List> sort(List> inhabitants) {
return inhabitants;
}
/**
* Called when we are responsible for handling the {@link InhabitantActivator} work.
*/
@SuppressWarnings("unchecked")
@Override
public void activate(Inhabitant inhabitant) {
inhabitant.get();
}
/**
* Called when we are responsible for handling the {@link InhabitantActivator} work.
*/
@SuppressWarnings("unchecked")
@Override
public void deactivate(Inhabitant inhabitant) {
inhabitant.release();
}
/**
* Obtains the "best" InhabitantSorter, first looking up out of
* the habitat any service registered under the same name as ourself,
* then defaulting to the first one registered by type alone, followed
* by ourself.
*
* @return an InhabitantActivator, defaulting to ourself
*/
protected InhabitantSorter getInhabitantSorter() {
InhabitantSorter is = habitat.getComponent(InhabitantSorter.class, getName());
if (null == is) {
is = habitat.getByContract(InhabitantSorter.class);
}
return (null == is) ? this : is;
}
/**
* Obtains the "best" InhabitantActivator, first looking up out of
* the habitat any service registered under the same name as ourself,
* then defaulting to the first one registered by type alone, followed
* by ourself.
*
* @return an InhabitantActivator, defaulting to ourself
*/
protected InhabitantActivator getInhabitantActivator() {
InhabitantActivator ia = habitat.getComponent(InhabitantActivator.class, getName());
if (null == ia) {
ia = habitat.getByContract(InhabitantActivator.class);
}
return (null == ia) ? this : ia;
}
private abstract class Worker implements Runnable {
// the target runLevel we want to achieve
protected volatile Integer planned;
// the active run level attempting to be activated
private Integer activeRunLevel;
// tracks the direction of any active proceedTo worker
protected Boolean upSide;
// records whether a cancel was actually an hard interrupt
protected Boolean isHardInterrupt;
protected Worker(int runLevel) {
this.planned = runLevel;
}
public Integer getPlannedRunLevel() {
return planned;
}
public Integer getActivatingRunLevel() {
return activeRunLevel;
}
/**
* Checks to see if this worker has been interrupted, and will abort
* if it finds it has been.
*
* @param e any error encountered during the nested proceedTo operation; may be null
*
* @param i the inhabitant that was being activated / released during the operation; may be null
*
* @param isHard true when this is a "hard" interrupt originating from an interrupt() call,
* false when it was from a "soft" interrupt involving a new proceedTo(), null when its
* unknown altogether
*/
protected void checkInterrupt(Exception e, Inhabitant> i, Boolean isHard) {
if (null != e) {
ServiceContext ctx = serviceContext(e, i);
boolean isHardInterrupt = isHardInterrupt(isHard, e);
if (isHardInterrupt) {
event(this, ListenerEvent.CANCEL, ctx, e, isHardInterrupt);
} else {
event(this, ListenerEvent.ERROR, ctx, e, isHardInterrupt);
}
}
}
/**
* Attempts to interrupt processing to go to a new runLevel.
*
* @param isHard if true, this was based on an explicit call to interrupt;
* false otherwise. The latter is the case for a new proceedTo() causing
* a cancel in order to proceedTo a new runLevel.
*
* @param runLevel optionally, the revised runLevel to proceedTo
*
* @return true, if its possible to go to the new runLevel; note that
* implementation may handle the interrupt by other means (i.e.,
* throwing an InterruptException for the synchronous case)
*/
public abstract boolean interrupt(boolean isHard, Integer runLevel);
/**
* Called after initialization to run the proceedTo operation
*
* @param runLevel the runLevel to proceedTo
*/
public abstract void proceedTo(int runLevel);
/**
* Core control logic.
*/
@Override
public void run() {
logger.log(Level.FINE, "proceedTo({0}) - " + getDescription(true), planned);
upSide = null;
if (null != planned) {
int current = (null == getCurrentRunLevel()) ? INITIAL_RUNLEVEL : getCurrentRunLevel();
if (planned > current) {
upSide = true;
int rl = current + 1;
while (rl <= planned) {
upActiveRecorder(rl);
rl++;
}
} else if (planned < current) {
upSide = false;
// start things off we a notification of the current runLevel
setCurrent(this, current);
down(current);
} else { // planned == current
upSide = false;
// force closure of any orphaned higher RunLevel services
down(current+1);
}
}
finished(this);
}
private void down(int start) {
int rl = start;
while (rl > planned) {
downActiveRecorder(rl);
rl--;
}
}
private void upActiveRecorder(int runLevel) {
activeRunLevel = runLevel;
// create demand for RunLevel (runLevel) components
activateRunLevel();
// don't set current until we've actually reached it
setCurrent(this, runLevel);
}
private void activateRunLevel() {
List> activations = new ArrayList>();
// TODO: we could cache this in top-level proceedTo()
Collection> runLevelInhabitants =
habitat.getAllInhabitantsByContract(RunLevel.class.getName());
for (Inhabitant> i : runLevelInhabitants) {
AbstractInhabitantImpl> ai = AbstractInhabitantImpl.class.cast(i);
RunLevel rl = ai.getAnnotation(RunLevel.class);
if (accept(ai, rl, activeRunLevel)) {
RunLevelInhabitant,?> rli = RunLevelInhabitant.class.cast(ai);
checkBinding(rli);
activations.add(rli);
}
}
if (!activations.isEmpty()) {
InhabitantSorter is = getInhabitantSorter();
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "sorting {0}", activations);
}
activations = is.sort(activations);
assert(null != activations);
InhabitantActivator ia = getInhabitantActivator();
for (Inhabitant> rli : activations) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "activating {0} - " + getDescription(true), rli);
}
try {
ia.activate(rli);
// assert(rli.isInstantiated());
// an escape hatch if we've been interrupted in some way
checkInterrupt(null, rli, null);
} catch (Exception e) {
checkInterrupt(e, rli, null);
}
}
}
}
protected void downActiveRecorder(int runLevel) {
activeRunLevel = runLevel;
// release stuff
deactiveRunLevel(runLevel);
// don't set current until we've actually reached it
setCurrent(this, activeRunLevel = runLevel-1);
}
private void deactiveRunLevel(int runLevel) {
List downRecorders = getRecordersToRelease(recorders, runLevel);
for (int current : downRecorders) {
Recorder downRecorder;
synchronized (lock) {
downRecorder = recorders.get(current);
}
if (null != downRecorder) {
// Causes release of the entire activationSet. Release occurs in the inverse
// order of the recordings. So A->B->C will have startUp ordering be (C,B,A)
// because of dependencies. The shutdown ordering will b (A,B,C).
InhabitantActivator ia = getInhabitantActivator();
Inhabitant> i;
while (null != (i = downRecorder.pop())) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "releasing {0} - " + getDescription(true), i);
}
try{
ia.deactivate(i);
// assert(!i.isInstantiated());
checkInterrupt(null, i, null);
} catch (Exception e) {
checkInterrupt(e, i, null);
}
}
}
}
}
protected boolean isHardInterrupt(Boolean isHard, Throwable e) {
if (null != isHard) {
return isHard;
}
return (null == isHardInterrupt) ? false : isHardInterrupt;
}
}
/**
* Sync worker
*/
private class SyncProceedToOp extends Worker {
// record the thread performing the operation
private final Thread activeThread = Thread.currentThread();
// the next planned runLevel (after interrupt)
protected Integer nextPlannedAfterInterrupt;
// records whether a cancel event was issued
private boolean cancelIssued;
private SyncProceedToOp(int runLevel) {
super(runLevel);
}
/**
* Interrupts are always handled in the synchronous case
* either by popping the stack for the reentrant call on
* same thread, or by sending a cancel event to the
* active thread doing the proceedTo() call.
*/
@Override
public boolean interrupt(boolean isHard, Integer runLevel) {
Thread ourThread = Thread.currentThread();
synchronized (lock) {
Integer planned = getPlannedRunLevel();
if (!isHard && null != planned && planned == runLevel) {
return true; // short circuit
}
nextPlannedAfterInterrupt = runLevel;
if (ourThread == activeThread) {
checkInterrupt(null, null, isHard);
} else {
// must interrupt another thread to do the new proceedTo().
// Note how this thread exhibits async behavior in this case.
// The cancel notification will happen on the other thread
logger.log(Level.FINE, "Interrupting thread {0} - " + getDescription(true), activeThread);
this.isHardInterrupt = isHard;
activeThread.interrupt();
}
}
return true;
}
@Override
public void proceedTo(int runLevel) {
synchronized (lock) {
planned = runLevel;
nextPlannedAfterInterrupt = null;
cancelIssued = false;
isHardInterrupt = null;
}
try {
run();
} catch (Exception e) {
handleInterruptException(e);
}
}
@Override
protected void checkInterrupt(Exception e, Inhabitant> i, Boolean isHard) {
synchronized (lock) {
boolean cancelled = isCancelled(this);
if (cancelled || null != nextPlannedAfterInterrupt) {
if (!cancelled && canUpdateProceedTo(nextPlannedAfterInterrupt)) {
planned = nextPlannedAfterInterrupt;
nextPlannedAfterInterrupt = null;
e = null;
} else {
// send cancel event, but only one time
if (!cancelIssued) {
cancelIssued = true;
boolean wasHardInterrupt = isHardInterrupt(isHard, e);
isHardInterrupt = null;
event(this, ListenerEvent.CANCEL, serviceContext(e, i), null, wasHardInterrupt);
}
// pop stack to last proceedTo()
throw new Interrupt();
}
}
}
super.checkInterrupt(e, i, isHard);
}
private boolean canUpdateProceedTo(Integer proposed) {
if (null != upSide) {
Integer planned = getPlannedRunLevel();
Integer active = getActivatingRunLevel();
if (null != planned && null != active && null != proposed) {
if (upSide && proposed > active) {
return true;
} else if (!upSide && proposed < active) {
return true;
}
}
}
return false;
}
private void handleInterruptException(Exception e) {
logger.log(Level.FINE, "Interrupt caught - " + getDescription(true), e);
Thread currentThread = Thread.currentThread();
// we want to handle the new proceedTo if interrupted by another thread,
// otherwise we fall out since we are not the owning thread.
Integer next = null;
if (activeThread == currentThread) {
next = nextPlannedAfterInterrupt;
}
if (null != next) {
proceedTo(next);
} else {
// RLS must continue / fall out
logger.log(Level.FINE, "swallowing exception - " + getDescription(true),
new ComponentException(e));
}
}
}
/**
* Async worker
*/
private class AsyncProceedToOp extends Worker implements Runnable {
// record the future for the operation
private Future> activeFuture;
private AsyncProceedToOp(int runLevel) {
super(runLevel);
}
/**
* Interrupts are never handled in the asynchronous case.
*
* Here, we just kill the worker, and expect a new one to form.
*/
@Override
public boolean interrupt(boolean isHard, Integer runLevel) {
boolean haveFuture;
synchronized (lock) {
haveFuture = (null != activeFuture);
if (haveFuture) {
// cancel previous, but down hit thread with interrupt
activeFuture.cancel(false);
activeFuture = null;
}
}
if (haveFuture) {
event(this, ListenerEvent.CANCEL, null, null, isHard);
}
return false;
}
@Override
public void run() {
super.run();
synchronized (lock) {
activeFuture = null;
isHardInterrupt = null;
}
}
@Override
public void proceedTo(int runLevel) {
assert(null == activeFuture);
activeFuture = exec.submit(this);
}
@Override
protected void checkInterrupt(Exception e, Inhabitant> i, Boolean isHard) {
if (isCancelled(this)) {
throw new Interrupt();
}
super.checkInterrupt(e, i, isHard);
}
}
protected ServiceContext serviceContext(Exception e, final Inhabitant> i) {
if (null == i) {
return null;
}
ServiceContext ctx = null;
if (e instanceof ComponentException) {
ctx = ((ComponentException) e).getFailureContext();
}
if (null == ctx) {
ctx = new ServiceContext() {
@Override
public ClassLoader getClassLoader() {
ClassLoader cl;
if (ClassLoaderHolder.class.isInstance(i)) {
cl = ((ClassLoaderHolder)i).getClassLoader();
} else {
cl = i.getClass().getClassLoader();
}
return cl;
}
@Override
public Inhabitant> getInhabitant() {
return i;
}
@Override
public String getType() {
return i.typeName();
}
@Override
public String toString() {
return i.toString();
}
};
}
return ctx;
}
private static class RunLevelServiceThread extends Thread {
private RunLevelServiceThread(Runnable r) {
super(r);
setDaemon(true);
setName(getClass().getSimpleName() + "-" + System.currentTimeMillis());
}
}
@SuppressWarnings("serial")
public static class Interrupt extends RuntimeException {
private Interrupt() {}
}
}