| /******************************************************************************* |
| * Copyright (c) 2012, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.osgi.container; |
| |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import org.eclipse.osgi.container.ModuleContainerAdaptor.ModuleEvent; |
| import org.eclipse.osgi.framework.util.ThreadInfoReport; |
| import org.eclipse.osgi.internal.container.EquinoxReentrantLock; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.eclipse.osgi.internal.messages.Msg; |
| import org.eclipse.osgi.report.resolution.ResolutionReport; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.AdminPermission; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.BundleReference; |
| import org.osgi.framework.startlevel.BundleStartLevel; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.service.resolver.ResolutionException; |
| |
| /** |
| * A module represents a set of revisions installed in a |
| * module {@link ModuleContainer container}. |
| * @since 3.10 |
| */ |
| public abstract class Module implements BundleReference, BundleStartLevel, Comparable<Module> { |
| /** |
| * The possible start options for a module |
| */ |
| public static enum StartOptions { |
| /** |
| * The module start operation is transient and the persistent |
| * autostart or activation policy setting of the module is not modified. |
| */ |
| TRANSIENT, |
| /** |
| * The module start operation must activate the module according to the module's declared |
| * activation policy. |
| */ |
| USE_ACTIVATION_POLICY, |
| /** |
| * The module start operation is transient and the persistent activation policy |
| * setting will be used. |
| */ |
| TRANSIENT_RESUME, |
| /** |
| * The module start operation is transient and will only happen if {@link Settings#AUTO_START auto start} |
| * setting is persistent. |
| */ |
| TRANSIENT_IF_AUTO_START, |
| /** |
| * The module start operation that indicates the module is being started because of a |
| * lazy start trigger class load. |
| */ |
| LAZY_TRIGGER; |
| |
| /** |
| * Tests if this option is contained in the specified options |
| */ |
| public boolean isContained(StartOptions... options) { |
| for (StartOptions option : options) { |
| if (equals(option)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * The possible start options for a module |
| */ |
| public static enum StopOptions { |
| /** |
| * The module stop operation is transient and the persistent |
| * autostart setting of the module is not modified. |
| */ |
| TRANSIENT; |
| |
| /** |
| * Tests if this option is contained in the specified options |
| */ |
| public boolean isContained(StopOptions... options) { |
| for (StopOptions option : options) { |
| if (equals(option)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * An enumeration of the possible {@link Module#getState() states} a module may be in. |
| */ |
| public static enum State { |
| /** |
| * The module is installed but not yet resolved. |
| */ |
| INSTALLED, |
| /** |
| * The module is resolved and able to be started. |
| */ |
| RESOLVED, |
| /** |
| * The module is waiting for a {@link StartOptions#LAZY_TRIGGER trigger} |
| * class load to proceed with starting. |
| */ |
| LAZY_STARTING, |
| /** |
| * The module is in the process of starting. |
| */ |
| STARTING, |
| /** |
| * The module is now running. |
| */ |
| ACTIVE, |
| /** |
| * The module is in the process of stopping |
| */ |
| STOPPING, |
| /** |
| * The module is uninstalled and may not be used. |
| */ |
| UNINSTALLED |
| } |
| |
| /** |
| * An enumeration of persistent settings for a module |
| */ |
| public static enum Settings { |
| /** |
| * The module has been set to auto start. |
| */ |
| AUTO_START, |
| /** |
| * The module has been set to use its activation policy. |
| */ |
| USE_ACTIVATION_POLICY, |
| /** |
| * The module has been set for parallel activation from start-level |
| * @since 3.15 |
| */ |
| PARALLEL_ACTIVATION |
| } |
| |
| /** |
| * A set of {@link State states} that indicate a module is active. |
| */ |
| public static final EnumSet<State> ACTIVE_SET = EnumSet.of(State.STARTING, State.LAZY_STARTING, State.ACTIVE, State.STOPPING); |
| /** |
| * A set of {@link State states} that indicate a module is resolved. |
| */ |
| public static final EnumSet<State> RESOLVED_SET = EnumSet.of(State.RESOLVED, State.STARTING, State.LAZY_STARTING, State.ACTIVE, State.STOPPING); |
| |
| private final Long id; |
| private final String location; |
| private final ModuleRevisions revisions; |
| final EquinoxReentrantLock stateChangeLock = new EquinoxReentrantLock(); |
| private final EnumSet<ModuleEvent> stateTransitionEvents = EnumSet.noneOf(ModuleEvent.class); |
| private final EnumSet<Settings> settings; |
| final AtomicInteger inStart = new AtomicInteger(0); |
| private volatile State state = State.INSTALLED; |
| private volatile int startlevel; |
| private volatile long lastModified; |
| |
| /** |
| * Constructs a new module with the specified id, location and |
| * container. |
| * @param id the new module id |
| * @param location the new module location |
| * @param container the container for the new module |
| * @param settings the persisted settings. May be {@code null} if there are no settings. |
| * @param startlevel the persisted start level or initial start level. |
| */ |
| public Module(Long id, String location, ModuleContainer container, EnumSet<Settings> settings, int startlevel) { |
| this.id = id; |
| this.location = location; |
| this.revisions = new ModuleRevisions(this, container); |
| this.settings = settings == null ? EnumSet.noneOf(Settings.class) : EnumSet.copyOf(settings); |
| this.startlevel = startlevel; |
| } |
| |
| /** |
| * Returns the module id. |
| * @return the module id. |
| */ |
| public final Long getId() { |
| return id; |
| } |
| |
| /** Returns the module location |
| * @return the module location |
| */ |
| public final String getLocation() { |
| return location; |
| } |
| |
| /** |
| * Returns the {@link ModuleRevisions} associated with this module. |
| * @return the {@link ModuleRevisions} associated with this module |
| */ |
| public final ModuleRevisions getRevisions() { |
| return revisions; |
| } |
| |
| /** |
| * Returns the module container this module is contained in. |
| * @return the module container. |
| */ |
| public final ModuleContainer getContainer() { |
| return revisions.getContainer(); |
| } |
| |
| /** |
| * Returns the current {@link ModuleRevision revision} associated with this module. |
| * If the module is uninstalled then the last current revision is returned. |
| * @return the current {@link ModuleRevision revision} associated with this module. |
| */ |
| public final ModuleRevision getCurrentRevision() { |
| return revisions.getCurrentRevision(); |
| } |
| |
| /** |
| * Returns the current {@link State state} of this module. |
| * @return the current state of this module. |
| */ |
| public final State getState() { |
| return state; |
| } |
| |
| final void setState(State state) { |
| this.state = state; |
| } |
| |
| @Override |
| public final int getStartLevel() { |
| checkValid(); |
| return this.startlevel; |
| } |
| |
| @Override |
| public final void setStartLevel(int startLevel) { |
| revisions.getContainer().setStartLevel(this, startLevel); |
| } |
| |
| @Override |
| public final boolean isPersistentlyStarted() { |
| checkValid(); |
| return settings.contains(Settings.AUTO_START); |
| } |
| |
| @Override |
| public final boolean isActivationPolicyUsed() { |
| checkValid(); |
| return settings.contains(Settings.USE_ACTIVATION_POLICY); |
| } |
| |
| final void storeStartLevel(int newStartLevel) { |
| this.startlevel = newStartLevel; |
| } |
| |
| /** |
| * Returns the time when this module was last modified. A module is considered |
| * to be modified when it is installed, updated or uninstalled. |
| * <p> |
| * The time value is a the number of milliseconds since January 1, 1970, 00:00:00 UTC. |
| * @return the time when this bundle was last modified. |
| */ |
| public final long getLastModified() { |
| return this.lastModified; |
| } |
| |
| final void setlastModified(long lastModified) { |
| this.lastModified = lastModified; |
| } |
| |
| private static final EnumSet<ModuleEvent> VALID_RESOLVED_TRANSITION = EnumSet.of(ModuleEvent.STARTED); |
| private static final EnumSet<ModuleEvent> VALID_STOPPED_TRANSITION = EnumSet.of(ModuleEvent.UPDATED, ModuleEvent.UNRESOLVED, ModuleEvent.UNINSTALLED); |
| |
| /** |
| * Acquires the module lock for state changes by the current thread for the specified |
| * transition event. Certain transition events locks may be nested within other |
| * transition event locks. For example, a resolved transition event lock may be |
| * nested within a started transition event lock. A stopped transition lock |
| * may be nested within an updated, unresolved or uninstalled transition lock. |
| * @param transitionEvent the transition event to acquire the lock for. |
| * @throws BundleException |
| */ |
| protected final void lockStateChange(ModuleEvent transitionEvent) throws BundleException { |
| boolean previousInterruption = Thread.interrupted(); |
| boolean invalid = false; |
| try { |
| boolean acquired = stateChangeLock.tryLock(revisions.getContainer().getModuleLockTimeout(), TimeUnit.SECONDS); |
| Set<ModuleEvent> currentTransition = Collections.emptySet(); |
| if (acquired) { |
| boolean isValidTransition = true; |
| switch (transitionEvent) { |
| case STARTED : |
| case UPDATED : |
| case UNINSTALLED : |
| case UNRESOLVED : |
| // These states must be initiating transition states |
| // no other transition state is allowed when these are kicked off |
| isValidTransition = stateTransitionEvents.isEmpty(); |
| break; |
| case RESOLVED : |
| isValidTransition = VALID_RESOLVED_TRANSITION.containsAll(stateTransitionEvents); |
| break; |
| case STOPPED : |
| isValidTransition = VALID_STOPPED_TRANSITION.containsAll(stateTransitionEvents); |
| break; |
| default : |
| isValidTransition = false; |
| break; |
| } |
| if (!isValidTransition) { |
| currentTransition = EnumSet.copyOf(stateTransitionEvents); |
| invalid = true; |
| stateChangeLock.unlock(); |
| } else { |
| stateTransitionEvents.add(transitionEvent); |
| return; |
| } |
| } else { |
| currentTransition = EnumSet.copyOf(stateTransitionEvents); |
| } |
| Throwable cause; |
| if (invalid) { |
| cause = new IllegalStateException(NLS.bind(Msg.Module_LockStateError, transitionEvent, currentTransition)); |
| } else { |
| cause = new TimeoutException(NLS.bind(Msg.Module_LockTimeout, revisions.getContainer().getModuleLockTimeout())).initCause(new ThreadInfoReport(stateChangeLock.toString())); |
| } |
| String exceptonInfo = toString() + ' ' + transitionEvent + ' ' + currentTransition; |
| throw new BundleException(Msg.Module_LockError + exceptonInfo, BundleException.STATECHANGE_ERROR, cause); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| throw new BundleException(Msg.Module_LockError + toString() + " " + transitionEvent, BundleException.STATECHANGE_ERROR, e); //$NON-NLS-1$ |
| } finally { |
| if (previousInterruption) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| |
| /** |
| * Releases the lock for state changes for the specified transition event. |
| * @param transitionEvent |
| */ |
| protected final void unlockStateChange(ModuleEvent transitionEvent) { |
| if (stateChangeLock.getHoldCount() == 0 || !stateTransitionEvents.contains(transitionEvent)) |
| throw new IllegalMonitorStateException("Current thread does not hold the state change lock for: " + transitionEvent); //$NON-NLS-1$ |
| stateTransitionEvents.remove(transitionEvent); |
| stateChangeLock.unlock(); |
| } |
| |
| /** |
| * Returns true if the current thread holds the state change lock for the specified transition event. |
| * @param transitionEvent |
| * @return true if the current thread holds the state change lock for the specified transition event. |
| */ |
| public final boolean holdsTransitionEventLock(ModuleEvent transitionEvent) { |
| return stateChangeLock.getHoldCount() > 0 && stateTransitionEvents.contains(transitionEvent); |
| } |
| |
| /** |
| * Returns the thread that currently owns the state change lock for this module, or |
| * <code>null</code> if not owned. |
| * @return the owner, or <code>null</code> if not owned. |
| */ |
| public final Thread getStateChangeOwner() { |
| return stateChangeLock.getOwner(); |
| } |
| |
| /** |
| * Starts this module |
| * @param options the options for starting |
| * @throws BundleException if an errors occurs while starting |
| */ |
| public void start(StartOptions... options) throws BundleException { |
| ModuleContainer container = getContainer(); |
| long startTime = 0; |
| if (container.DEBUG_BUNDLE_START_TIME) { |
| startTime = System.nanoTime(); |
| } |
| container.checkAdminPermission(getBundle(), AdminPermission.EXECUTE); |
| if (options == null) { |
| options = new StartOptions[0]; |
| } |
| ModuleEvent event; |
| if (StartOptions.LAZY_TRIGGER.isContained(options)) { |
| setTrigger(); |
| if (stateChangeLock.getHoldCount() > 0 && stateTransitionEvents.contains(ModuleEvent.STARTED)) { |
| // nothing to do here; the current thread is activating the bundle. |
| return; |
| } |
| } |
| BundleException startError = null; |
| boolean lockedStarted = false; |
| // Indicate we are in the middle of a start. |
| // This must be incremented before we acquire the STARTED lock the first time. |
| inStart.incrementAndGet(); |
| try { |
| lockStateChange(ModuleEvent.STARTED); |
| lockedStarted = true; |
| checkValid(); |
| if (StartOptions.TRANSIENT_IF_AUTO_START.isContained(options) && !settings.contains(Settings.AUTO_START)) { |
| // Do nothing; this is a request to start only if the module is set for auto start |
| return; |
| } |
| checkFragment(); |
| persistStartOptions(options); |
| if (getStartLevel() > container.getStartLevel()) { |
| if (StartOptions.TRANSIENT.isContained(options)) { |
| // it is an error to attempt to transient start a bundle without its start level met |
| throw new BundleException(Msg.Module_Transient_StartError, BundleException.START_TRANSIENT_ERROR); |
| } |
| // Do nothing; start level is not met |
| return; |
| } |
| if (State.ACTIVE.equals(getState())) |
| return; |
| if (getState().equals(State.INSTALLED)) { |
| ResolutionReport report; |
| // must unlock to avoid out of order locks when multiple unresolved |
| // bundles are started at the same time from different threads |
| unlockStateChange(ModuleEvent.STARTED); |
| lockedStarted = false; |
| try { |
| report = container.resolve(Collections.singletonList(this), true); |
| } finally { |
| lockStateChange(ModuleEvent.STARTED); |
| lockedStarted = true; |
| } |
| // need to check valid again in case someone uninstalled the bundle |
| checkValid(); |
| ResolutionException e = report.getResolutionException(); |
| if (e != null) { |
| if (e.getCause() instanceof BundleException) { |
| throw (BundleException) e.getCause(); |
| } |
| } |
| if (State.ACTIVE.equals(getState())) |
| return; |
| if (getState().equals(State.INSTALLED)) { |
| String reportMessage = report.getResolutionReportMessage(getCurrentRevision()); |
| throw new BundleException(Msg.Module_ResolveError + reportMessage, BundleException.RESOLVE_ERROR); |
| } |
| } |
| |
| try { |
| event = doStart(options); |
| } catch (BundleException e) { |
| // must return state to resolved |
| setState(State.RESOLVED); |
| startError = e; |
| // must always publish the STOPPED event on error |
| event = ModuleEvent.STOPPED; |
| } |
| } finally { |
| if (lockedStarted) { |
| unlockStateChange(ModuleEvent.STARTED); |
| } |
| inStart.decrementAndGet(); |
| } |
| |
| if (event != null) { |
| if (!EnumSet.of(ModuleEvent.STARTED, ModuleEvent.LAZY_ACTIVATION, ModuleEvent.STOPPED).contains(event)) |
| throw new IllegalStateException("Wrong event type: " + event); //$NON-NLS-1$ |
| publishEvent(event); |
| // only print bundleTime information if we actually fired an event for this bundle |
| if (container.DEBUG_BUNDLE_START_TIME) { |
| Debug.println(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms for total start time event " + event + " - " + this); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| if (startError != null) { |
| throw startError; |
| } |
| } |
| |
| final void publishEvent(ModuleEvent type) { |
| revisions.getContainer().getAdaptor().publishModuleEvent(type, this, this); |
| } |
| |
| /** |
| * Stops this module. |
| * @param options options for stopping |
| * @throws BundleException if an error occurs while stopping |
| */ |
| public void stop(StopOptions... options) throws BundleException { |
| revisions.getContainer().checkAdminPermission(getBundle(), AdminPermission.EXECUTE); |
| if (options == null) |
| options = new StopOptions[0]; |
| ModuleEvent event; |
| BundleException stopError = null; |
| lockStateChange(ModuleEvent.STOPPED); |
| try { |
| checkValid(); |
| checkFragment(); |
| persistStopOptions(options); |
| if (!Module.ACTIVE_SET.contains(getState())) |
| return; |
| try { |
| event = doStop(); |
| } catch (BundleException e) { |
| stopError = e; |
| // must always publish the STOPPED event |
| event = ModuleEvent.STOPPED; |
| } |
| } finally { |
| unlockStateChange(ModuleEvent.STOPPED); |
| } |
| |
| if (event != null) { |
| if (!ModuleEvent.STOPPED.equals(event)) |
| throw new IllegalStateException("Wrong event type: " + event); //$NON-NLS-1$ |
| publishEvent(event); |
| } |
| if (stopError != null) |
| throw stopError; |
| } |
| |
| private void checkFragment() throws BundleException { |
| ModuleRevision current = getCurrentRevision(); |
| if ((current.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { |
| throw new BundleException(Msg.Module_Fragment_InvalidOperation, BundleException.INVALID_OPERATION); |
| } |
| } |
| |
| @Override |
| public final int compareTo(Module o) { |
| int slcomp = this.startlevel - o.startlevel; |
| if (slcomp != 0) { |
| return slcomp; |
| } |
| long idcomp = getId() - o.getId(); |
| return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0); |
| } |
| |
| final void checkValid() { |
| if (getState().equals(State.UNINSTALLED)) |
| throw new IllegalStateException(Msg.Module_UninstalledError); |
| } |
| |
| private ModuleEvent doStart(StartOptions... options) throws BundleException { |
| boolean isLazyTrigger = StartOptions.LAZY_TRIGGER.isContained(options); |
| if (isLazyTrigger) { |
| if (!State.LAZY_STARTING.equals(getState())) { |
| // need to make sure we transition through the lazy starting state |
| setState(State.LAZY_STARTING); |
| // need to publish the lazy event |
| unlockStateChange(ModuleEvent.STARTED); |
| try { |
| publishEvent(ModuleEvent.LAZY_ACTIVATION); |
| } finally { |
| lockStateChange(ModuleEvent.STARTED); |
| } |
| if (State.ACTIVE.equals(getState())) { |
| // A sync listener must have caused the bundle to activate |
| return null; |
| } |
| // continue on to normal starting |
| } |
| if (getContainer().DEBUG_MONITOR_LAZY) { |
| Debug.printStackTrace(new Exception("Module is being lazy activated: " + this)); //$NON-NLS-1$ |
| } |
| } else { |
| if (isLazyActivate(options) && !isTriggerSet()) { |
| if (State.LAZY_STARTING.equals(getState())) { |
| // a sync listener must have tried to start this module again with the lazy option |
| return null; // no event to publish; nothing to do |
| } |
| // set the lazy starting state and return lazy activation event for firing |
| setState(State.LAZY_STARTING); |
| return ModuleEvent.LAZY_ACTIVATION; |
| } |
| } |
| |
| // time to actual start the module |
| if (!State.STARTING.equals(getState())) { |
| // TODO this starting state check should not be needed |
| // but we do it because of the way the system module init works |
| setState(State.STARTING); |
| publishEvent(ModuleEvent.STARTING); |
| } |
| try { |
| startWorker(); |
| setState(State.ACTIVE); |
| return ModuleEvent.STARTED; |
| } catch (Throwable t) { |
| // must fire stopping event |
| setState(State.STOPPING); |
| publishEvent(ModuleEvent.STOPPING); |
| if (t instanceof BundleException) |
| throw (BundleException) t; |
| throw new BundleException(Msg.Module_StartError, BundleException.ACTIVATOR_ERROR, t); |
| } |
| } |
| |
| private void setTrigger() { |
| ModuleLoader loader = getCurrentLoader(); |
| if (loader != null) { |
| loader.getAndSetTrigger(); |
| } |
| } |
| |
| private boolean isTriggerSet() { |
| ModuleLoader loader = getCurrentLoader(); |
| return loader == null ? false : loader.isTriggerSet(); |
| } |
| |
| private ModuleLoader getCurrentLoader() { |
| ModuleRevision current = getCurrentRevision(); |
| if (current == null) { |
| return null; |
| } |
| ModuleWiring wiring = current.getWiring(); |
| if (wiring == null) { |
| return null; |
| } |
| try { |
| return wiring.getModuleLoader(); |
| } catch (UnsupportedOperationException e) { |
| // just ignore and return null; |
| return null; |
| } |
| } |
| |
| /** |
| * Performs any work associated with starting a module. For example, |
| * loading and calling start on an activator. |
| * @throws BundleException if there was an exception starting the module |
| */ |
| protected void startWorker() throws BundleException { |
| // Do nothing |
| } |
| |
| private ModuleEvent doStop() throws BundleException { |
| setState(State.STOPPING); |
| publishEvent(ModuleEvent.STOPPING); |
| try { |
| stopWorker(); |
| return ModuleEvent.STOPPED; |
| } catch (Throwable t) { |
| if (t instanceof BundleException) |
| throw (BundleException) t; |
| throw new BundleException(Msg.Module_StopError, BundleException.ACTIVATOR_ERROR, t); |
| } finally { |
| // must always set the state to stopped |
| setState(State.RESOLVED); |
| } |
| } |
| |
| /** |
| * Performs any work associated with stopping a module. For example, |
| * calling stop on an activator. |
| * @throws BundleException if there was an exception stopping the module |
| */ |
| protected void stopWorker() throws BundleException { |
| // Do nothing |
| } |
| |
| @Override |
| public String toString() { |
| return getCurrentRevision() + " [id=" + id + "]"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| private void persistStartOptions(StartOptions... options) { |
| if (StartOptions.TRANSIENT.isContained(options) || StartOptions.TRANSIENT_RESUME.isContained(options) || StartOptions.LAZY_TRIGGER.isContained(options)) { |
| return; |
| } |
| |
| if (StartOptions.USE_ACTIVATION_POLICY.isContained(options)) { |
| settings.add(Settings.USE_ACTIVATION_POLICY); |
| } else { |
| settings.remove(Settings.USE_ACTIVATION_POLICY); |
| } |
| settings.add(Settings.AUTO_START); |
| revisions.getContainer().moduleDatabase.persistSettings(settings, this); |
| } |
| |
| private void persistStopOptions(StopOptions... options) { |
| if (StopOptions.TRANSIENT.isContained(options)) |
| return; |
| settings.remove(Settings.USE_ACTIVATION_POLICY); |
| settings.remove(Settings.AUTO_START); |
| revisions.getContainer().moduleDatabase.persistSettings(settings, this); |
| } |
| |
| /** |
| * Set if this module should be activated in parallel with other modules that have |
| * the same {@link #getStartLevel() start level}. |
| * @param parallelActivation true if the module should be started in parallel; false otherwise |
| * @since 3.15 |
| */ |
| public void setParallelActivation(boolean parallelActivation) { |
| if (parallelActivation) { |
| settings.add(Settings.PARALLEL_ACTIVATION); |
| } else { |
| settings.remove(Settings.PARALLEL_ACTIVATION); |
| } |
| revisions.getContainer().moduleDatabase.persistSettings(settings, this); |
| } |
| |
| /** |
| * Returns if this module should be activated in parallel with other modules that have |
| * the same {@link #getStartLevel() start level}. |
| * @return true if the module should be started in parallel; false otherwise |
| * @since 3.15 |
| */ |
| public boolean isParallelActivated() { |
| return settings.contains(Settings.PARALLEL_ACTIVATION); |
| } |
| |
| /** |
| * The container is done with the revision and it has been completely removed. |
| * This method allows the resources behind the revision to be cleaned up. |
| * @param revision the revision to clean up |
| */ |
| abstract protected void cleanup(ModuleRevision revision); |
| |
| final boolean isLazyActivate(StartOptions... options) { |
| if (StartOptions.TRANSIENT.isContained(options)) { |
| if (!StartOptions.USE_ACTIVATION_POLICY.isContained(options)) { |
| return false; |
| } |
| } else if (!settings.contains(Settings.USE_ACTIVATION_POLICY)) { |
| return false; |
| } |
| return hasLazyActivatePolicy(); |
| } |
| |
| final boolean hasLazyActivatePolicy() { |
| ModuleRevision current = getCurrentRevision(); |
| return current == null ? false : current.hasLazyActivatePolicy(); |
| } |
| |
| /** |
| * Used internally by the container to determine if any thread is in the middle |
| * of a start operation on this module. |
| * @return |
| */ |
| final boolean inStart() { |
| return inStart.get() > 0; |
| } |
| } |