/******************************************************************************* * Copyright (c) 2005, 2011 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.*; import java.net.URL; import java.net.URLConnection; import java.util.*; import org.eclipse.core.runtime.adaptor.LocationManager; import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; import org.eclipse.osgi.baseadaptor.hooks.*; 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.log.FrameworkLog; import org.eclipse.osgi.framework.log.FrameworkLogEntry; import org.eclipse.osgi.internal.baseadaptor.*; import org.eclipse.osgi.service.resolver.PlatformAdmin; import org.eclipse.osgi.service.resolver.State; import org.eclipse.osgi.util.NLS; import org.osgi.framework.*; import org.osgi.framework.wiring.BundleWiring; /** * A Framework adaptor implementation that allows additional functionality to be * hooked in. Hooks are configured using {@link HookConfigurator} * objects. A framework extension may add hook configurators which can be used * to add hooks to the {@link HookRegistry}. * @see HookConfigurator * @see HookRegistry * @see AdaptorHook * @since 3.2 */ public class BaseAdaptor implements FrameworkAdaptor { // System property used to set the parent classloader type (boot is the default) private static final String PROP_PARENT_CLASSLOADER = "osgi.parentClassloader"; //$NON-NLS-1$ // A parent classloader type that specifies the application classloader private static final String PARENT_CLASSLOADER_APP = "app"; //$NON-NLS-1$ // A parent classloader type that specifies the extension classlaoder private static final String PARENT_CLASSLOADER_EXT = "ext"; //$NON-NLS-1$ // A parent classloader type that specifies the boot classlaoder private static final String PARENT_CLASSLOADER_BOOT = "boot"; //$NON-NLS-1$ // A parent classloader type that specifies the framework classlaoder private static final String PARENT_CLASSLOADER_FWK = "fwk"; //$NON-NLS-1$ // The BundleClassLoader parent to use when creating BundleClassLoaders. private static ClassLoader bundleClassLoaderParent; static { // check property for specified parent // check the osgi defined property first String type = FrameworkProperties.getProperty(Constants.FRAMEWORK_BUNDLE_PARENT); if (type != null) { if (Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK.equals(type)) type = PARENT_CLASSLOADER_FWK; } else { type = FrameworkProperties.getProperty(BaseAdaptor.PROP_PARENT_CLASSLOADER, BaseAdaptor.PARENT_CLASSLOADER_BOOT); } if (BaseAdaptor.PARENT_CLASSLOADER_FWK.equalsIgnoreCase(type)) bundleClassLoaderParent = FrameworkAdaptor.class.getClassLoader(); else if (BaseAdaptor.PARENT_CLASSLOADER_APP.equalsIgnoreCase(type)) bundleClassLoaderParent = ClassLoader.getSystemClassLoader(); else if (BaseAdaptor.PARENT_CLASSLOADER_EXT.equalsIgnoreCase(type)) { ClassLoader appCL = ClassLoader.getSystemClassLoader(); if (appCL != null) bundleClassLoaderParent = appCL.getParent(); } // default to boot classloader if (bundleClassLoaderParent == null) bundleClassLoaderParent = new ClassLoader(Object.class.getClassLoader()) {/* boot class loader*/}; } private Framework eventPublisher; private boolean stopping; private HookRegistry hookRegistry; private FrameworkLog log; private BundleContext context; private BaseStorage storage; private BundleWatcher bundleWatcher; /** * Constructs a BaseAdaptor. * @param args arguments passed to the adaptor by the framework. */ public BaseAdaptor(String[] args) { if (LocationManager.getConfigurationLocation() == null) LocationManager.initializeLocations(); hookRegistry = new HookRegistry(this); FrameworkLogEntry[] errors = hookRegistry.initialize(); if (errors.length > 0) for (int i = 0; i < errors.length; i++) getFrameworkLog().log(errors[i]); // get the storage after the registry has been initialized storage = getStorage(); // TODO consider passing args to BaseAdaptorHooks } /** * This method will call all configured adaptor hooks {@link AdaptorHook#initialize(BaseAdaptor)} method. * @see FrameworkAdaptor#initialize(EventPublisher) */ public void initialize(EventPublisher publisher) { this.eventPublisher = (Framework) publisher; // set the adaptor for the adaptor hooks AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) adaptorHooks[i].initialize(this); } /** * @see FrameworkAdaptor#initializeStorage() */ public void initializeStorage() throws IOException { storage.initialize(this); } /** * @throws IOException * @see FrameworkAdaptor#compactStorage() */ public void compactStorage() throws IOException { storage.compact(); } /** * This method will call all the configured adaptor hook {@link AdaptorHook#addProperties(Properties)} methods. * @see FrameworkAdaptor#getProperties() */ public Properties getProperties() { Properties props = new Properties(); String resource = FrameworkProperties.getProperty(Constants.OSGI_PROPERTIES, Constants.DEFAULT_OSGI_PROPERTIES); try { InputStream in = null; File file = new File(resource); if (file.exists()) in = new FileInputStream(file); if (in == null) in = getClass().getResourceAsStream(resource); if (in != null) { try { props.load(new BufferedInputStream(in)); } finally { try { in.close(); } catch (IOException ee) { // nothing to do } } } else { if (Debug.DEBUG_GENERAL) Debug.println("Skipping osgi.properties: " + resource); //$NON-NLS-1$ } } catch (IOException e) { if (Debug.DEBUG_GENERAL) Debug.println("Unable to load osgi.properties: " + e.getMessage()); //$NON-NLS-1$ } // add the storage properties storage.addProperties(props); // add the properties from each adaptor hook AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) adaptorHooks[i].addProperties(props); return props; } /** * @see FrameworkAdaptor#getInstalledBundles() */ public BundleData[] getInstalledBundles() { return storage.getInstalledBundles(); } /** * This method will call each configured adaptor hook {@link AdaptorHook#mapLocationToURLConnection(String)} method * until one returns a non-null value. If none of the adaptor hooks return a non-null value then the * string is used to construct a new URL object to open a new url connection. * * @see FrameworkAdaptor#mapLocationToURLConnection(String) */ public URLConnection mapLocationToURLConnection(String location) throws BundleException { try { URLConnection result = null; // try the adaptor hooks first; AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) { result = adaptorHooks[i].mapLocationToURLConnection(location); if (result != null) return result; } // just do the default return (new URL(location).openConnection()); } catch (IOException e) { throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_URL_CREATE_EXCEPTION, location), e); } } /** * @see FrameworkAdaptor#installBundle(String, URLConnection) */ public BundleOperation installBundle(String location, URLConnection source) { return storage.installBundle(location, source); } /** * @see FrameworkAdaptor#updateBundle(BundleData, URLConnection) */ public BundleOperation updateBundle(BundleData bundledata, URLConnection source) { return storage.updateBundle((BaseData) bundledata, source); } /** * @see FrameworkAdaptor#uninstallBundle(BundleData) */ public BundleOperation uninstallBundle(BundleData bundledata) { return storage.uninstallBundle((BaseData) bundledata); } /** * @see FrameworkAdaptor#getTotalFreeSpace() */ public long getTotalFreeSpace() throws IOException { return storage.getFreeSpace(); } /** * @throws IOException * @see FrameworkAdaptor#getPermissionStorage() */ public PermissionStorage getPermissionStorage() throws IOException { return storage.getPermissionStorage(); } /** * This method calls all the configured adaptor hook {@link AdaptorHook#frameworkStart(BundleContext)} methods. * @see FrameworkAdaptor#frameworkStart(BundleContext) */ public void frameworkStart(BundleContext fwContext) throws BundleException { this.context = fwContext; stopping = false; // always start the storage first storage.frameworkStart(fwContext); AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) adaptorHooks[i].frameworkStart(fwContext); } /** * This method calls all the configured adaptor hook {@link AdaptorHook#frameworkStop(BundleContext)} methods. * @see FrameworkAdaptor#frameworkStop(BundleContext) */ public void frameworkStop(BundleContext fwContext) throws BundleException { // first inform all configured adaptor hooks AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) adaptorHooks[i].frameworkStop(fwContext); // stop the storage last storage.frameworkStop(fwContext); } /** * This method calls all the configured adaptor hook {@link AdaptorHook#frameworkStopping(BundleContext)} methods. * @see FrameworkAdaptor#frameworkStopping(BundleContext) */ public void frameworkStopping(BundleContext fwContext) { stopping = true; // always tell storage of stopping first storage.frameworkStopping(fwContext); // inform all configured adaptor hooks last AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) adaptorHooks[i].frameworkStopping(fwContext); } /** * @see FrameworkAdaptor#getInitialBundleStartLevel() */ public int getInitialBundleStartLevel() { return storage.getInitialBundleStartLevel(); } /** * @see FrameworkAdaptor#setInitialBundleStartLevel(int) */ public void setInitialBundleStartLevel(int value) { storage.setInitialBundleStartLevel(value); } /** * This method calls all configured adaptor hook {@link AdaptorHook#createFrameworkLog()} methods * until the first one returns a non-null value. If none of the adaptor hooks return a non-null * value then a framework log implementation which does nothing is returned. * @see FrameworkAdaptor#getFrameworkLog() */ public FrameworkLog getFrameworkLog() { if (log != null) return log; AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) { log = adaptorHooks[i].createFrameworkLog(); if (log != null) return log; } log = new FrameworkLog() { public void log(FrameworkEvent frameworkEvent) { log(new FrameworkLogEntry(frameworkEvent.getBundle().getSymbolicName() == null ? frameworkEvent.getBundle().getLocation() : frameworkEvent.getBundle().getSymbolicName(), FrameworkLogEntry.ERROR, 0, "FrameworkEvent.ERROR", 0, frameworkEvent.getThrowable(), null)); //$NON-NLS-1$ } public void log(FrameworkLogEntry logEntry) { System.err.print(logEntry.getEntry() + " "); //$NON-NLS-1$ System.err.println(logEntry.getMessage()); if (logEntry.getThrowable() != null) logEntry.getThrowable().printStackTrace(System.err); } public void setWriter(Writer newWriter, boolean append) { // do nothing } /** * @throws IOException */ public void setFile(File newFile, boolean append) throws IOException { // do nothing } public File getFile() { // do nothing return null; } public void setConsoleLog(boolean consoleLog) { // do nothing } public void close() { // do nothing } }; return log; } /** * @see FrameworkAdaptor#createSystemBundleData() */ public BundleData createSystemBundleData() throws BundleException { return new SystemBundleData(this); } /** * @see FrameworkAdaptor#getBundleWatcher() */ public BundleWatcher getBundleWatcher() { if (bundleWatcher != null) return bundleWatcher; final BundleWatcher[] watchers = hookRegistry.getWatchers(); if (watchers.length == 0) return null; bundleWatcher = new BundleWatcher() { public void watchBundle(Bundle bundle, int type) { for (int i = 0; i < watchers.length; i++) watchers[i].watchBundle(bundle, type); } }; return bundleWatcher; } /** * @see FrameworkAdaptor#getPlatformAdmin() */ public PlatformAdmin getPlatformAdmin() { return storage.getStateManager(); } /** * @see FrameworkAdaptor#getState() */ public State getState() { return storage.getStateManager().getSystemState(); } /** * This method calls all the configured classloading hooks {@link ClassLoadingHook#getBundleClassLoaderParent()} methods * until one returns a non-null value. * @see FrameworkAdaptor#getBundleClassLoaderParent() */ public ClassLoader getBundleClassLoaderParent() { // ask the configured adaptor hooks first ClassLoader result = null; ClassLoadingHook[] cpManagerHooks = getHookRegistry().getClassLoadingHooks(); for (int i = 0; i < cpManagerHooks.length; i++) { result = cpManagerHooks[i].getBundleClassLoaderParent(); if (result != null) return result; } // none of the configured adaptor hooks gave use a parent loader; use the default return bundleClassLoaderParent; } /** * This method calls all the configured adaptor hooks {@link AdaptorHook#handleRuntimeError(Throwable)} methods. * @see FrameworkAdaptor#handleRuntimeError(Throwable) */ public void handleRuntimeError(Throwable error) { AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks(); for (int i = 0; i < adaptorHooks.length; i++) adaptorHooks[i].handleRuntimeError(error); } /** * Returns true if the {@link #frameworkStopping(BundleContext)} method has been called * @return true if the framework is stopping */ public boolean isStopping() { return stopping; } /** * Returns the event publisher for this BaseAdaptor * @return the event publisher for this BaseAdaptor */ public EventPublisher getEventPublisher() { return eventPublisher; } /** * Returns the <code>HookRegistry</code> object for this adaptor. * @return the <code>HookRegistry</code> object for this adaptor. */ public HookRegistry getHookRegistry() { return hookRegistry; } /** * Returns the system bundle's context * @return the system bundle's context */ public BundleContext getContext() { return context; } /** * Returns the bundle with the specified identifier. This method * does not invoke and bundle find hooks and therefore does not * allow bundle find hooks to hide a bundle from the caller. * * @param id The identifier of the bundle to retrieve. * @return A {@code Bundle} object or {@code null} if the identifier does * not match any installed bundle. */ public Bundle getBundle(long id) { return eventPublisher.getBundle(id); } /** * Creates a bundle file object for the given content and base data. * This method must delegate to each configured bundle file factory * {@link BundleFileFactoryHook#createBundleFile(Object, BaseData, boolean)} method until one * factory returns a non-null value. If no bundle file factory returns a non-null value * then the the default behavior will be performed. * <p> * If the specified content is <code>null</code> then the base content of the specified * bundledata must be found before calling any bundle file factories. * </p> * <p> * After the bundle file has been created each configured bundle file wrapper factory * {@link BundleFileWrapperFactoryHook#wrapBundleFile(BundleFile, Object, BaseData, boolean)} * method is called to wrap the bundle file. * </p> * @param content The object which contains the content of a bundle file. A value of * <code>null</code> indicates that the storage must find the base content for the * specified BaseData. * @param data The BaseData associated with the content * @return a BundleFile object. * @throws IOException if an error occured while creating the BundleFile */ public BundleFile createBundleFile(Object content, BaseData data) throws IOException { return storage.createBundleFile(content, data); } /** * Returns true if the persistent storage is read-only * @return true if the persistent storage is read-only */ public boolean isReadOnly() { return storage.isReadOnly(); } /* * This is an experimental method to allow adaptors to replace the storage implementation by * extending BaseAdaptor and overriding this method. This method is experimental. * @return a base storage object. */ protected BaseStorage getStorage() { if (storage != null) return storage; // this bit of code assumes the registry is initialized with a BaseStorageHook // we want to make sure we are using the same BaseStorage instance as the BaseStorageHook StorageHook[] hooks = hookRegistry.getStorageHooks(); for (int i = 0; i < hooks.length && storage == null; i++) if (hooks[i] instanceof BaseStorageHook) storage = ((BaseStorageHook) hooks[i]).getStorage(); return storage; } /** * @see FrameworkAdaptor#findEntries(List, String, String, int) */ public Enumeration<URL> findEntries(List<BundleData> datas, String path, String filePattern, int options) { List<BundleFile> bundleFiles = new ArrayList<BundleFile>(datas.size()); for (BundleData data : datas) bundleFiles.add(((BaseData) data).getBundleFile()); // search all the bundle files List<String> pathList = listEntryPaths(bundleFiles, path, filePattern, options); // return null if no entries found if (pathList.size() == 0) return null; // create an enumeration to enumerate the pathList final String[] pathArray = pathList.toArray(new String[pathList.size()]); final BundleData[] dataArray = datas.toArray(new BundleData[datas.size()]); return new Enumeration<URL>() { private int curPathIndex = 0; private int curDataIndex = 0; private URL nextElement = null; public boolean hasMoreElements() { if (nextElement != null) return true; getNextElement(); return nextElement != null; } public URL nextElement() { if (!hasMoreElements()) throw new NoSuchElementException(); URL result = nextElement; // force the next element search getNextElement(); return result; } private void getNextElement() { nextElement = null; if (curPathIndex >= pathArray.length) // reached the end of the pathArray; no more elements return; while (nextElement == null && curPathIndex < pathArray.length) { String curPath = pathArray[curPathIndex]; // search the datas until we have searched them all while (nextElement == null && curDataIndex < dataArray.length) nextElement = dataArray[curDataIndex++].getEntry(curPath); // we have searched all datas then advance to the next path if (curDataIndex >= dataArray.length) { curPathIndex++; curDataIndex = 0; } } } }; } /** * Returns the names of resources available from a list of bundle files. * No duplicate resource names are returned, each name is unique. * @param bundleFiles the list of bundle files to search in * @param path The path name in which to look. * @param filePattern The file name pattern for selecting resource names in * the specified path. * @param options The options for listing resource names. * @return a list of resource names. If no resources are found then * the empty list is returned. * @see BundleWiring#listResources(String, String, int) */ public List<String> listEntryPaths(List<BundleFile> bundleFiles, String path, String filePattern, int options) { // a list used to store the results of the search List<String> pathList = new ArrayList<String>(); Filter patternFilter = null; Hashtable<String, String> patternProps = null; if (filePattern != null) { // Optimization: If the file pattern does not include a wildcard or escape char then it must represent a single file. // Avoid pattern matching and use BundleFile.getEntry() if recursion was not requested. if ((options & BundleWiring.FINDENTRIES_RECURSE) == 0 && filePattern.indexOf('*') == -1 && filePattern.indexOf('\\') == -1) { if (path.length() == 0) path = filePattern; else path += path.charAt(path.length() - 1) == '/' ? filePattern : '/' + filePattern; for (BundleFile bundleFile : bundleFiles) { if (bundleFile.getEntry(path) != null && !pathList.contains(path)) pathList.add(path); } return pathList; } // For when the file pattern includes a wildcard. try { // create a file pattern filter with 'filename' as the key patternFilter = FilterImpl.newInstance("(filename=" + sanitizeFilterInput(filePattern) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ // create a single hashtable to be shared during the recursive search patternProps = new Hashtable<String, String>(2); } catch (InvalidSyntaxException e) { // something unexpected happened; log error and return nothing Bundle b = context == null ? null : context.getBundle(); eventPublisher.publishFrameworkEvent(FrameworkEvent.ERROR, b, e); return pathList; } } // find the entry paths for the datas for (BundleFile bundleFile : bundleFiles) { listEntryPaths(bundleFile, path, patternFilter, patternProps, options, pathList); } return pathList; } private String sanitizeFilterInput(String filePattern) throws InvalidSyntaxException { StringBuffer buffer = null; boolean foundEscape = false; for (int i = 0; i < filePattern.length(); i++) { char c = filePattern.charAt(i); switch (c) { case '\\' : // we either used the escape found or found a new escape. foundEscape = foundEscape ? false : true; if (buffer != null) buffer.append(c); break; case '(' : case ')' : if (!foundEscape) { if (buffer == null) { buffer = new StringBuffer(filePattern.length() + 16); buffer.append(filePattern.substring(0, i)); } // must escape with '\' buffer.append('\\'); } else { foundEscape = false; // used the escape found } if (buffer != null) buffer.append(c); break; default : // if we found an escape it has been used foundEscape = false; if (buffer != null) buffer.append(c); break; } } if (foundEscape) throw new InvalidSyntaxException("Trailing escape characters must be escaped.", filePattern); //$NON-NLS-1$ return buffer == null ? filePattern : buffer.toString(); } private List<String> listEntryPaths(BundleFile bundleFile, String path, Filter patternFilter, Hashtable<String, String> patternProps, int options, List<String> pathList) { if (pathList == null) pathList = new ArrayList<String>(); Enumeration<String> entryPaths = bundleFile.getEntryPaths(path); if (entryPaths == null) return pathList; while (entryPaths.hasMoreElements()) { String entry = entryPaths.nextElement(); int lastSlash = entry.lastIndexOf('/'); if (patternProps != null) { int secondToLastSlash = entry.lastIndexOf('/', lastSlash - 1); int fileStart; int fileEnd = entry.length(); if (lastSlash < 0) fileStart = 0; else if (lastSlash != entry.length() - 1) fileStart = lastSlash + 1; else { fileEnd = lastSlash; // leave the lastSlash out if (secondToLastSlash < 0) fileStart = 0; else fileStart = secondToLastSlash + 1; } String fileName = entry.substring(fileStart, fileEnd); // set the filename to the current entry patternProps.put("filename", fileName); //$NON-NLS-1$ } // prevent duplicates and match on the patternFilter if (!pathList.contains(entry) && (patternFilter == null || patternFilter.matchCase(patternProps))) pathList.add(entry); // recurse only into entries that are directories if (((options & BundleWiring.FINDENTRIES_RECURSE) != 0) && !entry.equals(path) && entry.length() > 0 && lastSlash == (entry.length() - 1)) listEntryPaths(bundleFile, entry, patternFilter, patternProps, options, pathList); } return pathList; } }