blob: 62e0e8fa71b5f8996e0f24525daaeda88d76830e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2005 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.adaptor.core;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.osgi.framework.adaptor.*;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.internal.core.Constants;
import org.eclipse.osgi.framework.internal.protocol.bundleentry.Handler;
import org.eclipse.osgi.framework.util.Headers;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
/**
* An abstract BundleData class that has default implementations that most
* BundleData implementations can use.
*/
public abstract class AbstractBundleData implements BundleData, Cloneable {
/** the DefaultAdaptor for this BundleData */
protected AbstractFrameworkAdaptor adaptor;
/**
* The BundleManfifest for this BundleData.
*/
protected Dictionary manifest = null;
/**
* The Bundle object for this BundleData.
*/
protected Bundle bundle;
/** bundle id */
protected long id;
/** The top level storage directory for the BundleData */
protected File bundleStoreDir;
/** The base BundleFile object for this BundleData */
protected BundleFile baseBundleFile;
///////////////////// Begin Meta Data for the Bundle /////////////////////
/** bundle location */
private String location;
/** bundle's file name */
private String fileName;
/** native code paths for this BundleData */
private String[] nativePaths;
/** bundle generation */
private int generation = 1;
/** the bundles start level */
private int startLevel = -1;
/**
* The BundleData data directory
*/
protected File dirData;
/** the bundles status */
private int status = 0;
/** Is bundle a reference */
private boolean reference;
/** the bundles last modified timestamp */
private long lastModified;
///////////////////// End Meta Data for the Bundle /////////////////////
///////////////////// Begin values from Manifest /////////////////////
private String symbolicName;
private Version version;
private String activator;
private String classpath;
private String executionEnvironment;
private String dynamicImports;
private int type;
///////////////////// End values from Manifest /////////////////////
public AbstractBundleData(AbstractFrameworkAdaptor adaptor, long id) {
this.adaptor = adaptor;
this.id = id;
initBundleStoreDirs(String.valueOf(id));
}
/**
* Return the BundleManifest for the BundleData. If the manifest
* field is null this method will parse the bundle manifest file and
* construct a BundleManifest file to return. If the manifest field is
* not null then the manifest object is returned.
* @return BundleManifest for the BundleData.
* @throws BundleException if an error occurred while reading the
* bundle manifest data.
*/
public Dictionary getManifest() throws BundleException {
if (manifest == null) {
synchronized (this) {
// make sure the manifest is still null after we have aquired the lock.
if (manifest == null) {
URL url = getEntry(Constants.OSGI_BUNDLE_MANIFEST);
if (url == null) {
throw new BundleException(NLS.bind(AdaptorMsg.MANIFEST_NOT_FOUND_EXCEPTION, Constants.OSGI_BUNDLE_MANIFEST, getLocation()));
}
try {
manifest = Headers.parseManifest(url.openStream());
} catch (IOException e) {
throw new BundleException(NLS.bind(AdaptorMsg.MANIFEST_NOT_FOUND_EXCEPTION, Constants.OSGI_BUNDLE_MANIFEST, getLocation()), e);
}
}
}
}
return manifest;
}
/**
* Sets the Bundle object for this BundleData.
* @param bundle The Bundle Object for this BundleData.
*/
public void setBundle(Bundle bundle) {
this.bundle = bundle;
}
/**
* Returns the Bundle object for this BundleData.
* @return the Bundle object for this BundleData.
*/
public Bundle getBundle() {
return bundle;
}
/**
* Get the BundleData bundle ID. This will be used as the bundle
* ID by the framework.
* @return The BundleData ID.
*/
public long getBundleID() {
return (id);
}
/**
* Gets a <code>URL</code> to the bundle entry specified by path. This
* method must not use the BundleClassLoader to find the bundle entry since
* the ClassLoader will delegate to find the resource.
*
* @param path
* The bundle entry path.
* @return A URL used to access the entry or null if the entry does not
* exist.
*/
public URL getEntry(String path) {
BundleEntry entry = getBaseBundleFile().getEntry(path);
if (entry == null) {
return null;
}
try {
//use the constant string for the protocol to prevent duplication
return new URL(Constants.OSGI_ENTRY_URL_PROTOCOL, Long.toString(id), 0, path, new Handler(entry));
} catch (MalformedURLException e) {
return null;
}
}
/**
* Gets all of the bundle entries that exist under the specified path. For
* example:
* <p>
* <code>getEntryPaths("/META-INF")</code>
* <p>
* This will return all entries from the /META-INF directory of the bundle.
*
* @param path
* The path to a directory in the bundle.
* @return An Enumeration of the entry paths or null if the specified path
* does not exist.
*/
public Enumeration getEntryPaths(String path) {
return getBaseBundleFile().getEntryPaths(path);
}
/**
* Creates the ClassLoader for the BundleData. The ClassLoader created
* must use the <code>ClassLoaderDelegate</code> to delegate class, resource
* and library loading. The delegate is responsible for finding any resource
* or classes imported by the bundle or provided by bundle fragments or
* bundle hosts. The <code>ProtectionDomain</code> domain must be used
* by the Classloader when defining a class.
* @param delegate The <code>ClassLoaderDelegate</code> to delegate to.
* @param domain The <code>ProtectionDomain</code> to use when defining a class.
* @param bundleclasspath An array of bundle classpaths to use to create this
* classloader. This is specified by the Bundle-ClassPath manifest entry.
* @return The new ClassLoader for the BundleData.
*/
public org.eclipse.osgi.framework.adaptor.BundleClassLoader createClassLoader(ClassLoaderDelegate delegate, BundleProtectionDomain domain, String[] bundleclasspath) {
return getAdaptor().getElementFactory().createClassLoader(delegate, domain, bundleclasspath, this);
}
public AbstractFrameworkAdaptor getAdaptor() {
return adaptor;
}
/*
* Convenience method that retrieves the simbolic name string from the header.
* Note: clients may want to cache the returned value.
*/
public static String parseSymbolicName(Dictionary manifest) {
String symbolicNameEntry = (String) manifest.get(Constants.BUNDLE_SYMBOLICNAME);
if (symbolicNameEntry == null)
return null;
try {
return ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameEntry)[0].getValue();
} catch (BundleException e) {
// here is not the place to validate a manifest
}
return null;
}
static String[] getClassPath(ManifestElement[] classpath) {
if (classpath == null) {
if (Debug.DEBUG && Debug.DEBUG_LOADER)
Debug.println(" no classpath"); //$NON-NLS-1$
/* create default BundleClassPath */
return new String[] {"."}; //$NON-NLS-1$
}
ArrayList result = new ArrayList(classpath.length);
for (int i = 0; i < classpath.length; i++) {
if (Debug.DEBUG && Debug.DEBUG_LOADER)
Debug.println(" found classpath entry " + classpath[i].getValueComponents()); //$NON-NLS-1$
String[] paths = classpath[i].getValueComponents();
for (int j = 0; j < paths.length; j++) {
result.add(paths[j]);
}
}
return (String[]) result.toArray(new String[result.size()]);
}
///////////////////// Begin Meta Data Accessor Methods ////////////////////
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String[] getNativePaths() {
return nativePaths;
}
public String getNativePathsString() {
if (nativePaths == null || nativePaths.length == 0)
return null;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < nativePaths.length; i++) {
sb.append(nativePaths[i]);
if (i < nativePaths.length - 1)
sb.append(',');
}
return sb.toString();
}
public void setNativePaths(String[] nativePaths) {
this.nativePaths = nativePaths;
}
public void setNativePaths(String nativePaths) {
if (nativePaths == null)
return;
ArrayList result = new ArrayList(5);
StringTokenizer st = new StringTokenizer(nativePaths, ","); //$NON-NLS-1$
while (st.hasMoreTokens()) {
String path = st.nextToken();
result.add(path);
}
setNativePaths((String[]) result.toArray(new String[result.size()]));
}
public int getGeneration() {
return generation;
}
public void setGeneration(int generation) {
this.generation = generation;
}
public long getLastModified() {
return lastModified;
}
public void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
public int getStartLevel() {
return startLevel;
}
public void setStartLevel(int startLevel) {
this.startLevel = startLevel;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public boolean isReference() {
return reference;
}
public void setReference(boolean reference) {
this.reference = reference;
}
///////////////////// End Meta Data Accessor Methods ////////////////////
///////////////////// Begin Manifest Value Accessor Methods /////////////////////
public String getSymbolicName() {
return symbolicName;
}
public File getBundleStoreDir() {
return bundleStoreDir;
}
public void setSymbolicName(String symbolicName) {
this.symbolicName = symbolicName;
}
protected void loadFromManifest() throws IOException, BundleException {
getManifest();
if (manifest == null) {
throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_ERROR_GETTING_MANIFEST, getLocation())); //$NON-NLS-1$
}
setVersion(Version.parseVersion((String) manifest.get(Constants.BUNDLE_VERSION)));
setSymbolicName(AbstractBundleData.parseSymbolicName(manifest));
setClassPathString((String) manifest.get(Constants.BUNDLE_CLASSPATH));
setActivator((String) manifest.get(Constants.BUNDLE_ACTIVATOR));
String host = (String) manifest.get(Constants.FRAGMENT_HOST);
if (host != null) {
int bundleType = TYPE_FRAGMENT;
ManifestElement[] hostElement = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, host);
if (Constants.getInternalSymbolicName().equals(hostElement[0].getValue()) || Constants.OSGI_SYSTEM_BUNDLE.equals(hostElement[0].getValue())) {
String extensionType = hostElement[0].getDirective("extension"); //$NON-NLS-1$
if (extensionType == null || extensionType.equals("framework")) //$NON-NLS-1$
bundleType |= TYPE_FRAMEWORK_EXTENSION;
else
bundleType |= TYPE_BOOTCLASSPATH_EXTENSION;
}
setType(bundleType);
}
setExecutionEnvironment((String) manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
setDynamicImports((String) manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public String getActivator() {
return activator;
}
protected File getDataDir() {
return dirData;
}
protected void setBundleStoreDir(File bundleStoreDir) {
this.bundleStoreDir = bundleStoreDir;
}
protected void initBundleStoreDirs(String bundleID) {
setBundleStoreDir(new File(((AbstractFrameworkAdaptor) adaptor).getBundleStoreRootDir(), bundleID));
}
public void setActivator(String activator) {
this.activator = activator;
}
public String[] getClassPath() throws BundleException {
ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, classpath);
return getClassPath(classpathElements);
}
public String getClassPathString() {
return classpath;
}
public void setClassPathString(String classpath) {
this.classpath = classpath;
}
public String getExecutionEnvironment() {
return executionEnvironment;
}
public void setExecutionEnvironment(String executionEnvironment) {
this.executionEnvironment = executionEnvironment;
}
public String getDynamicImports() {
return dynamicImports;
}
public void setDynamicImports(String dynamicImports) {
this.dynamicImports = dynamicImports;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
///////////////////// End Manifest Value Accessor Methods /////////////////////
public boolean matchDNChain(String pattern) {
if (System.getSecurityManager() == null)
return false;
if (getBaseBundleFile() instanceof SignedBundle)
return ((SignedBundle) getBaseBundleFile()).matchDNChain(pattern);
return false;
}
/**
* Return a copy of this object with the
* generation dependent fields updated to
* the next free generation level.
*
* @throws IOException If there are no more available generation levels.
*/
protected AbstractBundleData nextGeneration(String referenceFile) throws IOException {
int nextGeneration = getGeneration();
while (nextGeneration < Integer.MAX_VALUE) {
nextGeneration++;
File nextDirGeneration = new File(getBundleStoreDir(), String.valueOf(nextGeneration));
if (nextDirGeneration.exists()) {
continue;
}
AbstractBundleData next;
try {
next = (AbstractBundleData) clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
next.setGeneration(nextGeneration);
if (referenceFile != null) {
next.setReference(true);
next.setFileName(referenceFile);
} else {
if (next.isReference()) {
next.setReference(false);
next.setFileName(((AbstractFrameworkAdaptor) adaptor).mapLocationToName(getLocation()));
}
}
// null out the manifest to force it to be re-read.
next.manifest = null;
return (next);
}
throw new IOException(AdaptorMsg.ADAPTOR_STORAGE_EXCEPTION);
}
public void initializeNewBundle() throws IOException, BundleException {
createBaseBundleFile();
loadFromManifest();
}
protected BundleFile createBaseBundleFile() throws IOException {
baseBundleFile = getAdaptor().createBaseBundleFile(getBaseFile(), this);
return baseBundleFile;
}
/**
* Return the base File for the bundle.
* Attempt to create the bundle generation directory if it does not exist.
*
* @return the base File object for the bundle.
*/
protected File getBaseFile() {
return isReference() ? new File(getFileName()) : new File(createGenerationDir(), getFileName());
}
protected File[] getClasspathFiles(String[] classpaths) {
File[] results = new File[classpaths.length];
for (int i = 0; i < classpaths.length; i++) {
if (".".equals(classpaths[i]))
results[i] = getBaseFile();
else
results[i] = getBaseBundleFile().getFile(classpaths[i]);
}
return results;
}
protected void setDataDir(File dirData) {
this.dirData = dirData;
}
/**
* Returns the absolute path name of a native library. The BundleData
* ClassLoader invokes this method to locate the native libraries that
* belong to classes loaded from this BundleData. Returns
* null if the library does not exist in this BundleData.
* @param libname The name of the library to find the absolute path to.
* @return The absolute path name of the native library or null if
* the library does not exist.
*/
public String findLibrary(String libname) {
String mappedName = System.mapLibraryName(libname);
String path = null;
if (Debug.DEBUG && Debug.DEBUG_LOADER) {
Debug.println(" mapped library name: " + mappedName); //$NON-NLS-1$
}
path = findNativePath(mappedName);
if (path == null) {
if (Debug.DEBUG && Debug.DEBUG_LOADER) {
Debug.println(" library does not exist: " + mappedName); //$NON-NLS-1$
}
path = findNativePath(libname);
}
if (Debug.DEBUG && Debug.DEBUG_LOADER) {
Debug.println(" returning library: " + path); //$NON-NLS-1$
}
return path;
}
/**
* Opens all resource for this BundleData. Reopens the BundleData if
* it was previosly closed.
*/
public void open() throws IOException {
baseBundleFile.open();
}
protected String findNativePath(String libname) {
String path = null;
if (!libname.startsWith("/")) { //$NON-NLS-1$
libname = '/' + libname;
}
String[] nativepaths = getNativePaths();
if (nativepaths != null) {
for (int i = 0; i < nativepaths.length; i++) {
if (nativepaths[i].endsWith(libname)) {
File nativeFile = baseBundleFile.getFile(nativepaths[i]);
path = nativeFile.getAbsolutePath();
}
}
}
return path;
}
/**
* Return the bundle generation directory.
* Attempt to create the directory if it does not exist.
*
* @return Bundle generation directory.
*/
/**
* Return the generation directory for the bundle data. The generation
* directory can be used by the framework to cache files from the bundle
* to the file system.
* @return The generation directory for the bundle data or null if not
* supported.
*/
public File createGenerationDir() {
File generationDir = getGenerationDir();
if (!generationDir.exists() && !generationDir.mkdirs()) {
if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
Debug.println("Unable to create bundle generation directory: " + generationDir.getPath()); //$NON-NLS-1$
}
}
return generationDir;
}
/**
* Return the base BundleFile for this BundleData. The base BundleFile
* is the BundleFile that contains all the content of the bundle.
* @return the base BundleFile.
*/
public BundleFile getBaseBundleFile() {
return baseBundleFile;
}
/**
* Close all resources for this BundleData
*/
public void close() throws IOException {
if (baseBundleFile != null) {
baseBundleFile.close();
}
}
/**
* Return the bundle data directory.
* Attempt to create the directory if it does not exist.
*
* @return Bundle data directory.
*/
public File getDataFile(String path) {
// lazily initialize dirData to prevent early access to instance location
if (getDataDir() == null) {
File dataRoot = adaptor.getDataRootDir();
if (dataRoot == null)
throw new IllegalStateException(AdaptorMsg.ADAPTOR_DATA_AREA_NOT_SET);
setDataDir(new File(dataRoot, id + "/" + AbstractFrameworkAdaptor.DATA_DIR_NAME)); //$NON-NLS-1$
}
if (!getDataDir().exists() && !getDataDir().mkdirs()) {
if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
Debug.println("Unable to create bundle data directory: " + getDataDir().getPath()); //$NON-NLS-1$
}
}
return (new File(getDataDir(), path));
}
/**
* Installs the native code paths for this BundleData. Each
* element of nativepaths must be installed for lookup when findLibrary
* is called.
* @param nativepaths The array of native code paths to install for
* the bundle.
* @throws BundleException If any error occurs during install.
*/
public void installNativeCode(String[] nativepaths) throws BundleException {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < nativepaths.length; i++) {
// extract the native code
File nativeFile = baseBundleFile.getFile(nativepaths[i]);
if (nativeFile == null) {
throw new BundleException(NLS.bind(AdaptorMsg.BUNDLE_NATIVECODE_EXCEPTION, nativepaths[i]));
}
sb.append(nativepaths[i]);
if (i < nativepaths.length - 1) {
sb.append(","); //$NON-NLS-1$
}
}
if (sb.length() > 0)
setNativePaths(sb.toString());
}
protected File getGenerationDir() {
return new File(getBundleStoreDir(), String.valueOf(getGeneration()));
}
}