/******************************************************************************* * Copyright (c) 2008, 2011 VMware Inc. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VMware Inc. - initial contribution * EclipseSource - Bug 358442 Change InstallArtifact graph from a tree to a DAG *******************************************************************************/ package org.eclipse.virgo.kernel.install.artifact.internal; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.virgo.kernel.artifact.fs.ArtifactFS; import org.eclipse.virgo.nano.core.AbortableSignal; import org.eclipse.virgo.nano.core.Signal; import org.eclipse.virgo.nano.deployer.api.core.DeployerLogEvents; import org.eclipse.virgo.nano.deployer.api.core.DeploymentException; import org.eclipse.virgo.kernel.install.artifact.ArtifactIdentity; import org.eclipse.virgo.kernel.install.artifact.ArtifactState; import org.eclipse.virgo.kernel.install.artifact.ArtifactStorage; import org.eclipse.virgo.kernel.install.artifact.GraphAssociableInstallArtifact; import org.eclipse.virgo.kernel.install.artifact.InstallArtifact; import org.eclipse.virgo.nano.serviceability.NonNull; import org.eclipse.virgo.medic.eventlog.EventLogger; import org.eclipse.virgo.util.common.GraphNode; import org.osgi.framework.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link AbstractInstallArtifact} is a base class for implementations of {@link InstallArtifact}. * <p /> * * <strong>Concurrent Semantics</strong><br /> * * This class is thread safe. * */ public abstract class AbstractInstallArtifact implements GraphAssociableInstallArtifact { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Object monitor = new Object(); private final ArtifactIdentity identity; protected final ArtifactStorage artifactStorage; private final Map<String, String> properties = new ConcurrentHashMap<String, String>(); private final Map<String, String> deploymentProperties = new ConcurrentHashMap<String, String>(); private final ArtifactStateMonitor artifactStateMonitor; private final String repositoryName; protected final EventLogger eventLogger; private GraphNode<InstallArtifact> graph; private volatile boolean isRefreshing; // Whether or not this artifact was the target of a deployment operation (rather than a child of such a target). private boolean isTopLevelDeployed = false; /* * If isTopLevelDeployed is true, whether or not this artifact is ACTIVE (or STARTING) from the perspective of the * deployer operations that have been performed on it. If isTopLevelDeployed is false, this flag is undefined. */ private boolean isTopLevelActive = false; /** * Construct an {@link AbstractInstallArtifact} from the given type, name, version, {@link ArtifactFS}, and * {@link ArtifactState}, none of which may be null. * * @param type a non-<code>null</code> artifact type * @param name a non-<code>null</code> artifact name * @param version a non-<code>null</code> artifact {@link Version} * @param artifactFS a non-<code>null</code> <code>ArtifactFS</code> * @param repositoryName the name of the source repository, or <code>null</code> if the artifact is not from a * repository */ protected AbstractInstallArtifact(@NonNull ArtifactIdentity identity, @NonNull ArtifactStorage artifactStorage, @NonNull ArtifactStateMonitor artifactStateMonitor, String repositoryName, EventLogger eventLogger) { this.identity = identity; this.artifactStorage = artifactStorage; this.artifactStateMonitor = artifactStateMonitor; this.repositoryName = repositoryName; this.eventLogger = eventLogger; this.isRefreshing = false; } final ArtifactIdentity getIdentity() { return this.identity; } public final boolean isRefreshing() { return this.isRefreshing; } public void beginInstall() throws DeploymentException { try { this.artifactStateMonitor.onInstalling(this); } catch (DeploymentException de) { failInstall(); throw de; } } public void failInstall() throws DeploymentException { this.artifactStateMonitor.onInstallFailed(this); } public void endInstall() throws DeploymentException { this.artifactStateMonitor.onInstalled(this); } public void beginResolve() throws DeploymentException { pushThreadContext(); try { this.artifactStateMonitor.onResolving(this); } finally { popThreadContext(); } } public void failResolve() throws DeploymentException { pushThreadContext(); try { this.artifactStateMonitor.onResolveFailed(this); } finally { popThreadContext(); } } public void endResolve() throws DeploymentException { pushThreadContext(); try { this.artifactStateMonitor.onResolved(this); } finally { popThreadContext(); } } /** * {@inheritDoc} */ @Override public final String getType() { return this.identity.getType(); } /** * {@inheritDoc} */ @Override public final String getName() { return this.identity.getName(); } /** * {@inheritDoc} */ @Override public final Version getVersion() { return this.identity.getVersion(); } /** * {@inheritDoc} */ @Override public final String getScopeName() { return this.identity.getScopeName(); } /** * {@inheritDoc} */ @Override public State getState() { return this.artifactStateMonitor.getState(); } /** * {@inheritDoc} */ @Override public void start() throws DeploymentException { start(null); } /** * {@inheritDoc} */ @Override public void start(AbortableSignal signal) throws DeploymentException { // If ACTIVE, signal successful completion immediately, otherwise // proceed with start processing. if (getState().equals(State.ACTIVE)) { if (signal != null) { signal.signalSuccessfulCompletion(); } } else { if (!hasStartingParent()) { topLevelStart(); } pushThreadContext(); try { boolean stateChanged = this.artifactStateMonitor.onStarting(this); if (stateChanged || signal != null) { driveDoStart(signal); } } finally { popThreadContext(); } } } protected final void driveDoStart(AbortableSignal signal) throws DeploymentException { AbortableSignal stateMonitorSignal = createStateMonitorSignal(signal); doStart(stateMonitorSignal); } protected final AbortableSignal createStateMonitorSignal(AbortableSignal signal) { return new StateMonitorSignal(signal); } private final class StateMonitorSignal implements AbortableSignal { private final AbortableSignal signal; public StateMonitorSignal(AbortableSignal signal) { this.signal = signal; } /** * {@inheritDoc} */ @Override public void signalSuccessfulCompletion() { try { asyncStartSucceeded(); AbstractInstallArtifact.signalSuccessfulCompletion(this.signal); } catch (DeploymentException de) { signalFailure(de); } } /** * {@inheritDoc} */ @Override public void signalFailure(Throwable cause) { asyncStartFailed(cause); try { stop(); } catch (DeploymentException de) { AbstractInstallArtifact.this.logger.error("Stop failed", de); } AbstractInstallArtifact.signalFailure(this.signal, cause); } /** * {@inheritDoc} */ @Override public void signalAborted() { asyncStartAborted(); try { stop(); } catch (DeploymentException de) { AbstractInstallArtifact.this.logger.error("Stop aborted", de); } AbstractInstallArtifact.signalAbortion(this.signal); } } protected static void signalSuccessfulCompletion(Signal signal) { if (signal != null) { signal.signalSuccessfulCompletion(); } } protected static void signalFailure(Signal signal, Throwable e) { if (signal != null) { signal.signalFailure(e); } } protected static void signalAbortion(AbortableSignal signal) { if (signal != null) { signal.signalAborted(); } } /** * Perform the actual start of this {@link InstallArtifact} and drive the given {@link Signal} on successful or * unsuccessful completion. * * @param signal the <code>Signal</code> to be driven * @throws DeploymentException if the start fails synchronously */ protected abstract void doStart(AbortableSignal signal) throws DeploymentException; private final void asyncStartSucceeded() throws DeploymentException { pushThreadContext(); try { this.artifactStateMonitor.onStarted(this); } finally { popThreadContext(); } } private final void asyncStartFailed(Throwable cause) { topLevelStop(); pushThreadContext(); try { this.artifactStateMonitor.onStartFailed(this, cause); } catch (DeploymentException e) { logger.error(String.format("listener for %s threw DeploymentException", this), e); } finally { popThreadContext(); } } private final void asyncStartAborted() { topLevelStop(); pushThreadContext(); try { this.artifactStateMonitor.onStartAborted(this); } catch (DeploymentException e) { logger.error(String.format("listener for %s threw DeploymentException", this), e); } finally { popThreadContext(); } } /** * {@inheritDoc} */ @Override public void stop() throws DeploymentException { // Only stop if ACTIVE or STARTING and this artifact should stop given its parent's states. if ((getState().equals(State.ACTIVE) || getState().equals(State.STARTING)) && shouldStop()) { pushThreadContext(); try { this.artifactStateMonitor.onStopping(this); try { doStop(); this.artifactStateMonitor.onStopped(this); } catch (DeploymentException e) { this.artifactStateMonitor.onStopFailed(this, e); } } finally { popThreadContext(); } } } protected boolean shouldStop() { /* * The artifact should stop if it was explicitly stopped (not via a parent) or if it was implicitly stopped (via * a parents) and it has no parents that are ACTIVE or STARTING. */ boolean explicitStop = explicitStop(); if (explicitStop) { topLevelStop(); } return explicitStop || !hasActiveParent(); } public boolean explicitStop() { return !hasStoppingParent(); } private boolean hasActiveParent() { synchronized (this.monitor) { if (this.isTopLevelDeployed && this.isTopLevelActive) { return true; } } for (GraphNode<InstallArtifact> parent : this.graph.getParents()) { State parentState = parent.getValue().getState(); if (parentState.equals(State.ACTIVE) || parentState.equals(State.STARTING)) { return true; } } return false; } private boolean hasStoppingParent() { return hasParentInState(State.STOPPING); } private boolean hasParentInState(State state) { for (GraphNode<InstallArtifact> parent : this.graph.getParents()) { State parentState = parent.getValue().getState(); if (parentState.equals(state)) { return true; } } return false; } private void topLevelStop() { synchronized (this.monitor) { if (this.isTopLevelDeployed) { this.isTopLevelActive = false; } } } protected boolean hasStartingParent() { for (GraphNode<InstallArtifact> parent : this.graph.getParents()) { State parentState = parent.getValue().getState(); if (parentState.equals(State.STARTING)) { return true; } } return false; } protected void topLevelStart() { synchronized (this.monitor) { if (this.isTopLevelDeployed) { this.isTopLevelActive = true; } } } /** * @see stop */ protected abstract void doStop() throws DeploymentException; /** * {@inheritDoc} */ @Override public void uninstall() throws DeploymentException { if ((getState().equals(State.STARTING) || getState().equals(State.ACTIVE) || getState().equals(State.RESOLVED) || getState().equals(State.INSTALLED) || getState().equals(State.INITIAL))) { try { if (!getState().equals(State.INITIAL)) { pushThreadContext(); try { if (getState().equals(State.ACTIVE) || getState().equals(State.STARTING)) { stop(); } if (shouldUninstall()) { this.artifactStateMonitor.onUninstalling(this); try { doUninstall(); this.artifactStateMonitor.onUninstalled(this); } catch (DeploymentException e) { this.artifactStateMonitor.onUninstallFailed(this, e); } } } finally { popThreadContext(); } } } finally { this.artifactStorage.delete(); } } } private boolean shouldUninstall() { boolean explicitUninstall = explicitUninstall(); if (explicitUninstall) { topLevelUninstall(); } return allParentsInState(State.UNINSTALLING); } private boolean allParentsInState(State state) { for (GraphNode<InstallArtifact> parent : this.graph.getParents()) { State parentState = parent.getValue().getState(); if (!parentState.equals(state)) { return false; } } return true; } public boolean explicitUninstall() { return !hasUninstallingParent(); } private boolean hasUninstallingParent() { return hasParentInState(State.UNINSTALLING); } private void topLevelUninstall() { synchronized (this.monitor) { this.isTopLevelDeployed = false; } } /** * @see uninstall */ protected abstract void doUninstall() throws DeploymentException; /** * {@inheritDoc} */ @Override public final ArtifactFS getArtifactFS() { return this.artifactStorage.getArtifactFS(); } /** * {@inheritDoc} */ @Override public String toString() { return this.identity.toString(); } /** * Push the thread context including any application trace name and thread context class loader. The caller is * responsible for calling <code>popThreadContext</code>. */ public void pushThreadContext() { // There is no default thread context. Subclasses must override to // provide one. } /** * Pop a previously pushed thread context. */ public void popThreadContext() { // There is no default thread context. Subclasses must override to // provide one. } protected final ArtifactStateMonitor getStateMonitor() { return this.artifactStateMonitor; } /** * @return false */ @Override public boolean refresh() throws DeploymentException { try { this.isRefreshing = true; this.eventLogger.log(DeployerLogEvents.REFRESHING, getType(), getName(), getVersion()); this.artifactStorage.synchronize(); boolean refreshed = doRefresh(); if (refreshed) { this.eventLogger.log(DeployerLogEvents.REFRESHED, getType(), getName(), getVersion()); } else { failRefresh(); } return refreshed; } catch (DeploymentException de) { failRefresh(de); throw de; } catch (RuntimeException re) { failRefresh(re); throw re; } finally { this.isRefreshing = false; } } private void failRefresh() { failRefresh(null); } private void failRefresh(Exception ex) { this.artifactStorage.rollBack(); issueFailedRefreshMessage(ex); } public void issueFailedRefreshMessage(Exception ex) { if (ex == null) { this.eventLogger.log(DeployerLogEvents.REFRESH_FAILED, getType(), getName(), getVersion()); } else { this.eventLogger.log(DeployerLogEvents.REFRESH_FAILED, ex, getType(), getName(), getVersion()); } } protected boolean doRefresh() throws DeploymentException { return false; } public boolean refresh(String symbolicName) throws DeploymentException { try { this.isRefreshing = true; this.eventLogger.log(DeployerLogEvents.REFRESHING, getType(), getName(), getVersion()); boolean refreshed = doRefresh(symbolicName); if (refreshed) { this.eventLogger.log(DeployerLogEvents.REFRESHED, getType(), getName(), getVersion()); } else { issueFailedRefreshMessage(null); } return refreshed; } catch (DeploymentException de) { issueFailedRefreshMessage(de); throw de; } catch (RuntimeException re) { issueFailedRefreshMessage(re); throw re; } finally { this.isRefreshing = false; } } protected boolean doRefresh(String symbolicName) throws DeploymentException { return false; } /** * {@inheritDoc} */ @Override public final String getProperty(@NonNull String name) { return this.properties.get(name); } /** * {@inheritDoc} */ @Override public final Set<String> getPropertyNames() { HashSet<String> propertyNames = new HashSet<String>(this.properties.keySet()); return Collections.unmodifiableSet(propertyNames); } /** * {@inheritDoc} */ @Override public final String setProperty(String name, String value) { return this.properties.put(name, value); } public Map<String, String> getDeploymentProperties() { return this.deploymentProperties; } /** * {@inheritDoc} */ @Override public final String getRepositoryName() { return this.repositoryName; } /** * @param graph to set * @throws DeploymentException possible from overriding methods */ public void setGraph(GraphNode<InstallArtifact> graph) throws DeploymentException { synchronized (this.monitor) { this.graph = graph; } } /** * {@inheritDoc} */ @Override public final GraphNode<InstallArtifact> getGraph() { synchronized (this.monitor) { return this.graph; } } public void setTopLevelDeployed() { synchronized (this.monitor) { this.isTopLevelDeployed = true; this.isTopLevelActive = true; } } public boolean getTopLevelDeployed() { synchronized (this.monitor) { return this.isTopLevelDeployed; } } }