blob: 90e928c24a6d1014949f447e923cbed248710363 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 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.baseadaptor;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
import org.eclipse.osgi.baseadaptor.hooks.*;
import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader;
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.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.*;
/**
* The BundleData implementation used by the BaseAdaptor.
* @see BaseAdaptor
* @see BundleData
* @see StorageHook
* @see ClassLoadingHook
* @since 3.2
*/
public class BaseData implements BundleData {
private long id;
private BaseAdaptor adaptor;
private Bundle bundle;
private int startLevel = -1;
private int status = 0;
private StorageHook[] storageHooks;
private String location;
private long lastModified;
protected BundleFile bundleFile;
private boolean dirty = false;
protected Dictionary manifest;
// This field is only used by PDE source lookup, and is set by a hook (bug 126517). It serves no purpose at runtime.
protected String fileName;
// This is only used to keep track of when the same native library is loaded more than once
protected Collection loadedNativeCode;
///////////////////// 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 /////////////////////
/**
* Constructs a new BaseData with the specified id for the specified adaptor
* @param id the id of the BaseData
* @param adaptor the adaptor of the BaseData
*/
public BaseData(long id, BaseAdaptor adaptor) {
this.id = id;
this.adaptor = adaptor;
}
/**
* This method calls all the configured class loading hooks {@link ClassLoadingHook#createClassLoader(ClassLoader, ClassLoaderDelegate, BundleProtectionDomain, BaseData, String[])}
* methods until on returns a non-null value. If none of the class loading hooks returns a non-null value
* then the default classloader implementation is used.
* @see BundleData#createClassLoader(ClassLoaderDelegate, BundleProtectionDomain, String[])
*/
public BundleClassLoader createClassLoader(ClassLoaderDelegate delegate, BundleProtectionDomain domain, String[] bundleclasspath) {
ClassLoadingHook[] hooks = adaptor.getHookRegistry().getClassLoadingHooks();
ClassLoader parent = adaptor.getBundleClassLoaderParent();
BaseClassLoader cl = null;
for (int i = 0; i < hooks.length && cl == null; i++)
cl = hooks[i].createClassLoader(parent, delegate, domain, this, bundleclasspath);
if (cl == null)
cl = new DefaultClassLoader(parent, delegate, domain, this, bundleclasspath);
return cl;
}
public final URL getEntry(String path) {
BundleEntry entry = getBundleFile().getEntry(path);
if (entry == null)
return null;
if (path.length() == 0 || path.charAt(0) != '/')
path = '/' + path;
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;
}
}
public final Enumeration getEntryPaths(String path) {
return getBundleFile().getEntryPaths(path);
}
/**
* This method calls each configured classloading hook {@link ClassLoadingHook#findLibrary(BaseData, String)} method
* until the first one returns a non-null value.
* @see BundleData#findLibrary(String)
*/
public String findLibrary(String libname) {
ClassLoadingHook[] hooks = adaptor.getHookRegistry().getClassLoadingHooks();
String result = null;
for (int i = 0; i < hooks.length; i++) {
result = hooks[i].findLibrary(this, libname);
if (result != null)
break;
}
// check to see if this library has been loaded by another class loader
if (result != null)
synchronized (this) {
if (loadedNativeCode == null)
loadedNativeCode = new ArrayList(1);
if (loadedNativeCode.contains(result)) {
// we must copy the library to a temp space to allow another class loader to load the library
String temp = copyToTempLibrary(result);
if (temp != null)
result = temp;
} else {
loadedNativeCode.add(result);
}
}
return result;
}
private String copyToTempLibrary(String result) {
try {
return adaptor.getStorage().copyToTempLibrary(this, result);
} catch (IOException e) {
adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
}
return null;
}
public void installNativeCode(String[] nativepaths) throws BundleException {
adaptor.getStorage().installNativeCode(this, nativepaths);
}
public File getDataFile(String path) {
return adaptor.getStorage().getDataFile(this, path);
}
public Dictionary getManifest() throws BundleException {
if (manifest == null)
manifest = adaptor.getStorage().loadManifest(this);
return manifest;
}
public long getBundleID() {
return id;
}
public final String getLocation() {
return location;
}
/**
* Sets the location of this bundledata
* @param location the location of this bundledata
*/
public final void setLocation(String location) {
this.location = location;
}
public final long getLastModified() {
return lastModified;
}
/**
* Sets the last modified time stamp of this bundledata
* @param lastModified the last modified time stamp of this bundledata
*/
public final void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
public void close() throws IOException {
if (bundleFile != null)
getBundleFile().close(); // only close the bundleFile if it already exists.
}
public void open() throws IOException {
getBundleFile().open();
}
public final void setBundle(Bundle bundle) {
this.bundle = bundle;
}
/**
* Returns the bundle object of this BaseData
* @return the bundle object of this BaseData
*/
public final Bundle getBundle() {
return bundle;
}
public int getStartLevel() {
return startLevel;
}
public int getStatus() {
return status;
}
/**
* This method calls each configured storage hook {@link StorageHook#forgetStartLevelChange(int)} method.
* If one returns true then this bundledata is not marked dirty.
* @see BundleData#setStartLevel(int)
*/
public void setStartLevel(int value) {
startLevel = setPersistentData(value, true, startLevel);
}
/**
* This method calls each configured storage hook {@link StorageHook#forgetStatusChange(int)} method.
* If one returns true then this bundledata is not marked dirty.
* @see BundleData#setStatus(int)
*/
public void setStatus(int value) {
status = setPersistentData(value, false, status);
}
private int setPersistentData(int value, boolean isStartLevel, int orig) {
StorageHook[] hooks = getStorageHooks();
for (int i = 0; i < hooks.length; i++)
if (isStartLevel) {
if (hooks[i].forgetStartLevelChange(value))
return value;
} else {
if (hooks[i].forgetStatusChange(value))
return value;
}
if (value != orig)
dirty = true;
return value;
}
public void save() throws IOException {
adaptor.getStorage().save(this);
}
/**
* Returns true if this bundledata is dirty
* @return true if this bundledata is dirty
*/
public boolean isDirty() {
return dirty;
}
/**
* Sets the dirty flag for this BaseData
* @param dirty the dirty flag
*/
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
public final String getSymbolicName() {
return symbolicName;
}
/**
* Sets the symbolic name of this BaseData
* @param symbolicName the symbolic name
*/
public final void setSymbolicName(String symbolicName) {
this.symbolicName = symbolicName;
}
public final Version getVersion() {
return version;
}
/**
* Sets the version of this BaseData
* @param version the version
*/
public final void setVersion(Version version) {
this.version = version;
}
public final int getType() {
return type;
}
/**
* Sets the type of this BaseData
* @param type the type
*/
public final void setType(int type) {
this.type = type;
}
public final String[] getClassPath() throws BundleException {
ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, classpath);
return getClassPath(classpathElements);
}
// TODO make classpath a String[] instead of saving a comma separated string.
public String getClassPathString() {
return classpath;
}
//TODO make classpath a String[] instead of saving a comma separated string.
public void setClassPathString(String classpath) {
this.classpath = classpath;
}
public final String getActivator() {
return activator;
}
/**
* Sets the activator of this BaseData
* @param activator the activator
*/
public final void setActivator(String activator) {
this.activator = activator;
}
public final String getExecutionEnvironment() {
return executionEnvironment;
}
/**
* Sets the execution environment of this BaseData
* @param executionEnvironment the execution environment
*/
public void setExecutionEnvironment(String executionEnvironment) {
this.executionEnvironment = executionEnvironment;
}
public final String getDynamicImports() {
return dynamicImports;
}
/**
* Sets the dynamic imports of this BaseData
* @param dynamicImports the dynamic imports
*/
public void setDynamicImports(String dynamicImports) {
this.dynamicImports = dynamicImports;
}
/**
* This method calls each configured storage hook {@link StorageHook#matchDNChain(String)} method
* until one returns true. If no configured storage hook returns true then false is returned.
* @see BundleData#matchDNChain(String)
*/
public final boolean matchDNChain(String pattern) {
StorageHook[] hooks = getStorageHooks();
for (int i = 0; i < hooks.length; i++)
if (hooks[i].matchDNChain(pattern))
return true;
return false;
}
/**
* Returns the adaptor for this BaseData
* @return the adaptor
*/
public final BaseAdaptor getAdaptor() {
return adaptor;
}
/**
* Returns the BundleFile for this BaseData. The first time this method is called the
* configured storage {@link BaseAdaptor#createBundleFile(Object, BaseData)} method is called.
* @return the BundleFile
* @throws IllegalArgumentException
*/
public synchronized BundleFile getBundleFile() throws IllegalArgumentException {
if (bundleFile == null)
try {
bundleFile = adaptor.createBundleFile(null, this);
} catch (IOException e) {
throw new IllegalArgumentException(e.getMessage());
}
return bundleFile;
}
private 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()]);
}
/**
* Returns the storage hook which is keyed by the specified key
* @param key the key of the storage hook to get
* @return the storage hook which is keyed by the specified key
*/
public StorageHook getStorageHook(String key) {
if (storageHooks == null)
return null;
for (int i = 0; i < storageHooks.length; i++)
if (storageHooks[i].getKey().equals(key))
return storageHooks[i];
return null;
}
/**
* Sets the instance storage hooks for this base data. This is method
* may only be called once for the lifetime of the base data. Once set,
* the list of storage hooks remains constant.
* @param storageHooks the storage hook to add
*/
public void setStorageHooks(StorageHook[] storageHooks) {
if (this.storageHooks != null)
return; // only allow this to be set once.
this.storageHooks = storageHooks;
}
/**
* Returns all the storage hooks associated with this BaseData
* @return all the storage hooks associated with this BaseData
*/
public StorageHook[] getStorageHooks() {
return storageHooks == null ? new StorageHook[0] : storageHooks;
}
/**
* Gets called by BundleFile during {@link BundleFile#getFile(String, boolean)}. This method
* will allocate a File object where content of the specified path may be
* stored for the current generation of the base data. The returned File object may
* not exist if the content has not previously be stored.
* @param path the path to the content to extract from the base data
* @return a file object where content of the specified path may be stored.
*/
public File getExtractFile(String path) {
return adaptor.getStorage().getExtractFile(this, path);
}
/**
* This is only used to support PDE source lookup. The field named &quot;fileName&quot;
* must be set for PDE to access the absolute path string.
* @param fileName an absolute path string to the base bundle file.
*/
// This is only done for PDE source lookup (bug 126517)
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* Return a string representation of the bundle that can be used in debug messages.
*
* @return String representation of the bundle
*/
public String toString() {
String name = getSymbolicName();
if (name == null)
return getLocation();
Version ver = getVersion();
if (ver == null)
return name;
return name+"_"+ver; //$NON-NLS-1$
}
}