/******************************************************************************* * Copyright (c) 2005, 2010 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.security.AccessController; import java.security.PrivilegedAction; import java.util.*; import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook; import org.eclipse.osgi.baseadaptor.hooks.StorageHook; import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader; import org.eclipse.osgi.baseadaptor.loader.ClasspathManager; import org.eclipse.osgi.framework.adaptor.*; import org.eclipse.osgi.framework.debug.Debug; import org.eclipse.osgi.framework.internal.core.*; 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.ArrayMap; 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 final static boolean COPY_NATIVES = Boolean.valueOf(FrameworkProperties.getProperty("osgi.classloader.copy.natives")).booleanValue(); //$NON-NLS-1$ 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 ArrayMap<Object, BundleFile> bundleFiles; private boolean dirty = false; protected Dictionary<String, String> 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<String> 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(final String path) { if (System.getSecurityManager() == null) return getEntry0(path); return AccessController.doPrivileged(new PrivilegedAction<URL>() { public URL run() { return getEntry0(path); } }); } final URL getEntry0(String path) { BundleEntry entry = getBundleFile().getEntry(path); if (entry == null) return null; path = BundleFile.fixTrailingSlash(path, entry); try { //use the constant string for the protocol to prevent duplication return new URL(Constants.OSGI_ENTRY_URL_PROTOCOL, Long.toString(id) + BundleResourceHandler.BID_FWKID_SEPARATOR + Integer.toString(adaptor.hashCode()), 0, path, new Handler(entry, adaptor)); } catch (MalformedURLException e) { return null; } } public final Enumeration<String> 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<String>(1); if (loadedNativeCode.contains(result) || COPY_NATIVES) { // 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<String, String> 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 synchronized void close() throws IOException { if (bundleFile != null) getBundleFile().close(); // only close the bundleFile if it already exists. if (bundleFiles != null) { for (BundleFile bundlefile : bundleFiles.getValues()) bundlefile.close(); bundleFiles.clear(); } } 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; } /** * @throws IOException */ 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; } /** * 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 (IllegalArgumentException) new IllegalArgumentException(e.getMessage()).initCause(e); } return bundleFile; } public synchronized BundleFile getBundleFile(Object content, boolean base) { return base ? bundleFile : (bundleFiles == null) ? null : bundleFiles.get(content); } public synchronized void setBundleFile(Object content, BundleFile bundleFile) { if (bundleFiles == null) bundleFiles = new ArrayMap<Object, BundleFile>(1); bundleFiles.put(content, bundleFile); } private static String[] getClassPath(ManifestElement[] classpath) { if (classpath == null) { if (Debug.DEBUG_LOADER) Debug.println(" no classpath"); //$NON-NLS-1$ /* create default BundleClassPath */ return new String[] {"."}; //$NON-NLS-1$ } List<String> result = new ArrayList<String>(classpath.length); for (int i = 0; i < classpath.length; i++) { if (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 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 "fileName" * 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$ } public Enumeration<URL> findLocalResources(String path) { String[] cp; try { cp = getClassPath(); } catch (BundleException e) { cp = new String[0]; } if (cp == null) cp = new String[0]; // this is not optimized; degenerate case of someone calling getResource on an unresolved bundle! ClasspathManager cm = new ClasspathManager(this, cp, null); cm.initialize(); Enumeration<URL> result = cm.findLocalResources(path); // no need to close ClassPathManager because the BundleFile objects are stored in the BaseData and closed on shutdown or uninstall return result; } }