/*******************************************************************************
* 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.pipeline.stage.transform.internal;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import org.eclipse.virgo.kernel.artifact.fs.ArtifactFS;
import org.eclipse.virgo.kernel.artifact.fs.ArtifactFSEntry;
import org.eclipse.virgo.kernel.install.artifact.ArtifactIdentity;
import org.eclipse.virgo.kernel.install.artifact.ArtifactIdentityDeterminer;
import org.eclipse.virgo.kernel.install.artifact.ArtifactStorage;
import org.eclipse.virgo.kernel.install.artifact.BundleInstallArtifact;
import org.eclipse.virgo.kernel.install.artifact.InstallArtifact;
import org.eclipse.virgo.kernel.install.artifact.InstallArtifactGraphFactory;
import org.eclipse.virgo.kernel.install.artifact.PlanInstallArtifact;
import org.eclipse.virgo.kernel.install.artifact.internal.ArtifactStorageFactory;
import org.eclipse.virgo.kernel.install.artifact.internal.scoping.ScopeNameFactory;
import org.eclipse.virgo.kernel.install.environment.InstallEnvironment;
import org.eclipse.virgo.kernel.install.pipeline.stage.transform.Transformer;
import org.eclipse.virgo.nano.deployer.api.core.DeploymentException;
import org.eclipse.virgo.nano.deployer.api.core.FatalDeploymentException;
import org.eclipse.virgo.util.common.GraphNode;
import org.eclipse.virgo.util.io.IOUtils;
import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory;
import org.eclipse.virgo.util.osgi.manifest.ImportBundle;
import org.osgi.framework.Version;
/**
* A {@link Transformer} implementation that examines the install graph and, for each scoped plan found within the graph,
* adds a synthetic context bundle to the plan.
*
* <p />
*
* <strong>Concurrent Semantics</strong><br />
*
* Thread-safe.
*
*/
final class SyntheticContextBundleCreatingTransformer implements Transformer, ScopedPlanInstallArtifactProcessor {
private static final int SYNTHETIC_BUNDLE_MANIFEST_VERSION = 2;
private static final String SYNTHETIC_CONTEXT_SUFFIX = "-synthetic.context";
private final InstallArtifactGraphFactory installArtifactGraphFactory;
private final ArtifactStorageFactory artifactStorageFactory;
SyntheticContextBundleCreatingTransformer(InstallArtifactGraphFactory installArtifactGraphFactory, ArtifactStorageFactory artifactStorageFactory) {
this.installArtifactGraphFactory = installArtifactGraphFactory;
this.artifactStorageFactory = artifactStorageFactory;
}
/**
* {@inheritDoc}
*/
public void transform(GraphNode<InstallArtifact> installGraph, InstallEnvironment installEnvironment) throws DeploymentException {
installGraph.visit(new ScopedPlanIdentifyingDirectedAcyclicGraphVisitor(this));
}
/**
* {@inheritDoc}
*
*/
public void processScopedPlanInstallArtifact(GraphNode<InstallArtifact> graph) throws DeploymentException {
if (!syntheticContextExists(graph)) {
Set<BundleInstallArtifact> childBundles = getBundlesInScope(graph);
PlanInstallArtifact planArtifact = (PlanInstallArtifact) graph.getValue();
String scopeName = determineSyntheticContextScopeName(planArtifact);
String name = scopeName + SYNTHETIC_CONTEXT_SUFFIX;
Version version = planArtifact.getVersion();
ArtifactIdentity identity = new ArtifactIdentity(ArtifactIdentityDeterminer.BUNDLE_TYPE, name, version, scopeName);
BundleManifest syntheticContextBundleManifest = createSyntheticContextBundleManifest(identity, childBundles);
ArtifactStorage artifactStorage = this.artifactStorageFactory.createDirectoryStorage(identity, name + ".jar");
writeSyntheticContextBundle(syntheticContextBundleManifest, artifactStorage.getArtifactFS());
GraphNode<InstallArtifact> syntheticContextBundle = this.installArtifactGraphFactory.constructInstallArtifactGraph(
identity, artifactStorage, null, null);
graph.addChild(syntheticContextBundle);
}
}
private boolean syntheticContextExists(GraphNode<InstallArtifact> plan) {
PlanInstallArtifact planInstallArtifact = (PlanInstallArtifact) plan.getValue();
String syntheticContextBundleSymbolicName = determineSyntheticContextScopeName(planInstallArtifact) + SYNTHETIC_CONTEXT_SUFFIX;
List<GraphNode<InstallArtifact>> children = plan.getChildren();
for (GraphNode<InstallArtifact> child : children) {
if (syntheticContextBundleSymbolicName.equals(child.getValue().getName())) {
return true;
}
}
return false;
}
private Set<BundleInstallArtifact> getBundlesInScope(GraphNode<InstallArtifact> plan) {
BundleInstallArtifactGatheringGraphVisitor visitor = new BundleInstallArtifactGatheringGraphVisitor();
plan.visit(visitor);
return visitor.getChildBundles();
}
private void writeSyntheticContextBundle(BundleManifest syntheticContextBundleManifest, ArtifactFS artifactFS) {
ArtifactFSEntry entry = artifactFS.getEntry(JarFile.MANIFEST_NAME);
Writer manifestWriter = new OutputStreamWriter(entry.getOutputStream(), UTF_8);
try {
syntheticContextBundleManifest.write(manifestWriter);
} catch (IOException ioe) {
throw new FatalDeploymentException("Failed to write out synthetic context's manifest", ioe);
} finally {
IOUtils.closeQuietly(manifestWriter);
}
}
private BundleManifest createSyntheticContextBundleManifest(ArtifactIdentity identity, Set<BundleInstallArtifact> childBundles) {
BundleManifest bundleManifest = BundleManifestFactory.createBundleManifest();
bundleManifest.setBundleVersion(identity.getVersion());
bundleManifest.setBundleManifestVersion(SYNTHETIC_BUNDLE_MANIFEST_VERSION);
bundleManifest.getBundleSymbolicName().setSymbolicName(identity.getName());
bundleManifest.setModuleScope(identity.getScopeName());
addImportForEachChildBundle(bundleManifest, childBundles);
return bundleManifest;
}
private String determineSyntheticContextScopeName(PlanInstallArtifact plan) {
return ScopeNameFactory.createScopeName(plan.getName(), plan.getVersion());
}
private void addImportForEachChildBundle(BundleManifest bundleManifest, Set<BundleInstallArtifact> childBundles) {
ImportBundle importBundle = bundleManifest.getImportBundle();
for (BundleInstallArtifact bundle : childBundles) {
String symbolicName = bundle.getName();
importBundle.addImportedBundle(symbolicName);
}
}
}