All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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