![JAR search and dependency download from the Maven repository](/logo.png)
org.jvnet.hk2.component.internal.runlevel.DefaultRunLevelService Maven / Gradle / Ivy
The newest version!
/*
* 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.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import org.glassfish.hk2.AsyncPostConstruct;
import org.glassfish.hk2.RunLevelDefaultScope;
import org.jvnet.hk2.annotations.Priority;
import org.jvnet.hk2.annotations.RunLevel;
import org.jvnet.hk2.component.AsyncWaiter;
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.MultiThreadedInhabitantActivator;
import org.jvnet.hk2.component.RunLevelException;
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 org.jvnet.hk2.component.SimpleServiceLocator;
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
*/
@SuppressWarnings("deprecation")
public class DefaultRunLevelService implements RunLevelService,
Enableable, RunLevelState, InhabitantListener, HabitatListener,
InhabitantSorter, InhabitantActivator {
// the initial run level
public static final int INITIAL_RUNLEVEL = -2;
// the default name
public static final String DEFAULT_NAME = "default";
// the default mode - sync or async.
public static final boolean DEFAULT_ASYNC_ENABLED = false;
// the default timeout in milliseconds (to wait for async service types)
public static final long DEFAULT_ASYNC_WAIT = 3000;
// the default scope
public static final Class> DEFAULT_SCOPE = RunLevelDefaultScope.class;
private static final Logger logger = Logger.getLogger(DefaultRunLevelService.class.getName());
private static final Level LEVEL = Level.FINE;
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 {@link DEFAULT_NAME}
protected final String name;
// the target scope, defaulting to {@link DEFAULT_SCOPE}
private final Class> targetScope;
// the habitat for this instance of the RunLevelService
private final SimpleServiceLocator 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;
// the alternative, stand-in listener if any
private RunLevelListener listener;
// the alternative, stand-in sorter if any
private InhabitantSorter sorter;
// the alternative, stand-in activator if any
private InhabitantActivator activator;
// used for Async service types
private final AsyncWaiter waiter;
// the parent, when facade/delegation is used
private RunLevelService> parent;
// used for eventing an {@link RunLevelListener}s
private enum ListenerEvent {
PROGRESS, CANCEL, ERROR,
}
public DefaultRunLevelService(SimpleServiceLocator habitat) {
this(habitat, DEFAULT_ASYNC_ENABLED, null, null, null);
}
public DefaultRunLevelService(SimpleServiceLocator habitat, boolean async, String name,
Class> targetScope, 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;
}
});
// waiter is now the responsibility of the activator
this.waiter = null;
} else {
this.exec = null;
// waiter is our responsibility
this.waiter = new AsyncWaiter();
}
this.name = (null == name) ? DEFAULT_NAME : name;
this.targetScope = (null == targetScope) ? RunLevelDefaultScope.class
: targetScope;
this.recorders = (null == recorders) ? new LinkedHashMap()
: recorders;
if (Habitat.class.isInstance(habitat)) {
// 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.class.cast(habitat).addHabitatListener(this);
} else {
// initialize now since we will not be getting any events later
inhabitantChanged(HabitatListener.EventType.HABITAT_INITIALIZED, null, null);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "-" + System.identityHashCode(this)
+ "(" + getDescription(false) + "delegate: " + delegate + ")";
}
public boolean isDefault() {
return RunLevelDefaultScope.class.equals(targetScope);
}
private void assertNotIsDefault() {
if (isDefault()) {
throw new IllegalStateException(
"this operation is not supported on the default RunLevelService");
}
}
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("scope=").append(getScopeName()).append(", ");
if (extended) {
b.append("thrd=").append(Thread.currentThread()).append(", ");
}
return b.toString();
}
@SuppressWarnings("unused")
private void setDelegate(RunLevelState stateProvider) {
assert (this != stateProvider);
this.delegate = stateProvider;
}
public void setParent(RunLevelService> parent) {
assertNotIsDefault();
this.parent = parent;
}
public RunLevelService> getParent() {
return parent;
}
public String getName() {
return name;
}
@Override
public RunLevelState getState() {
return (null == delegate) ? this : delegate;
}
@Override
public String getScopeName() {
return (null == delegate) ? targetScope.getName() : delegate
.getScopeName();
}
@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 activeRunLevel
* the current runLevel
*
* @return
*/
protected boolean accept(AbstractInhabitantImpl> i, int activeRunLevel) {
if (i.isActive()) {
return false;
}
Integer runlevel = getRunLevel(i);
if (null != runlevel) {
if (Integer.valueOf(runlevel) != activeRunLevel) {
return false;
}
}
// avoid loading of the class until we have to ... at this point we have
// to
RunLevel rl = i.getAnnotation(RunLevel.class);
return (rl.value() == activeRunLevel && rl.runLevelScope() == targetScope);
}
/**
* 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 || getParent() == delegate) : "delegate was "
+ delegate
+ " but was instead expected to be either "
+ this + " or " + getParent() + " for " + rli;
} 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, "event {0} - " + getDescription(true), event);
if (isCancelled(worker)) {
logger.log(LEVEL, "Ignoring this notification!");
} else {
Interrupt lastInterrupt = null;
Collection activeListeners = getListeners();
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 RunLevelException(e));
}
}
if (null != lastInterrupt) {
throw lastInterrupt;
} else {
if (null != error) {
logger.log(LEVEL, "swallowing exception - " + context,
new RunLevelException(error));
}
}
}
}
/**
* Overrides the default behavior of getting all listeners from the habitat
* to use a stand-in listener instead. If set to null, the default behavior
* will be restored.
*
* @param listener
* the alternative, stand-in listener
*/
public synchronized void setListener(RunLevelListener listener) {
assertNotIsDefault();
this.listener = listener;
}
protected synchronized Collection getListeners() {
Collection listeners;
if (null == listener) {
Collection> inhabs =
habitat.getInhabitantsByContract(RunLevelListener.class.getName());
listeners = narrow(inhabs);
} else {
listeners = Collections.singleton(listener);
}
return listeners;
}
/**
* narrow down and return the collection for those runLevelScopes that match
*/
@SuppressWarnings("unchecked")
protected Collection narrow(Collection> inhabs) {
if (inhabs.isEmpty()) {
return Collections.emptySet();
}
// narrow the listeners to only our run level scope
Collection services = new ArrayList();
for (Inhabitant> i : inhabs) {
String scope = i.metadata().getFirst(RunLevel.META_SCOPE_TAG);
if (null == scope || scope.equals(targetScope.getName())) {
services.add((T) i.get());
}
}
return services;
}
@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);
Integer activeRunLevel = getRunLevel(ai);
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,
getScopeName());
recorders.put(activeRunLevel, activeRecorder);
}
}
if (null != activeRecorder) {
activeRecorder.inhabitantChanged(eventType, inhabitant);
}
}
}
// we always want to maintain our subscription
return true;
}
/**
* Attempts to obtain the RunLevel value from the metadata() instead of from
* the annotation which requires a class load. If it can't get it from the
* metadata() it will default to load the class to obtain the RunLevel
* value.
*
* @param i
* the inhabitant to get the runLevel for
*
* @return
*/
protected Integer getRunLevel(AbstractInhabitantImpl> i) {
Integer activeRunLevel;
String runlevel = i.metadata().getOne(RunLevel.META_VAL_TAG);
if (null != runlevel && runlevel.length() > 0) {
activeRunLevel = Integer.valueOf(runlevel);
} else {
RunLevel rl = i.getAnnotation(RunLevel.class);
activeRunLevel = (null == rl) ? null : rl.value();
}
return activeRunLevel;
}
/**
* 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 (HabitatListener.EventType.HABITAT_INITIALIZED == eventType) {
if (isEnabled()) {
proceedTo(RunLevel.KERNEL_RUNLEVEL);
}
}
return (null != habitat && !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 < 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) {
Collections.sort(inhabitants, getInhabitantComparator());
return inhabitants;
}
/**
* Called when we are responsible for handling the
* {@link InhabitantActivator} work.
*/
@Override
public void activate(Inhabitant> inhabitant) {
inhabitant.get();
}
/**
* Called when we are responsible for handling the
* {@link InhabitantActivator} work.
*/
@Override
public void deactivate(Inhabitant> inhabitant) {
inhabitant.release();
}
/**
* Await completion for all {@link AsyncPostConstruct} service types
*/
@Override
public void awaitCompletion()
throws InterruptedException, ExecutionException, TimeoutException {
if (null != waiter) {
long start = System.currentTimeMillis();
logger.log(Level.FINER, "awaiting completion");
waiter.waitForDone();
logger.log(Level.FINER, "finished awaiting completion - {0} ms", System.currentTimeMillis() - start);
}
}
/**
* No-op, since we are not a {@link MultiThreadedInhabitantActivator}
*/
@Override
public void awaitCompletion(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException, ExecutionException {
if (null != waiter) {
long start = System.currentTimeMillis();
logger.log(Level.FINER, "awaiting completion");
boolean done = waiter.waitForDone(timeout, unit);
logger.log(Level.FINER, "finished awaiting completion - {0} ms; done = {1}",
new Object[] {System.currentTimeMillis() - start, done});
}
}
/**
* Overrides the default behavior of getting the inhabitant sorter from the
* habitat to use a stand-in sorter instead. If set to null, the default
* behavior will be restored.
*
* @param sorter
* the alternative, stand-in sorter
*/
public synchronized void setInhabitantSorter(InhabitantSorter sorter) {
assertNotIsDefault();
this.sorter = sorter;
}
/**
* 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 synchronized InhabitantSorter getInhabitantSorter() {
if (null != sorter) {
return sorter;
}
Collection> inhabs =
habitat.getInhabitantsByContract(InhabitantSorter.class.getName());
Collection sorters = narrow(inhabs);
return (sorters.isEmpty()) ? this : sorters.iterator().next();
}
/**
* Overrides the default behavior of getting the inhabitant activator from
* the habitat to use a stand-in activator instead. If set to null, the
* default behavior will be restored.
*
* @param activator
* the alternative, stand-in activator
*/
public synchronized void setInhabitantActivator(
InhabitantActivator activator) {
assertNotIsDefault();
this.activator = activator;
}
/**
* 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 synchronized InhabitantActivator getInhabitantActivator() {
if (null != activator) {
return activator;
}
Collection> inhabs =
habitat.getInhabitantsByContract(InhabitantActivator.class.getName());
Collection activators = narrow(inhabs);
return (activators.isEmpty()) ? this : activators.iterator().next();
}
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, "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
.getInhabitantsByContract(RunLevel.class.getName());
for (Inhabitant> i : runLevelInhabitants) {
AbstractInhabitantImpl> ai = AbstractInhabitantImpl.class
.cast(i);
if (accept(ai, activeRunLevel)) {
RunLevelInhabitant, ?> rli = RunLevelInhabitant.class
.cast(ai);
checkBinding(rli);
activations.add(rli);
}
}
if (!activations.isEmpty()) {
InhabitantSorter is = getInhabitantSorter();
if (logger.isLoggable(LEVEL)) {
logger.log(LEVEL, "sorting {0}", activations);
}
activations = is.sort(activations);
assert (null != activations);
InhabitantActivator ia = getInhabitantActivator();
// clear out the old set of watches
if (null != waiter) {
waiter.clear();
}
for (Inhabitant> rli : activations) {
if (logger.isLoggable(LEVEL)) {
logger.log(LEVEL, "activating {0} - "
+ getDescription(true), rli);
}
try {
ia.activate(rli);
// assert(rli.isActive());
if (null != waiter) {
waiter.watchIfNecessary(rli);
}
// an escape hatch if we've been interrupted in some way
checkInterrupt(null, rli, null);
} catch (Exception e) {
checkInterrupt(e, rli, null);
}
}
try {
ia.awaitCompletion(DEFAULT_ASYNC_WAIT, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Inhabitant> i = (null == waiter) ? null : waiter.getLastInhabitantWorkingOn();
checkInterrupt(e, i, 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)) {
logger.log(LEVEL, "releasing {0} - "
+ getDescription(true), i);
}
try {
ia.deactivate(i);
// assert(!i.isActive()); <- this might happen
// asynchronously
checkInterrupt(null, i, null);
} catch (Exception e) {
checkInterrupt(e, i, null);
}
}
try {
ia.awaitCompletion();
} catch (Exception e) {
checkInterrupt(e, null, 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.equals(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, "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, "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, "swallowing exception - "
+ getDescription(true), new RunLevelException(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 {
if (System.getSecurityManager()==null) {
cl = i.getClass().getClassLoader();
} else {
cl = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public ClassLoader run() {
return 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() {
}
}
static Comparator> getInhabitantComparator() {
return new Comparator>() {
public int compare(Inhabitant> o1, Inhabitant> o2) {
int o1level = (o1.type().getAnnotation(Priority.class) != null ? o1
.type().getAnnotation(Priority.class).value()
: 5);
int o2level = (o2.type().getAnnotation(Priority.class) != null ? o2
.type().getAnnotation(Priority.class).value()
: 5);
if (o1level == o2level) {
return 0;
} else if (o1level < o2level) {
return -1;
} else {
return 1;
}
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy