/******************************************************************************* * 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.deployer.core.internal; import java.io.File; import java.net.URI; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.virgo.nano.core.KernelException; import org.eclipse.virgo.nano.deployer.api.core.ApplicationDeployer; import org.eclipse.virgo.nano.deployer.api.core.DeployUriNormaliser; import org.eclipse.virgo.nano.deployer.api.core.DeployerConfiguration; import org.eclipse.virgo.nano.deployer.api.core.DeployerLogEvents; import org.eclipse.virgo.nano.deployer.api.core.DeploymentException; import org.eclipse.virgo.nano.deployer.api.core.DeploymentIdentity; import org.eclipse.virgo.nano.deployer.api.core.DeploymentOptions; import org.eclipse.virgo.kernel.deployer.core.internal.event.DeploymentListener; import org.eclipse.virgo.kernel.deployer.model.DuplicateDeploymentIdentityException; import org.eclipse.virgo.kernel.deployer.model.DuplicateFileNameException; import org.eclipse.virgo.kernel.deployer.model.DuplicateLocationException; import org.eclipse.virgo.kernel.deployer.model.GCRoots; import org.eclipse.virgo.kernel.deployer.model.RuntimeArtifactModel; import org.eclipse.virgo.kernel.install.artifact.ArtifactIdentity; import org.eclipse.virgo.kernel.install.artifact.ArtifactIdentityDeterminer; import org.eclipse.virgo.kernel.install.artifact.InstallArtifact; import org.eclipse.virgo.kernel.install.artifact.InstallArtifactGraphInclosure; import org.eclipse.virgo.kernel.install.artifact.PlanInstallArtifact; import org.eclipse.virgo.kernel.install.artifact.internal.AbstractInstallArtifact; import org.eclipse.virgo.kernel.install.environment.InstallEnvironment; import org.eclipse.virgo.kernel.install.environment.InstallEnvironmentFactory; import org.eclipse.virgo.kernel.install.pipeline.Pipeline; import org.eclipse.virgo.kernel.osgi.framework.UnableToSatisfyBundleDependenciesException; import org.eclipse.virgo.kernel.osgi.framework.UnableToSatisfyDependenciesException; import org.eclipse.virgo.nano.serviceability.NonNull; import org.eclipse.virgo.medic.eventlog.EventLogger; import org.eclipse.virgo.repository.Repository; import org.eclipse.virgo.repository.WatchableRepository; import org.eclipse.virgo.util.common.GraphNode; import org.eclipse.virgo.util.io.PathReference; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; /** * {@link PipelinedApplicationDeployer} is an implementation of {@link ApplicationDeployer} which creates a * {@link GraphNode} of {@link InstallArtifact InstallArtifacts} and processes the graph by passing it through a * {@link Pipeline} while operating on an {@link InstallEnvironment}. * <p /> * * <strong>Concurrent Semantics</strong><br /> * * This class is thread safe. * */ final class PipelinedApplicationDeployer implements ApplicationDeployer, ApplicationRecoverer { private static final String BUNDLE_TYPE = "bundle"; private final EventLogger eventLogger; private final Object monitor = new Object(); private final InstallEnvironmentFactory installEnvironmentFactory; private final InstallArtifactGraphInclosure installArtifactGraphInclosure; private final ArtifactIdentityDeterminer artifactIdentityDeterminer; private final RuntimeArtifactModel ram; private final DeploymentListener deploymentListener; private final Map<DeploymentIdentity, DeploymentOptions> deploymentOptionsMap = new HashMap<DeploymentIdentity, DeploymentOptions>(); private final Pipeline pipeline; private final DeployUriNormaliser deployUriNormaliser; private final int deployerConfiguredTimeoutInSeconds; private final BundleContext bundleContext; public PipelinedApplicationDeployer(@NonNull Pipeline pipeline, @NonNull InstallArtifactGraphInclosure installArtifactGraphInclosure, @NonNull ArtifactIdentityDeterminer artifactIdentityDeterminer, @NonNull InstallEnvironmentFactory installEnvironmentFactory, @NonNull RuntimeArtifactModel ram, @NonNull DeploymentListener deploymentListener, @NonNull EventLogger eventLogger, @NonNull DeployUriNormaliser normaliser, @NonNull DeployerConfiguration deployerConfiguration, @NonNull BundleContext bundleContext) { this.eventLogger = eventLogger; this.installArtifactGraphInclosure = installArtifactGraphInclosure; this.artifactIdentityDeterminer = artifactIdentityDeterminer; this.installEnvironmentFactory = installEnvironmentFactory; this.ram = ram; this.deploymentListener = deploymentListener; this.deployUriNormaliser = normaliser; this.bundleContext = bundleContext; this.pipeline = pipeline; this.deployerConfiguredTimeoutInSeconds = deployerConfiguration.getDeploymentTimeoutSeconds(); } /** * {@inheritDoc} */ public DeploymentIdentity deploy(URI location) throws DeploymentException { synchronized (this.monitor) { return deploy(location, new DeploymentOptions()); } } private URI normaliseDeploymentUri(URI uri) throws DeploymentException { URI normalisedLocation = this.deployUriNormaliser.normalise(uri); if (normalisedLocation == null) { this.eventLogger.log(DeployerLogEvents.UNSUPPORTED_URI_SCHEME, uri, uri.getScheme()); throw new DeploymentException("PipelinedApplicationDeployer.deploy does not support '" + uri.getScheme() + "' scheme URIs"); } return normalisedLocation; } public DeploymentIdentity install(URI location) throws DeploymentException { return install(location, new DeploymentOptions()); } public DeploymentIdentity install(URI uri, DeploymentOptions deploymentOptions) throws DeploymentException { URI normalisedUri = normaliseDeploymentUri(uri); DeploymentIdentity deploymentIdentity = doInstall(normalisedUri, deploymentOptions); this.deploymentListener.deployed(normalisedUri, deploymentOptions); return deploymentIdentity; } private DeploymentIdentity doInstall(URI normalisedUri, DeploymentOptions deploymentOptions) throws DeploymentException { synchronized (this.monitor) { InstallArtifact existingArtifact = this.ram.get(normalisedUri); if (existingArtifact != null) { DeploymentIdentity refreshedIdentity = refreshExistingArtifact(normalisedUri, existingArtifact); if (refreshedIdentity != null) { return refreshedIdentity; } } GraphNode<InstallArtifact> installNode; boolean shared = false; try { ArtifactIdentity artifactIdentity = determineIdentity(normalisedUri); installNode = findSharedNode(artifactIdentity); if (installNode == null) { installNode = this.installArtifactGraphInclosure.constructGraphNode(artifactIdentity, new File(normalisedUri), null, null); } else { shared = true; } } catch (Exception e) { throw new DeploymentException(e.getMessage() + ": uri='" + normalisedUri + "'", e); } DeploymentIdentity deploymentIdentity; try { deploymentIdentity = addGraphToModel(normalisedUri, installNode); } catch (KernelException ke) { if (!shared) { destroyInstallGraph(installNode); } throw new DeploymentException(ke.getMessage(), ke); } if (!shared) { this.deploymentOptionsMap.put(deploymentIdentity, deploymentOptions); try { driveInstallPipeline(normalisedUri, installNode); } catch (DeploymentException de) { removeFromModel(deploymentIdentity); destroyInstallGraph(installNode); throw de; } catch (RuntimeException re) { removeFromModel(deploymentIdentity); destroyInstallGraph(installNode); throw re; } } return deploymentIdentity; } } private ArtifactIdentity determineIdentity(URI artifactUri) throws DeploymentException { try { File artifact = new File(artifactUri); if (!artifact.exists()) { throw new DeploymentException(artifact + " does not exist"); } return determineIdentity(artifact, null); } catch (Exception e) { throw new DeploymentException(e.getMessage() + ": uri='" + artifactUri + "'", e); } } private GraphNode<InstallArtifact> findSharedNode(ArtifactIdentity artifactIdentity) { GCRoots gcRoots = (GCRoots) this.ram; return ExistingNodeLocator.findSharedNode(gcRoots, artifactIdentity); } private void destroyInstallGraph(GraphNode<InstallArtifact> installGraph) throws DeploymentException { installGraph.getValue().uninstall(); } private void removeFromModel(DeploymentIdentity deploymentIdentity) throws DeploymentException { this.ram.delete(deploymentIdentity); } private DeploymentIdentity refreshExistingArtifact(URI normalisedLocation, InstallArtifact existingArtifact) throws DeploymentException { String oldType = existingArtifact.getType(); String oldName = existingArtifact.getName(); Version oldVersion = existingArtifact.getVersion(); DeploymentIdentity deploymentIdentity = refreshArtifact(normalisedLocation, existingArtifact); if (deploymentIdentity != null) { return deploymentIdentity; } DeploymentIdentity oldDeploymentIdentity = new StandardDeploymentIdentity(oldType, oldName, oldVersion.toString()); undeployInternal(oldDeploymentIdentity, true, false); return null; } /** * {@inheritDoc} */ public DeploymentIdentity deploy(URI location, DeploymentOptions deploymentOptions) throws DeploymentException { URI normalisedLocation = normaliseDeploymentUri(location); InstallArtifact installedArtifact; DeploymentIdentity deploymentIdentity; synchronized (this.monitor) { deploymentIdentity = install(location, deploymentOptions); installedArtifact = this.ram.get(normalisedLocation); } try { start(installedArtifact, deploymentOptions.getSynchronous()); } catch (DeploymentException de) { synchronized (this.monitor) { stopArtifact(installedArtifact); uninstallArtifact(installedArtifact); } throw de; } this.deploymentListener.deployed(normalisedLocation, deploymentOptions); return deploymentIdentity; } private DeploymentIdentity refreshArtifact(URI location, InstallArtifact installArtifact) throws DeploymentException { DeploymentIdentity deploymentIdentity = null; if (installArtifact.refresh()) { this.deploymentListener.refreshed(location); deploymentIdentity = new StandardDeploymentIdentity(installArtifact.getType(), installArtifact.getName(), installArtifact.getVersion().toString()); } return deploymentIdentity; } /** * {@inheritDoc} */ public DeploymentIdentity deploy(String type, String name, Version version) throws DeploymentException { throw new UnsupportedOperationException( "PipelinedApplicationDeployer ApplicationDeployer does not support deployment by type, name, and version"); } /** * {@inheritDoc} */ public DeploymentIdentity deploy(String type, String name, Version version, DeploymentOptions options) throws DeploymentException { throw new UnsupportedOperationException( "PipelinedApplicationDeployer ApplicationDeployer does not support deployment by type, name, and version"); } private DeploymentIdentity addGraphToModel(URI location, GraphNode<InstallArtifact> installGraph) throws DuplicateFileNameException, DuplicateLocationException, DuplicateDeploymentIdentityException, DeploymentException { InstallArtifact installArtifact = installGraph.getValue(); ((AbstractInstallArtifact) installArtifact).setTopLevelDeployed(); return this.ram.add(location, installArtifact); } /** * {@inheritDoc} */ public void recoverDeployment(URI uri, DeploymentOptions options) throws DeploymentException { GraphNode<InstallArtifact> installNode = null; boolean shared = false; File artifact = new File(uri); if (options.getRecoverable() && (!options.getDeployerOwned() || artifact.exists())) { ArtifactIdentity artifactIdentity = determineIdentity(artifact, null); installNode = findSharedNode(artifactIdentity); if (installNode == null) { installNode = this.installArtifactGraphInclosure.recoverInstallGraph(artifactIdentity, artifact); } else { shared = true; } } if (installNode == null) { // Remove the URI from the recovery log. this.deploymentListener.undeployed(uri); } else { if (!shared) { driveInstallPipeline(uri, installNode); start(installNode.getValue(), options.getSynchronous()); } try { addGraphToModel(uri, installNode); } catch (KernelException e) { throw new DeploymentException(e.getMessage(), e); } } } private ArtifactIdentity determineIdentity(File file, String scopeName) throws DeploymentException { ArtifactIdentity artifactIdentity = this.artifactIdentityDeterminer.determineIdentity(file, scopeName); if (artifactIdentity == null) { this.eventLogger.log(DeployerLogEvents.INDETERMINATE_ARTIFACT_TYPE, file); throw new DeploymentException("Cannot determine the artifact identity of the file '" + file + "'"); } return artifactIdentity; } private void driveInstallPipeline(URI uri, GraphNode<InstallArtifact> installGraph) throws DeploymentException { refreshWatchedRepositories(); InstallEnvironment installEnvironment = this.installEnvironmentFactory.createInstallEnvironment(installGraph.getValue()); try { this.pipeline.process(installGraph, installEnvironment); } catch (UnableToSatisfyBundleDependenciesException utsbde) { logDependencySatisfactionException(uri, utsbde); throw new DeploymentException("Dependency satisfaction failed", utsbde); } finally { installEnvironment.destroy(); } } private void logDependencySatisfactionException(URI uri, UnableToSatisfyDependenciesException ex) { this.eventLogger.log(DeployerLogEvents.UNABLE_TO_SATISFY_CONSTRAINTS, ex, uri, ex.getSymbolicName(), ex.getVersion(), ex.getFailureDescription()); } private void start(InstallArtifact installArtifact, boolean synchronous) throws DeploymentException { BlockingAbortableSignal blockingSignal = new BlockingAbortableSignal(synchronous); installArtifact.start(blockingSignal); if (synchronous && this.deployerConfiguredTimeoutInSeconds > 0) { boolean complete = blockingSignal.awaitCompletion(this.deployerConfiguredTimeoutInSeconds); if (blockingSignal.isAborted()) { this.eventLogger.log(DeployerLogEvents.START_ABORTED, installArtifact.getType(), installArtifact.getName(), installArtifact.getVersion(), this.deployerConfiguredTimeoutInSeconds); } else if (!complete) { this.eventLogger.log(DeployerLogEvents.START_TIMED_OUT, installArtifact.getType(), installArtifact.getName(), installArtifact.getVersion(), this.deployerConfiguredTimeoutInSeconds); } } else { // Completion messages will have been issued if complete, so ignore return value. blockingSignal.checkComplete(); } } /** * {@inheritDoc} */ public DeploymentIdentity[] getDeploymentIdentities() { synchronized (this.monitor) { return this.ram.getDeploymentIdentities(); } } /** * {@inheritDoc} */ public DeploymentIdentity getDeploymentIdentity(URI location) { synchronized (this.monitor) { InstallArtifact installArtifact = this.ram.get(location); if (installArtifact != null) { return getDeploymentIdentity(installArtifact); } } return null; } private DeploymentIdentity getDeploymentIdentity(InstallArtifact installArtifact) { return new StandardDeploymentIdentity(installArtifact.getType(), installArtifact.getName(), installArtifact.getVersion().toString()); } /** * {@inheritDoc} */ public boolean isDeployed(URI location) { URI normalisedLocation; try { normalisedLocation = this.deployUriNormaliser.normalise(location); } catch (DeploymentException e) { return false; } if (normalisedLocation == null) { this.eventLogger.log(DeployerLogEvents.UNSUPPORTED_URI_SCHEME, location.toString(), location.getScheme()); return false; } synchronized (this.monitor) { return this.ram.get(normalisedLocation) != null; } } /** * {@inheritDoc} */ public DeploymentIdentity refresh(URI location, String symbolicName) throws DeploymentException { URI normalisedLocation = this.deployUriNormaliser.normalise(location); if (normalisedLocation == null) { this.eventLogger.log(DeployerLogEvents.UNSUPPORTED_URI_SCHEME, location.toString(), location.getScheme()); throw new DeploymentException("PipelinedApplicationDeployer.refresh does not support '" + location.getScheme() + "' scheme URIs"); } DeploymentIdentity deploymentIdentity; synchronized (this.monitor) { InstallArtifact installArtifact = this.ram.get(normalisedLocation); if (installArtifact == null) { this.eventLogger.log(DeployerLogEvents.REFRESH_REQUEST_URI_NOT_FOUND, location.toString()); throw new DeploymentException("Refresh not possible as no application is deployed from URI " + location); } else { DeploymentIdentity originalDeploymentIdentity = getDeploymentIdentity(installArtifact); deploymentIdentity = originalDeploymentIdentity; try { // Attempt to refresh the artifact and escalate to redeploy if this fails. if (refreshInternal(symbolicName, installArtifact)) { this.deploymentListener.refreshed(normalisedLocation); } else { DeploymentOptions deploymentOptions = this.deploymentOptionsMap.get(deploymentIdentity); if (deploymentOptions == null) { deploymentOptions = DeploymentOptions.DEFAULT_DEPLOYMENT_OPTIONS; } deploymentIdentity = redeploy(originalDeploymentIdentity, normalisedLocation, deploymentOptions); } this.eventLogger.log(DeployerLogEvents.REFRESH_REQUEST_COMPLETED, symbolicName, originalDeploymentIdentity.getType(), originalDeploymentIdentity.getSymbolicName(), originalDeploymentIdentity.getVersion()); } catch (RuntimeException e) { this.eventLogger.log(DeployerLogEvents.REFRESH_REQUEST_FAILED, e, symbolicName, originalDeploymentIdentity.getType(), originalDeploymentIdentity.getSymbolicName(), originalDeploymentIdentity.getVersion()); throw e; } catch (Exception e) { this.eventLogger.log(DeployerLogEvents.REFRESH_REQUEST_FAILED, e, symbolicName, originalDeploymentIdentity.getType(), originalDeploymentIdentity.getSymbolicName(), originalDeploymentIdentity.getVersion()); throw new DeploymentException("refresh failed", e); } } } return deploymentIdentity; } private boolean refreshInternal(String symbolicName, InstallArtifact installArtifact) throws DeploymentException { if (installArtifact instanceof PlanInstallArtifact) { return ((PlanInstallArtifact) installArtifact).refresh(symbolicName); } else { return installArtifact.refresh(); } } private DeploymentIdentity redeploy(DeploymentIdentity toUndeploy, URI toDeploy, DeploymentOptions deploymentOptions) throws DeploymentException { synchronized (this.monitor) { undeployInternal(toUndeploy, true, false); } return deploy(toDeploy, deploymentOptions); } /** * {@inheritDoc} */ public void refreshBundle(String bundleSymbolicName, String bundleVersion) throws DeploymentException { DeploymentIdentity deploymentIdentity = new StandardDeploymentIdentity(BUNDLE_TYPE, bundleSymbolicName, bundleVersion); InstallArtifact bundleInstallArtifact; synchronized (this.monitor) { bundleInstallArtifact = this.ram.get(deploymentIdentity); } if (bundleInstallArtifact == null) { this.eventLogger.log(DeployerLogEvents.REFRESH_ARTEFACT_NOT_FOUND, BUNDLE_TYPE, bundleSymbolicName, bundleVersion); throw new DeploymentException("Refresh not possible as no " + BUNDLE_TYPE + " with name " + bundleSymbolicName + " and version " + bundleVersion + " is deployed"); } bundleInstallArtifact.refresh(); } /** * {@inheritDoc} */ public void undeploy(String symbolicName, String version) throws DeploymentException { // This method is deprecated and should be deleted when it is no longer used. Meanwhile, just try undeploying // the possible types... DeploymentException de = null; try { undeploy(BUNDLE_TYPE, symbolicName, version); return; } catch (DeploymentException e) { de = e; } try { undeploy("par", symbolicName, version); return; } catch (DeploymentException e) { de = e; } try { undeploy("plan", symbolicName, version); return; } catch (DeploymentException e) { de = e; } try { undeploy("properties", symbolicName, version); return; } catch (DeploymentException e) { de = e; } throw de; } /** * {@inheritDoc} */ public void undeploy(String type, String symbolicName, String version) throws DeploymentException { DeploymentIdentity deploymentIdentity = new StandardDeploymentIdentity(type, symbolicName, version); synchronized (this.monitor) { undeployInternal(deploymentIdentity, false, false); } } /** * {@inheritDoc} */ public void undeploy(DeploymentIdentity deploymentIdentity) throws DeploymentException { synchronized (this.monitor) { undeployInternal(deploymentIdentity, false, false); } } /** * {@inheritDoc} */ public void undeploy(DeploymentIdentity deploymentIdentity, boolean deleted) throws DeploymentException { synchronized (this.monitor) { undeployInternal(deploymentIdentity, false, true); } } /** * All the undeploy work goes on in here -- it is assumed that any required monitors are already held by the caller. * <p> * The deleted parameter indicates whether the undeployment is a consequence of the artifact having been deleted. * This affects the processing of "deployer owned" artifacts which undeploy would normally delete automatically. If * the undeploy is a consequence of the artifact having been deleted, then undeploy must not delete the artifact * automatically since this may actually delete a "new" artifact which has arrived shortly after the "old" artifact * was deleted. * * @param deploymentIdentity identity of artifact to undeploy * @param redeploying flag to indicate if we are performing a re-deploy * @param deleted <code>true</code> if and only if undeploy is being driven as a consequence of the artifact having * been deleted * @throws DeploymentException */ private void undeployInternal(DeploymentIdentity deploymentIdentity, boolean redeploying, boolean deleted) throws DeploymentException { DeploymentOptions options = this.deploymentOptionsMap.remove(deploymentIdentity); URI location = doUndeploy(deploymentIdentity); if (location != null && !redeploying) { deleteArtifactIfNecessary(location, options, deleted); } } private void deleteArtifactIfNecessary(URI location, DeploymentOptions options, boolean deleted) { if (options != null && options.getDeployerOwned() && !deleted) { new PathReference(location).delete(true); } } private URI doUndeploy(DeploymentIdentity deploymentIdentity) throws DeploymentException { synchronized (this.monitor) { InstallArtifact installArtifact = this.ram.get(deploymentIdentity); if (installArtifact == null) { String type = deploymentIdentity.getType(); String symbolicName = deploymentIdentity.getSymbolicName(); String version = deploymentIdentity.getVersion(); this.eventLogger.log(DeployerLogEvents.UNDEPLOY_ARTEFACT_NOT_FOUND, type, symbolicName, version); throw new DeploymentException("Undeploy not possible as no " + type + " with name " + symbolicName + " and version " + version + " is deployed"); } else { URI location = this.ram.getLocation(deploymentIdentity); this.ram.delete(deploymentIdentity); stopArtifact(installArtifact); uninstallArtifact(installArtifact); return location; } } } private void stopArtifact(InstallArtifact installArtifact) throws DeploymentException { installArtifact.stop(); } private void uninstallArtifact(InstallArtifact installArtifact) throws DeploymentException { installArtifact.uninstall(); } private void refreshWatchedRepositories() { try { Collection<ServiceReference<WatchableRepository>> references = this.bundleContext.getServiceReferences(WatchableRepository.class, null); for (ServiceReference<WatchableRepository> reference : references) { WatchableRepository watchableRepository = this.bundleContext.getService(reference); try { watchableRepository.forceCheck(); } catch (Exception e) { String name; if (watchableRepository instanceof Repository) { name = ((Repository) watchableRepository).getName(); } else { name = "unknown repository type"; } this.eventLogger.log(DeployerLogEvents.WATCHED_REPOSITORY_REFRESH_FAILED, name); } this.bundleContext.ungetService(reference); } } catch (InvalidSyntaxException e) { this.eventLogger.log(DeployerLogEvents.WATCHED_REPOSITORIES_REFRESH_FAILED); } } @Override public DeploymentIdentity[] bulkDeploy(List<URI> arg0, DeploymentOptions arg1) throws DeploymentException { throw new UnsupportedOperationException(); } }