/*******************************************************************************
* Copyright (c) 2008, 2010 VMware Inc.
* 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
*******************************************************************************/
package org.eclipse.virgo.kernel.userregion.internal.equinox;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Stack;
import java.util.jar.JarFile;
import org.eclipse.osgi.baseadaptor.BaseData;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
import org.eclipse.virgo.kernel.equinox.extensions.hooks.BundleFileWrapper;
import org.eclipse.virgo.kernel.osgi.framework.ImportExpander;
import org.eclipse.virgo.kernel.osgi.framework.ManifestTransformer;
import org.eclipse.virgo.kernel.osgi.framework.UnableToSatisfyDependenciesException;
import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory;
/**
* A <code>BundleFileWrapper</code> implementation that wraps {@link BundleFile BundleFiles} and replaces the manifest in the
* <code>BundleFile</code> will one that has been transformed in memory.
* <p />
*
* <strong>Concurrent Semantics</strong><br />
* Thread-safe.
*
*/
public class TransformedManifestProvidingBundleFileWrapper implements BundleFileWrapper {
private final ImportExpander importExpander;
private final ThreadLocal<Stack<ManifestTransformer>> manifestTransformer;
public TransformedManifestProvidingBundleFileWrapper(ImportExpander importExpander) {
this.manifestTransformer = new ManifestTransformerStackThreadLocal();
this.importExpander = importExpander;
}
/**
* {@inheritDoc}
*/
public BundleFile wrapBundleFile(BundleFile bundleFile) {
return new TransformedManifestProvidingBundleFile(bundleFile, this.importExpander);
}
public void pushManifestTransformer(ManifestTransformer manifestTransformer) {
this.manifestTransformer.get().push(manifestTransformer);
}
public void popManifestTransformer() {
this.manifestTransformer.get().pop();
}
private static final class ManifestTransformerStackThreadLocal extends ThreadLocal<Stack<ManifestTransformer>> {
@Override
public Stack<ManifestTransformer> initialValue() {
return new Stack<ManifestTransformer>();
}
}
/**
* A concrete extension of {@link BundleFile} that intercepts attempts to access a bundle's manifest
* a returns a transform copy of the manifest that has, e.g. expanded any Import-Library and Import-Bundle
* headers into the corresponding Import-Package header entries.
* <p>
* <strong>Concurrent Semantics</strong><br />
* As thread-safe as the encapsulated <code>BundleFile</code> instance.
*
*/
private class TransformedManifestProvidingBundleFile extends BundleFile {
private final BundleFile bundleFile;
private final ImportExpander importExpander;
private volatile BundleEntry manifestEntry;
private final Object monitor = new Object();
private TransformedManifestProvidingBundleFile(BundleFile bundleFile, ImportExpander importExpander) {
this.bundleFile = bundleFile;
this.importExpander = importExpander;
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
this.bundleFile.close();
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsDir(String dir) {
return this.bundleFile.containsDir(dir);
}
/**
* {@inheritDoc}
*/
@Override
public BundleEntry getEntry(String path) {
if (path.equals(JarFile.MANIFEST_NAME)) {
synchronized (monitor) {
if (this.manifestEntry == null) {
BundleEntry entry = this.bundleFile.getEntry(path);
Stack<ManifestTransformer> manifestTransformers = TransformedManifestProvidingBundleFileWrapper.this.manifestTransformer.get();
ManifestTransformer manifestTransformer;
if (!manifestTransformers.isEmpty()) {
manifestTransformer = manifestTransformers.peek();
} else {
manifestTransformer = null;
}
BundleManifest originalManifest;
if (entry != null) {
try (InputStreamReader manifestReader = new InputStreamReader(entry.getInputStream(), UTF_8)) {
originalManifest = BundleManifestFactory.createBundleManifest(manifestReader);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} else {
originalManifest = BundleManifestFactory.createBundleManifest();
}
BundleManifest transformedManifest = originalManifest;
if (manifestTransformer != null) {
transformedManifest = manifestTransformer.transform(originalManifest);
}
try {
this.importExpander.expandImports(Collections.singletonList(transformedManifest));
} catch (UnableToSatisfyDependenciesException utsde) {
throw new RuntimeException(utsde);
}
try {
this.manifestEntry = new TransformedManifestBundleEntry(transformedManifest);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return this.manifestEntry;
} else {
return this.bundleFile.getEntry(path);
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Enumeration getEntryPaths(String path) {
return this.bundleFile.getEntryPaths(path);
}
/**
* {@inheritDoc}
*/
@Override
public File getFile(String path, boolean nativeCode) {
return this.bundleFile.getFile(path, nativeCode);
}
/**
* {@inheritDoc}
*/
@Override
public void open() throws IOException {
this.bundleFile.open();
}
/**
* {@inheritDoc}
*/
@Override
public File getBaseFile() {
return this.bundleFile.getBaseFile();
}
@Override
@SuppressWarnings("deprecation")
public URL getResourceURL(String path, long hostBundleID, int index) {
return this.bundleFile.getResourceURL(path, hostBundleID, index);
}
@Override
@SuppressWarnings("deprecation")
public URL getResourceURL(String path, long hostBundleID) {
return this.bundleFile.getResourceURL(path, hostBundleID);
}
/**
* {@inheritDoc}
*/
@Override
public URL getResourceURL(String path, BaseData hostData, int index) {
return this.bundleFile.getResourceURL(path, hostData, index);
}
}
private static class TransformedManifestBundleEntry extends BundleEntry {
private final byte[] manifestBytes;
private final long time;
private TransformedManifestBundleEntry(BundleManifest transformedManifest) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
transformedManifest.write(new OutputStreamWriter(baos, UTF_8));
this.manifestBytes = baos.toByteArray();
time = System.currentTimeMillis();
}
/**
* {@inheritDoc}
*/
@Override
public URL getFileURL() {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.manifestBytes);
}
/**
* {@inheritDoc}
*/
@Override
public URL getLocalURL() {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return "MANIFEST.MF";
}
/**
* {@inheritDoc}
*/
@Override
public long getSize() {
return this.manifestBytes.length;
}
/**
* {@inheritDoc}
*/
@Override
public long getTime() {
return this.time;
}
}
}