diff options
author | Thomas Watson | 2012-06-13 14:04:56 +0000 |
---|---|---|
committer | Thomas Watson | 2012-07-16 21:03:11 +0000 |
commit | 7e980cfb94c1eb742bf1c9986055e70373f76392 (patch) | |
tree | f652dd6795661a52dc837fe11cc2db363cc35903 /bundles/org.eclipse.osgi/container/src/org/eclipse/core | |
parent | f843e83246bb55c8e035fb0fa3e5515516070cd2 (diff) | |
download | rt.equinox.framework-7e980cfb94c1eb742bf1c9986055e70373f76392.tar.gz rt.equinox.framework-7e980cfb94c1eb742bf1c9986055e70373f76392.tar.xz rt.equinox.framework-7e980cfb94c1eb742bf1c9986055e70373f76392.zip |
Consolidate the source folders
Diffstat (limited to 'bundles/org.eclipse.osgi/container/src/org/eclipse/core')
30 files changed, 7917 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java new file mode 100644 index 000000000..5783d5a9e --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java @@ -0,0 +1,1529 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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 + * Alex Blewitt (bug 172969) + *******************************************************************************/ +package org.eclipse.core.runtime.adaptor; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.*; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.*; +import org.eclipse.core.runtime.internal.adaptor.*; +import org.eclipse.osgi.framework.adaptor.*; +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.BaseStorageHook; +import org.eclipse.osgi.internal.profile.Profile; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.runnable.ApplicationLauncher; +import org.eclipse.osgi.service.runnable.StartupMonitor; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.service.startlevel.StartLevel; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Special startup class for the Eclipse Platform. This class cannot be + * instantiated; all functionality is provided by static methods. + * <p> + * The Eclipse Platform makes heavy use of Java class loaders for loading + * plug-ins. Even the Eclipse Runtime itself and the OSGi framework need + * to be loaded by special class loaders. The upshot is that a + * client program (such as a Java main program, a servlet) cannot + * reference any part of Eclipse directly. Instead, a client must use this + * loader class to start the platform, invoking functionality defined + * in plug-ins, and shutting down the platform when done. + * </p> + * <p>Note that the fields on this class are not API. </p> + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public class EclipseStarter { + private static FrameworkAdaptor adaptor; + private static BundleContext context; + private static boolean initialize = false; + public static boolean debug = false; + private static boolean running = false; + private static Framework framework = null; + private static ServiceRegistration<?> defaultMonitorRegistration = null; + private static ServiceRegistration<?> appLauncherRegistration = null; + private static ServiceRegistration<?> splashStreamRegistration = null; + + // command line arguments + private static final String CLEAN = "-clean"; //$NON-NLS-1$ + private static final String CONSOLE = "-console"; //$NON-NLS-1$ + private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$ + private static final String DEBUG = "-debug"; //$NON-NLS-1$ + private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$ + private static final String DEV = "-dev"; //$NON-NLS-1$ + private static final String WS = "-ws"; //$NON-NLS-1$ + private static final String OS = "-os"; //$NON-NLS-1$ + private static final String ARCH = "-arch"; //$NON-NLS-1$ + private static final String NL = "-nl"; //$NON-NLS-1$ + private static final String NL_EXTENSIONS = "-nlExtensions"; //$NON-NLS-1$ + private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$ + private static final String USER = "-user"; //$NON-NLS-1$ + private static final String NOEXIT = "-noExit"; //$NON-NLS-1$ + private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$ + + // this is more of an Eclipse argument but this OSGi implementation stores its + // metadata alongside Eclipse's. + private static final String DATA = "-data"; //$NON-NLS-1$ + + // System properties + public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$ + public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ //The start level used to install the bundles + public static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$ + public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$ //The start level when the fwl start + public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$ + public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$ + public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$ + public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$ + public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$ + public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$ + public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$ + public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$ + public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$ + private static final String PROP_NL_EXTENSIONS = "osgi.nl.extensions"; //$NON-NLS-1$ + public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$ + public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$ + public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$ + public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$ + public static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$ + public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$ + public static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ //the shape of the fwk (jar, or folder) + public static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$ + private static final String PROP_FORCED_RESTART = "osgi.forcedRestart"; //$NON-NLS-1$ + + public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$ + public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$ + public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$ + public static final String PROP_IGNOREAPP = "eclipse.ignoreApp"; //$NON-NLS-1$ + public static final String PROP_REFRESH_BUNDLES = "eclipse.refreshBundles"; //$NON-NLS-1$ + private static final String PROP_ALLOW_APPRELAUNCH = "eclipse.allowAppRelaunch"; //$NON-NLS-1$ + private static final String PROP_APPLICATION_LAUNCHDEFAULT = "eclipse.application.launchDefault"; //$NON-NLS-1$ + + private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$ + private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$ + private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$ + private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$ + /** string containing the classname of the adaptor to be used in this framework instance */ + protected static final String DEFAULT_ADAPTOR_CLASS = "org.eclipse.osgi.baseadaptor.BaseAdaptor"; //$NON-NLS-1$ + + private static final int DEFAULT_INITIAL_STARTLEVEL = 6; // default value for legacy purposes + private static final String DEFAULT_BUNDLES_STARTLEVEL = "4"; //$NON-NLS-1$ + + private static FrameworkLog log; + // directory of serch candidates keyed by directory abs path -> directory listing (bug 122024) + private static Map<String, String[]> searchCandidates = new HashMap<String, String[]>(4); + private static EclipseAppLauncher appLauncher; + private static List<Runnable> shutdownHandlers; + + private static ConsoleManager consoleMgr = null; + + /** + * This is the main to start osgi. + * It only works when the framework is being jared as a single jar + */ + public static void main(String[] args) throws Exception { + if (FrameworkProperties.getProperty("eclipse.startTime") == null) //$NON-NLS-1$ + FrameworkProperties.setProperty("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$ + if (FrameworkProperties.getProperty(PROP_NOSHUTDOWN) == null) + FrameworkProperties.setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ + // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 178477) + if (FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION) == null) + FrameworkProperties.setProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$ + Object result = run(args, null); + if (result instanceof Integer && !Boolean.valueOf(FrameworkProperties.getProperty(PROP_NOSHUTDOWN)).booleanValue()) + System.exit(((Integer) result).intValue()); + } + + /** + * Launches the platform and runs a single application. The application is either identified + * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code> + * System property. This convenience method starts + * up the platform, runs the indicated application, and then shuts down the + * platform. The platform must not be running already. + * + * @param args the command line-style arguments used to configure the platform + * @param endSplashHandler the block of code to run to tear down the splash + * screen or <code>null</code> if no tear down is required + * @return the result of running the application + * @throws Exception if anything goes wrong + */ + public static Object run(String[] args, Runnable endSplashHandler) throws Exception { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logEnter("EclipseStarter.run()", null); //$NON-NLS-1$ + if (running) + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING); + boolean startupFailed = true; + try { + startup(args, endSplashHandler); + startupFailed = false; + if (Boolean.valueOf(FrameworkProperties.getProperty(PROP_IGNOREAPP)).booleanValue() || isForcedRestart()) + return null; + return run(null); + } catch (Throwable e) { + // ensure the splash screen is down + if (endSplashHandler != null) + endSplashHandler.run(); + // may use startupFailed to understand where the error happened + FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, startupFailed ? EclipseAdaptorMsg.ECLIPSE_STARTUP_STARTUP_ERROR : EclipseAdaptorMsg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null); + if (log != null) + log.log(logEntry); + else + // TODO desperate measure - ideally, we should write this to disk (a la Main.log) + e.printStackTrace(); + } finally { + try { + // The application typically sets the exit code however the framework can request that + // it be re-started. We need to check for this and potentially override the exit code. + if (isForcedRestart()) + FrameworkProperties.setProperty(PROP_EXITCODE, "23"); //$NON-NLS-1$ + if (!Boolean.valueOf(FrameworkProperties.getProperty(PROP_NOSHUTDOWN)).booleanValue()) + shutdown(); + } catch (Throwable e) { + FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_SHUTDOWN_ERROR, 1, e, null); + if (log != null) + log.log(logEntry); + else + // TODO desperate measure - ideally, we should write this to disk (a la Main.log) + e.printStackTrace(); + } + if (Profile.PROFILE && Profile.STARTUP) + Profile.logExit("EclipseStarter.run()"); //$NON-NLS-1$ + if (Profile.PROFILE) { + String report = Profile.getProfileLog(); + // avoiding writing to the console if there is nothing to print + if (report != null && report.length() > 0) + System.out.println(report); + } + } + // we only get here if an error happened + if (FrameworkProperties.getProperty(PROP_EXITCODE) == null) { + FrameworkProperties.setProperty(PROP_EXITCODE, "13"); //$NON-NLS-1$ + FrameworkProperties.setProperty(PROP_EXITDATA, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_CHECK_LOG, log == null ? null : log.getFile().getPath())); + } + return null; + } + + /** + * Returns true if the platform is already running, false otherwise. + * @return whether or not the platform is already running + */ + public static boolean isRunning() { + return running; + } + + /** + * Starts the platform and sets it up to run a single application. The application is either identified + * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code> + * System property. The platform must not be running already. + * <p> + * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required. + * </p> + * @param args the arguments passed to the application + * @return BundleContext the context of the system bundle + * @throws Exception if anything goes wrong + */ + public static BundleContext startup(String[] args, Runnable endSplashHandler) throws Exception { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logEnter("EclipseStarter.startup()", null); //$NON-NLS-1$ + if (running) + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING); + FrameworkProperties.initializeProperties(); + processCommandLine(args); + LocationManager.initializeLocations(); + loadConfigurationInfo(); + finalizeProperties(); + if (Profile.PROFILE) + Profile.initProps(); // catch any Profile properties set in eclipse.properties... + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("EclipseStarter.startup()", "props inited"); //$NON-NLS-1$ //$NON-NLS-2$ + adaptor = createAdaptor(); + log = adaptor.getFrameworkLog(); + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("EclipseStarter.startup()", "adapter created"); //$NON-NLS-1$ //$NON-NLS-2$ + framework = new Framework(adaptor); + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("EclipseStarter.startup()", "OSGi created"); //$NON-NLS-1$ //$NON-NLS-2$ + context = framework.getBundle(0).getBundleContext(); + registerFrameworkShutdownHandlers(); + publishSplashScreen(endSplashHandler); + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("EclipseStarter.startup()", "osgi launched"); //$NON-NLS-1$ //$NON-NLS-2$ + consoleMgr = ConsoleManager.startConsole(framework); + if (Profile.PROFILE && Profile.STARTUP) { + Profile.logTime("EclipseStarter.startup()", "console started"); //$NON-NLS-1$ //$NON-NLS-2$ + } + framework.launch(); + // save the cached timestamp before loading basic bundles; this is needed so we can do a proper timestamp check when logging resolver errors + long stateStamp = adaptor.getState().getTimeStamp(); + Bundle[] startBundles = loadBasicBundles(); + + if (startBundles == null || ("true".equals(FrameworkProperties.getProperty(PROP_REFRESH_BUNDLES)) && refreshPackages(getCurrentBundles(false)))) { //$NON-NLS-1$ + waitForShutdown(); + return context; // cannot continue; loadBasicBundles caused refreshPackages to shutdown the framework + } + + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("EclipseStarter.startup()", "loading basic bundles"); //$NON-NLS-1$ //$NON-NLS-2$ + + // set the framework start level to the ultimate value. This will actually start things + // running if they are persistently active. + setStartLevel(getStartLevel()); + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("EclipseStarter.startup()", "StartLevel set"); //$NON-NLS-1$ //$NON-NLS-2$ + // they should all be active by this time + ensureBundlesActive(startBundles); + + // in the case where the built-in console is disabled we should try to start the console bundle + try { + consoleMgr.checkForConsoleBundle(); + } catch (BundleException e) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null); + log.log(entry); + } + if (debug || FrameworkProperties.getProperty(PROP_DEV) != null) + // only spend time showing unresolved bundles in dev/debug mode and the state has changed + if (stateStamp != adaptor.getState().getTimeStamp()) + logUnresolvedBundles(context.getBundles()); + running = true; + if (Profile.PROFILE && Profile.STARTUP) + Profile.logExit("EclipseStarter.startup()"); //$NON-NLS-1$ + return context; + } + + private static int getStartLevel() { + String level = FrameworkProperties.getProperty(PROP_INITIAL_STARTLEVEL); + if (level != null) + try { + return Integer.parseInt(level); + } catch (NumberFormatException e) { + if (debug) + System.out.println("Start level = " + level + " parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$ + } + return DEFAULT_INITIAL_STARTLEVEL; + } + + /** + * Runs the application for which the platform was started. The platform + * must be running. + * <p> + * The given argument is passed to the application being run. If it is <code>null</code> + * then the command line arguments used in starting the platform, and not consumed + * by the platform code, are passed to the application as a <code>String[]</code>. + * </p> + * @param argument the argument passed to the application. May be <code>null</code> + * @return the result of running the application + * @throws Exception if anything goes wrong + */ + public static Object run(Object argument) throws Exception { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logEnter("EclipseStarter.run(Object)()", null); //$NON-NLS-1$ + if (!running) + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_NOT_RUNNING); + // if we are just initializing, do not run the application just return. + if (initialize) + return new Integer(0); + try { + if (appLauncher == null) { + boolean launchDefault = Boolean.valueOf(FrameworkProperties.getProperty(PROP_APPLICATION_LAUNCHDEFAULT, "true")).booleanValue(); //$NON-NLS-1$ + // create the ApplicationLauncher and register it as a service + appLauncher = new EclipseAppLauncher(context, Boolean.valueOf(FrameworkProperties.getProperty(PROP_ALLOW_APPRELAUNCH)).booleanValue(), launchDefault, log); + appLauncherRegistration = context.registerService(ApplicationLauncher.class.getName(), appLauncher, null); + // must start the launcher AFTER service restration because this method + // blocks and runs the application on the current thread. This method + // will return only after the application has stopped. + return appLauncher.start(argument); + } + return appLauncher.reStart(argument); + } catch (Exception e) { + if (log != null && context != null) // context can be null if OSGi failed to launch (bug 151413) + logUnresolvedBundles(context.getBundles()); + throw e; + } + } + + /** + * Shuts down the Platform. The state of the Platform is not automatically + * saved before shutting down. + * <p> + * On return, the Platform will no longer be running (but could be re-launched + * with another call to startup). If relaunching, care must be taken to reinitialize + * any System properties which the platform uses (e.g., osgi.instance.area) as + * some policies in the platform do not allow resetting of such properties on + * subsequent runs. + * </p><p> + * Any objects handed out by running Platform, + * including Platform runnables obtained via getRunnable, will be + * permanently invalid. The effects of attempting to invoke methods + * on invalid objects is undefined. + * </p> + * @throws Exception if anything goes wrong + */ + public static void shutdown() throws Exception { + if (!running || framework == null) + return; + if (appLauncherRegistration != null) + appLauncherRegistration.unregister(); + if (splashStreamRegistration != null) + splashStreamRegistration.unregister(); + if (defaultMonitorRegistration != null) + defaultMonitorRegistration.unregister(); + if (appLauncher != null) + appLauncher.shutdown(); + appLauncherRegistration = null; + appLauncher = null; + splashStreamRegistration = null; + defaultMonitorRegistration = null; + if (consoleMgr != null) { + consoleMgr.stopConsole(); + consoleMgr = null; + } + framework.close(); + framework = null; + context = null; + running = false; + } + + private static void ensureBundlesActive(Bundle[] bundles) { + ServiceTracker<StartLevel, StartLevel> tracker = null; + try { + for (int i = 0; i < bundles.length; i++) { + if (bundles[i].getState() != Bundle.ACTIVE) { + if (bundles[i].getState() == Bundle.INSTALLED) { + // Log that the bundle is not resolved + log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i].getLocation()), 0, null, null)); + continue; + } + // check that the startlevel allows the bundle to be active (111550) + if (tracker == null) { + tracker = new ServiceTracker<StartLevel, StartLevel>(context, StartLevel.class.getName(), null); + tracker.open(); + } + StartLevel sl = tracker.getService(); + if (sl != null && (sl.getBundleStartLevel(bundles[i]) <= sl.getStartLevel())) { + log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE, bundles[i]), 0, null, null)); + } + } + } + } finally { + if (tracker != null) + tracker.close(); + } + } + + private static void logUnresolvedBundles(Bundle[] bundles) { + State state = adaptor.getState(); + FrameworkLog logService = adaptor.getFrameworkLog(); + StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper(); + + // first lets look for missing leaf constraints (bug 114120) + VersionConstraint[] leafConstraints = stateHelper.getUnsatisfiedLeaves(state.getBundles()); + // hash the missing leaf constraints by the declaring bundles + Map<BundleDescription, List<VersionConstraint>> missing = new HashMap<BundleDescription, List<VersionConstraint>>(); + for (int i = 0; i < leafConstraints.length; i++) { + // only include non-optional and non-dynamic constraint leafs + if (leafConstraints[i] instanceof BundleSpecification && ((BundleSpecification) leafConstraints[i]).isOptional()) + continue; + if (leafConstraints[i] instanceof ImportPackageSpecification) { + if (ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(((ImportPackageSpecification) leafConstraints[i]).getDirective(Constants.RESOLUTION_DIRECTIVE))) + continue; + if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) leafConstraints[i]).getDirective(Constants.RESOLUTION_DIRECTIVE))) + continue; + } + BundleDescription bundle = leafConstraints[i].getBundle(); + List<VersionConstraint> constraints = missing.get(bundle); + if (constraints == null) { + constraints = new ArrayList<VersionConstraint>(); + missing.put(bundle, constraints); + } + constraints.add(leafConstraints[i]); + } + + // found some bundles with missing leaf constraints; log them first + if (missing.size() > 0) { + FrameworkLogEntry[] rootChildren = new FrameworkLogEntry[missing.size()]; + int rootIndex = 0; + for (Iterator<BundleDescription> iter = missing.keySet().iterator(); iter.hasNext(); rootIndex++) { + BundleDescription description = iter.next(); + String symbolicName = description.getSymbolicName() == null ? FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME : description.getSymbolicName(); + String generalMessage = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, description.getLocation()); + List<VersionConstraint> constraints = missing.get(description); + FrameworkLogEntry[] logChildren = new FrameworkLogEntry[constraints.size()]; + for (int i = 0; i < logChildren.length; i++) + logChildren[i] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, MessageHelper.getResolutionFailureMessage(constraints.get(i)), 0, null, null); + rootChildren[rootIndex] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, generalMessage, 0, null, logChildren); + } + logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_ROOTS_NOT_RESOLVED, 0, null, rootChildren)); + } + + // There may be some bundles unresolved for other reasons, causing the system to be unresolved + // log all unresolved constraints now + List<FrameworkLogEntry> allChildren = new ArrayList<FrameworkLogEntry>(); + for (int i = 0; i < bundles.length; i++) + if (bundles[i].getState() == Bundle.INSTALLED) { + String symbolicName = bundles[i].getSymbolicName() == null ? FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME : bundles[i].getSymbolicName(); + String generalMessage = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i]); + BundleDescription description = state.getBundle(bundles[i].getBundleId()); + // for some reason, the state does not know about that bundle + if (description == null) + continue; + FrameworkLogEntry[] logChildren = null; + VersionConstraint[] unsatisfied = stateHelper.getUnsatisfiedConstraints(description); + if (unsatisfied.length > 0) { + // the bundle wasn't resolved due to some of its constraints were unsatisfiable + logChildren = new FrameworkLogEntry[unsatisfied.length]; + for (int j = 0; j < unsatisfied.length; j++) + logChildren[j] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, MessageHelper.getResolutionFailureMessage(unsatisfied[j]), 0, null, null); + } else { + ResolverError[] resolverErrors = state.getResolverErrors(description); + if (resolverErrors.length > 0) { + logChildren = new FrameworkLogEntry[resolverErrors.length]; + for (int j = 0; j < resolverErrors.length; j++) + logChildren[j] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, resolverErrors[j].toString(), 0, null, null); + } + } + + allChildren.add(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, generalMessage, 0, null, logChildren)); + } + if (allChildren.size() > 0) + logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_ALL_NOT_RESOLVED, 0, null, allChildren.toArray(new FrameworkLogEntry[allChildren.size()]))); + } + + private static void publishSplashScreen(final Runnable endSplashHandler) { + if (endSplashHandler == null) + return; + // register the output stream to the launcher if it exists + try { + Method method = endSplashHandler.getClass().getMethod("getOutputStream", new Class[0]); //$NON-NLS-1$ + Object outputStream = method.invoke(endSplashHandler, new Object[0]); + if (outputStream instanceof OutputStream) { + Dictionary<String, Object> osProperties = new Hashtable<String, Object>(); + osProperties.put("name", "splashstream"); //$NON-NLS-1$//$NON-NLS-2$ + splashStreamRegistration = context.registerService(OutputStream.class.getName(), outputStream, osProperties); + } + } catch (Exception ex) { + // ignore + } + // keep this splash handler as the default startup monitor + try { + Dictionary<String, Object> monitorProps = new Hashtable<String, Object>(); + monitorProps.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); + defaultMonitorRegistration = context.registerService(StartupMonitor.class.getName(), new DefaultStartupMonitor(endSplashHandler), monitorProps); + } catch (IllegalStateException e) { + //splash handler did not provide the necessary methods, ignore it + } + } + + @SuppressWarnings("deprecation") + private static URL searchForBundle(String name, String parent) throws MalformedURLException { + URL url = null; + File fileLocation = null; + boolean reference = false; + try { + new URL(name); // quick check to see if the name is a valid URL + url = new URL(new File(parent).toURL(), name); + } catch (MalformedURLException e) { + // TODO this is legacy support for non-URL names. It should be removed eventually. + // if name was not a URL then construct one. + // Assume it should be a reference and that it is relative. This support need not + // be robust as it is temporary.. + File child = new File(name); + fileLocation = child.isAbsolute() ? child : new File(parent, name); + url = new URL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm()); + reference = true; + } + // if the name was a URL then see if it is relative. If so, insert syspath. + if (!reference) { + URL baseURL = url; + // if it is a reference URL then strip off the reference: and set base to the file:... + if (url.getProtocol().equals(REFERENCE_PROTOCOL)) { + reference = true; + String baseSpec = url.getFile(); + if (baseSpec.startsWith(FILE_SCHEME)) { + File child = new File(baseSpec.substring(5)); + baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL(); + } else + baseURL = new URL(baseSpec); + } + + fileLocation = new File(baseURL.getFile()); + // if the location is relative, prefix it with the parent + if (!fileLocation.isAbsolute()) + fileLocation = new File(parent, fileLocation.toString()); + } + // If the result is a reference then search for the real result and + // reconstruct the answer. + if (reference) { + String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath()); + if (result != null) + url = new URL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result); + else + return null; + } + + // finally we have something worth trying + try { + URLConnection result = url.openConnection(); + result.connect(); + return url; + } catch (IOException e) { + // int i = location.lastIndexOf('_'); + // return i == -1? location : location.substring(0, i); + return null; + } + } + + /* + * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing + * all basic bundles that are marked to start. + * Returns null if the framework has been shutdown as a result of refreshPackages + */ + private static Bundle[] loadBasicBundles() { + long startTime = System.currentTimeMillis(); + String osgiBundles = FrameworkProperties.getProperty(PROP_BUNDLES); + String osgiExtensions = FrameworkProperties.getProperty(PROP_EXTENSIONS); + if (osgiExtensions != null && osgiExtensions.length() > 0) { + osgiBundles = osgiExtensions + ',' + osgiBundles; + FrameworkProperties.setProperty(PROP_BUNDLES, osgiBundles); + } + String[] installEntries = getArrayFromList(osgiBundles, ","); //$NON-NLS-1$ + // get the initial bundle list from the installEntries + InitialBundle[] initialBundles = getInitialBundles(installEntries); + // get the list of currently installed initial bundles from the framework + Bundle[] curInitBundles = getCurrentBundles(true); + + // list of bundles to be refreshed + List<Bundle> toRefresh = new ArrayList<Bundle>(curInitBundles.length); + // uninstall any of the currently installed bundles that do not exist in the + // initial bundle list from installEntries. + uninstallBundles(curInitBundles, initialBundles, toRefresh); + + // install the initialBundles that are not already installed. + List<Bundle> startBundles = new ArrayList<Bundle>(installEntries.length); + List<Bundle> lazyActivationBundles = new ArrayList<Bundle>(installEntries.length); + installBundles(initialBundles, curInitBundles, startBundles, lazyActivationBundles, toRefresh); + + // If we installed/uninstalled something, force a refresh of all installed/uninstalled bundles + if (!toRefresh.isEmpty() && refreshPackages(toRefresh.toArray(new Bundle[toRefresh.size()]))) + return null; // cannot continue; refreshPackages shutdown the framework + + // schedule all basic bundles to be started + Bundle[] startInitBundles = startBundles.toArray(new Bundle[startBundles.size()]); + Bundle[] lazyInitBundles = lazyActivationBundles.toArray(new Bundle[lazyActivationBundles.size()]); + startBundles(startInitBundles, lazyInitBundles); + + if (debug) + System.out.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$ + return startInitBundles; + } + + private static InitialBundle[] getInitialBundles(String[] installEntries) { + searchCandidates.clear(); + List<InitialBundle> result = new ArrayList<InitialBundle>(installEntries.length); + int defaultStartLevel = Integer.parseInt(FrameworkProperties.getProperty(PROP_BUNDLES_STARTLEVEL, DEFAULT_BUNDLES_STARTLEVEL)); + String syspath = getSysPath(); + // should canonicalize the syspath. + try { + syspath = new File(syspath).getCanonicalPath(); + } catch (IOException ioe) { + // do nothing + } + for (int i = 0; i < installEntries.length; i++) { + String name = installEntries[i]; + int level = defaultStartLevel; + boolean start = false; + int index = name.lastIndexOf('@'); + if (index >= 0) { + String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$ + for (int j = 0; j < attributes.length; j++) { + String attribute = attributes[j]; + if (attribute.equals("start")) //$NON-NLS-1$ + start = true; + else { + try { + level = Integer.parseInt(attribute); + } catch (NumberFormatException e) { // bug 188089 + index = name.length(); + continue; + } + } + } + name = name.substring(0, index); + } + try { + URL location = searchForBundle(name, syspath); + if (location == null) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_BUNDLE_NOT_FOUND, installEntries[i]), 0, null, null); + log.log(entry); + // skip this entry + continue; + } + location = makeRelative(LocationManager.getInstallLocation().getURL(), location); + String locationString = INITIAL_LOCATION + location.toExternalForm(); + result.add(new InitialBundle(locationString, location, level, start)); + } catch (IOException e) { + log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null)); + } + } + return result.toArray(new InitialBundle[result.size()]); + } + + // returns true if the refreshPackages operation caused the framework to shutdown + private static boolean refreshPackages(Bundle[] bundles) { + ServiceReference<?> packageAdminRef = context.getServiceReference(PackageAdmin.class.getName()); + PackageAdmin packageAdmin = null; + if (packageAdminRef != null) + packageAdmin = (PackageAdmin) context.getService(packageAdminRef); + if (packageAdmin == null) + return false; + // TODO this is such a hack it is silly. There are still cases for race conditions etc + // but this should allow for some progress... + final Semaphore semaphore = new Semaphore(0); + StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.PACKAGES_REFRESHED); + context.addFrameworkListener(listener); + context.addBundleListener(listener); + packageAdmin.refreshPackages(bundles); + context.ungetService(packageAdminRef); + updateSplash(semaphore, listener); + if (isForcedRestart()) + return true; + return false; + } + + private static void waitForShutdown() { + if (!isForcedRestart()) + return; + // wait for the system bundle to stop + Bundle systemBundle = framework.getBundle(0); + int i = 0; + while (i < 5000 && (systemBundle.getState() & (Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING)) != 0) { + i += 200; + try { + Thread.sleep(200); + } catch (InterruptedException e) { + break; + } + } + } + + /** + * Creates and returns the adaptor + * + * @return a FrameworkAdaptor object + */ + private static FrameworkAdaptor createAdaptor() throws Exception { + String adaptorClassName = FrameworkProperties.getProperty(PROP_ADAPTOR, DEFAULT_ADAPTOR_CLASS); + Class<?> adaptorClass = Class.forName(adaptorClassName); + Class<?>[] constructorArgs = new Class[] {String[].class}; + Constructor<?> constructor = adaptorClass.getConstructor(constructorArgs); + return (FrameworkAdaptor) constructor.newInstance(new Object[] {new String[0]}); + } + + private static String[] processCommandLine(String[] args) throws Exception { + EclipseEnvironmentInfo.setAllArgs(args); + if (args.length == 0) { + EclipseEnvironmentInfo.setFrameworkArgs(args); + EclipseEnvironmentInfo.setAllArgs(args); + return args; + } + int[] configArgs = new int[args.length]; + configArgs[0] = -1; // need to initialize the first element to something that could not be an index. + int configArgIndex = 0; + for (int i = 0; i < args.length; i++) { + boolean found = false; + // check for args without parameters (i.e., a flag arg) + + // check if debug should be enabled for the entire platform + // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), + // simply enable debug. Otherwise, assume that that the following arg is + // actually the filename of an options file. This will be processed below. + if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ + FrameworkProperties.setProperty(PROP_DEBUG, ""); //$NON-NLS-1$ + debug = true; + found = true; + } + + // check if development mode should be enabled for the entire platform + // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), + // simply enable development mode. Otherwise, assume that that the following arg is + // actually some additional development time class path entries. This will be processed below. + if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ + FrameworkProperties.setProperty(PROP_DEV, ""); //$NON-NLS-1$ + found = true; + } + + // look for the initialization arg + if (args[i].equalsIgnoreCase(INITIALIZE)) { + initialize = true; + found = true; + } + + // look for the clean flag. + if (args[i].equalsIgnoreCase(CLEAN)) { + FrameworkProperties.setProperty(PROP_CLEAN, "true"); //$NON-NLS-1$ + found = true; + } + + // look for the consoleLog flag + if (args[i].equalsIgnoreCase(CONSOLE_LOG)) { + FrameworkProperties.setProperty(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$ + found = true; + } + + // look for the console with no port. + if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ + FrameworkProperties.setProperty(PROP_CONSOLE, ""); //$NON-NLS-1$ + found = true; + } + + if (args[i].equalsIgnoreCase(NOEXIT)) { + FrameworkProperties.setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ + found = true; + } + + if (found) { + configArgs[configArgIndex++] = i; + continue; + } + // check for args with parameters. If we are at the last argument or if the next one + // has a '-' as the first character, then we can't have an arg with a parm so continue. + if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$ + continue; + } + String arg = args[++i]; + + // look for the console and port. + if (args[i - 1].equalsIgnoreCase(CONSOLE)) { + FrameworkProperties.setProperty(PROP_CONSOLE, arg); + found = true; + } + + // look for the configuration location . + if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) { + FrameworkProperties.setProperty(LocationManager.PROP_CONFIG_AREA, arg); + found = true; + } + + // look for the data location for this instance. + if (args[i - 1].equalsIgnoreCase(DATA)) { + FrameworkProperties.setProperty(LocationManager.PROP_INSTANCE_AREA, arg); + found = true; + } + + // look for the user location for this instance. + if (args[i - 1].equalsIgnoreCase(USER)) { + FrameworkProperties.setProperty(LocationManager.PROP_USER_AREA, arg); + found = true; + } + + // look for the launcher location + if (args[i - 1].equalsIgnoreCase(LAUNCHER)) { + FrameworkProperties.setProperty(LocationManager.PROP_LAUNCHER, arg); + found = true; + } + // look for the development mode and class path entries. + if (args[i - 1].equalsIgnoreCase(DEV)) { + FrameworkProperties.setProperty(PROP_DEV, arg); + found = true; + } + + // look for the debug mode and option file location. + if (args[i - 1].equalsIgnoreCase(DEBUG)) { + FrameworkProperties.setProperty(PROP_DEBUG, arg); + debug = true; + found = true; + } + + // look for the window system. + if (args[i - 1].equalsIgnoreCase(WS)) { + FrameworkProperties.setProperty(PROP_WS, arg); + found = true; + } + + // look for the operating system + if (args[i - 1].equalsIgnoreCase(OS)) { + FrameworkProperties.setProperty(PROP_OS, arg); + found = true; + } + + // look for the system architecture + if (args[i - 1].equalsIgnoreCase(ARCH)) { + FrameworkProperties.setProperty(PROP_ARCH, arg); + found = true; + } + + // look for the nationality/language + if (args[i - 1].equalsIgnoreCase(NL)) { + FrameworkProperties.setProperty(PROP_NL, arg); + found = true; + } + + // look for the locale extensions + if (args[i - 1].equalsIgnoreCase(NL_EXTENSIONS)) { + FrameworkProperties.setProperty(PROP_NL_EXTENSIONS, arg); + found = true; + } + + // done checking for args. Remember where an arg was found + if (found) { + configArgs[configArgIndex++] = i - 1; + configArgs[configArgIndex++] = i; + } + } + + // remove all the arguments consumed by this argument parsing + if (configArgIndex == 0) { + EclipseEnvironmentInfo.setFrameworkArgs(new String[0]); + EclipseEnvironmentInfo.setAppArgs(args); + return args; + } + String[] appArgs = new String[args.length - configArgIndex]; + String[] frameworkArgs = new String[configArgIndex]; + configArgIndex = 0; + int j = 0; + int k = 0; + for (int i = 0; i < args.length; i++) { + if (i == configArgs[configArgIndex]) { + frameworkArgs[k++] = args[i]; + configArgIndex++; + } else + appArgs[j++] = args[i]; + } + EclipseEnvironmentInfo.setFrameworkArgs(frameworkArgs); + EclipseEnvironmentInfo.setAppArgs(appArgs); + return appArgs; + } + + /** + * Returns the result of converting a list of comma-separated tokens into an array + * + * @return the array of string tokens + * @param prop the initial comma-separated string + */ + private static String[] getArrayFromList(String prop, String separator) { + return ManifestElement.getArrayFromList(prop, separator); + } + + protected static String getSysPath() { + String result = FrameworkProperties.getProperty(PROP_SYSPATH); + if (result != null) + return result; + result = getSysPathFromURL(FrameworkProperties.getProperty(PROP_FRAMEWORK)); + if (result == null) + result = getSysPathFromCodeSource(); + if (result == null) + throw new IllegalStateException("Can not find the system path."); //$NON-NLS-1$ + if (Character.isUpperCase(result.charAt(0))) { + char[] chars = result.toCharArray(); + chars[0] = Character.toLowerCase(chars[0]); + result = new String(chars); + } + FrameworkProperties.setProperty(PROP_SYSPATH, result); + return result; + } + + private static String getSysPathFromURL(String urlSpec) { + if (urlSpec == null) + return null; + URL url = LocationHelper.buildURL(urlSpec, false); + if (url == null) + return null; + File fwkFile = new File(url.getFile()); + fwkFile = new File(fwkFile.getAbsolutePath()); + fwkFile = new File(fwkFile.getParent()); + return fwkFile.getAbsolutePath(); + } + + private static String getSysPathFromCodeSource() { + ProtectionDomain pd = EclipseStarter.class.getProtectionDomain(); + if (pd == null) + return null; + CodeSource cs = pd.getCodeSource(); + if (cs == null) + return null; + URL url = cs.getLocation(); + if (url == null) + return null; + String result = url.getFile(); + if (result.endsWith(".jar")) { //$NON-NLS-1$ + result = result.substring(0, result.lastIndexOf('/')); + if ("folder".equals(FrameworkProperties.getProperty(PROP_FRAMEWORK_SHAPE))) //$NON-NLS-1$ + result = result.substring(0, result.lastIndexOf('/')); + } else { + if (result.endsWith("/")) //$NON-NLS-1$ + result = result.substring(0, result.length() - 1); + result = result.substring(0, result.lastIndexOf('/')); + result = result.substring(0, result.lastIndexOf('/')); + } + return result; + } + + private static Bundle[] getCurrentBundles(boolean includeInitial) { + Bundle[] installed = context.getBundles(); + List<Bundle> initial = new ArrayList<Bundle>(); + for (int i = 0; i < installed.length; i++) { + Bundle bundle = installed[i]; + if (bundle.getLocation().startsWith(INITIAL_LOCATION)) { + if (includeInitial) + initial.add(bundle); + } else if (!includeInitial && bundle.getBundleId() != 0) + initial.add(bundle); + } + return initial.toArray(new Bundle[initial.size()]); + } + + private static Bundle getBundleByLocation(String location, Bundle[] bundles) { + for (int i = 0; i < bundles.length; i++) { + Bundle bundle = bundles[i]; + if (location.equalsIgnoreCase(bundle.getLocation())) + return bundle; + } + return null; + } + + private static void uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh) { + for (int i = 0; i < curInitBundles.length; i++) { + boolean found = false; + for (int j = 0; j < newInitBundles.length; j++) { + if (curInitBundles[i].getLocation().equalsIgnoreCase(newInitBundles[j].locationString)) { + found = true; + break; + } + } + if (!found) + try { + curInitBundles[i].uninstall(); + toRefresh.add(curInitBundles[i]); + } catch (BundleException e) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_UNINSTALL, curInitBundles[i].getLocation()), 0, e, null); + log.log(entry); + } + } + } + + private static void installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh) { + ServiceReference<?> reference = context.getServiceReference(StartLevel.class.getName()); + StartLevel startService = null; + if (reference != null) + startService = (StartLevel) context.getService(reference); + try { + for (int i = 0; i < initialBundles.length; i++) { + Bundle osgiBundle = getBundleByLocation(initialBundles[i].locationString, curInitBundles); + try { + // don't need to install if it is already installed + if (osgiBundle == null) { + InputStream in = initialBundles[i].location.openStream(); + try { + osgiBundle = context.installBundle(initialBundles[i].locationString, in); + } catch (BundleException e) { + StatusException status = e instanceof StatusException ? (StatusException) e : null; + if (status != null && status.getStatusCode() == StatusException.CODE_OK && status.getStatus() instanceof Bundle) { + osgiBundle = (Bundle) status.getStatus(); + } else + throw e; + } + // only check for lazy activation header if this is a newly installed bundle and is not marked for persistent start + if (!initialBundles[i].start && hasLazyActivationPolicy(osgiBundle)) + lazyActivationBundles.add(osgiBundle); + } + // always set the startlevel incase it has changed (bug 111549) + // this is a no-op if the level is the same as previous launch. + if ((osgiBundle.getState() & Bundle.UNINSTALLED) == 0 && initialBundles[i].level >= 0 && startService != null) + startService.setBundleStartLevel(osgiBundle, initialBundles[i].level); + // if this bundle is supposed to be started then add it to the start list + if (initialBundles[i].start) + startBundles.add(osgiBundle); + // include basic bundles in case they were not resolved before + if ((osgiBundle.getState() & Bundle.INSTALLED) != 0) + toRefresh.add(osgiBundle); + } catch (BundleException e) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null); + log.log(entry); + } catch (IOException e) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null); + log.log(entry); + } + } + } finally { + if (reference != null) + context.ungetService(reference); + } + } + + @SuppressWarnings("deprecation") + private static boolean hasLazyActivationPolicy(Bundle target) { + // check the bundle manifest to see if it defines a lazy activation policy + Dictionary<String, String> headers = target.getHeaders(""); //$NON-NLS-1$ + // first check to see if this is a fragment bundle + String fragmentHost = headers.get(Constants.FRAGMENT_HOST); + if (fragmentHost != null) + return false; // do not activate fragment bundles + // look for the OSGi defined Bundle-ActivationPolicy header + String activationPolicy = headers.get(Constants.BUNDLE_ACTIVATIONPOLICY); + try { + if (activationPolicy != null) { + ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, activationPolicy); + if (elements != null && elements.length > 0) { + // if the value is "lazy" then it has a lazy activation poliyc + if (Constants.ACTIVATION_LAZY.equals(elements[0].getValue())) + return true; + } + } else { + // check for Eclipse specific lazy start headers "Eclipse-LazyStart" and "Eclipse-AutoStart" + String eclipseLazyStart = headers.get(Constants.ECLIPSE_LAZYSTART); + if (eclipseLazyStart == null) + eclipseLazyStart = headers.get(Constants.ECLIPSE_AUTOSTART); + ManifestElement[] elements = ManifestElement.parseHeader(Constants.ECLIPSE_LAZYSTART, eclipseLazyStart); + if (elements != null && elements.length > 0) { + // if the value is true then it is lazy activated + if ("true".equals(elements[0].getValue())) //$NON-NLS-1$ + return true; + // otherwise it is only lazy activated if it defines an exceptions directive. + else if (elements[0].getDirective("exceptions") != null) //$NON-NLS-1$ + return true; + } + } + } catch (BundleException be) { + // ignore this + } + return false; + } + + private static void startBundles(Bundle[] startBundles, Bundle[] lazyBundles) { + for (int i = 0; i < startBundles.length; i++) + startBundle(startBundles[i], 0); + for (int i = 0; i < lazyBundles.length; i++) + startBundle(lazyBundles[i], Bundle.START_ACTIVATION_POLICY); + } + + private static void startBundle(Bundle bundle, int options) { + try { + bundle.start(options); + } catch (BundleException e) { + if ((bundle.getState() & Bundle.RESOLVED) != 0) { + // only log errors if the bundle is resolved + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null); + log.log(entry); + } + } + } + + private static void loadConfigurationInfo() { + Location configArea = LocationManager.getConfigurationLocation(); + if (configArea == null) + return; + + URL location = null; + try { + location = new URL(configArea.getURL().toExternalForm() + LocationManager.CONFIG_FILE); + } catch (MalformedURLException e) { + // its ok. This should never happen + } + mergeProperties(FrameworkProperties.getProperties(), loadProperties(location)); + } + + private static Properties loadProperties(URL location) { + Properties result = new Properties(); + if (location == null) + return result; + try { + InputStream in = location.openStream(); + try { + result.load(in); + } finally { + in.close(); + } + } catch (IOException e) { + // its ok if there is no file. We'll just use the defaults for everything + // TODO but it might be nice to log something with gentle wording (i.e., it is not an error) + } + return substituteVars(result); + } + + private static Properties substituteVars(Properties result) { + if (result == null) { + //nothing todo. + return null; + } + for (Enumeration<Object> eKeys = result.keys(); eKeys.hasMoreElements();) { + Object key = eKeys.nextElement(); + if (key instanceof String) { + String value = result.getProperty((String) key); + if (value != null) + result.put(key, BaseStorageHook.substituteVars(value)); + } + } + return result; + } + + /** + * Returns a URL which is equivalent to the given URL relative to the + * specified base URL. Works only for file: URLs + * @throws MalformedURLException + */ + private static URL makeRelative(URL base, URL location) throws MalformedURLException { + if (base == null) + return location; + if (!"file".equals(base.getProtocol())) //$NON-NLS-1$ + return location; + if (!location.getProtocol().equals(REFERENCE_PROTOCOL)) + return location; // we can only make reference urls relative + URL nonReferenceLocation = new URL(location.getPath()); + // if some URL component does not match, return the original location + if (!base.getProtocol().equals(nonReferenceLocation.getProtocol())) + return location; + File locationPath = new File(nonReferenceLocation.getPath()); + // if location is not absolute, return original location + if (!locationPath.isAbsolute()) + return location; + File relativePath = makeRelative(new File(base.getPath()), locationPath); + String urlPath = relativePath.getPath(); + if (File.separatorChar != '/') + urlPath = urlPath.replace(File.separatorChar, '/'); + if (nonReferenceLocation.getPath().endsWith("/")) //$NON-NLS-1$ + // restore original trailing slash + urlPath += '/'; + // couldn't use File to create URL here because it prepends the path with user.dir + URL relativeURL = new URL(base.getProtocol(), base.getHost(), base.getPort(), urlPath); + // now make it back to a reference URL + relativeURL = new URL(REFERENCE_SCHEME + relativeURL.toExternalForm()); + return relativeURL; + } + + private static File makeRelative(File base, File location) { + if (!location.isAbsolute()) + return location; + File relative = new File(new FilePath(base).makeRelative(new FilePath(location))); + return relative; + } + + private static void mergeProperties(Properties destination, Properties source) { + for (Enumeration<?> e = source.keys(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + String value = source.getProperty(key); + if (destination.getProperty(key) == null) + destination.setProperty(key, value); + } + } + + private static void setStartLevel(final int value) { + ServiceReference<?> reference = context.getServiceReference(StartLevel.class.getName()); + final StartLevel startLevel = reference != null ? (StartLevel) context.getService(reference) : null; + if (startLevel == null) + return; + final Semaphore semaphore = new Semaphore(0); + StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.STARTLEVEL_CHANGED); + context.addFrameworkListener(listener); + context.addBundleListener(listener); + startLevel.setStartLevel(value); + context.ungetService(reference); + updateSplash(semaphore, listener); + } + + static class StartupEventListener implements SynchronousBundleListener, FrameworkListener { + private final Semaphore semaphore; + private final int frameworkEventType; + + public StartupEventListener(Semaphore semaphore, int frameworkEventType) { + this.semaphore = semaphore; + this.frameworkEventType = frameworkEventType; + } + + public void bundleChanged(BundleEvent event) { + if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING) + semaphore.release(); + } + + public void frameworkEvent(FrameworkEvent event) { + if (event.getType() == frameworkEventType) + semaphore.release(); + } + + } + + private static void updateSplash(Semaphore semaphore, StartupEventListener listener) { + ServiceTracker<StartupMonitor, StartupMonitor> monitorTracker = new ServiceTracker<StartupMonitor, StartupMonitor>(context, StartupMonitor.class.getName(), null); + monitorTracker.open(); + try { + while (true) { + StartupMonitor monitor = monitorTracker.getService(); + if (monitor != null) { + try { + monitor.update(); + } catch (Throwable e) { + // ignore exceptions thrown by the monitor + } + } + // can we acquire the semaphore yet? + if (semaphore.acquire(50)) + break; //done + //else still working, spin another update + } + } finally { + if (listener != null) { + context.removeFrameworkListener(listener); + context.removeBundleListener(listener); + } + monitorTracker.close(); + } + } + + /** + * Searches for the given target directory immediately under + * the given start location. If one is found then this location is returned; + * otherwise an exception is thrown. + * + * @return the location where target directory was found + * @param start the location to begin searching + */ + private static String searchFor(final String target, String start) { + String[] candidates = searchCandidates.get(start); + if (candidates == null) { + File startFile = new File(start); + // Pre-check if file exists, if not, and it contains escape characters, + // try decoding the path + if (!startFile.exists() && start.indexOf('%') >= 0) { + String decodePath = FrameworkProperties.decode(start); + File f = new File(decodePath); + if (f.exists()) + startFile = f; + } + candidates = startFile.list(); + if (candidates != null) + searchCandidates.put(start, candidates); + } + if (candidates == null) + return null; + String result = null; + Object[] maxVersion = null; + boolean resultIsFile = false; + for (int i = 0; i < candidates.length; i++) { + String candidateName = candidates[i]; + if (!candidateName.startsWith(target)) + continue; + boolean simpleJar = false; + final char versionSep = candidateName.length() > target.length() ? candidateName.charAt(target.length()) : 0; + if (candidateName.length() > target.length() && versionSep != '_' && versionSep != '-') { + // make sure this is not just a jar with no (_|-)version tacked on the end + if (candidateName.length() == 4 + target.length() && candidateName.endsWith(".jar")) //$NON-NLS-1$ + simpleJar = true; + else + // name does not match the target properly with an (_|-) version at the end + continue; + } + // Note: directory with version suffix is always > than directory without version suffix + String version = candidateName.length() > target.length() + 1 && (versionSep == '_' || versionSep == '-') ? candidateName.substring(target.length() + 1) : ""; //$NON-NLS-1$ + Object[] currentVersion = getVersionElements(version); + if (currentVersion != null && compareVersion(maxVersion, currentVersion) < 0) { + File candidate = new File(start, candidateName); + boolean candidateIsFile = candidate.isFile(); + // if simple jar; make sure it is really a file before accepting it + if (!simpleJar || candidateIsFile) { + result = candidate.getAbsolutePath(); + resultIsFile = candidateIsFile; + maxVersion = currentVersion; + } + } + } + if (result == null) + return null; + return result.replace(File.separatorChar, '/') + (resultIsFile ? "" : "/"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Do a quick parse of version identifier so its elements can be correctly compared. + * If we are unable to parse the full version, remaining elements are initialized + * with suitable defaults. + * @return an array of size 4; first three elements are of type Integer (representing + * major, minor and service) and the fourth element is of type String (representing + * qualifier). A value of null is returned if there are no valid Integers. Note, that + * returning anything else will cause exceptions in the caller. + */ + private static Object[] getVersionElements(String version) { + Object[] result = {new Integer(-1), new Integer(-1), new Integer(-1), ""}; //$NON-NLS-1$ + StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$ + String token; + for (int i = 0; t.hasMoreTokens() && i < 4; i++) { + token = t.nextToken(); + if (i < 3) { + // major, minor or service ... numeric values + try { + result[i] = new Integer(token); + } catch (Exception e) { + if (i == 0) + return null; // return null if no valid numbers are present + // invalid number format - use default numbers (-1) for the rest + break; + } + } else { + // qualifier ... string value + result[i] = token; + } + } + return result; + } + + /** + * Compares version strings. + * @return result of comparison, as integer; + * <code><0</code> if left < right; + * <code>0</code> if left == right; + * <code>>0</code> if left > right; + */ + private static int compareVersion(Object[] left, Object[] right) { + if (left == null) + return -1; + int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major + if (result != 0) + return result; + + result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor + if (result != 0) + return result; + + result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service + if (result != 0) + return result; + + return ((String) left[3]).compareTo((String) right[3]); // compare qualifier + } + + private static void finalizeProperties() { + // if check config is unknown and we are in dev mode, + if (FrameworkProperties.getProperty(PROP_DEV) != null && FrameworkProperties.getProperty(PROP_CHECK_CONFIG) == null) + FrameworkProperties.setProperty(PROP_CHECK_CONFIG, "true"); //$NON-NLS-1$ + } + + private static class InitialBundle { + public final String locationString; + public final URL location; + public final int level; + public final boolean start; + + InitialBundle(String locationString, URL location, int level, boolean start) { + this.locationString = locationString; + this.location = location; + this.level = level; + this.start = start; + } + } + + /** + * Sets the initial properties for the platform. + * This method must be called before calling the {@link #run(String[], Runnable)} or + * {@link #startup(String[], Runnable)} methods for the properties to be used in + * a launched instance of the platform. + * <p> + * If the specified properties contains a null value then the key for that value + * will be cleared from the properties of the platform. + * </p> + * @param initialProperties the initial properties to set for the platform. + * @since 3.2 + */ + public static void setInitialProperties(Map<String, String> initialProperties) { + if (initialProperties == null || initialProperties.isEmpty()) + return; + for (Map.Entry<String, String> entry : initialProperties.entrySet()) { + if (entry.getValue() != null) + FrameworkProperties.setProperty(entry.getKey(), entry.getValue()); + else + FrameworkProperties.clearProperty(entry.getKey()); + } + } + + /** + * Returns the context of the system bundle. A value of + * <code>null</code> is returned if the platform is not running. + * @return the context of the system bundle + * @throws java.lang.SecurityException If the caller does not have the + * appropriate <code>AdminPermission[system.bundle,CONTEXT]</code>, and + * the Java Runtime Environment supports permissions. + */ + public static BundleContext getSystemBundleContext() { + if (context == null || !running) + return null; + return context.getBundle().getBundleContext(); + } + + private static boolean isForcedRestart() { + return Boolean.valueOf(FrameworkProperties.getProperty(PROP_FORCED_RESTART)).booleanValue(); + } + + /* + * NOTE: This is an internal/experimental method used by launchers that need to react when the framework + * is shutdown internally. + * + * Adds a framework shutdown handler. <p> + * A handler implements the {@link Runnable} interface. When the framework is shutdown + * the {@link Runnable#run()} method is called for each registered handler. Handlers should + * make no assumptions on the thread it is being called from. If a handler object is + * registered multiple times it will be called once for each registration. + * <p> + * At the time a handler is called the framework is shutdown. Handlers must not depend on + * a running framework to execute or attempt to load additional classes from bundles + * installed in the framework. + * @param handler the framework shutdown handler + * @throws IllegalStateException if the platform is already running + */ + static void internalAddFrameworkShutdownHandler(Runnable handler) { + if (running) + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING); + + if (shutdownHandlers == null) + shutdownHandlers = new ArrayList<Runnable>(); + + shutdownHandlers.add(handler); + } + + /* + * NOTE: This is an internal/experimental method used by launchers that need to react when the framework + * is shutdown internally. + * + * Removes a framework shutdown handler. <p> + * @param handler the framework shutdown handler + * @throws IllegalStateException if the platform is already running + */ + static void internalRemoveFrameworkShutdownHandler(Runnable handler) { + if (running) + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING); + + if (shutdownHandlers != null) + shutdownHandlers.remove(handler); + } + + private static void registerFrameworkShutdownHandlers() { + if (shutdownHandlers == null) + return; + + final Bundle systemBundle = context.getBundle(); + for (Iterator<Runnable> it = shutdownHandlers.iterator(); it.hasNext();) { + final Runnable handler = it.next(); + BundleListener listener = new BundleListener() { + public void bundleChanged(BundleEvent event) { + if (event.getBundle() == systemBundle && event.getType() == BundleEvent.STOPPED) { + handler.run(); + } + } + }; + context.addBundleListener(listener); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java new file mode 100644 index 000000000..2e06c32fc --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java @@ -0,0 +1,415 @@ +/******************************************************************************* + * Copyright (c) 2004, 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.core.runtime.adaptor; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; +import org.eclipse.core.runtime.internal.adaptor.*; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil; +import org.eclipse.osgi.service.datalocation.Location; + +/** + * This class is used to manage the various Locations for Eclipse. + * <p> + * Clients may not extend this class. + * </p> + * @since 3.1 + * @noextend This class is not intended to be subclassed by clients. + */ +public class LocationManager { + private static Location installLocation = null; + private static Location configurationLocation = null; + private static Location userLocation = null; + private static Location instanceLocation = null; + private static Location eclipseHomeLocation = null; + + public static final String READ_ONLY_AREA_SUFFIX = ".readOnly"; //$NON-NLS-1$ + public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$ + public static final String PROP_CONFIG_AREA = "osgi.configuration.area"; //$NON-NLS-1$ + public static final String PROP_CONFIG_AREA_DEFAULT = "osgi.configuration.area.default"; //$NON-NLS-1$ + public static final String PROP_SHARED_CONFIG_AREA = "osgi.sharedConfiguration.area"; //$NON-NLS-1$ + public static final String PROP_INSTANCE_AREA = "osgi.instance.area"; //$NON-NLS-1$ + public static final String PROP_INSTANCE_AREA_DEFAULT = "osgi.instance.area.default"; //$NON-NLS-1$ + public static final String PROP_USER_AREA = "osgi.user.area"; //$NON-NLS-1$ + public static final String PROP_USER_AREA_DEFAULT = "osgi.user.area.default"; //$NON-NLS-1$ + public static final String PROP_MANIFEST_CACHE = "osgi.manifest.cache"; //$NON-NLS-1$ + public static final String PROP_USER_HOME = "user.home"; //$NON-NLS-1$ + public static final String PROP_USER_DIR = "user.dir"; //$NON-NLS-1$ + public static final String PROP_HOME_LOCATION_AREA = "eclipse.home.location"; //$NON-NLS-1$ + static final String PROP_LAUNCHER = "eclipse.launcher"; //$NON-NLS-1$ + + // configuration area file/dir names + public static final String BUNDLES_DIR = "bundles"; //$NON-NLS-1$ + public static final String STATE_FILE = ".state"; //$NON-NLS-1$ + public static final String LAZY_FILE = ".lazy"; //$NON-NLS-1$ + public static final String BUNDLE_DATA_FILE = ".bundledata"; //$NON-NLS-1$ + public static final String MANIFESTS_DIR = "manifests"; //$NON-NLS-1$ + public static final String CONFIG_FILE = "config.ini"; //$NON-NLS-1$ + public static final String ECLIPSE_PROPERTIES = "eclipse.properties"; //$NON-NLS-1$ + + // Constants for configuration location discovery + private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$ + private static final String PRODUCT_SITE_MARKER = ".eclipseproduct"; //$NON-NLS-1$ + private static final String PRODUCT_SITE_ID = "id"; //$NON-NLS-1$ + private static final String PRODUCT_SITE_VERSION = "version"; //$NON-NLS-1$ + + private static final String CONFIG_DIR = "configuration"; //$NON-NLS-1$ + + // Data mode constants for user, configuration and data locations. + private static final String NONE = "@none"; //$NON-NLS-1$ + private static final String NO_DEFAULT = "@noDefault"; //$NON-NLS-1$ + private static final String USER_HOME = "@user.home"; //$NON-NLS-1$ + private static final String USER_DIR = "@user.dir"; //$NON-NLS-1$ + // Placeholder for hashcode of installation directory + private static final String INSTALL_HASH_PLACEHOLDER = "@install.hash"; //$NON-NLS-1$ + + private static final String INSTANCE_DATA_AREA_PREFIX = ".metadata/.plugins/"; //$NON-NLS-1$ + + /** + * Builds a URL with the given specification + * @param spec the URL specification + * @param trailingSlash flag to indicate a trailing slash on the spec + * @return a URL + */ + public static URL buildURL(String spec, boolean trailingSlash) { + return LocationHelper.buildURL(spec, trailingSlash); + } + + private static void mungeConfigurationLocation() { + // if the config property was set, munge it for backwards compatibility. + String location = FrameworkProperties.getProperty(PROP_CONFIG_AREA); + if (location != null) { + if (location.endsWith(".cfg")) { //$NON-NLS-1$ + int index = location.lastIndexOf('/'); + if (index < 0) + index = location.lastIndexOf('\\'); + location = location.substring(0, index + 1); + FrameworkProperties.setProperty(PROP_CONFIG_AREA, location); + } + } + } + + /** + * Initializes the Location objects for the LocationManager. + */ + public static void initializeLocations() { + // set the osgi storage area if it exists + String osgiStorage = FrameworkProperties.getProperty(Constants.FRAMEWORK_STORAGE); + if (osgiStorage != null) + FrameworkProperties.setProperty(PROP_CONFIG_AREA, osgiStorage); + // do install location initialization first since others may depend on it + // assumes that the property is already set + installLocation = buildLocation(PROP_INSTALL_AREA, null, "", true, false, null); //$NON-NLS-1$ + + // TODO not sure what the data area prefix should be here for the user area + Location temp = buildLocation(PROP_USER_AREA_DEFAULT, null, "", false, false, null); //$NON-NLS-1$ + URL defaultLocation = temp == null ? null : temp.getURL(); + if (defaultLocation == null) + defaultLocation = buildURL(new File(FrameworkProperties.getProperty(PROP_USER_HOME), "user").getAbsolutePath(), true); //$NON-NLS-1$ + userLocation = buildLocation(PROP_USER_AREA, defaultLocation, "", false, false, null); //$NON-NLS-1$ + + temp = buildLocation(PROP_INSTANCE_AREA_DEFAULT, null, "", false, false, INSTANCE_DATA_AREA_PREFIX); //$NON-NLS-1$ + defaultLocation = temp == null ? null : temp.getURL(); + if (defaultLocation == null) + defaultLocation = buildURL(new File(FrameworkProperties.getProperty(PROP_USER_DIR), "workspace").getAbsolutePath(), true); //$NON-NLS-1$ + instanceLocation = buildLocation(PROP_INSTANCE_AREA, defaultLocation, "", false, false, INSTANCE_DATA_AREA_PREFIX); //$NON-NLS-1$ + + mungeConfigurationLocation(); + // compute a default but it is very unlikely to be used since main will have computed everything + temp = buildLocation(PROP_CONFIG_AREA_DEFAULT, null, "", false, false, null); //$NON-NLS-1$ + defaultLocation = temp == null ? null : temp.getURL(); + if (defaultLocation == null && FrameworkProperties.getProperty(PROP_CONFIG_AREA) == null) + // only compute the default if the configuration area property is not set + defaultLocation = buildURL(computeDefaultConfigurationLocation(), true); + configurationLocation = buildLocation(PROP_CONFIG_AREA, defaultLocation, "", false, false, null); //$NON-NLS-1$ + // get the parent location based on the system property. This will have been set on the + // way in either by the caller/user or by main. There will be no parent location if we are not + // cascaded. + URL parentLocation = computeSharedConfigurationLocation(); + if (parentLocation != null && !parentLocation.equals(configurationLocation.getURL())) { + Location parent = new BasicLocation(null, parentLocation, true, null); + ((BasicLocation) configurationLocation).setParent(parent); + } + initializeDerivedConfigurationLocations(); + + if (FrameworkProperties.getProperty(PROP_HOME_LOCATION_AREA) == null) { + String eclipseLauncher = FrameworkProperties.getProperty(PROP_LAUNCHER); + String eclipseHomeLocationPath = getEclipseHomeLocation(eclipseLauncher); + if (eclipseHomeLocationPath != null) + FrameworkProperties.setProperty(PROP_HOME_LOCATION_AREA, eclipseHomeLocationPath); + } + // if eclipse.home.location is not set then default to osgi.install.area + if (FrameworkProperties.getProperty(PROP_HOME_LOCATION_AREA) == null && FrameworkProperties.getProperty(PROP_INSTALL_AREA) != null) + FrameworkProperties.setProperty(PROP_HOME_LOCATION_AREA, FrameworkProperties.getProperty(PROP_INSTALL_AREA)); + eclipseHomeLocation = buildLocation(PROP_HOME_LOCATION_AREA, null, "", true, true, null); //$NON-NLS-1$ + } + + private static String getEclipseHomeLocation(String launcher) { + if (launcher == null) + return null; + File launcherFile = new File(launcher); + if (launcherFile.getParent() == null) + return null; + File launcherDir = new File(launcherFile.getParent()); + // check for mac os; the os check is copied from EclipseEnvironmentInfo. + String macosx = org.eclipse.osgi.service.environment.Constants.OS_MACOSX; + if (macosx.equals(EclipseEnvironmentInfo.getDefault().getOS())) + launcherDir = getMacOSEclipsoeHomeLocation(launcherDir); + return (launcherDir.exists() && launcherDir.isDirectory()) ? launcherDir.getAbsolutePath() : null; + } + + private static File getMacOSEclipsoeHomeLocation(File launcherDir) { + // TODO for now we go up three directories from the launcher dir as long as the parent dir is named MacOS; is this always the case? + // TODO not sure if case is important + if (!launcherDir.getName().equalsIgnoreCase("macos")) //$NON-NLS-1$ + return launcherDir; // don't do the up three stuff if not in macos directory + String launcherParent = launcherDir.getParent(); + if (launcherParent != null) + launcherParent = new File(launcherParent).getParent(); + if (launcherParent != null) + launcherParent = new File(launcherParent).getParent(); + return launcherParent == null ? null : new File(launcherParent); + } + + @SuppressWarnings("deprecation") + private static Location buildLocation(String property, URL defaultLocation, String userDefaultAppendage, boolean readOnlyDefault, boolean computeReadOnly, String dataAreaPrefix) { + String location = FrameworkProperties.clearProperty(property); + // the user/product may specify a non-default readOnly setting + String userReadOnlySetting = FrameworkProperties.getProperty(property + READ_ONLY_AREA_SUFFIX); + boolean readOnly = (userReadOnlySetting == null ? readOnlyDefault : Boolean.valueOf(userReadOnlySetting).booleanValue()); + // if the instance location is not set, predict where the workspace will be and + // put the instance area inside the workspace meta area. + if (location == null) + return new BasicLocation(property, defaultLocation, userReadOnlySetting != null || !computeReadOnly ? readOnly : !canWrite(defaultLocation), dataAreaPrefix); + String trimmedLocation = location.trim(); + if (trimmedLocation.equalsIgnoreCase(NONE)) + return null; + if (trimmedLocation.equalsIgnoreCase(NO_DEFAULT)) + return new BasicLocation(property, null, readOnly, dataAreaPrefix); + if (trimmedLocation.startsWith(USER_HOME)) { + String base = substituteVar(location, USER_HOME, PROP_USER_HOME); + location = new File(base, userDefaultAppendage).getAbsolutePath(); + } else if (trimmedLocation.startsWith(USER_DIR)) { + String base = substituteVar(location, USER_DIR, PROP_USER_DIR); + location = new File(base, userDefaultAppendage).getAbsolutePath(); + } + int idx = location.indexOf(INSTALL_HASH_PLACEHOLDER); + if (idx == 0) { + throw new RuntimeException("The location cannot start with '" + INSTALL_HASH_PLACEHOLDER + "': " + location); //$NON-NLS-1$ //$NON-NLS-2$ + } else if (idx > 0) { + location = location.substring(0, idx) + getInstallDirHash() + location.substring(idx + INSTALL_HASH_PLACEHOLDER.length()); + } + URL url = buildURL(location, true); + BasicLocation result = null; + if (url != null) { + result = new BasicLocation(property, null, userReadOnlySetting != null || !computeReadOnly ? readOnly : !canWrite(url), dataAreaPrefix); + result.setURL(url, false); + } + return result; + } + + private static String substituteVar(String source, String var, String prop) { + String value = FrameworkProperties.getProperty(prop, ""); //$NON-NLS-1$ + return value + source.substring(var.length()); + } + + private static void initializeDerivedConfigurationLocations() { + if (FrameworkProperties.getProperty(PROP_MANIFEST_CACHE) == null) + FrameworkProperties.setProperty(PROP_MANIFEST_CACHE, getConfigurationFile(MANIFESTS_DIR).getAbsolutePath()); + } + + private static URL computeInstallConfigurationLocation() { + String property = FrameworkProperties.getProperty(PROP_INSTALL_AREA); + if (property != null) + return LocationHelper.buildURL(property, true); + return null; + } + + private static URL computeSharedConfigurationLocation() { + String property = FrameworkProperties.getProperty(PROP_SHARED_CONFIG_AREA); + if (property == null) + return null; + try { + URL sharedConfigurationURL = LocationHelper.buildURL(property, true); + if (sharedConfigurationURL == null) + return null; + if (sharedConfigurationURL.getPath().startsWith("/")) //$NON-NLS-1$ + // absolute + return sharedConfigurationURL; + URL installURL = installLocation.getURL(); + if (!sharedConfigurationURL.getProtocol().equals(installURL.getProtocol())) + // different protocol + return sharedConfigurationURL; + sharedConfigurationURL = new URL(installURL, sharedConfigurationURL.getPath()); + FrameworkProperties.setProperty(PROP_SHARED_CONFIG_AREA, sharedConfigurationURL.toExternalForm()); + } catch (MalformedURLException e) { + // do nothing here since it is basically impossible to get a bogus url + } + return null; + } + + private static String computeDefaultConfigurationLocation() { + // 1) We store the config state relative to the 'eclipse' directory if possible + // 2) If this directory is read-only + // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home> + // is unique for each local user, and <application-id> is the one + // defined in .eclipseproduct marker file. If .eclipseproduct does not + // exist, use "eclipse" as the application-id. + + URL installURL = computeInstallConfigurationLocation(); + if (installURL != null && "file".equals(installURL.getProtocol())) { //$NON-NLS-1$ + File installDir = new File(installURL.getFile()); + File defaultConfigDir = new File(installDir, CONFIG_DIR); + if (!defaultConfigDir.exists()) + defaultConfigDir.mkdirs(); + if (defaultConfigDir.exists() && AdaptorUtil.canWrite(defaultConfigDir)) + return defaultConfigDir.getAbsolutePath(); + } + // We can't write in the eclipse install dir so try for some place in the user's home dir + return computeDefaultUserAreaLocation(CONFIG_DIR); + } + + private static boolean canWrite(URL location) { + if (location != null && "file".equals(location.getProtocol())) { //$NON-NLS-1$ + File locationDir = new File(location.getFile()); + if (!locationDir.exists()) + locationDir.mkdirs(); + if (locationDir.exists() && AdaptorUtil.canWrite(locationDir)) + return true; + } + return false; + } + + private static String computeDefaultUserAreaLocation(String pathAppendage) { + // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home> + // is unique for each local user, and <application-id> is the one + // defined in .eclipseproduct marker file. If .eclipseproduct does not + // exist, use "eclipse" as the application-id. + String installProperty = FrameworkProperties.getProperty(PROP_INSTALL_AREA); + URL installURL = buildURL(installProperty, true); + if (installURL == null) + return null; + File installDir = new File(installURL.getFile()); + String installDirHash = getInstallDirHash(); + + String appName = "." + ECLIPSE; //$NON-NLS-1$ + File eclipseProduct = new File(installDir, PRODUCT_SITE_MARKER); + if (eclipseProduct.exists()) { + Properties props = new Properties(); + try { + props.load(new FileInputStream(eclipseProduct)); + String appId = props.getProperty(PRODUCT_SITE_ID); + if (appId == null || appId.trim().length() == 0) + appId = ECLIPSE; + String appVersion = props.getProperty(PRODUCT_SITE_VERSION); + if (appVersion == null || appVersion.trim().length() == 0) + appVersion = ""; //$NON-NLS-1$ + appName += File.separator + appId + "_" + appVersion + "_" + installDirHash; //$NON-NLS-1$ //$NON-NLS-2$ + } catch (IOException e) { + // Do nothing if we get an exception. We will default to a standard location + // in the user's home dir. + // add the hash to help prevent collisions + appName += File.separator + installDirHash; + } + } else { + // add the hash to help prevent collisions + appName += File.separator + installDirHash; + } + String userHome = FrameworkProperties.getProperty(PROP_USER_HOME); + return new File(userHome, appName + "/" + pathAppendage).getAbsolutePath(); //$NON-NLS-1$ + } + + /** + * Return hash code identifying an absolute installation path + * @return hash code as String + */ + private static String getInstallDirHash() { + // compute an install dir hash to prevent configuration area collisions with other eclipse installs + String installProperty = FrameworkProperties.getProperty(PROP_INSTALL_AREA); + URL installURL = buildURL(installProperty, true); + if (installURL == null) + return ""; //$NON-NLS-1$ + File installDir = new File(installURL.getFile()); + int hashCode; + try { + hashCode = installDir.getCanonicalPath().hashCode(); + } catch (IOException ioe) { + // fall back to absolute path + hashCode = installDir.getAbsolutePath().hashCode(); + } + if (hashCode < 0) + hashCode = -(hashCode); + String installDirHash = String.valueOf(hashCode); + return installDirHash; + } + + /** + * Returns the user Location object + * @return the user Location object + */ + public static Location getUserLocation() { + return userLocation; + } + + /** + * Returns the configuration Location object + * @return the configuration Location object + */ + public static Location getConfigurationLocation() { + return configurationLocation; + } + + /** + * Returns the install Location object + * @return the install Location object + */ + public static Location getInstallLocation() { + return installLocation; + } + + /** + * Returns the instance Location object + * @return the instance Location object + */ + public static Location getInstanceLocation() { + return instanceLocation; + } + + public static Location getEclipseHomeLocation() { + return eclipseHomeLocation; + } + + /** + * Returns the File object under the configuration location used for the OSGi configuration + * @return the OSGi configuration directory + */ + public static File getOSGiConfigurationDir() { + // TODO assumes the URL is a file: url + return new File(configurationLocation.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME); + } + + /** + * Returns a file from the configuration area that can be used by the framework + * @param filename the filename + * @return a file from the configuration area + */ + public static File getConfigurationFile(String filename) { + File dir = getOSGiConfigurationDir(); + if (!dir.exists()) + dir.mkdirs(); + return new File(dir, filename); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html new file mode 100644 index 000000000..65b481997 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides API to start the platform. +<h2> +Package Specification</h2> +This package specifies API to start the platform. +<p> +Clients may use the <tt>EclipseStarter</tt> loader class to start the platform. The +<tt>EclipseStarter</tt> class is the only defined API in this package. +</p> +</body> +</html> diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java new file mode 100644 index 000000000..e2eb1d829 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2004, 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.core.runtime.internal.adaptor; + +import java.util.ResourceBundle; +import org.eclipse.osgi.service.localization.BundleLocalization; +import org.osgi.framework.Bundle; + +/** + * The implementation of the service that gets ResourceBundle objects from a given + * bundle with a given locale. + * + * <p>Internal class.</p> + */ + +public class BundleLocalizationImpl implements BundleLocalization { + /** + * The getLocalization method gets a ResourceBundle object for the given + * locale and bundle. + * + * @return A <code>ResourceBundle</code> object for the given bundle and locale. + * If null is passed for the locale parameter, the default locale is used. + */ + public ResourceBundle getLocalization(Bundle bundle, String locale) { + return ((org.eclipse.osgi.framework.internal.core.AbstractBundle) (bundle)).getResourceBundle(locale); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java new file mode 100644 index 000000000..550c2e24b --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2003, 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.core.runtime.internal.adaptor; + +import java.util.Dictionary; +import java.util.Enumeration; +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.framework.util.Headers; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.BundleException; +import org.osgi.framework.Version; + +/** + * Internal class. + */ +public class CachedManifest extends Dictionary<String, String> { + static final String SERVICE_COMPONENT = "Service-Component"; //$NON-NLS-1$ + static boolean DEBUG = false; + private Dictionary<String, String> manifest = null; + private EclipseStorageHook storageHook; + + public CachedManifest(EclipseStorageHook storageHook) { + this.storageHook = storageHook; + } + + public Dictionary<String, String> getManifest() { + if (manifest == null) + try { + if (DEBUG) + System.out.println("Reading manifest for: " + storageHook.getBaseData()); //$NON-NLS-1$ + manifest = storageHook.createCachedManifest(true); + } catch (BundleException e) { + final String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CACHEDMANIFEST_UNEXPECTED_EXCEPTION, storageHook.getBaseData().getLocation()); + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null); + storageHook.getAdaptor().getFrameworkLog().log(entry); + } + if (manifest == null) { + Headers<String, String> empty = new Headers<String, String>(0); + empty.setReadOnly(); + manifest = empty; + return empty; + } + return manifest; + } + + public int size() { + return getManifest().size(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public Enumeration<String> elements() { + return getManifest().elements(); + } + + public Enumeration<String> keys() { + return getManifest().keys(); + } + + @SuppressWarnings("deprecation") + public String get(Object key) { + if (manifest != null) + return manifest.get(key); + String keyString = (String) key; + if (Constants.BUNDLE_VERSION.equalsIgnoreCase(keyString)) { + Version result = storageHook.getBaseData().getVersion(); + return result == null ? null : result.toString(); + } + if (Constants.PLUGIN_CLASS.equalsIgnoreCase(keyString)) + return storageHook.getPluginClass(); + if (Constants.BUNDLE_SYMBOLICNAME.equalsIgnoreCase(keyString)) { + if ((storageHook.getBaseData().getType() & BundleData.TYPE_SINGLETON) == 0) + return storageHook.getBaseData().getSymbolicName(); + return storageHook.getBaseData().getSymbolicName() + ';' + Constants.SINGLETON_DIRECTIVE + ":=true"; //$NON-NLS-1$ + } + if (Constants.BUDDY_LOADER.equalsIgnoreCase(keyString)) + return storageHook.getBuddyList(); + if (Constants.REGISTERED_POLICY.equalsIgnoreCase(keyString)) + return storageHook.getRegisteredBuddyList(); + if (Constants.BUNDLE_ACTIVATOR.equalsIgnoreCase(keyString)) + return storageHook.getBaseData().getActivator(); + if (Constants.BUNDLE_ACTIVATIONPOLICY.equals(keyString)) { + if (!storageHook.isAutoStartable()) + return null; + String[] excludes = storageHook.getLazyStartExcludes(); + String[] includes = storageHook.getLazyStartIncludes(); + if (excludes == null && includes == null) + return Constants.ACTIVATION_LAZY; + StringBuffer result = new StringBuffer(Constants.ACTIVATION_LAZY); + if (excludes != null) { + result.append(';').append(Constants.EXCLUDE_DIRECTIVE).append(":=\""); //$NON-NLS-1$ + for (int i = 0; i < excludes.length; i++) { + if (i > 0) + result.append(','); + result.append(excludes[i]); + } + result.append("\""); //$NON-NLS-1$ + } + if (includes != null) { + result.append(';').append(Constants.INCLUDE_DIRECTIVE).append(":=\""); //$NON-NLS-1$ + for (int i = 0; i < includes.length; i++) { + if (i > 0) + result.append(','); + result.append(includes[i]); + } + result.append("\""); //$NON-NLS-1$ + } + } + if (Constants.ECLIPSE_LAZYSTART.equals(keyString) || Constants.ECLIPSE_AUTOSTART.equals(keyString)) { + if (!storageHook.isAutoStartable()) + return null; + if (storageHook.getLazyStartExcludes() == null) + return Boolean.TRUE.toString(); + StringBuffer result = new StringBuffer(storageHook.isLazyStart() ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); + result.append(";").append(Constants.ECLIPSE_LAZYSTART_EXCEPTIONS).append("=\""); //$NON-NLS-1$ //$NON-NLS-2$ + String[] exceptions = storageHook.getLazyStartExcludes(); + for (int i = 0; i < exceptions.length; i++) { + if (i > 0) + result.append(","); //$NON-NLS-1$ + result.append(exceptions[i]); + } + result.append("\""); //$NON-NLS-1$ + return result.toString(); + } + if (Constants.BUNDLE_MANIFESTVERSION.equals(keyString)) + return storageHook.getBundleManifestVersion() == 0 ? null : Integer.toString(storageHook.getBundleManifestVersion()); + if (SERVICE_COMPONENT.equals(keyString)) + return storageHook.getServiceComponent(); + Dictionary<String, String> result = getManifest(); + if (DEBUG) + System.out.println("Manifest read because of header: " + key); //$NON-NLS-1$ + return result == null ? null : result.get(key); + } + + public String remove(Object key) { + return getManifest().remove(key); + } + + public String put(String key, String value) { + return getManifest().put(key, value); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java new file mode 100644 index 000000000..80e3c28cd --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.core.runtime.internal.adaptor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.Manifest; +import org.eclipse.osgi.baseadaptor.BaseData; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.loader.*; +import org.eclipse.osgi.framework.util.KeyedElement; + +public class ClasspathManifest implements KeyedElement { + public static final Object KEY = new Object(); + public static final int HASHCODE = KEY.hashCode(); + + private Manifest manifest; + private boolean initialized = false; + + public int getKeyHashCode() { + return HASHCODE; + } + + public boolean compare(KeyedElement other) { + return other.getKey() == KEY; + } + + public Object getKey() { + return KEY; + } + + public synchronized Manifest getManifest(ClasspathEntry cpEntry, ClasspathManager loader) { + if (initialized) + return manifest; + if (!hasPackageInfo(cpEntry, loader)) { + initialized = true; + manifest = null; + return manifest; + } + BundleEntry mfEntry = cpEntry.getBundleFile().getEntry(org.eclipse.osgi.framework.internal.core.Constants.OSGI_BUNDLE_MANIFEST); + if (mfEntry != null) { + InputStream manIn = null; + try { + try { + manIn = mfEntry.getInputStream(); + manifest = new Manifest(manIn); + } finally { + if (manIn != null) + manIn.close(); + } + } catch (IOException e) { + // do nothing + } + } + initialized = true; + return manifest; + } + + private boolean hasPackageInfo(ClasspathEntry cpEntry, ClasspathManager loader) { + BaseData bundledata = null; + if (cpEntry.getBundleFile() == loader.getBaseData().getBundleFile()) + bundledata = loader.getBaseData(); + if (bundledata == null) { + FragmentClasspath[] fragCPs = loader.getFragmentClasspaths(); + if (fragCPs != null) + for (int i = 0; i < fragCPs.length; i++) + if (cpEntry.getBundleFile() == fragCPs[i].getBundleData().getBundleFile()) { + bundledata = fragCPs[i].getBundleData(); + break; + } + } + if (bundledata == null) + return true; + EclipseStorageHook storageHook = (EclipseStorageHook) bundledata.getStorageHook(EclipseStorageHook.KEY); + return storageHook == null ? true : storageHook.hasPackageInfo(); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java new file mode 100644 index 000000000..9ecc15b45 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * 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.core.runtime.internal.adaptor; + +import java.io.IOException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import org.eclipse.osgi.framework.adaptor.BundleClassLoader; + +public class ContextFinder extends ClassLoader implements PrivilegedAction<List<ClassLoader>> { + static final class Finder extends SecurityManager { + public Class<?>[] getClassContext() { + return super.getClassContext(); + } + } + + //This is used to detect cycle that could be caused while delegating the loading to other classloaders + //It keeps track on a thread basis of the set of requested classes and resources + private static ThreadLocal<Set<String>> cycleDetector = new ThreadLocal<Set<String>>(); + static ClassLoader finderClassLoader; + static Finder contextFinder; + static { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + finderClassLoader = ContextFinder.class.getClassLoader(); + contextFinder = new Finder(); + return null; + } + }); + } + + private static Class<ContextFinder> THIS = ContextFinder.class; + + private final ClassLoader parentContextClassLoader; + + public ContextFinder(ClassLoader contextClassLoader) { + super(contextClassLoader); + this.parentContextClassLoader = contextClassLoader != null ? contextClassLoader : new ClassLoader(Object.class.getClassLoader()) {/*boot classloader*/}; + } + + // Return a list of all classloaders on the stack that are neither the + // ContextFinder classloader nor the boot classloader. The last classloader + // in the list is either a bundle classloader or the framework's classloader + // We assume that the bootclassloader never uses the context classloader to find classes in itself. + List<ClassLoader> basicFindClassLoaders() { + Class<?>[] stack = contextFinder.getClassContext(); + List<ClassLoader> result = new ArrayList<ClassLoader>(1); + ClassLoader previousLoader = null; + for (int i = 1; i < stack.length; i++) { + ClassLoader tmp = stack[i].getClassLoader(); + if (stack[i] != THIS && tmp != null && tmp != this) { + if (checkClassLoader(tmp)) { + if (previousLoader != tmp) { + result.add(tmp); + previousLoader = tmp; + } + } + // stop at the framework classloader or the first bundle classloader + if (tmp == finderClassLoader || tmp instanceof BundleClassLoader) + break; + } + } + return result; + } + + // ensures that a classloader does not have the ContextFinder as part of the + // parent hierachy. A classloader which has the ContextFinder as a parent must + // not be used as a delegate, otherwise we endup in endless recursion. + private boolean checkClassLoader(ClassLoader classloader) { + if (classloader == null || classloader == getParent()) + return false; + for (ClassLoader parent = classloader.getParent(); parent != null; parent = parent.getParent()) + if (parent == this) + return false; + return true; + } + + private List<ClassLoader> findClassLoaders() { + if (System.getSecurityManager() == null) + return basicFindClassLoaders(); + return AccessController.doPrivileged(this); + } + + public List<ClassLoader> run() { + return basicFindClassLoaders(); + } + + //Return whether the request for loading "name" should proceed. + //False is returned when a cycle is being detected + private boolean startLoading(String name) { + Set<String> classesAndResources = cycleDetector.get(); + if (classesAndResources != null && classesAndResources.contains(name)) + return false; + + if (classesAndResources == null) { + classesAndResources = new HashSet<String>(3); + cycleDetector.set(classesAndResources); + } + classesAndResources.add(name); + return true; + } + + private void stopLoading(String name) { + cycleDetector.get().remove(name); + } + + protected Class<?> loadClass(String arg0, boolean arg1) throws ClassNotFoundException { + //Shortcut cycle + if (startLoading(arg0) == false) + throw new ClassNotFoundException(arg0); + + try { + List<ClassLoader> toConsult = findClassLoaders(); + for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) + try { + return loaders.next().loadClass(arg0); + } catch (ClassNotFoundException e) { + // go to the next class loader + } + // avoid calling super.loadClass here because it checks the local cache (bug 127963) + return parentContextClassLoader.loadClass(arg0); + } finally { + stopLoading(arg0); + } + } + + public URL getResource(String arg0) { + //Shortcut cycle + if (startLoading(arg0) == false) + return null; + try { + List<ClassLoader> toConsult = findClassLoaders(); + for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) { + URL result = loaders.next().getResource(arg0); + if (result != null) + return result; + // go to the next class loader + } + return super.getResource(arg0); + } finally { + stopLoading(arg0); + } + } + + protected Enumeration<URL> findResources(String arg0) throws IOException { + //Shortcut cycle + if (startLoading(arg0) == false) { + @SuppressWarnings("unchecked") + Enumeration<URL> result = Collections.enumeration(Collections.EMPTY_LIST); + return result; + } + try { + List<ClassLoader> toConsult = findClassLoaders(); + for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) { + Enumeration<URL> result = loaders.next().getResources(arg0); + if (result != null && result.hasMoreElements()) + return result; + // go to the next class loader + } + return super.findResources(arg0); + } finally { + stopLoading(arg0); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java new file mode 100644 index 000000000..9ac29d38d --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 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: + * Andrew Niefer - IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.runtime.internal.adaptor; + +import java.lang.reflect.Method; +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.core.runtime.internal.stats.StatsManager; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.service.runnable.StartupMonitor; + +public class DefaultStartupMonitor implements StartupMonitor { + + private Method updateMethod = null; + private Runnable splashHandler = null; + + /** + * Create a new startup monitor using the given splash handler. The splash handle must + * have an updateSplash method. + * + * @param splashHandler + * @throws IllegalStateException + */ + public DefaultStartupMonitor(Runnable splashHandler) throws IllegalStateException { + this.splashHandler = splashHandler; + + try { + updateMethod = splashHandler.getClass().getMethod("updateSplash", (Class[]) null); //$NON-NLS-1$ + } catch (SecurityException e) { + throw (IllegalStateException) new IllegalStateException(e.getMessage()).initCause(e); + } catch (NoSuchMethodException e) { + //TODO maybe we could do something else in the update method in this case, like print something to the console? + throw (IllegalStateException) new IllegalStateException(e.getMessage()).initCause(e); + } + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.adaptor.StartupMonitor#update() + */ + public void update() { + if (updateMethod != null) { + try { + updateMethod.invoke(splashHandler, (Object[]) null); + } catch (Throwable e) { + // ignore, this is best effort + } + } else { + //TODO maybe we could print something interesting to the console? + } + } + + public void applicationRunning() { + if (EclipseStarter.debug) { + String timeString = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$ + long time = timeString == null ? 0L : Long.parseLong(timeString); + System.out.println("Application Started: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ + } + StatsManager.doneBooting(); + splashHandler.run(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java new file mode 100644 index 000000000..657e6be86 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * 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.core.runtime.internal.adaptor; + +import java.io.IOException; +import java.net.URLConnection; +import java.util.*; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParserFactory; +import org.eclipse.core.runtime.adaptor.LocationManager; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.debug.FrameworkDebugOptions; +import org.eclipse.osgi.framework.internal.core.BundleHost; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.osgi.service.pluginconversion.PluginConverter; +import org.eclipse.osgi.service.resolver.PlatformAdmin; +import org.eclipse.osgi.service.urlconversion.URLConverter; +import org.osgi.framework.*; + +public class EclipseAdaptorHook implements AdaptorHook, HookConfigurator { + /** The SAX factory name */ + public static final String SAXFACTORYNAME = "javax.xml.parsers.SAXParserFactory"; //$NON-NLS-1$ + /** The DOM factory name */ + public static final String DOMFACTORYNAME = "javax.xml.parsers.DocumentBuilderFactory"; //$NON-NLS-1$ + private static final String RUNTIME_ADAPTOR = FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + "/eclipseadaptor"; //$NON-NLS-1$ + private static final String OPTION_CONVERTER = RUNTIME_ADAPTOR + "/converter/debug"; //$NON-NLS-1$ + private static final String OPTION_LOCATION = RUNTIME_ADAPTOR + "/debug/location"; //$NON-NLS-1$ + private static final String OPTION_CACHEDMANIFEST = RUNTIME_ADAPTOR + "/debug/cachedmanifest"; //$NON-NLS-1$ + static final boolean SET_TCCL_XMLFACTORY = "true".equals(FrameworkProperties.getProperty("eclipse.parsers.setTCCL", "true"));//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private BaseAdaptor adaptor; + private boolean noXML = false; + private List<ServiceRegistration<?>> registrations = new ArrayList<ServiceRegistration<?>>(10); + + /** + * @throws BundleException + */ + public void frameworkStart(BundleContext context) throws BundleException { + registrations.clear(); + registerEndorsedXMLParser(context); + Dictionary<String, Object> locationProperties = new Hashtable<String, Object>(1); + Location location = LocationManager.getUserLocation(); + if (location != null) { + locationProperties.put("type", LocationManager.PROP_USER_AREA); //$NON-NLS-1$ + registrations.add(context.registerService(Location.class.getName(), location, locationProperties)); + } + location = LocationManager.getInstanceLocation(); + if (location != null) { + locationProperties.put("type", LocationManager.PROP_INSTANCE_AREA); //$NON-NLS-1$ + registrations.add(context.registerService(Location.class.getName(), location, locationProperties)); + } + location = LocationManager.getConfigurationLocation(); + if (location != null) { + locationProperties.put("type", LocationManager.PROP_CONFIG_AREA); //$NON-NLS-1$ + registrations.add(context.registerService(Location.class.getName(), location, locationProperties)); + } + location = LocationManager.getInstallLocation(); + if (location != null) { + locationProperties.put("type", LocationManager.PROP_INSTALL_AREA); //$NON-NLS-1$ + registrations.add(context.registerService(Location.class.getName(), location, locationProperties)); + } + + location = LocationManager.getEclipseHomeLocation(); + if (location != null) { + locationProperties.put("type", LocationManager.PROP_HOME_LOCATION_AREA); //$NON-NLS-1$ + registrations.add(context.registerService(Location.class.getName(), location, locationProperties)); + } + + Dictionary<String, Object> urlProperties = new Hashtable<String, Object>(); + urlProperties.put("protocol", new String[] {"bundleentry", "bundleresource"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + registrations.add(context.registerService(URLConverter.class.getName(), new URLConverterImpl(), urlProperties)); + + registrations.add(AdaptorUtil.register(org.eclipse.osgi.service.environment.EnvironmentInfo.class.getName(), EclipseEnvironmentInfo.getDefault(), context)); + registrations.add(AdaptorUtil.register(PlatformAdmin.class.getName(), adaptor.getPlatformAdmin(), context)); + PluginConverter converter = PluginConverterImpl.getDefault(); + if (converter == null) + converter = new PluginConverterImpl(adaptor, context); + registrations.add(AdaptorUtil.register(PluginConverter.class.getName(), converter, context)); + registrations.add(AdaptorUtil.register(org.eclipse.osgi.service.localization.BundleLocalization.class.getName(), new BundleLocalizationImpl(), context)); + } + + private void registerEndorsedXMLParser(BundleContext bc) { + try { + Class.forName(SAXFACTORYNAME); + registrations.add(bc.registerService(SAXFACTORYNAME, new ParsingService(true), null)); + Class.forName(DOMFACTORYNAME); + registrations.add(bc.registerService(DOMFACTORYNAME, new ParsingService(false), null)); + } catch (ClassNotFoundException e) { + noXML = true; + if (Debug.DEBUG_ENABLED) { + String message = EclipseAdaptorMsg.ECLIPSE_ADAPTOR_ERROR_XML_SERVICE; + adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null)); + } + } + } + + private static class ParsingService implements ServiceFactory<Object> { + private final boolean isSax; + + public ParsingService(boolean isSax) { + this.isSax = isSax; + } + + public Object getService(Bundle bundle, ServiceRegistration<Object> registration) { + BundleHost host = (bundle instanceof BundleHost) ? (BundleHost) bundle : null; + if (!SET_TCCL_XMLFACTORY || bundle == null) + return createService(); + /* + * Set the TCCL while creating jaxp factory instances to the + * requesting bundles class loader. This is needed to + * work around bug 285505. There are issues if multiple + * xerces implementations are available on the bundles class path + * + * The real issue is that the ContextFinder will only delegate + * to the framework class loader in this case. This class + * loader forces the requesting bundle to be delegated to for + * TCCL loads. + */ + final ClassLoader savedClassLoader = Thread.currentThread().getContextClassLoader(); + try { + ClassLoader cl = host.getClassLoader(); + if (cl != null) + Thread.currentThread().setContextClassLoader(cl); + return createService(); + } finally { + Thread.currentThread().setContextClassLoader(savedClassLoader); + } + } + + private Object createService() { + if (isSax) + return SAXParserFactory.newInstance(); + return DocumentBuilderFactory.newInstance(); + } + + public void ungetService(Bundle bundle, ServiceRegistration<Object> registration, Object service) { + // Do nothing. + } + } + + /** + * @throws BundleException + */ + public void frameworkStop(BundleContext context) throws BundleException { + printStats(); + if (!noXML) + PluginParser.releaseXMLParsing(); + // unregister services + for (ServiceRegistration<?> registration : registrations) + registration.unregister(); + registrations.clear(); + } + + private void printStats() { + FrameworkDebugOptions debugOptions = FrameworkDebugOptions.getDefault(); + if (debugOptions == null) + return; + String registryParsing = debugOptions.getOption("org.eclipse.core.runtime/registry/parsing/timing/value"); //$NON-NLS-1$ + if (registryParsing != null) + MessageHelper.debug("Time spent in registry parsing: " + registryParsing); //$NON-NLS-1$ + String packageAdminResolution = debugOptions.getOption("debug.packageadmin/timing/value"); //$NON-NLS-1$ + if (packageAdminResolution != null) + System.out.println("Time spent in package admin resolve: " + packageAdminResolution); //$NON-NLS-1$ + String constraintResolution = debugOptions.getOption("org.eclipse.core.runtime.adaptor/resolver/timing/value"); //$NON-NLS-1$ + if (constraintResolution != null) + System.out.println("Time spent resolving the dependency system: " + constraintResolution); //$NON-NLS-1$ + } + + public void frameworkStopping(BundleContext context) { + // do nothing + } + + public void addProperties(Properties properties) { + // do nothing + } + + /** + * @throws IOException + */ + public URLConnection mapLocationToURLConnection(String location) throws IOException { + // do nothing + return null; + } + + public void handleRuntimeError(Throwable error) { + // do nothing + } + + public FrameworkLog createFrameworkLog() { + // do nothing + return null; + } + + public void initialize(BaseAdaptor initAdaptor) { + this.adaptor = initAdaptor; + // EnvironmentInfo has to be initialized first to compute defaults for system context (see bug 88925) + EclipseEnvironmentInfo.getDefault(); + setDebugOptions(); + } + + private void setDebugOptions() { + FrameworkDebugOptions options = FrameworkDebugOptions.getDefault(); + // may be null if debugging is not enabled + if (options == null) + return; + PluginConverterImpl.DEBUG = options.getBooleanOption(OPTION_CONVERTER, false); + BasicLocation.DEBUG = options.getBooleanOption(OPTION_LOCATION, false); + CachedManifest.DEBUG = options.getBooleanOption(OPTION_CACHEDMANIFEST, false); + } + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addAdaptorHook(this); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java new file mode 100644 index 000000000..a4106fdb6 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * 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.core.runtime.internal.adaptor; + +import java.lang.reflect.Method; +import java.util.Map; +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.profile.Profile; +import org.eclipse.osgi.service.runnable.*; +import org.osgi.framework.*; + +public class EclipseAppLauncher implements ApplicationLauncher { + volatile private ParameterizedRunnable runnable = null; + private Object appContext = null; + private Semaphore runningLock = new Semaphore(1); + private Semaphore waitForAppLock = new Semaphore(0); + private BundleContext context; + private boolean relaunch = false; + private boolean failOnNoDefault = false; + private FrameworkLog log; + + public EclipseAppLauncher(BundleContext context, boolean relaunch, boolean failOnNoDefault, FrameworkLog log) { + this.context = context; + this.relaunch = relaunch; + this.failOnNoDefault = failOnNoDefault; + this.log = log; + findRunnableService(); + } + + /* + * Used for backwards compatibility with < 3.2 runtime + */ + private void findRunnableService() { + // look for a ParameterizedRunnable registered as a service by runtimes (3.0, 3.1) + String appClass = ParameterizedRunnable.class.getName(); + ServiceReference<?>[] runRefs = null; + try { + runRefs = context.getServiceReferences(ParameterizedRunnable.class.getName(), "(&(objectClass=" + appClass + ")(eclipse.application=*))"); //$NON-NLS-1$//$NON-NLS-2$ + } catch (InvalidSyntaxException e) { + // ignore this. It should never happen as we have tested the above format. + } + if (runRefs != null && runRefs.length > 0) { + // found the service use it as the application. + runnable = (ParameterizedRunnable) context.getService(runRefs[0]); + // we will never be able to relaunch with a pre 3.2 runtime + relaunch = false; + waitForAppLock.release(); + } + } + + /* + * Starts this application launcher on the current thread. This method + * should be called by the main thread to ensure that applications are + * launched in the main thread. + */ + public Object start(Object defaultContext) throws Exception { + // here we assume that launch has been called by runtime before we started + // TODO this may be a bad assumption but it works for now because we register the app launcher as a service and runtime synchronously calls launch on the service + if (failOnNoDefault && runnable == null) + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION); + Object result = null; + boolean doRelaunch; + do { + try { + result = runApplication(defaultContext); + } catch (Exception e) { + if (!relaunch || (context.getBundle().getState() & Bundle.ACTIVE) == 0) + throw e; + if (log != null) + log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null)); + } + doRelaunch = (relaunch && (context.getBundle().getState() & Bundle.ACTIVE) != 0) || FrameworkProperties.getProperty(Constants.PROP_OSGI_RELAUNCH) != null; + } while (doRelaunch); + return result; + } + + /* + * Waits for an application to be launched and the runs the application on the + * current thread (main). + */ + private Object runApplication(Object defaultContext) throws Exception { + // wait for an application to be launched. + waitForAppLock.acquire(); + // an application is ready; acquire the running lock. + // this must happen after we have acquired an application (by acquiring waitForAppLock above). + runningLock.acquire(); + if (EclipseStarter.debug) { + String timeString = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$ + long time = timeString == null ? 0L : Long.parseLong(timeString); + System.out.println("Starting application: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ + } + if (Profile.PROFILE && (Profile.STARTUP || Profile.BENCHMARK)) + Profile.logTime("EclipseStarter.run(Object)()", "framework initialized! starting application..."); //$NON-NLS-1$ //$NON-NLS-2$ + try { + // run the actual application on the current thread (main). + return runnable.run(appContext != null ? appContext : defaultContext); + } finally { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logExit("EclipseStarter.run(Object)()"); //$NON-NLS-1$ + // free the runnable application and release the lock to allow another app to be launched. + runnable = null; + appContext = null; + runningLock.release(); + } + } + + public void launch(ParameterizedRunnable app, Object applicationContext) { + waitForAppLock.acquire(-1); // clear out any pending apps notifications + if (!runningLock.acquire(-1)) // check to see if an application is currently running + throw new IllegalStateException("An application is aready running."); //$NON-NLS-1$ + this.runnable = app; + this.appContext = applicationContext; + waitForAppLock.release(); // notify the main thread to launch an application. + runningLock.release(); // release the running lock + } + + public void shutdown() { + // this method will aquire and keep the runningLock to prevent + // all future application launches. + if (runningLock.acquire(-1)) + return; // no application is currently running. + ParameterizedRunnable currentRunnable = runnable; + if (currentRunnable instanceof ApplicationRunnable) { + ((ApplicationRunnable) currentRunnable).stop(); + runningLock.acquire(60000); // timeout after 1 minute. + } + } + + /* + * Similar to the start method this method will restart the default method on current thread. + * This method assumes that the default application was launched at least once and that an ApplicationDescriptor + * exists that can be used to relaunch the default application. + */ + public Object reStart(Object argument) throws Exception { + ServiceReference<?> ref[] = null; + ref = context.getServiceReferences("org.osgi.service.application.ApplicationDescriptor", "(eclipse.application.default=true)"); //$NON-NLS-1$//$NON-NLS-2$ + if (ref != null && ref.length > 0) { + Object defaultApp = context.getService(ref[0]); + Method launch = defaultApp.getClass().getMethod("launch", new Class[] {Map.class}); //$NON-NLS-1$ + launch.invoke(defaultApp, new Object[] {null}); + return start(argument); + } + throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java new file mode 100644 index 000000000..c24307844 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * 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.core.runtime.internal.adaptor; + +import java.io.File; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import org.eclipse.osgi.baseadaptor.*; +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.loader.*; +import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain; +import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.internal.baseadaptor.BaseClassLoadingHook; +import org.eclipse.osgi.internal.baseadaptor.BaseStorageHook; + +public class EclipseClassLoadingHook implements ClassLoadingHook, HookConfigurator { + private static String[] NL_JAR_VARIANTS = buildNLJarVariants(EclipseEnvironmentInfo.getDefault().getNL()); + private static boolean DEFINE_PACKAGES; + private final static boolean DEFINE_PACKAGE_ATTRIBUTES = !"noattributes".equals(FrameworkProperties.getProperty("osgi.classloader.define.packages")); //$NON-NLS-1$ //$NON-NLS-2$ + private static String[] LIB_VARIANTS = buildLibraryVariants(); + private Object pkgLock = new Object(); + + static { + try { + Class.forName("java.lang.Package"); //$NON-NLS-1$ + DEFINE_PACKAGES = true; + } catch (ClassNotFoundException e) { + DEFINE_PACKAGES = false; + } + } + + private static String[] buildLibraryVariants() { + List<String> result = new ArrayList<String>(); + EclipseEnvironmentInfo info = EclipseEnvironmentInfo.getDefault(); + result.add("ws/" + info.getWS() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ + result.add("os/" + info.getOS() + "/" + info.getOSArch() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + result.add("os/" + info.getOS() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ + String nl = info.getNL(); + nl = nl.replace('_', '/'); + while (nl.length() > 0) { + result.add("nl/" + nl + "/"); //$NON-NLS-1$ //$NON-NLS-2$ + int i = nl.lastIndexOf('/'); + nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$ + } + result.add(""); //$NON-NLS-1$ + return result.toArray(new String[result.size()]); + } + + public byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { + if (!DEFINE_PACKAGES) + return null; + // Define the package if it is not the default package. + int lastIndex = name.lastIndexOf('.'); + if (lastIndex < 0) + return null; + String packageName = name.substring(0, lastIndex); + Object pkg; + synchronized (pkgLock) { + pkg = manager.getBaseClassLoader().publicGetPackage(packageName); + if (pkg != null) + return null; + } + + // get info about the package from the classpath entry's manifest. + String specTitle = null, specVersion = null, specVendor = null, implTitle = null, implVersion = null, implVendor = null; + + if (DEFINE_PACKAGE_ATTRIBUTES) { + ClasspathManifest cpm = (ClasspathManifest) classpathEntry.getUserObject(ClasspathManifest.KEY); + if (cpm == null) { + cpm = new ClasspathManifest(); + classpathEntry.addUserObject(cpm); + } + Manifest mf = cpm.getManifest(classpathEntry, manager); + if (mf != null) { + Attributes mainAttributes = mf.getMainAttributes(); + String dirName = packageName.replace('.', '/') + '/'; + Attributes packageAttributes = mf.getAttributes(dirName); + boolean noEntry = false; + if (packageAttributes == null) { + noEntry = true; + packageAttributes = mainAttributes; + } + specTitle = packageAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE); + if (specTitle == null && !noEntry) + specTitle = mainAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE); + specVersion = packageAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION); + if (specVersion == null && !noEntry) + specVersion = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION); + specVendor = packageAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR); + if (specVendor == null && !noEntry) + specVendor = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR); + implTitle = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE); + if (implTitle == null && !noEntry) + implTitle = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE); + implVersion = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + if (implVersion == null && !noEntry) + implVersion = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + implVendor = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); + if (implVendor == null && !noEntry) + implVendor = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); + } + } + + // The package is not defined yet define it before we define the class. + // TODO still need to seal packages. + synchronized (pkgLock) { + pkg = manager.getBaseClassLoader().publicGetPackage(packageName); + if (pkg != null) + return null; + manager.getBaseClassLoader().publicDefinePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, null); + } + // not doing any byte processing + return null; + } + + public boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain) { + String var = hasPrefix(cp); + if (var != null) + // find internal library using eclipse predefined vars + return addInternalClassPath(var, cpEntries, cp, hostmanager, sourcedata, sourcedomain); + if (cp.startsWith(BaseStorageHook.EXTERNAL_LIB_PREFIX)) { + cp = cp.substring(BaseStorageHook.EXTERNAL_LIB_PREFIX.length()); + // find external library using system property substitution + ClasspathEntry cpEntry = hostmanager.getExternalClassPath(BaseStorageHook.substituteVars(cp), sourcedata, sourcedomain); + if (cpEntry != null) { + cpEntries.add(cpEntry); + return true; + } + } + return false; + } + + private boolean addInternalClassPath(String var, ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostloader, BaseData sourcedata, ProtectionDomain sourcedomain) { + if (var.equals("ws")) //$NON-NLS-1$ + return ClasspathManager.addClassPathEntry(cpEntries, "ws/" + EclipseEnvironmentInfo.getDefault().getWS() + cp.substring(4), hostloader, sourcedata, sourcedomain); //$NON-NLS-1$ + if (var.equals("os")) //$NON-NLS-1$ + return ClasspathManager.addClassPathEntry(cpEntries, "os/" + EclipseEnvironmentInfo.getDefault().getOS() + cp.substring(4), hostloader, sourcedata, sourcedomain); //$NON-NLS-1$ + if (var.equals("nl")) { //$NON-NLS-1$ + cp = cp.substring(4); + for (int i = 0; i < NL_JAR_VARIANTS.length; i++) + if (ClasspathManager.addClassPathEntry(cpEntries, "nl/" + NL_JAR_VARIANTS[i] + cp, hostloader, sourcedata, sourcedomain)) //$NON-NLS-1$ + return true; + } + return false; + } + + //return a String representing the string found between the $s + private static String hasPrefix(String libPath) { + if (libPath.startsWith("$ws$")) //$NON-NLS-1$ + return "ws"; //$NON-NLS-1$ + if (libPath.startsWith("$os$")) //$NON-NLS-1$ + return "os"; //$NON-NLS-1$ + if (libPath.startsWith("$nl$")) //$NON-NLS-1$ + return "nl"; //$NON-NLS-1$ + return null; + } + + private static String[] buildNLJarVariants(String nl) { + List<String> result = new ArrayList<String>(); + nl = nl.replace('_', '/'); + while (nl.length() > 0) { + result.add("nl/" + nl + "/"); //$NON-NLS-1$ //$NON-NLS-2$ + int i = nl.lastIndexOf('/'); + nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$ + } + result.add(""); //$NON-NLS-1$ + return result.toArray(new String[result.size()]); + } + + public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { + // do nothing + } + + public String findLibrary(BaseData data, String libName) { + if (libName.length() == 0) + return null; + if (libName.charAt(0) == '/' || libName.charAt(0) == '\\') + libName = libName.substring(1); + String mappedLibName = System.mapLibraryName(libName); + String result = searchVariants(data, mappedLibName); + if (result != null) + return result; + String[] mappedLibNames = BaseClassLoadingHook.mapLibraryNames(mappedLibName); + for (int i = 0; i < mappedLibNames.length && result == null; i++) + result = searchVariants(data, mappedLibNames[i]); + return result; + } + + private String searchVariants(BaseData bundledata, String path) { + for (int i = 0; i < LIB_VARIANTS.length; i++) { + BundleFile baseBundleFile = bundledata.getBundleFile(); + BundleEntry libEntry = baseBundleFile.getEntry(LIB_VARIANTS[i] + path); + if (libEntry != null) { + File libFile = baseBundleFile.getFile(LIB_VARIANTS[i] + path, true); + if (libFile == null) + return null; + // see bug 88697 - HP requires libraries to have executable permissions + if (org.eclipse.osgi.service.environment.Constants.OS_HPUX.equals(EclipseEnvironmentInfo.getDefault().getOS())) { + try { + // use the string array method in case there is a space in the path + Runtime.getRuntime().exec(new String[] {"chmod", "755", libFile.getAbsolutePath()}).waitFor(); //$NON-NLS-1$ //$NON-NLS-2$ + } catch (Exception e) { + e.printStackTrace(); + } + } + return libFile.getAbsolutePath(); + } + } + return null; + } + + public ClassLoader getBundleClassLoaderParent() { + return null; // do nothing + } + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addClassLoadingHook(this); + } + + public BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath) { + // do nothing + return null; + } + + public void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data) { + // do nothing + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java new file mode 100644 index 000000000..0d091d7f4 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * Copyright (c) 2003, 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.core.runtime.internal.adaptor; + +import java.util.*; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.service.environment.Constants; +import org.eclipse.osgi.service.environment.EnvironmentInfo; +import org.eclipse.osgi.util.NLS; + +/** + * Internal class. + */ +public class EclipseEnvironmentInfo implements EnvironmentInfo { + private static EclipseEnvironmentInfo singleton; + private static String nl; + private static String os; + private static String ws; + private static String arch; + private volatile static String[] allArgs; + private volatile static String[] frameworkArgs; + private volatile static String[] appArgs; + + // While we recognize the SunOS operating system, we change + // this internally to be Solaris. + private static final String INTERNAL_OS_SUNOS = "SunOS"; //$NON-NLS-1$ + private static final String INTERNAL_OS_LINUX = "Linux"; //$NON-NLS-1$ + private static final String INTERNAL_OS_MACOSX = "Mac OS"; //$NON-NLS-1$ + private static final String INTERNAL_OS_AIX = "AIX"; //$NON-NLS-1$ + private static final String INTERNAL_OS_HPUX = "HP-UX"; //$NON-NLS-1$ + private static final String INTERNAL_OS_QNX = "QNX"; //$NON-NLS-1$ + private static final String INTERNAL_OS_OS400 = "OS/400"; //$NON-NLS-1$ + private static final String INTERNAL_OS_OS390 = "OS/390"; //$NON-NLS-1$ + private static final String INTERNAL_OS_ZOS = "z/OS"; //$NON-NLS-1$ + + // While we recognize the i386 architecture, we change + // this internally to be x86. + private static final String INTERNAL_ARCH_I386 = "i386"; //$NON-NLS-1$ + // While we recognize the amd64 architecture, we change + // this internally to be x86_64. + private static final String INTERNAL_AMD64 = "amd64"; //$NON-NLS-1$ + + private EclipseEnvironmentInfo() { + super(); + setupSystemContext(); + } + + public static EclipseEnvironmentInfo getDefault() { + if (singleton == null) + singleton = new EclipseEnvironmentInfo(); + return singleton; + } + + public boolean inDevelopmentMode() { + return FrameworkProperties.getProperty("osgi.dev") != null; //$NON-NLS-1$ + } + + public boolean inDebugMode() { + return FrameworkProperties.getProperty("osgi.debug") != null; //$NON-NLS-1$ + } + + public String[] getCommandLineArgs() { + return allArgs; + } + + public String[] getFrameworkArgs() { + return frameworkArgs; + } + + public String[] getNonFrameworkArgs() { + return appArgs; + } + + public String getOSArch() { + return arch; + } + + public String getNL() { + return nl; + } + + public String getOS() { + return os; + } + + public String getWS() { + return ws; + } + + /** + * Initializes the execution context for this run of the platform. The context + * includes information about the locale, operating system and window system. + * + * NOTE: The OS, WS, and ARCH values should never be null. The executable should + * be setting these values and therefore this code path is obsolete for Eclipse + * when run from the executable. + */ + private static void setupSystemContext() { + // if the user didn't set the locale with a command line argument then use the default. + nl = FrameworkProperties.getProperty("osgi.nl"); //$NON-NLS-1$ + if (nl != null) { + StringTokenizer tokenizer = new StringTokenizer(nl, "_"); //$NON-NLS-1$ + int segments = tokenizer.countTokens(); + try { + Locale userLocale = null; + switch (segments) { + case 1 : + // use the 2 arg constructor to maintain compatibility with 1.3.1 + userLocale = new Locale(tokenizer.nextToken(), ""); //$NON-NLS-1$ + break; + case 2 : + userLocale = new Locale(tokenizer.nextToken(), tokenizer.nextToken()); + break; + case 3 : + userLocale = new Locale(tokenizer.nextToken(), tokenizer.nextToken(), tokenizer.nextToken()); + break; + default : + // if the user passed us in a bogus value then log a message and use the default + System.err.println(NLS.bind(EclipseAdaptorMsg.error_badNL, nl)); + userLocale = Locale.getDefault(); + break; + } + Locale.setDefault(userLocale); + // TODO what the heck is this for?? why not just use osgi.nl + FrameworkProperties.setProperty("osgi.nl.user", nl); //$NON-NLS-1$ + } catch (NoSuchElementException e) { + // fall through and use the default + } + } + nl = Locale.getDefault().toString(); + FrameworkProperties.setProperty("osgi.nl", nl); //$NON-NLS-1$ + + // if the user didn't set the operating system with a command line + // argument then use the default. + os = FrameworkProperties.getProperty("osgi.os"); //$NON-NLS-1$ + if (os == null) { + os = guessOS(FrameworkProperties.getProperty("os.name"));//$NON-NLS-1$); + FrameworkProperties.setProperty("osgi.os", os); //$NON-NLS-1$ + } + + // if the user didn't set the window system with a command line + // argument then use the default. + ws = FrameworkProperties.getProperty("osgi.ws"); //$NON-NLS-1$ + if (ws == null) { + ws = guessWS(os); + FrameworkProperties.setProperty("osgi.ws", ws); //$NON-NLS-1$ + } + + // if the user didn't set the system architecture with a command line + // argument then use the default. + arch = FrameworkProperties.getProperty("osgi.arch"); //$NON-NLS-1$ + if (arch == null) { + String name = FrameworkProperties.getProperty("os.arch");//$NON-NLS-1$ + // Map i386 architecture to x86 + if (name.equalsIgnoreCase(INTERNAL_ARCH_I386)) + arch = Constants.ARCH_X86; + // Map amd64 architecture to x86_64 + else if (name.equalsIgnoreCase(INTERNAL_AMD64)) + arch = Constants.ARCH_X86_64; + else + arch = name; + FrameworkProperties.setProperty("osgi.arch", arch); //$NON-NLS-1$ + } + } + + public static void setAllArgs(String[] allArgs) { + // do not check if this is set already to allow arguments to change when multiple applications are launched + EclipseEnvironmentInfo.allArgs = allArgs; + } + + public static void setAppArgs(String[] appArgs) { + // do not check if this is set already to allow arguments to change when multiple applications are launched + EclipseEnvironmentInfo.appArgs = appArgs; + } + + public static void setFrameworkArgs(String[] frameworkArgs) { + // do not check if this is set already to allow arguments to change when multiple applications are launched + EclipseEnvironmentInfo.frameworkArgs = frameworkArgs; + } + + public static String guessWS(String osName) { + // setup default values for known OSes if nothing was specified + if (osName.equals(Constants.OS_WIN32)) + return Constants.WS_WIN32; + if (osName.equals(Constants.OS_LINUX)) + return Constants.WS_GTK; + if (osName.equals(Constants.OS_MACOSX)) + return Constants.WS_COCOA; + if (osName.equals(Constants.OS_HPUX)) + return Constants.WS_MOTIF; + if (osName.equals(Constants.OS_AIX)) + return Constants.WS_MOTIF; + if (osName.equals(Constants.OS_SOLARIS)) + return Constants.WS_GTK; + if (osName.equals(Constants.OS_QNX)) + return Constants.WS_PHOTON; + return Constants.WS_UNKNOWN; + } + + public static String guessOS(String osName) { + // check to see if the OS name is "Windows 98" or some other + // flavour which should be converted to win32. + if (osName.regionMatches(true, 0, Constants.OS_WIN32, 0, 3)) + return Constants.OS_WIN32; + // EXCEPTION: All mappings of SunOS convert to Solaris + if (osName.equalsIgnoreCase(INTERNAL_OS_SUNOS)) + return Constants.OS_SOLARIS; + if (osName.equalsIgnoreCase(INTERNAL_OS_LINUX)) + return Constants.OS_LINUX; + if (osName.equalsIgnoreCase(INTERNAL_OS_QNX)) + return Constants.OS_QNX; + if (osName.equalsIgnoreCase(INTERNAL_OS_AIX)) + return Constants.OS_AIX; + if (osName.equalsIgnoreCase(INTERNAL_OS_HPUX)) + return Constants.OS_HPUX; + if (osName.equalsIgnoreCase(INTERNAL_OS_OS400)) + return Constants.OS_OS400; + if (osName.equalsIgnoreCase(INTERNAL_OS_OS390)) + return Constants.OS_OS390; + if (osName.equalsIgnoreCase(INTERNAL_OS_ZOS)) + return Constants.OS_ZOS; + // os.name on Mac OS can be either Mac OS or Mac OS X + if (osName.regionMatches(true, 0, INTERNAL_OS_MACOSX, 0, INTERNAL_OS_MACOSX.length())) + return Constants.OS_MACOSX; + return Constants.OS_UNKNOWN; + } + + public String getProperty(String key) { + return FrameworkProperties.getProperty(key); + } + + public String setProperty(String key, String value) { + return FrameworkProperties.setProperty(key, value); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java new file mode 100644 index 000000000..afad88113 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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.core.runtime.internal.adaptor; + +import java.io.IOException; +import java.net.URLConnection; +import java.util.Properties; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; + +public class EclipseErrorHandler implements AdaptorHook, HookConfigurator { + // System property used to prevent VM exit when unexpected errors occur + private static final String PROP_EXITONERROR = "eclipse.exitOnError"; //$NON-NLS-1$ + private BaseAdaptor adaptor; + + /** + * @throws BundleException + */ + public void frameworkStart(BundleContext context) throws BundleException { + // do nothing + } + + /** + * @throws BundleException + */ + public void frameworkStop(BundleContext context) throws BundleException { + // do nothing + } + + public void frameworkStopping(BundleContext context) { + // do nothing + } + + public void addProperties(Properties properties) { + // do nothing + } + + /** + * @throws IOException + */ + public URLConnection mapLocationToURLConnection(String location) throws IOException { + // do nothing + return null; + } + + private boolean isFatalException(Throwable error) { + if (error instanceof VirtualMachineError) { + return true; + } + if (error instanceof ThreadDeath) { + return true; + } + return false; + } + + public void handleRuntimeError(Throwable error) { + // this is the important method to handle errors + boolean exitOnError = false; + try { + // check the prop each time this happens (should NEVER happen!) + exitOnError = Boolean.valueOf(FrameworkProperties.getProperty(EclipseErrorHandler.PROP_EXITONERROR, "true")).booleanValue(); //$NON-NLS-1$ + String message = EclipseAdaptorMsg.ECLIPSE_ADAPTOR_RUNTIME_ERROR; + if (exitOnError && isFatalException(error)) + message += ' ' + EclipseAdaptorMsg.ECLIPSE_ADAPTOR_EXITING; + FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, error, null); + adaptor.getFrameworkLog().log(logEntry); + } catch (Throwable t) { + // we may be in a currupted state and must be able to handle any + // errors (ie OutOfMemoryError) + // that may occur when handling the first error; this is REALLY the + // last resort. + try { + error.printStackTrace(); + t.printStackTrace(); + } catch (Throwable t1) { + // if we fail that then we are beyond help. + } + } finally { + // do the exit outside the try block just incase another runtime + // error was thrown while logging + if (exitOnError && isFatalException(error)) + System.exit(13); + } + } + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addAdaptorHook(this); + } + + public FrameworkLog createFrameworkLog() { + // do nothing + return null; + } + + public void initialize(BaseAdaptor initAdaptor) { + this.adaptor = initAdaptor; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java new file mode 100644 index 000000000..e1c34d4df --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java @@ -0,0 +1,281 @@ +/******************************************************************************* + * Copyright (c) 2006, 2012 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.core.runtime.internal.adaptor; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.util.*; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook; +import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry; +import org.eclipse.osgi.baseadaptor.loader.ClasspathManager; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.adaptor.StatusException; +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.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.StateHelper; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +public class EclipseLazyStarter implements ClassLoadingStatsHook, AdaptorHook, HookConfigurator { + private static final boolean throwErrorOnFailedStart = "true".equals(FrameworkProperties.getProperty("osgi.compatibility.errorOnFailedStart", "true")); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + private BaseAdaptor adaptor; + // holds the current activation trigger class and the ClasspathManagers that need to be activated + private ThreadLocal<List<Object>> activationStack = new ThreadLocal<List<Object>>(); + // used to store exceptions that occurred while activating a bundle + // keyed by ClasspathManager->Exception + // WeakHashMap is used to prevent pinning the ClasspathManager objects. + private final Map<ClasspathManager, TerminatingClassNotFoundException> errors = Collections.synchronizedMap(new WeakHashMap<ClasspathManager, TerminatingClassNotFoundException>()); + + public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException { + AbstractBundle bundle = (AbstractBundle) manager.getBaseData().getBundle(); + // If the bundle is active, uninstalled or stopping then the bundle has already + // been initialized (though it may have been destroyed) so just return the class. + if ((bundle.getState() & (Bundle.ACTIVE | Bundle.UNINSTALLED | Bundle.STOPPING)) != 0) + return; + EclipseStorageHook storageHook = (EclipseStorageHook) manager.getBaseData().getStorageHook(EclipseStorageHook.KEY); + // The bundle is not active and does not require activation, just return the class + if (!shouldActivateFor(name, manager.getBaseData(), storageHook, manager)) + return; + List<Object> stack = activationStack.get(); + if (stack == null) { + stack = new ArrayList<Object>(6); + activationStack.set(stack); + } + // the first element in the stack is the name of the trigger class, + // each element after the trigger class is a classpath manager + // that must be activated after the trigger class has been defined (see postFindLocalClass) + int size = stack.size(); + if (size > 1) { + for (int i = size - 1; i >= 1; i--) + if (manager == stack.get(i)) + // the manager is already on the stack in which case we are already in the process of loading the trigger class + return; + } + Thread threadChangingState = bundle.getStateChanging(); + if (bundle.getState() == Bundle.STARTING && threadChangingState == Thread.currentThread()) + return; // this thread is starting the bundle already + if (size == 0) + stack.add(name); + stack.add(manager); + } + + public void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) throws ClassNotFoundException { + List<Object> stack = activationStack.get(); + if (stack == null) + return; + int size = stack.size(); + if (size <= 1 || stack.get(0) != name) + return; + // if we have a stack we must clear it even if (clazz == null) + ClasspathManager[] managers = null; + managers = new ClasspathManager[size - 1]; + for (int i = 1; i < size; i++) + managers[i - 1] = (ClasspathManager) stack.get(i); + stack.clear(); + if (clazz == null) + return; + for (int i = managers.length - 1; i >= 0; i--) { + if (errors.get(managers[i]) != null) { + if (throwErrorOnFailedStart) + throw errors.get(managers[i]); + continue; + } + + // The bundle must be started. + // Note that another thread may already be starting this bundle; + // In this case we will timeout after a default of 5 seconds and record the BundleException + long startTime = System.currentTimeMillis(); + try { + // do not persist the start of this bundle + managers[i].getBaseClassLoader().getDelegate().setLazyTrigger(); + } catch (BundleException e) { + Bundle bundle = managers[i].getBaseData().getBundle(); + Throwable cause = e.getCause(); + if (cause != null && cause instanceof StatusException) { + StatusException status = (StatusException) cause; + if ((status.getStatusCode() & StatusException.CODE_ERROR) == 0) { + if (status.getStatus() instanceof Thread) { + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CONCURRENT_STARTUP, new Object[] {Thread.currentThread(), name, status.getStatus(), bundle, new Long(System.currentTimeMillis() - startTime)}); + adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, e, null)); + } + continue; + } + } + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_ACTIVATION, bundle.getSymbolicName(), Long.toString(bundle.getBundleId())); + TerminatingClassNotFoundException error = new TerminatingClassNotFoundException(message, e); + errors.put(managers[i], error); + if (throwErrorOnFailedStart) { + adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null)); + throw error; + } + adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(message, e)); + } + } + } + + public void preFindLocalResource(String name, ClasspathManager manager) { + // do nothing + } + + public void postFindLocalResource(String name, URL resource, ClasspathManager manager) { + // do nothing + } + + public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { + // do nothing + } + + private boolean shouldActivateFor(String className, BaseData bundledata, EclipseStorageHook storageHook, ClasspathManager manager) throws ClassNotFoundException { + if (!isLazyStartable(className, bundledata, storageHook)) + return false; + //if (manager.getBaseClassLoader().getDelegate().isLazyTriggerSet()) + // return false; + // Don't activate non-starting bundles + if (bundledata.getBundle().getState() == Bundle.RESOLVED) { + if (throwErrorOnFailedStart) { + TerminatingClassNotFoundException error = errors.get(manager); + if (error != null) + throw error; + } + return (bundledata.getStatus() & Constants.BUNDLE_STARTED) != 0; + } + return true; + } + + private boolean isLazyStartable(String className, BaseData bundledata, EclipseStorageHook storageHook) { + if (storageHook == null) + return false; + boolean lazyStart = storageHook.isLazyStart(); + String[] excludes = storageHook.getLazyStartExcludes(); + String[] includes = storageHook.getLazyStartIncludes(); + // no exceptions, it is easy to figure it out + if (excludes == null && includes == null) + return lazyStart; + // otherwise, we need to check if the package is in the exceptions list + int dotPosition = className.lastIndexOf('.'); + // the class has no package name... no exceptions apply + if (dotPosition == -1) + return lazyStart; + String packageName = className.substring(0, dotPosition); + if (lazyStart) + return ((includes == null || contains(includes, packageName)) && (excludes == null || !contains(excludes, packageName))); + return (excludes != null && contains(excludes, packageName)); + } + + private boolean contains(String[] array, String element) { + for (int i = 0; i < array.length; i++) + if (array[i].equals(element)) + return true; + return false; + } + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addClassLoadingStatsHook(this); + hookRegistry.addAdaptorHook(this); + } + + public void addProperties(Properties properties) { + // do nothing + } + + public FrameworkLog createFrameworkLog() { + // do nothing + return null; + } + + /** + * @throws BundleException + */ + public void frameworkStart(BundleContext context) throws BundleException { + // nothing + } + + /** + * @throws BundleException + */ + public void frameworkStop(BundleContext context) throws BundleException { + // nothing + } + + public void frameworkStopping(BundleContext context) { + if (!Debug.DEBUG_ENABLED) + return; + + BundleDescription[] allBundles = adaptor.getState().getResolvedBundles(); + StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper(); + Object[][] cycles = stateHelper.sortBundles(allBundles); + logCycles(cycles); + } + + public void handleRuntimeError(Throwable error) { + // do nothing + + } + + public void initialize(BaseAdaptor baseAdaptor) { + this.adaptor = baseAdaptor; + } + + /** + * @throws IOException + */ + public URLConnection mapLocationToURLConnection(String location) throws IOException { + // do nothing + return null; + } + + private void logCycles(Object[][] cycles) { + // log cycles + if (cycles.length > 0) { + StringBuffer cycleText = new StringBuffer("["); //$NON-NLS-1$ + for (int i = 0; i < cycles.length; i++) { + cycleText.append('['); + for (int j = 0; j < cycles[i].length; j++) { + cycleText.append(((BundleDescription) cycles[i][j]).getSymbolicName()); + cycleText.append(','); + } + cycleText.insert(cycleText.length() - 1, ']'); + } + cycleText.setCharAt(cycleText.length() - 1, ']'); + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_BUNDLESTOPPER_CYCLES_FOUND, cycleText); + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, null, null); + adaptor.getFrameworkLog().log(entry); + } + } + + private static class TerminatingClassNotFoundException extends ClassNotFoundException implements StatusException { + private static final long serialVersionUID = -6730732895632169173L; + private Throwable cause; + + public TerminatingClassNotFoundException(String message, Throwable cause) { + super(message, cause); + this.cause = cause; + } + + public Object getStatus() { + return cause; + } + + public int getStatusCode() { + return StatusException.CODE_ERROR; + } + + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java new file mode 100644 index 000000000..416bbdada --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.core.runtime.internal.adaptor; + +import java.io.*; +import org.eclipse.equinox.log.Logger; +import org.eclipse.equinox.log.internal.LogServiceManager; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.osgi.framework.*; +import org.osgi.service.log.LogService; + +public class EclipseLogFactory implements ServiceFactory<FrameworkLog> { + final EclipseLogWriter defaultWriter; + final LogServiceManager logManager; + + public EclipseLogFactory(EclipseLogWriter defaultWriter, LogServiceManager logManager) { + this.defaultWriter = defaultWriter; + this.logManager = logManager; + } + + public FrameworkLog getService(final Bundle bundle, ServiceRegistration<FrameworkLog> registration) { + return createFrameworkLog(bundle, defaultWriter); + } + + FrameworkLog createFrameworkLog(Bundle bundle, EclipseLogWriter eclipseWriter) { + final EclipseLogWriter logWriter = eclipseWriter == null ? defaultWriter : eclipseWriter; + final Logger logger = bundle == null ? logManager.getSystemBundleLog().getLogger(eclipseWriter.getLoggerName()) : logManager.getSystemBundleLog().getLogger(bundle, logWriter.getLoggerName()); + return new FrameworkLog() { + + public void setWriter(Writer newWriter, boolean append) { + logWriter.setWriter(newWriter, append); + } + + public void setFile(File newFile, boolean append) throws IOException { + logWriter.setFile(newFile, append); + } + + public void setConsoleLog(boolean consoleLog) { + logWriter.setConsoleLog(consoleLog); + } + + public void log(FrameworkLogEntry logEntry) { + logger.log(logEntry, convertLevel(logEntry), logEntry.getMessage(), logEntry.getThrowable()); + } + + public void log(FrameworkEvent frameworkEvent) { + Bundle b = frameworkEvent.getBundle(); + Throwable t = frameworkEvent.getThrowable(); + String entry = b.getSymbolicName() == null ? b.getLocation() : b.getSymbolicName(); + int severity; + switch (frameworkEvent.getType()) { + case FrameworkEvent.INFO : + severity = FrameworkLogEntry.INFO; + break; + case FrameworkEvent.ERROR : + severity = FrameworkLogEntry.ERROR; + break; + case FrameworkEvent.WARNING : + severity = FrameworkLogEntry.WARNING; + break; + default : + severity = FrameworkLogEntry.OK; + } + FrameworkLogEntry logEntry = new FrameworkLogEntry(entry, severity, 0, "", 0, t, null); //$NON-NLS-1$ + log(logEntry); + } + + public File getFile() { + return logWriter.getFile(); + } + + public void close() { + logWriter.close(); + } + }; + } + + public void ungetService(Bundle bundle, ServiceRegistration<FrameworkLog> registration, FrameworkLog service) { + // nothing + } + + static int convertLevel(FrameworkLogEntry logEntry) { + switch (logEntry.getSeverity()) { + case FrameworkLogEntry.ERROR : + return LogService.LOG_ERROR; + case FrameworkLogEntry.WARNING : + return LogService.LOG_WARNING; + case FrameworkLogEntry.INFO : + return LogService.LOG_INFO; + case FrameworkLogEntry.OK : + return LogService.LOG_DEBUG; + case FrameworkLogEntry.CANCEL : + default : + return 32; // unknown + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java new file mode 100644 index 000000000..f4555e6b5 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.core.runtime.internal.adaptor; + +import java.io.*; +import java.net.URLConnection; +import java.util.*; +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.core.runtime.adaptor.LocationManager; +import org.eclipse.equinox.log.internal.LogServiceManager; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil; +import org.eclipse.osgi.service.datalocation.Location; +import org.osgi.framework.*; + +public class EclipseLogHook implements HookConfigurator, AdaptorHook { + static final String EQUINOX_LOGGER_NAME = "org.eclipse.equinox.logger"; //$NON-NLS-1$ + static final String PERF_LOGGER_NAME = "org.eclipse.performance.logger"; //$NON-NLS-1$ + private static final String PROP_LOG_ENABLED = "eclipse.log.enabled"; //$NON-NLS-1$ + + // The eclipse log file extension */ + private static final String LOG_EXT = ".log"; //$NON-NLS-1$ + private final LogServiceManager logServiceManager; + private final EclipseLogFactory eclipseLogFactory; + private final EclipseLogWriter logWriter; + private final EclipseLogWriter perfWriter; + + public EclipseLogHook() { + String logFileProp = FrameworkProperties.getProperty(EclipseStarter.PROP_LOGFILE); + boolean enabled = "true".equals(FrameworkProperties.getProperty(PROP_LOG_ENABLED, "true")); //$NON-NLS-1$ //$NON-NLS-2$ + if (logFileProp != null) { + logWriter = new EclipseLogWriter(new File(logFileProp), EQUINOX_LOGGER_NAME, enabled); + } else { + Location location = LocationManager.getConfigurationLocation(); + File configAreaDirectory = null; + if (location != null) + // TODO assumes the URL is a file: url + configAreaDirectory = new File(location.getURL().getFile()); + + if (configAreaDirectory != null) { + String logFileName = Long.toString(System.currentTimeMillis()) + EclipseLogHook.LOG_EXT; + File logFile = new File(configAreaDirectory, logFileName); + FrameworkProperties.setProperty(EclipseStarter.PROP_LOGFILE, logFile.getAbsolutePath()); + logWriter = new EclipseLogWriter(logFile, EQUINOX_LOGGER_NAME, enabled); + } else + logWriter = new EclipseLogWriter((Writer) null, EQUINOX_LOGGER_NAME, enabled); + } + + File logFile = logWriter.getFile(); + if (logFile != null) { + File perfLogFile = new File(logFile.getParentFile(), "performance.log"); //$NON-NLS-1$ + perfWriter = new EclipseLogWriter(perfLogFile, PERF_LOGGER_NAME, true); + } else { + perfWriter = new EclipseLogWriter((Writer) null, PERF_LOGGER_NAME, true); + } + if ("true".equals(FrameworkProperties.getProperty(EclipseStarter.PROP_CONSOLE_LOG))) //$NON-NLS-1$ + logWriter.setConsoleLog(true); + logServiceManager = new LogServiceManager(logWriter, perfWriter); + eclipseLogFactory = new EclipseLogFactory(logWriter, logServiceManager); + + } + + private ServiceRegistration<?> frameworkLogReg; + private ServiceRegistration<?> perfLogReg; + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addAdaptorHook(this); + } + + public void initialize(BaseAdaptor initAdaptor) { + // Nothing + } + + /** + * @throws BundleException + */ + public void frameworkStart(BundleContext context) throws BundleException { + logServiceManager.start(context); + frameworkLogReg = AdaptorUtil.register(FrameworkLog.class.getName(), eclipseLogFactory, context); + perfLogReg = registerPerformanceLog(context); + } + + /** + * @throws BundleException + */ + public void frameworkStop(BundleContext context) throws BundleException { + frameworkLogReg.unregister(); + perfLogReg.unregister(); + logServiceManager.stop(context); + } + + public void frameworkStopping(BundleContext context) { + // do nothing + + } + + public void addProperties(Properties properties) { + // do nothing + } + + /** + * @throws IOException + */ + public URLConnection mapLocationToURLConnection(String location) throws IOException { + // do nothing + return null; + } + + public void handleRuntimeError(Throwable error) { + // do nothing + } + + public FrameworkLog createFrameworkLog() { + return eclipseLogFactory.createFrameworkLog(null, logWriter); + } + + private ServiceRegistration<?> registerPerformanceLog(BundleContext context) { + Object service = createPerformanceLog(context.getBundle()); + String serviceName = FrameworkLog.class.getName(); + Dictionary<String, Object> serviceProperties = new Hashtable<String, Object>(7); + Dictionary<String, String> headers = context.getBundle().getHeaders(); + + serviceProperties.put(Constants.SERVICE_VENDOR, headers.get(Constants.BUNDLE_VENDOR)); + serviceProperties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); + serviceProperties.put(Constants.SERVICE_PID, context.getBundle().getBundleId() + '.' + service.getClass().getName()); + serviceProperties.put(FrameworkLog.SERVICE_PERFORMANCE, Boolean.TRUE.toString()); + + return context.registerService(serviceName, service, serviceProperties); + } + + private FrameworkLog createPerformanceLog(Bundle systemBundle) { + return eclipseLogFactory.createFrameworkLog(systemBundle, perfWriter); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java new file mode 100644 index 000000000..36eab8b08 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java @@ -0,0 +1,726 @@ +/******************************************************************************* + * Copyright (c) 2004, 2012 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.core.runtime.internal.adaptor; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessController; +import java.util.Calendar; +import java.util.Date; +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.equinox.log.*; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.framework.util.SecureAction; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogService; + +public class EclipseLogWriter implements SynchronousLogListener, LogFilter { + private static final String PASSWORD = "-password"; //$NON-NLS-1$ + /** The session tag */ + private static final String SESSION = "!SESSION"; //$NON-NLS-1$ + /** The entry tag */ + private static final String ENTRY = "!ENTRY"; //$NON-NLS-1$ + /** The sub-entry tag */ + private static final String SUBENTRY = "!SUBENTRY"; //$NON-NLS-1$ + /** The message tag */ + private static final String MESSAGE = "!MESSAGE"; //$NON-NLS-1$ + /** The stacktrace tag */ + private static final String STACK = "!STACK"; //$NON-NLS-1$ + + /** The line separator used in the log output */ + private static final String LINE_SEPARATOR; + static { + String s = System.getProperty("line.separator"); //$NON-NLS-1$ + LINE_SEPARATOR = s == null ? "\n" : s; //$NON-NLS-1$ + } + //Constants for rotating log file + /** The default size a log file can grow before it is rotated */ + private static final int DEFAULT_LOG_SIZE = 1000; + /** The default number of backup log files */ + private static final int DEFAULT_LOG_FILES = 10; + /** The minimum size limit for log rotation */ + private static final int LOG_SIZE_MIN = 10; + + /** The system property used to specify the log level */ + private static final String PROP_LOG_LEVEL = "eclipse.log.level"; //$NON-NLS-1$ + /** The system property used to specify size a log file can grow before it is rotated */ + private static final String PROP_LOG_SIZE_MAX = "eclipse.log.size.max"; //$NON-NLS-1$ + /** The system property used to specify the maximim number of backup log files to use */ + private static final String PROP_LOG_FILE_MAX = "eclipse.log.backup.max"; //$NON-NLS-1$ + /** The extension used for log files */ + private static final String LOG_EXT = ".log"; //$NON-NLS-1$ + /** The extension markup to use for backup log files*/ + private static final String BACKUP_MARK = ".bak_"; //$NON-NLS-1$ + + /** The system property used to specify command line args should be omitted from the log */ + private static final String PROP_LOG_INCLUDE_COMMAND_LINE = "eclipse.log.include.commandline"; //$NON-NLS-1$ + private static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); + + /** Indicates if the console messages should be printed to the console (System.out) */ + private boolean consoleLog = false; + /** Indicates if the next log message is part of a new session */ + private boolean newSession = true; + /** + * The File object to store messages. This value may be null. + */ + private File outFile; + + /** + * The Writer to log messages to. + */ + private Writer writer; + + private final String loggerName; + private final boolean enabled; + + int maxLogSize = DEFAULT_LOG_SIZE; // The value is in KB. + int maxLogFiles = DEFAULT_LOG_FILES; + int backupIdx = 0; + + private int logLevel = FrameworkLogEntry.OK; + private boolean includeCommandLine = true; + + /** + * Constructs an EclipseLog which uses the specified File to log messages to + * @param outFile a file to log messages to + */ + public EclipseLogWriter(File outFile, String loggerName, boolean enabled) { + this.outFile = outFile; + this.writer = null; + this.loggerName = loggerName; + this.enabled = enabled; + readLogProperties(); + } + + /** + * Constructs an EclipseLog which uses the specified Writer to log messages to + * @param writer a writer to log messages to + */ + public EclipseLogWriter(Writer writer, String loggerName, boolean enabled) { + if (writer == null) + // log to System.err by default + this.writer = logForStream(System.err); + else + this.writer = writer; + this.loggerName = loggerName; + this.enabled = enabled; + } + + private Throwable getRoot(Throwable t) { + Throwable root = null; + if (t instanceof BundleException) + root = ((BundleException) t).getNestedException(); + if (t instanceof InvocationTargetException) + root = ((InvocationTargetException) t).getTargetException(); + // skip inner InvocationTargetExceptions and BundleExceptions + if (root instanceof InvocationTargetException || root instanceof BundleException) { + Throwable deeplyNested = getRoot(root); + if (deeplyNested != null) + // if we have something more specific, use it, otherwise keep what we have + root = deeplyNested; + } + return root; + } + + /** + * Helper method for writing out argument arrays. + * @param header the header + * @param args the list of arguments + */ + private void writeArgs(String header, String[] args) throws IOException { + if (args == null || args.length == 0) + return; + write(header); + for (int i = 0; i < args.length; i++) { + //mask out the password argument for security + if (i > 0 && PASSWORD.equals(args[i - 1])) + write(" (omitted)"); //$NON-NLS-1$ + else + write(" " + args[i]); //$NON-NLS-1$ + } + writeln(); + } + + /** + * Returns the session timestamp. This is the time the platform was started + * @return the session timestamp + */ + private String getSessionTimestamp() { + // Main should have set the session start-up timestamp so return that. + // Return the "now" time if not available. + String ts = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$ + if (ts != null) { + try { + return getDate(new Date(Long.parseLong(ts))); + } catch (NumberFormatException e) { + // fall through and use the timestamp from right now + } + } + return getDate(new Date()); + } + + /** + * Writes the session + * @throws IOException if an error occurs writing to the log + */ + private void writeSession() throws IOException { + write(SESSION); + writeSpace(); + String date = getSessionTimestamp(); + write(date); + writeSpace(); + for (int i = SESSION.length() + date.length(); i < 78; i++) { + write("-"); //$NON-NLS-1$ + } + writeln(); + // Write out certain values found in System.getProperties() + try { + String key = "eclipse.buildId"; //$NON-NLS-1$ + String value = FrameworkProperties.getProperty(key, "unknown"); //$NON-NLS-1$ + writeln(key + "=" + value); //$NON-NLS-1$ + + key = "java.fullversion"; //$NON-NLS-1$ + value = System.getProperty(key); + if (value == null) { + key = "java.version"; //$NON-NLS-1$ + value = System.getProperty(key); + writeln(key + "=" + value); //$NON-NLS-1$ + key = "java.vendor"; //$NON-NLS-1$ + value = System.getProperty(key); + writeln(key + "=" + value); //$NON-NLS-1$ + } else { + writeln(key + "=" + value); //$NON-NLS-1$ + } + } catch (Exception e) { + // If we're not allowed to get the values of these properties + // then just skip over them. + } + // The Bootloader has some information that we might be interested in. + write("BootLoader constants: OS=" + EclipseEnvironmentInfo.getDefault().getOS()); //$NON-NLS-1$ + write(", ARCH=" + EclipseEnvironmentInfo.getDefault().getOSArch()); //$NON-NLS-1$ + write(", WS=" + EclipseEnvironmentInfo.getDefault().getWS()); //$NON-NLS-1$ + writeln(", NL=" + EclipseEnvironmentInfo.getDefault().getNL()); //$NON-NLS-1$ + // Add the command-line arguments used to invoke the platform + // XXX: this includes runtime-private arguments - should we do that? + if (includeCommandLine) { + writeArgs("Framework arguments: ", EclipseEnvironmentInfo.getDefault().getNonFrameworkArgs()); //$NON-NLS-1$ + writeArgs("Command-line arguments: ", EclipseEnvironmentInfo.getDefault().getCommandLineArgs()); //$NON-NLS-1$ + } + } + + public void close() { + try { + if (writer != null) { + Writer tmpWriter = writer; + writer = null; + tmpWriter.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * If a File is used to log messages to then the File opened and a Writer is created + * to log messages to. + */ + private void openFile() { + if (writer == null) { + if (outFile != null) { + try { + writer = logForStream(secureAction.getFileOutputStream(outFile, true)); + } catch (IOException e) { + writer = logForStream(System.err); + } + } else { + writer = logForStream(System.err); + } + } + } + + /** + * If a File is used to log messages to then the writer is closed. + */ + private void closeFile() { + if (outFile != null) { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + // we cannot log here; just print the stacktrace. + e.printStackTrace(); + } + writer = null; + } + } + } + + private synchronized void log(FrameworkLogEntry logEntry) { + if (logEntry == null) + return; + if (!isLoggable(logEntry.getSeverity())) + return; + try { + checkLogFileSize(); + openFile(); + if (newSession) { + writeSession(); + newSession = false; + } + writeLog(0, logEntry); + writer.flush(); + } catch (Exception e) { + // any exceptions during logging should be caught + System.err.println("An exception occurred while writing to the platform log:");//$NON-NLS-1$ + e.printStackTrace(System.err); + System.err.println("Logging to the console instead.");//$NON-NLS-1$ + //we failed to write, so dump log entry to console instead + try { + writer = logForStream(System.err); + writeLog(0, logEntry); + writer.flush(); + } catch (Exception e2) { + System.err.println("An exception occurred while logging to the console:");//$NON-NLS-1$ + e2.printStackTrace(System.err); + } + } finally { + closeFile(); + } + } + + public synchronized void setWriter(Writer newWriter, boolean append) { + setOutput(null, newWriter, append); + } + + /** + * @throws IOException + */ + public synchronized void setFile(File newFile, boolean append) throws IOException { + if (newFile != null && !newFile.equals(this.outFile)) { + // If it's a new file, then reset. + readLogProperties(); + backupIdx = 0; + } + setOutput(newFile, null, append); + FrameworkProperties.setProperty(EclipseStarter.PROP_LOGFILE, newFile == null ? "" : newFile.getAbsolutePath()); //$NON-NLS-1$ + } + + public synchronized File getFile() { + return outFile; + } + + public void setConsoleLog(boolean consoleLog) { + this.consoleLog = consoleLog; + } + + private void setOutput(File newOutFile, Writer newWriter, boolean append) { + if (newOutFile == null || !newOutFile.equals(this.outFile)) { + if (this.writer != null) { + try { + this.writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + this.writer = null; + } + // Append old outFile to newWriter. We only attempt to do this + // if the current Writer is backed by a File and this is not + // a new session. + File oldOutFile = this.outFile; + this.outFile = newOutFile; + this.writer = newWriter; + boolean copyFailed = false; + if (append && oldOutFile != null && oldOutFile.isFile()) { + Reader fileIn = null; + try { + openFile(); + fileIn = new InputStreamReader(secureAction.getFileInputStream(oldOutFile), "UTF-8"); //$NON-NLS-1$ + copyReader(fileIn, this.writer); + } catch (IOException e) { + copyFailed = true; + e.printStackTrace(); + } finally { + if (fileIn != null) { + try { + fileIn.close(); + } catch (IOException e) { + e.printStackTrace(); + } + // delete the old file if copying didn't fail + if (!copyFailed) + oldOutFile.delete(); + } + closeFile(); + } + } + } + } + + private void copyReader(Reader reader, Writer aWriter) throws IOException { + char buffer[] = new char[1024]; + int count; + while ((count = reader.read(buffer, 0, buffer.length)) > 0) { + aWriter.write(buffer, 0, count); + } + } + + /** + * Returns a date string using the correct format for the log. + * @param date the Date to format + * @return a date string. + */ + private String getDate(Date date) { + Calendar c = Calendar.getInstance(); + c.setTime(date); + StringBuffer sb = new StringBuffer(); + appendPaddedInt(c.get(Calendar.YEAR), 4, sb).append('-'); + appendPaddedInt(c.get(Calendar.MONTH) + 1, 2, sb).append('-'); + appendPaddedInt(c.get(Calendar.DAY_OF_MONTH), 2, sb).append(' '); + appendPaddedInt(c.get(Calendar.HOUR_OF_DAY), 2, sb).append(':'); + appendPaddedInt(c.get(Calendar.MINUTE), 2, sb).append(':'); + appendPaddedInt(c.get(Calendar.SECOND), 2, sb).append('.'); + appendPaddedInt(c.get(Calendar.MILLISECOND), 3, sb); + return sb.toString(); + } + + private StringBuffer appendPaddedInt(int value, int pad, StringBuffer buffer) { + pad = pad - 1; + if (pad == 0) + return buffer.append(Integer.toString(value)); + int padding = (int) Math.pow(10, pad); + if (value >= padding) + return buffer.append(Integer.toString(value)); + while (padding > value && padding > 1) { + buffer.append('0'); + padding = padding / 10; + } + buffer.append(value); + return buffer; + } + + /** + * Returns a stacktrace string using the correct format for the log + * @param t the Throwable to get the stacktrace for + * @return a stacktrace string + */ + private String getStackTrace(Throwable t) { + if (t == null) + return null; + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + t.printStackTrace(pw); + // ensure the root exception is fully logged + Throwable root = getRoot(t); + if (root != null) { + pw.println("Root exception:"); //$NON-NLS-1$ + root.printStackTrace(pw); + } + return sw.toString(); + } + + /** + * Returns a Writer for the given OutputStream + * @param output an OutputStream to use for the Writer + * @return a Writer for the given OutputStream + */ + private Writer logForStream(OutputStream output) { + try { + return new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + return new BufferedWriter(new OutputStreamWriter(output)); + } + } + + /** + * Writes the log entry to the log using the specified depth. A depth value of 0 + * indicates that the log entry is the root entry. Any value greater than 0 indicates + * a sub-entry. + * @param depth the depth of th entry + * @param entry the entry to log + * @throws IOException if any error occurs writing to the log + */ + private void writeLog(int depth, FrameworkLogEntry entry) throws IOException { + writeEntry(depth, entry); + writeMessage(entry); + writeStack(entry); + + FrameworkLogEntry[] children = entry.getChildren(); + if (children != null) { + for (int i = 0; i < children.length; i++) { + writeLog(depth + 1, children[i]); + } + } + } + + /** + * Writes the ENTRY or SUBENTRY header for an entry. A depth value of 0 + * indicates that the log entry is the root entry. Any value greater than 0 indicates + * a sub-entry. + * @param depth the depth of th entry + * @param entry the entry to write the header for + * @throws IOException if any error occurs writing to the log + */ + private void writeEntry(int depth, FrameworkLogEntry entry) throws IOException { + if (depth == 0) { + writeln(); // write a blank line before all !ENTRY tags bug #64406 + write(ENTRY); + } else { + write(SUBENTRY); + writeSpace(); + write(Integer.toString(depth)); + } + writeSpace(); + write(entry.getEntry()); + writeSpace(); + write(Integer.toString(entry.getSeverity())); + writeSpace(); + write(Integer.toString(entry.getBundleCode())); + writeSpace(); + write(getDate(new Date())); + writeln(); + } + + /** + * Writes the MESSAGE header to the log for the given entry. + * @param entry the entry to write the message for + * @throws IOException if any error occurs writing to the log + */ + private void writeMessage(FrameworkLogEntry entry) throws IOException { + write(MESSAGE); + writeSpace(); + writeln(entry.getMessage()); + } + + /** + * Writes the STACK header to the log for the given entry. + * @param entry the entry to write the stacktrace for + * @throws IOException if any error occurs writing to the log + */ + private void writeStack(FrameworkLogEntry entry) throws IOException { + Throwable t = entry.getThrowable(); + if (t != null) { + String stack = getStackTrace(t); + write(STACK); + writeSpace(); + write(Integer.toString(entry.getStackCode())); + writeln(); + write(stack); + } + } + + /** + * Writes the given message to the log. + * @param message the message + * @throws IOException if any error occurs writing to the log + */ + private void write(String message) throws IOException { + if (message != null) { + writer.write(message); + if (consoleLog) + System.out.print(message); + } + } + + /** + * Writes the given message to the log and a newline. + * @param s the message + * @throws IOException if any error occurs writing to the log + */ + private void writeln(String s) throws IOException { + write(s); + writeln(); + } + + /** + * Writes a newline log. + * @throws IOException if any error occurs writing to the log + */ + private void writeln() throws IOException { + write(LINE_SEPARATOR); + } + + /** + * Writes a space to the log. + * @throws IOException if any error occurs writing to the log + */ + private void writeSpace() throws IOException { + write(" "); //$NON-NLS-1$ + } + + /** + * Checks the log file size. If the log file size reaches the limit then the log + * is rotated + * @return false if an error occured trying to rotate the log + */ + private boolean checkLogFileSize() { + if (maxLogSize == 0) + return true; // no size limitation. + + boolean isBackupOK = true; + if (outFile != null) { + if ((secureAction.length(outFile) >> 10) > maxLogSize) { // Use KB as file size unit. + String logFilename = outFile.getAbsolutePath(); + + // Delete old backup file that will be replaced. + String backupFilename = ""; //$NON-NLS-1$ + if (logFilename.toLowerCase().endsWith(LOG_EXT)) { + backupFilename = logFilename.substring(0, logFilename.length() - LOG_EXT.length()) + BACKUP_MARK + backupIdx + LOG_EXT; + } else { + backupFilename = logFilename + BACKUP_MARK + backupIdx; + } + File backupFile = new File(backupFilename); + if (backupFile.exists()) { + if (!backupFile.delete()) { + System.err.println("Error when trying to delete old log file: " + backupFile.getName());//$NON-NLS-1$ + if (backupFile.renameTo(new File(backupFile.getAbsolutePath() + System.currentTimeMillis()))) { + System.err.println("So we rename it to filename: " + backupFile.getName()); //$NON-NLS-1$ + } else { + System.err.println("And we also cannot rename it!"); //$NON-NLS-1$ + isBackupOK = false; + } + } + } + + // Rename current log file to backup one. + boolean isRenameOK = outFile.renameTo(backupFile); + if (!isRenameOK) { + System.err.println("Error when trying to rename log file to backup one."); //$NON-NLS-1$ + isBackupOK = false; + } + File newFile = new File(logFilename); + setOutput(newFile, null, false); + + // Write a new SESSION header to new log file. + openFile(); + try { + writeSession(); + writeln(); + writeln("This is a continuation of log file " + backupFile.getAbsolutePath());//$NON-NLS-1$ + writeln("Created Time: " + getDate(new Date(System.currentTimeMillis()))); //$NON-NLS-1$ + writer.flush(); + } catch (IOException ioe) { + ioe.printStackTrace(System.err); + } + closeFile(); + backupIdx = (++backupIdx) % maxLogFiles; + } + } + return isBackupOK; + } + + /** + * Reads the PROP_LOG_SIZE_MAX and PROP_LOG_FILE_MAX properties. + */ + private void readLogProperties() { + String newMaxLogSize = secureAction.getProperty(PROP_LOG_SIZE_MAX); + if (newMaxLogSize != null) { + maxLogSize = Integer.parseInt(newMaxLogSize); + if (maxLogSize != 0 && maxLogSize < LOG_SIZE_MIN) { + // If the value is '0', then it means no size limitation. + // Also, make sure no inappropriate(too small) assigned value. + maxLogSize = LOG_SIZE_MIN; + } + } + + String newMaxLogFiles = secureAction.getProperty(PROP_LOG_FILE_MAX); + if (newMaxLogFiles != null) { + maxLogFiles = Integer.parseInt(newMaxLogFiles); + if (maxLogFiles < 1) { + // Make sure no invalid assigned value. (at least >= 1) + maxLogFiles = DEFAULT_LOG_FILES; + } + } + + String newLogLevel = secureAction.getProperty(PROP_LOG_LEVEL); + if (newLogLevel != null) { + if (newLogLevel.equals("ERROR")) //$NON-NLS-1$ + logLevel = FrameworkLogEntry.ERROR; + else if (newLogLevel.equals("WARNING")) //$NON-NLS-1$ + logLevel = FrameworkLogEntry.ERROR | FrameworkLogEntry.WARNING; + else if (newLogLevel.equals("INFO")) //$NON-NLS-1$ + logLevel = FrameworkLogEntry.INFO | FrameworkLogEntry.ERROR | FrameworkLogEntry.WARNING | FrameworkLogEntry.CANCEL; + else + logLevel = FrameworkLogEntry.OK; // OK (0) means log everything + } + + includeCommandLine = "true".equals(secureAction.getProperty(PROP_LOG_INCLUDE_COMMAND_LINE, "true")); //$NON-NLS-1$//$NON-NLS-2$ + } + + /** + * Determines if the log entry should be logged based on log level. + */ + private boolean isLoggable(int fwkEntrySeverity) { + if (logLevel == 0) + return true; + return (fwkEntrySeverity & logLevel) != 0; + } + + public boolean isLoggable(Bundle bundle, String loggableName, int loggableLevel) { + if (!enabled) + return false; + if (loggerName.equals(loggableName)) + return isLoggable(convertSeverity(loggableLevel)); + if (EclipseLogHook.PERF_LOGGER_NAME.equals(loggableName)) + // we don't want to do anything with performance logger unless + // this is the performance logger (check done above). + return false; + if (!EclipseLogHook.EQUINOX_LOGGER_NAME.equals(loggerName)) + // only the equinox log writer should pay attention to other logs + return false; + // for now only log errors; probably need this to be configurable + return loggableLevel == LogService.LOG_ERROR; + } + + public void logged(LogEntry entry) { + if (!(entry instanceof ExtendedLogEntry)) + // TODO this should never happen + return; + ExtendedLogEntry extended = (ExtendedLogEntry) entry; + Object context = extended.getContext(); + if (context instanceof FrameworkLogEntry) { + log((FrameworkLogEntry) context); + return; + } + // OK we are now in a case where someone logged a normal entry to the real LogService + log(new FrameworkLogEntry(getFwkEntryTag(entry), convertSeverity(entry.getLevel()), 0, entry.getMessage(), 0, entry.getException(), null)); + } + + private static String getFwkEntryTag(LogEntry entry) { + Bundle b = entry.getBundle(); + if (b != null && b.getSymbolicName() != null) + return b.getSymbolicName(); + return "unknown"; //$NON-NLS-1$ + } + + private static int convertSeverity(int entryLevel) { + switch (entryLevel) { + case LogService.LOG_ERROR : + return FrameworkLogEntry.ERROR; + case LogService.LOG_WARNING : + return FrameworkLogEntry.WARNING; + case LogService.LOG_INFO : + return FrameworkLogEntry.INFO; + case LogService.LOG_DEBUG : + return FrameworkLogEntry.OK; + default : + return 32; // unknown + } + } + + public String getLoggerName() { + return loggerName; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java new file mode 100644 index 000000000..78aa333aa --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java @@ -0,0 +1,522 @@ +/******************************************************************************* + * 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.core.runtime.internal.adaptor; + +import java.io.*; +import java.net.URL; +import java.security.*; +import java.util.Dictionary; +import java.util.Enumeration; +import org.eclipse.core.runtime.adaptor.LocationManager; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.hooks.StorageHook; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.framework.util.Headers; +import org.eclipse.osgi.framework.util.KeyedElement; +import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.osgi.service.pluginconversion.PluginConversionException; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.BundleException; +import org.osgi.framework.Version; + +public final class EclipseStorageHook implements StorageHook, HookConfigurator { + // System property used to check timestamps of the bundles in the configuration + private static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$ + private static final String PROP_COMPATIBILITY_LAZYSTART = "osgi.compatibility.eagerStart.LazyActivation"; //$NON-NLS-1$ + private static final boolean COMPATIBILITY_LAZYSTART = Boolean.valueOf(FrameworkProperties.getProperty(PROP_COMPATIBILITY_LAZYSTART, "true")).booleanValue(); //$NON-NLS-1$ + private static final int STORAGE_VERION = 4; + + public static final String KEY = EclipseStorageHook.class.getName(); + public static final int HASHCODE = KEY.hashCode(); + + private static final byte FLAG_LAZY_START = 0x01; + private static final byte FLAG_HAS_PACKAGE_INFO = 0x02; + // Note that the 0x04 was used in previous versions, if a new flag is needed then do not reuse this one + //private static final byte FLAG_ACTIVATE_ON_CLASSLOAD = 0x04; + // Flag to indicate that an include directive is present on the lazy activation policy + private static final byte FLAG_HAS_LAZY_INCLUDE = 0x08; + + /** data to detect modification made in the manifest */ + private long manifestTimeStamp = 0; + private byte manifestType = PluginConverterImpl.MANIFEST_TYPE_UNKNOWN; + + private BaseData bundledata; + + /** the Plugin-Class header */ + private String pluginClass = null; + /** Eclipse-LazyStart header */ + private String[] lazyStartExcludes; + private String[] lazyStartIncludes; + private int bundleManfestVersion; + /** shortcut to know if a bundle has a buddy */ + private String buddyList; + /** shortcut to know if a bundle is a registrant to a registered policy */ + private String registeredBuddyList; + /** DS Service Component header */ + private String serviceComponent; + private byte flags = 0; + + public int getStorageVersion() { + return STORAGE_VERION; + } + + /** + * @throws BundleException + */ + public StorageHook create(BaseData data) throws BundleException { + EclipseStorageHook storageHook = new EclipseStorageHook(); + storageHook.bundledata = data; + return storageHook; + } + + @SuppressWarnings("deprecation") + public void initialize(Dictionary<String, String> manifest) throws BundleException { + String activationPolicy = manifest.get(Constants.BUNDLE_ACTIVATIONPOLICY); + if (activationPolicy != null) { + parseActivationPolicy(this, activationPolicy); + } else { + String lazyStart = manifest.get(Constants.ECLIPSE_LAZYSTART); + if (lazyStart == null) + lazyStart = manifest.get(Constants.ECLIPSE_AUTOSTART); + parseLazyStart(this, lazyStart); + } + try { + String versionString = manifest.get(Constants.BUNDLE_MANIFESTVERSION); + bundleManfestVersion = versionString == null ? 0 : Integer.parseInt(versionString); + } catch (NumberFormatException nfe) { + bundleManfestVersion = 0; + } + pluginClass = manifest.get(Constants.PLUGIN_CLASS); + buddyList = manifest.get(Constants.BUDDY_LOADER); + registeredBuddyList = manifest.get(Constants.REGISTERED_POLICY); + if (hasPackageInfo(bundledata.getEntry(Constants.OSGI_BUNDLE_MANIFEST))) + flags |= FLAG_HAS_PACKAGE_INFO; + String genFrom = manifest.get(PluginConverterImpl.GENERATED_FROM); + if (genFrom != null) { + ManifestElement generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, genFrom)[0]; + if (generatedFrom != null) { + manifestTimeStamp = Long.parseLong(generatedFrom.getValue()); + manifestType = Byte.parseByte(generatedFrom.getAttribute(PluginConverterImpl.MANIFEST_TYPE_ATTRIBUTE)); + } + } + if (isAutoStartable()) { + bundledata.setStatus(bundledata.getStatus() | Constants.BUNDLE_LAZY_START); + if (COMPATIBILITY_LAZYSTART) + bundledata.setStatus(bundledata.getStatus() | Constants.BUNDLE_STARTED | Constants.BUNDLE_ACTIVATION_POLICY); + } + serviceComponent = manifest.get(CachedManifest.SERVICE_COMPONENT); + } + + public StorageHook load(BaseData target, DataInputStream in) throws IOException { + EclipseStorageHook storageHook = new EclipseStorageHook(); + storageHook.bundledata = target; + storageHook.flags = in.readByte(); + int pkgCount = in.readInt(); + String[] packageList = pkgCount > 0 ? new String[pkgCount] : null; + for (int i = 0; i < pkgCount; i++) + packageList[i] = in.readUTF(); + storageHook.lazyStartExcludes = packageList; + if ((storageHook.flags & FLAG_HAS_LAZY_INCLUDE) != 0) { + pkgCount = in.readInt(); + packageList = pkgCount > 0 ? new String[pkgCount] : null; + for (int i = 0; i < pkgCount; i++) + packageList[i] = in.readUTF(); + storageHook.lazyStartIncludes = packageList; + } + storageHook.buddyList = AdaptorUtil.readString(in, false); + storageHook.registeredBuddyList = AdaptorUtil.readString(in, false); + storageHook.pluginClass = AdaptorUtil.readString(in, false); + storageHook.manifestTimeStamp = in.readLong(); + storageHook.manifestType = in.readByte(); + storageHook.bundleManfestVersion = in.readInt(); + if (storageHook.isAutoStartable()) { + if ((target.getStatus() & Constants.BUNDLE_LAZY_START) == 0) + target.setStatus(target.getStatus() | Constants.BUNDLE_LAZY_START); + // if the compatibility flag is set then we must make sure the persistent start bit is set and the activation policy bit; + // if the persistent start bit was already set then we should not set the activation policy bit because this is an "eager" started bundle. + if (COMPATIBILITY_LAZYSTART && (target.getStatus() & Constants.BUNDLE_STARTED) == 0) + target.setStatus(target.getStatus() | Constants.BUNDLE_STARTED | Constants.BUNDLE_ACTIVATION_POLICY); + } + storageHook.serviceComponent = AdaptorUtil.readString(in, false); + return storageHook; + } + + public void save(DataOutputStream out) throws IOException { + if (bundledata == null) + throw new IllegalStateException(); + // when this is stored back we always use the has include/exclude flag + out.writeByte(flags); + String[] excludes = getLazyStartExcludes(); + if (excludes == null) + out.writeInt(0); + else { + out.writeInt(excludes.length); + for (int i = 0; i < excludes.length; i++) + out.writeUTF(excludes[i]); + } + if ((flags & FLAG_HAS_LAZY_INCLUDE) != 0) { + String[] includes = getLazyStartIncludes(); + if (includes == null) + out.writeInt(0); + else { + out.writeInt(includes.length); + for (int i = 0; i < includes.length; i++) + out.writeUTF(includes[i]); + } + } + AdaptorUtil.writeStringOrNull(out, getBuddyList()); + AdaptorUtil.writeStringOrNull(out, getRegisteredBuddyList()); + AdaptorUtil.writeStringOrNull(out, getPluginClass()); + out.writeLong(getManifestTimeStamp()); + out.writeByte(getManifestType()); + out.writeInt(getBundleManifestVersion()); + AdaptorUtil.writeStringOrNull(out, serviceComponent); + } + + public int getKeyHashCode() { + return HASHCODE; + } + + public boolean compare(KeyedElement other) { + return other.getKey() == KEY; + } + + public Object getKey() { + return KEY; + } + + public boolean isLazyStart() { + return (flags & FLAG_LAZY_START) == FLAG_LAZY_START; + } + + public String[] getLazyStartExcludes() { + return lazyStartExcludes; + } + + public String[] getLazyStartIncludes() { + return lazyStartIncludes; + } + + public String getBuddyList() { + return buddyList; + } + + public boolean hasPackageInfo() { + return (flags & FLAG_HAS_PACKAGE_INFO) == FLAG_HAS_PACKAGE_INFO; + } + + public String getPluginClass() { + return pluginClass; + } + + public String getRegisteredBuddyList() { + return registeredBuddyList; + } + + public long getManifestTimeStamp() { + return manifestTimeStamp; + } + + public byte getManifestType() { + return manifestType; + } + + public int getBundleManifestVersion() { + return bundleManfestVersion; + } + + public String getServiceComponent() { + return serviceComponent; + } + + /** + * Checks whether this bundle is auto started for all resource/class loads or only for a + * subset of resource/classloads + * @return true if the bundle is auto started; false otherwise + */ + public boolean isAutoStartable() { + return isLazyStart() || (lazyStartExcludes != null && lazyStartExcludes.length > 0); + } + + private void parseLazyStart(EclipseStorageHook storageHook, String headerValue) { + storageHook.lazyStartExcludes = null; + ManifestElement[] allElements = null; + try { + allElements = ManifestElement.parseHeader(Constants.ECLIPSE_LAZYSTART, headerValue); + } catch (BundleException e) { + // just use the default settings (no auto activation) + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CANNOT_GET_HEADERS, storageHook.bundledata.getLocation()); + bundledata.getAdaptor().getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null)); + } + //Eclipse-AutoStart not found... + if (allElements == null) + return; + // the single value for this element should be true|false + if ("true".equalsIgnoreCase(allElements[0].getValue())) //$NON-NLS-1$ + storageHook.flags |= FLAG_LAZY_START; + // look for any exceptions (the attribute) to the autoActivate setting + String[] exceptions = ManifestElement.getArrayFromList(allElements[0].getAttribute(Constants.ECLIPSE_LAZYSTART_EXCEPTIONS)); + storageHook.lazyStartExcludes = exceptions; + } + + private void parseActivationPolicy(EclipseStorageHook storageHook, String headerValue) { + storageHook.lazyStartExcludes = null; + ManifestElement[] allElements = null; + try { + allElements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, headerValue); + } catch (BundleException e) { + // just use the default settings (no auto activation) + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CANNOT_GET_HEADERS, storageHook.bundledata.getLocation()); + bundledata.getAdaptor().getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null)); + } + //Bundle-ActivationPolicy not found. + if (allElements == null) + return; + // the single value for this type is lazy + if (!Constants.ACTIVATION_LAZY.equalsIgnoreCase(allElements[0].getValue())) + return; + storageHook.flags |= FLAG_LAZY_START; + // look for any include or exclude attrs + storageHook.lazyStartExcludes = ManifestElement.getArrayFromList(allElements[0].getDirective(Constants.EXCLUDE_DIRECTIVE)); + storageHook.lazyStartIncludes = ManifestElement.getArrayFromList(allElements[0].getDirective(Constants.INCLUDE_DIRECTIVE)); + if (storageHook.lazyStartIncludes != null) + storageHook.flags |= FLAG_HAS_LAZY_INCLUDE; + } + + // Used to check the bundle manifest file for any package information. + // This is used when '.' is on the Bundle-ClassPath to prevent reading + // the bundle manifest for pacakge information when loading classes. + private static boolean hasPackageInfo(URL url) { + if (url == null) + return false; + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(url.openStream())); + String line; + while ((line = br.readLine()) != null) { + if (line.length() < 20) + continue; + switch (line.charAt(0)) { + case 'S' : + if (line.charAt(1) == 'p') + if (line.startsWith("Specification-Title: ") || line.startsWith("Specification-Version: ") || line.startsWith("Specification-Vendor: ")) //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ + return true; + break; + case 'I' : + if (line.startsWith("Implementation-Title: ") || line.startsWith("Implementation-Version: ") || line.startsWith("Implementation-Vendor: ")) //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ + return true; + break; + } + } + } catch (IOException ioe) { + // do nothing + } finally { + if (br != null) + try { + br.close(); + } catch (IOException e) { + // do nothing + } + } + return false; + } + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addStorageHook(this); + } + + private void checkTimeStamp() throws IllegalArgumentException { + if (!checkManifestTimeStamp()) + throw new IllegalArgumentException(); + } + + private boolean checkManifestTimeStamp() { + if (!"true".equalsIgnoreCase(FrameworkProperties.getProperty(EclipseStorageHook.PROP_CHECK_CONFIG))) //$NON-NLS-1$ + return true; + if (PluginConverterImpl.getTimeStamp(bundledata.getBundleFile().getBaseFile(), getManifestType()) == getManifestTimeStamp()) { + if ((getManifestType() & (PluginConverterImpl.MANIFEST_TYPE_JAR | PluginConverterImpl.MANIFEST_TYPE_BUNDLE)) != 0) + return true; + String cacheLocation = FrameworkProperties.getProperty(LocationManager.PROP_MANIFEST_CACHE); + Location parentConfiguration = LocationManager.getConfigurationLocation().getParentLocation(); + if (parentConfiguration != null) { + try { + return checkManifestAndParent(cacheLocation, bundledata.getSymbolicName(), bundledata.getVersion().toString(), getManifestType()) != null; + } catch (BundleException e) { + return false; + } + } + File cacheFile = new File(cacheLocation, bundledata.getSymbolicName() + '_' + bundledata.getVersion() + ".MF"); //$NON-NLS-1$ + if (cacheFile.isFile()) + return true; + } + return false; + } + + private Headers<String, String> checkManifestAndParent(String cacheLocation, String symbolicName, String version, byte inputType) throws BundleException { + Headers<String, String> result = basicCheckManifest(cacheLocation, symbolicName, version, inputType); + if (result != null) + return result; + Location parentConfiguration = null; + if ((parentConfiguration = LocationManager.getConfigurationLocation().getParentLocation()) != null) + result = basicCheckManifest(new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + '/' + LocationManager.MANIFESTS_DIR).toString(), symbolicName, version, inputType); + return result; + } + + private Headers<String, String> basicCheckManifest(String cacheLocation, String symbolicName, String version, byte inputType) throws BundleException { + File currentFile = new File(cacheLocation, symbolicName + '_' + version + ".MF"); //$NON-NLS-1$ + if (PluginConverterImpl.upToDate(currentFile, bundledata.getBundleFile().getBaseFile(), inputType)) { + try { + return Headers.parseManifest(new FileInputStream(currentFile)); + } catch (FileNotFoundException e) { + // do nothing. + } + } + return null; + } + + Dictionary<String, String> createCachedManifest(boolean firstTime) throws BundleException { + return firstTime ? getGeneratedManifest() : new CachedManifest(this); + } + + public Dictionary<String, String> getGeneratedManifest() throws BundleException { + if (System.getSecurityManager() == null) + return getGeneratedManifest0(); + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction<Dictionary<String, String>>() { + public Dictionary<String, String> run() throws BundleException { + return getGeneratedManifest0(); + } + }); + } catch (PrivilegedActionException e) { + throw (BundleException) e.getException(); + } + } + + final Dictionary<String, String> getGeneratedManifest0() throws BundleException { + Dictionary<String, String> builtIn = AdaptorUtil.loadManifestFrom(bundledata); + if (builtIn != null) { + // the bundle has a built-in manifest - we may not have to generate one + if (!isComplete(builtIn)) { + Dictionary<String, String> generatedManifest = generateManifest(builtIn); + if (generatedManifest != null) + return generatedManifest; + } + // the manifest is complete or we could not complete it - take it as it is + manifestType = PluginConverterImpl.MANIFEST_TYPE_BUNDLE; + File baseFile = bundledata.getBundleFile().getBaseFile(); + if (baseFile != null && bundledata.getBundleFile().getBaseFile().isFile()) { + manifestTimeStamp = bundledata.getBundleFile().getBaseFile().lastModified(); + manifestType |= PluginConverterImpl.MANIFEST_TYPE_JAR; + } else + manifestTimeStamp = bundledata.getBundleFile().getEntry(Constants.OSGI_BUNDLE_MANIFEST).getTime(); + return builtIn; + } + Dictionary<String, String> result = generateManifest(null); + if (result == null) + throw new BundleException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_DATA_MANIFEST_NOT_FOUND, bundledata.getLocation())); + return result; + } + + private Dictionary<String, String> generateManifest(Dictionary<String, String> builtIn) throws BundleException { + String cacheLocation = FrameworkProperties.getProperty(LocationManager.PROP_MANIFEST_CACHE); + if (bundledata.getSymbolicName() != null) { + Headers<String, String> existingHeaders = checkManifestAndParent(cacheLocation, bundledata.getSymbolicName(), bundledata.getVersion().toString(), manifestType); + if (existingHeaders != null) + return existingHeaders; + } + + PluginConverterImpl converter = PluginConverterImpl.getDefault(); + if (converter == null) + converter = new PluginConverterImpl(bundledata.getAdaptor(), bundledata.getAdaptor().getContext()); + + Dictionary<String, String> generatedManifest; + try { + generatedManifest = converter.convertManifest(bundledata.getBundleFile().getBaseFile(), true, null, true, null); + } catch (PluginConversionException pce) { + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_CONVERTING, bundledata.getBundleFile().getBaseFile()); + throw new BundleException(message, BundleException.MANIFEST_ERROR, pce); + } + + //Now we know the symbolicId and the version of the bundle, we check to see if don't have a manifest for it already + Version version = Version.parseVersion(generatedManifest.get(Constants.BUNDLE_VERSION)); + String symbolicName = ManifestElement.parseHeader(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME, generatedManifest.get(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME))[0].getValue(); + ManifestElement generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, generatedManifest.get(PluginConverterImpl.GENERATED_FROM))[0]; + Headers<String, String> existingHeaders = checkManifestAndParent(cacheLocation, symbolicName, version.toString(), Byte.parseByte(generatedFrom.getAttribute(PluginConverterImpl.MANIFEST_TYPE_ATTRIBUTE))); + //We don't have a manifest. + manifestTimeStamp = Long.parseLong(generatedFrom.getValue()); + manifestType = Byte.parseByte(generatedFrom.getAttribute(PluginConverterImpl.MANIFEST_TYPE_ATTRIBUTE)); + if (bundledata.getAdaptor().isReadOnly() || existingHeaders != null) + return existingHeaders; + + //merge the original manifest with the generated one + if (builtIn != null) { + Enumeration<String> keysEnum = builtIn.keys(); + while (keysEnum.hasMoreElements()) { + String key = keysEnum.nextElement(); + generatedManifest.put(key, builtIn.get(key)); + } + } + + //write the generated manifest + File bundleManifestLocation = new File(cacheLocation, symbolicName + '_' + version.toString() + ".MF"); //$NON-NLS-1$ + try { + converter.writeManifest(bundleManifestLocation, generatedManifest, true); + } catch (Exception e) { + //TODO Need to log + } + return generatedManifest; + + } + + private boolean isComplete(Dictionary<String, String> manifest) { + // a manifest is complete if it has a Bundle-SymbolicName entry... + if (manifest.get(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME) != null) + return true; + // ...or it does not have a plugin/fragment manifest where to get the other entries from + return bundledata.getEntry(PluginConverterImpl.PLUGIN_MANIFEST) == null && bundledata.getEntry(PluginConverterImpl.FRAGMENT_MANIFEST) == null; + } + + public BaseData getBaseData() { + return bundledata; + } + + public void copy(StorageHook storageHook) { + // copy nothing all must be re-read from a manifest + } + + public void validate() throws IllegalArgumentException { + checkTimeStamp(); + } + + public FrameworkAdaptor getAdaptor() { + if (bundledata != null) + return bundledata.getAdaptor(); + return null; + } + + public Dictionary<String, String> getManifest(boolean firstLoad) throws BundleException { + return createCachedManifest(firstLoad); + } + + public boolean forgetStatusChange(int status) { + return false; + } + + public boolean forgetStartLevelChange(int startlevel) { + return false; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java new file mode 100644 index 000000000..1a870c822 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.adaptor; + +/** + * Internal class. + */ +public interface IModel { + public static final int INDENT = 2; + public static final int RADIX = 36; + + public static final String TRUE = "true"; //$NON-NLS-1$ + public static final String FALSE = "false"; //$NON-NLS-1$ + + public static final String REGISTRY = "plugin-registry"; //$NON-NLS-1$ + public static final String REGISTRY_PATH = "path"; //$NON-NLS-1$ + + public static final String FRAGMENT = "fragment"; //$NON-NLS-1$ + public static final String FRAGMENT_ID = "id"; //$NON-NLS-1$ + public static final String FRAGMENT_NAME = "name"; //$NON-NLS-1$ + public static final String FRAGMENT_PROVIDER = "provider-name"; //$NON-NLS-1$ + public static final String FRAGMENT_VERSION = "version"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_ID = "plugin-id"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_VERSION = "plugin-version"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH = "match"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_PERFECT = "perfect"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_EQUIVALENT = "equivalent"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_COMPATIBLE = "compatible"; //$NON-NLS-1$ + public static final String FRAGMENT_PLUGIN_MATCH_GREATER_OR_EQUAL = "greaterOrEqual"; //$NON-NLS-1$ + + public static final String PLUGIN = "plugin"; //$NON-NLS-1$ + public static final String PLUGIN_ID = "id"; //$NON-NLS-1$ + public static final String PLUGIN_NAME = "name"; //$NON-NLS-1$ + public static final String PLUGIN_VENDOR = "vendor-name"; //$NON-NLS-1$ + public static final String PLUGIN_PROVIDER = "provider-name"; //$NON-NLS-1$ + public static final String PLUGIN_VERSION = "version"; //$NON-NLS-1$ + public static final String PLUGIN_CLASS = "class"; //$NON-NLS-1$ + + public static final String PLUGIN_REQUIRES = "requires"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_PLATFORM = "platform-version"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_PLUGIN = "plugin"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_PLUGIN_VERSION = "version"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_OPTIONAL = "optional"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_IMPORT = "import"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_EXPORT = "export"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH = "match"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_EXACT = "exact"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_PERFECT = "perfect"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_EQUIVALENT = "equivalent"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_COMPATIBLE = "compatible"; //$NON-NLS-1$ + public static final String PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL = "greaterOrEqual"; //$NON-NLS-1$ + + public static final String PLUGIN_KEY_VERSION_SEPARATOR = "_"; //$NON-NLS-1$ + + public static final String RUNTIME = "runtime"; //$NON-NLS-1$ + + public static final String LIBRARY = "library"; //$NON-NLS-1$ + public static final String LIBRARY_NAME = "name"; //$NON-NLS-1$ + public static final String LIBRARY_SOURCE = "source"; //$NON-NLS-1$ + public static final String LIBRARY_TYPE = "type"; //$NON-NLS-1$ + public static final String LIBRARY_EXPORT = "export"; //$NON-NLS-1$ + public static final String LIBRARY_EXPORT_MASK = "name"; //$NON-NLS-1$ + public static final String LIBRARY_PACKAGES = "packages"; //$NON-NLS-1$ + public static final String LIBRARY_PACKAGES_PREFIXES = "prefixes"; //$NON-NLS-1$ + + public static final String EXTENSION_POINT = "extension-point"; //$NON-NLS-1$ + public static final String EXTENSION_POINT_NAME = "name"; //$NON-NLS-1$ + public static final String EXTENSION_POINT_ID = "id"; //$NON-NLS-1$ + public static final String EXTENSION_POINT_SCHEMA = "schema"; //$NON-NLS-1$ + + public static final String EXTENSION = "extension"; //$NON-NLS-1$ + public static final String EXTENSION_NAME = "name"; //$NON-NLS-1$ + public static final String EXTENSION_ID = "id"; //$NON-NLS-1$ + public static final String EXTENSION_TARGET = "point"; //$NON-NLS-1$ + + public static final String ELEMENT = "element"; //$NON-NLS-1$ + public static final String ELEMENT_NAME = "name"; //$NON-NLS-1$ + public static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$ + + public static final String PROPERTY = "property"; //$NON-NLS-1$ + public static final String PROPERTY_NAME = "name"; //$NON-NLS-1$ + public static final String PROPERTY_VALUE = "value"; //$NON-NLS-1$ +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java new file mode 100644 index 000000000..9520f88eb --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2003, 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.core.runtime.internal.adaptor; + +import java.util.*; + +/** + * Interface used as an entry to the IPluginConverter + * + * <p>Internal class.</p> + */ +public interface IPluginInfo { + public Map<String, List<String>> getLibraries(); + + public String[] getLibrariesName(); + + public ArrayList<PluginParser.Prerequisite> getRequires(); + + public String getMasterId(); + + public String getMasterVersion(); + + public String getMasterMatch(); + + public String getPluginClass(); + + public String getUniqueId(); + + public String getVersion(); + + public boolean isFragment(); + + public Set<String> getPackageFilters(); + + public String getPluginName(); + + public String getProviderName(); + + public boolean isSingleton(); + + public boolean hasExtensionExtensionPoints(); + + String validateForm(); +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java new file mode 100644 index 000000000..e1cf601b1 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.core.runtime.internal.adaptor; + +import java.util.Date; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Constants; + +/** + * @since 3.3 + */ +public class MessageHelper { + public static String getResolutionFailureMessage(VersionConstraint unsatisfied) { + if (unsatisfied.isResolved()) + throw new IllegalArgumentException(); + if (unsatisfied instanceof ImportPackageSpecification) { + if (ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(((ImportPackageSpecification) unsatisfied).getDirective(Constants.RESOLUTION_DIRECTIVE))) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_OPTIONAL_IMPORTED_PACKAGE, toString(unsatisfied)); + if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) unsatisfied).getDirective(Constants.RESOLUTION_DIRECTIVE))) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_DYNAMIC_IMPORTED_PACKAGE, toString(unsatisfied)); + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_IMPORTED_PACKAGE, toString(unsatisfied)); + } else if (unsatisfied instanceof BundleSpecification) { + if (((BundleSpecification) unsatisfied).isOptional()) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_OPTIONAL_REQUIRED_BUNDLE, toString(unsatisfied)); + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_REQUIRED_BUNDLE, toString(unsatisfied)); + } else if (unsatisfied instanceof HostSpecification) { + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_HOST, toString(unsatisfied)); + } else if (unsatisfied instanceof NativeCodeSpecification) { + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_NATIVECODE, unsatisfied.toString()); + } else if (unsatisfied instanceof GenericSpecification) { + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_REQUIRED_CAPABILITY, unsatisfied.toString()); + } + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_REQUIREMENT, unsatisfied.toString()); + } + + /** + * Print a debug message to the console. + * Pre-pend the message with the current date and the name of the current thread. + */ + public static void debug(String message) { + StringBuffer buffer = new StringBuffer(); + buffer.append(new Date(System.currentTimeMillis())); + buffer.append(" - ["); //$NON-NLS-1$ + buffer.append(Thread.currentThread().getName()); + buffer.append("] "); //$NON-NLS-1$ + buffer.append(message); + System.out.println(buffer.toString()); + } + + private static String toString(VersionConstraint constraint) { + org.eclipse.osgi.service.resolver.VersionRange versionRange = constraint.getVersionRange(); + if (versionRange == null) + return constraint.getName(); + return constraint.getName() + '_' + versionRange; + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java new file mode 100644 index 000000000..fe1447f2e --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java @@ -0,0 +1,761 @@ +/******************************************************************************* + * Copyright (c) 2003, 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.core.runtime.internal.adaptor; + +import java.io.*; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.eclipse.core.runtime.adaptor.LocationManager; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.baseadaptor.DevClassPathHelper; +import org.eclipse.osgi.service.pluginconversion.PluginConversionException; +import org.eclipse.osgi.service.pluginconversion.PluginConverter; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +/** + * Internal class. + */ +public class PluginConverterImpl implements PluginConverter { + public static boolean DEBUG = false; + /** bundle manifest type unknown */ + static public final byte MANIFEST_TYPE_UNKNOWN = 0x00; + /** bundle manifest type bundle (META-INF/MANIFEST.MF) */ + static public final byte MANIFEST_TYPE_BUNDLE = 0x01; + /** bundle manifest type plugin (plugin.xml) */ + static public final byte MANIFEST_TYPE_PLUGIN = 0x02; + /** bundle manifest type fragment (fragment.xml) */ + static public final byte MANIFEST_TYPE_FRAGMENT = 0x04; + /** bundle manifest type jared bundle */ + static public final byte MANIFEST_TYPE_JAR = 0x08; + private static final String SEMICOLON = "; "; //$NON-NLS-1$ + private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$ + private static final String LIST_SEPARATOR = ",\n "; //$NON-NLS-1$ + private static final String LINE_SEPARATOR = "\n "; //$NON-NLS-1$ + private static final String DOT = "."; //$NON-NLS-1$ + private static int MAXLINE = 511; + private BundleContext context; + private FrameworkAdaptor adaptor; + private BufferedWriter out; + private IPluginInfo pluginInfo; + private File pluginManifestLocation; + private ZipFile pluginZip; + private Dictionary<String, String> generatedManifest; + private byte manifestType; + private Version target; + private Dictionary<String, String> devProperties; + static final Version TARGET31 = new Version(3, 1, 0); + static final Version TARGET32 = new Version(3, 2, 0); + private static final String MANIFEST_VERSION = "Manifest-Version"; //$NON-NLS-1$ + private static final String PLUGIN_PROPERTIES_FILENAME = "plugin"; //$NON-NLS-1$ + private static PluginConverterImpl instance; + @SuppressWarnings("deprecation") + private static final String[] ARCH_LIST = {org.eclipse.osgi.service.environment.Constants.ARCH_PA_RISC, org.eclipse.osgi.service.environment.Constants.ARCH_PPC, org.eclipse.osgi.service.environment.Constants.ARCH_SPARC, org.eclipse.osgi.service.environment.Constants.ARCH_X86, org.eclipse.osgi.service.environment.Constants.ARCH_AMD64, org.eclipse.osgi.service.environment.Constants.ARCH_IA64}; + static public final String FRAGMENT_MANIFEST = "fragment.xml"; //$NON-NLS-1$ + static public final String GENERATED_FROM = "Generated-from"; //$NON-NLS-1$ + static public final String MANIFEST_TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$ + private static final String[] OS_LIST = {org.eclipse.osgi.service.environment.Constants.OS_AIX, org.eclipse.osgi.service.environment.Constants.OS_HPUX, org.eclipse.osgi.service.environment.Constants.OS_LINUX, org.eclipse.osgi.service.environment.Constants.OS_MACOSX, org.eclipse.osgi.service.environment.Constants.OS_QNX, org.eclipse.osgi.service.environment.Constants.OS_SOLARIS, org.eclipse.osgi.service.environment.Constants.OS_WIN32}; + protected static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$ + protected static final String PI_BOOT = "org.eclipse.core.boot"; //$NON-NLS-1$ + protected static final String PI_RUNTIME_COMPATIBILITY = "org.eclipse.core.runtime.compatibility"; //$NON-NLS-1$ + static public final String PLUGIN_MANIFEST = "plugin.xml"; //$NON-NLS-1$ + private static final String COMPATIBILITY_ACTIVATOR = "org.eclipse.core.internal.compatibility.PluginActivator"; //$NON-NLS-1$ + private static final String[] WS_LIST = {org.eclipse.osgi.service.environment.Constants.WS_CARBON, org.eclipse.osgi.service.environment.Constants.WS_GTK, org.eclipse.osgi.service.environment.Constants.WS_MOTIF, org.eclipse.osgi.service.environment.Constants.WS_PHOTON, org.eclipse.osgi.service.environment.Constants.WS_WIN32}; + private static final String IGNORE_DOT = "@ignoredot@"; //$NON-NLS-1$ + + public static PluginConverterImpl getDefault() { + return instance; + } + + public PluginConverterImpl(FrameworkAdaptor adaptor, BundleContext context) { + this.context = context; + this.adaptor = adaptor; + instance = this; + } + + private void init() { + // need to make sure these fields are cleared out for each conversion. + out = null; + pluginInfo = null; + pluginManifestLocation = null; + pluginZip = null; + generatedManifest = new Hashtable<String, String>(10); + manifestType = MANIFEST_TYPE_UNKNOWN; + target = null; + devProperties = null; + } + + private void fillPluginInfo(File pluginBaseLocation) throws PluginConversionException { + pluginManifestLocation = pluginBaseLocation; + if (pluginManifestLocation == null) + throw new IllegalArgumentException(); + InputStream pluginFile = null; + try { + try { + pluginFile = findPluginManifest(pluginBaseLocation); + } catch (IOException e) { + throw new PluginConversionException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_FILENOTFOUND, pluginBaseLocation.getAbsolutePath()), e); + } + if (pluginFile == null) + throw new PluginConversionException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_FILENOTFOUND, pluginBaseLocation.getAbsolutePath())); + pluginInfo = parsePluginInfo(pluginFile); + } finally { + if (pluginZip != null) + try { + pluginZip.close(); + pluginZip = null; + } catch (IOException e) { + // ignore + } + } + String validation = pluginInfo.validateForm(); + if (validation != null) + throw new PluginConversionException(validation); + } + + private Set<String> filterExport(Set<String> exportToFilter, Collection<String> filter) { + if (filter == null || filter.contains("*")) //$NON-NLS-1$ + return exportToFilter; + Set<String> filteredExport = new HashSet<String>(exportToFilter.size()); + for (String anExport : exportToFilter) { + for (String aFilter : filter) { + int dotStar = aFilter.indexOf(".*"); //$NON-NLS-1$ + if (dotStar != -1) + aFilter = aFilter.substring(0, dotStar); + if (anExport.equals(aFilter)) { + filteredExport.add(anExport); + break; + } + } + } + return filteredExport; + } + + private List<String> findOSJars(File pluginRoot, String path, boolean filter) { + path = path.substring(4); + List<String> found = new ArrayList<String>(0); + for (int i = 0; i < OS_LIST.length; i++) { + //look for os/osname/path + String searchedPath = "os/" + OS_LIST[i] + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$ + if (new File(pluginRoot, searchedPath).exists()) + found.add(searchedPath + (filter ? ";(os=" + WS_LIST[i] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + //look for os/osname/archname/path + for (int j = 0; j < ARCH_LIST.length; j++) { + searchedPath = "os/" + OS_LIST[i] + "/" + ARCH_LIST[j] + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (new File(pluginRoot, searchedPath).exists()) { + found.add(searchedPath + (filter ? ";(& (os=" + WS_LIST[i] + ") (arch=" + ARCH_LIST[j] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + } + } + return found; + } + + private InputStream findPluginManifest(File baseLocation) throws IOException { + //Here, we can not use the bundlefile because it may explode the jar and returns a location from which we will not be able to derive the jars location + if (pluginZip != null) + try { + pluginZip.close(); + } catch (IOException e) { + // ignore + } + pluginZip = null; + if (!baseLocation.isDirectory()) { + manifestType |= MANIFEST_TYPE_JAR; + pluginZip = new ZipFile(baseLocation); + } + + if (pluginZip != null) { + ZipEntry manifestEntry = pluginZip.getEntry(PLUGIN_MANIFEST); + if (manifestEntry != null) { + manifestType |= MANIFEST_TYPE_PLUGIN; + return pluginZip.getInputStream(manifestEntry); + } + } else { + File manifestFile = new File(baseLocation, PLUGIN_MANIFEST); + if (manifestFile.exists()) { + manifestType |= MANIFEST_TYPE_PLUGIN; + return new FileInputStream(manifestFile); + } + } + + if (pluginZip != null) { + ZipEntry manifestEntry = pluginZip.getEntry(FRAGMENT_MANIFEST); + if (manifestEntry != null) { + manifestType |= MANIFEST_TYPE_PLUGIN; + return pluginZip.getInputStream(manifestEntry); + } + } else { + File manifestFile = new File(baseLocation, FRAGMENT_MANIFEST); + if (manifestFile.exists()) { + manifestType |= MANIFEST_TYPE_FRAGMENT; + return new FileInputStream(manifestFile); + } + } + + return null; + } + + private List<String> findWSJars(File pluginRoot, String path, boolean filter) { + path = path.substring(4); + List<String> found = new ArrayList<String>(0); + for (int i = 0; i < WS_LIST.length; i++) { + String searchedPath = "ws/" + WS_LIST[i] + path; //$NON-NLS-1$ + if (new File(pluginRoot, searchedPath).exists()) { + found.add(searchedPath + (filter ? ";(ws=" + WS_LIST[i] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + return found; + } + + protected void fillManifest(boolean compatibilityManifest, boolean analyseJars) { + generateManifestVersion(); + generateHeaders(); + generateClasspath(); + generateActivator(); + generatePluginClass(); + if (analyseJars) + generateProvidePackage(); + generateRequireBundle(); + generateLocalizationEntry(); + generateEclipseHeaders(); + if (compatibilityManifest) { + generateTimestamp(); + } + } + + @SuppressWarnings("deprecation") + public void writeManifest(File generationLocation, Dictionary<String, String> manifestToWrite, boolean compatibilityManifest) throws PluginConversionException { + long start = System.currentTimeMillis(); + try { + File parentFile = new File(generationLocation.getParent()); + parentFile.mkdirs(); + generationLocation.createNewFile(); + if (!generationLocation.isFile()) { + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST, this.pluginInfo.getUniqueId(), generationLocation); + throw new PluginConversionException(message); + } + // replaces any eventual existing file + manifestToWrite = new Hashtable<String, String>((Hashtable) manifestToWrite); + // MANIFEST.MF files must be written using UTF-8 + out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generationLocation), UTF_8)); + writeEntry(MANIFEST_VERSION, manifestToWrite.remove(MANIFEST_VERSION)); + writeEntry(GENERATED_FROM, manifestToWrite.remove(GENERATED_FROM)); //Need to do this first uptoDate check expect the generated-from tag to be in the first line + // always attempt to write the Bundle-ManifestVersion header if it exists (bug 109863) + writeEntry(Constants.BUNDLE_MANIFESTVERSION, manifestToWrite.remove(Constants.BUNDLE_MANIFESTVERSION)); + writeEntry(Constants.BUNDLE_NAME, manifestToWrite.remove(Constants.BUNDLE_NAME)); + writeEntry(Constants.BUNDLE_SYMBOLICNAME, manifestToWrite.remove(Constants.BUNDLE_SYMBOLICNAME)); + writeEntry(Constants.BUNDLE_VERSION, manifestToWrite.remove(Constants.BUNDLE_VERSION)); + writeEntry(Constants.BUNDLE_CLASSPATH, manifestToWrite.remove(Constants.BUNDLE_CLASSPATH)); + writeEntry(Constants.BUNDLE_ACTIVATOR, manifestToWrite.remove(Constants.BUNDLE_ACTIVATOR)); + writeEntry(Constants.BUNDLE_VENDOR, manifestToWrite.remove(Constants.BUNDLE_VENDOR)); + writeEntry(Constants.FRAGMENT_HOST, manifestToWrite.remove(Constants.FRAGMENT_HOST)); + writeEntry(Constants.BUNDLE_LOCALIZATION, manifestToWrite.remove(Constants.BUNDLE_LOCALIZATION)); + // always attempt to write the Export-Package header if it exists (bug 109863) + writeEntry(Constants.EXPORT_PACKAGE, manifestToWrite.remove(Constants.EXPORT_PACKAGE)); + // always attempt to write the Provide-Package header if it exists (bug 109863) + writeEntry(Constants.PROVIDE_PACKAGE, manifestToWrite.remove(Constants.PROVIDE_PACKAGE)); + writeEntry(Constants.REQUIRE_BUNDLE, manifestToWrite.remove(Constants.REQUIRE_BUNDLE)); + Enumeration<String> keys = manifestToWrite.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + writeEntry(key, manifestToWrite.get(key)); + } + out.flush(); + } catch (IOException e) { + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST, this.pluginInfo.getUniqueId(), generationLocation); + throw new PluginConversionException(message, e); + } finally { + if (out != null) + try { + out.close(); + } catch (IOException e) { + // only report problems writing to/flushing the file + } + } + if (DEBUG) + System.out.println("Time to write out converted manifest to: " + generationLocation + ": " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + private void generateLocalizationEntry() { + generatedManifest.put(Constants.BUNDLE_LOCALIZATION, PLUGIN_PROPERTIES_FILENAME); + } + + private void generateManifestVersion() { + generatedManifest.put(MANIFEST_VERSION, "1.0"); //$NON-NLS-1$ + } + + private boolean requireRuntimeCompatibility() { + ArrayList<PluginParser.Prerequisite> requireList = pluginInfo.getRequires(); + for (Iterator<PluginParser.Prerequisite> iter = requireList.iterator(); iter.hasNext();) { + if (iter.next().getName().equalsIgnoreCase(PI_RUNTIME_COMPATIBILITY)) + return true; + } + return false; + } + + private void generateActivator() { + if (!pluginInfo.isFragment()) + if (!requireRuntimeCompatibility()) { + String pluginClass = pluginInfo.getPluginClass(); + if (pluginClass != null && !pluginClass.trim().equals("")) //$NON-NLS-1$ + generatedManifest.put(Constants.BUNDLE_ACTIVATOR, pluginClass); + } else { + generatedManifest.put(Constants.BUNDLE_ACTIVATOR, COMPATIBILITY_ACTIVATOR); + } + } + + private void generateClasspath() { + String[] classpath = pluginInfo.getLibrariesName(); + if (classpath.length != 0) + generatedManifest.put(Constants.BUNDLE_CLASSPATH, getStringFromArray(classpath, LIST_SEPARATOR)); + } + + private void generateHeaders() { + if (TARGET31.compareTo(target) <= 0) + generatedManifest.put(Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$ + generatedManifest.put(Constants.BUNDLE_NAME, pluginInfo.getPluginName()); + generatedManifest.put(Constants.BUNDLE_VERSION, pluginInfo.getVersion()); + generatedManifest.put(Constants.BUNDLE_SYMBOLICNAME, getSymbolicNameEntry()); + String provider = pluginInfo.getProviderName(); + if (provider != null) + generatedManifest.put(Constants.BUNDLE_VENDOR, provider); + if (pluginInfo.isFragment()) { + StringBuffer hostBundle = new StringBuffer(); + hostBundle.append(pluginInfo.getMasterId()); + String versionRange = getVersionRange(pluginInfo.getMasterVersion(), pluginInfo.getMasterMatch()); // TODO need to get match rule here! + if (versionRange != null) + hostBundle.append(versionRange); + generatedManifest.put(Constants.FRAGMENT_HOST, hostBundle.toString()); + } + } + + /* + * Generates an entry in the form: + * <symbolic-name>[; singleton=true] + */ + private String getSymbolicNameEntry() { + // false is the default, so don't bother adding anything + if (!pluginInfo.isSingleton()) + return pluginInfo.getUniqueId(); + StringBuffer result = new StringBuffer(pluginInfo.getUniqueId()); + result.append(SEMICOLON); + result.append(Constants.SINGLETON_DIRECTIVE); + String assignment = TARGET31.compareTo(target) <= 0 ? ":=" : "="; //$NON-NLS-1$ //$NON-NLS-2$ + result.append(assignment).append("true"); //$NON-NLS-1$ + return result.toString(); + } + + private void generatePluginClass() { + if (requireRuntimeCompatibility()) { + String pluginClass = pluginInfo.getPluginClass(); + if (pluginClass != null) + generatedManifest.put(Constants.PLUGIN_CLASS, pluginClass); + } + } + + @SuppressWarnings("deprecation") + private void generateProvidePackage() { + Set<String> exports = getExports(); + if (exports != null && exports.size() != 0) { + generatedManifest.put(TARGET31.compareTo(target) <= 0 ? Constants.EXPORT_PACKAGE : Constants.PROVIDE_PACKAGE, getStringFromCollection(exports, LIST_SEPARATOR)); + } + } + + @SuppressWarnings("deprecation") + private void generateRequireBundle() { + ArrayList<PluginParser.Prerequisite> requiredBundles = pluginInfo.getRequires(); + if (requiredBundles.size() == 0) + return; + StringBuffer bundleRequire = new StringBuffer(); + for (Iterator<PluginParser.Prerequisite> iter = requiredBundles.iterator(); iter.hasNext();) { + PluginParser.Prerequisite element = iter.next(); + StringBuffer modImport = new StringBuffer(element.getName()); + String versionRange = getVersionRange(element.getVersion(), element.getMatch()); + if (versionRange != null) + modImport.append(versionRange); + if (element.isExported()) { + if (TARGET31.compareTo(target) <= 0) + modImport.append(';').append(Constants.VISIBILITY_DIRECTIVE).append(":=").append(Constants.VISIBILITY_REEXPORT);//$NON-NLS-1$ + else + modImport.append(';').append(Constants.REPROVIDE_ATTRIBUTE).append("=true");//$NON-NLS-1$ + } + if (element.isOptional()) { + if (TARGET31.compareTo(target) <= 0) + modImport.append(';').append(Constants.RESOLUTION_DIRECTIVE).append(":=").append(Constants.RESOLUTION_OPTIONAL);//$NON-NLS-1$ + else + modImport.append(';').append(Constants.OPTIONAL_ATTRIBUTE).append("=true");//$NON-NLS-1$ + } + bundleRequire.append(modImport.toString()); + if (iter.hasNext()) + bundleRequire.append(LIST_SEPARATOR); + } + generatedManifest.put(Constants.REQUIRE_BUNDLE, bundleRequire.toString()); + } + + private void generateTimestamp() { + // so it is easy to tell which ones are generated + generatedManifest.put(GENERATED_FROM, Long.toString(getTimeStamp(pluginManifestLocation, manifestType)) + ";" + MANIFEST_TYPE_ATTRIBUTE + "=" + manifestType); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @SuppressWarnings("deprecation") + private void generateEclipseHeaders() { + if (pluginInfo.isFragment()) + return; + + String pluginClass = pluginInfo.getPluginClass(); + if (pluginInfo.hasExtensionExtensionPoints() || (pluginClass != null && !pluginClass.trim().equals(""))) //$NON-NLS-1$ + generatedManifest.put(TARGET32.compareTo(target) <= 0 ? Constants.ECLIPSE_LAZYSTART : Constants.ECLIPSE_AUTOSTART, "true"); //$NON-NLS-1$ + } + + private Set<String> getExports() { + Map<String, List<String>> libs = pluginInfo.getLibraries(); + if (libs == null) + return null; + + //If we are in dev mode, then add the binary folders on the list libs with the export clause set to be the cumulation of the export clause of the real libs + if (devProperties != null || DevClassPathHelper.inDevelopmentMode()) { + String[] devClassPath = DevClassPathHelper.getDevClassPath(pluginInfo.getUniqueId(), devProperties); + // collect export clauses + List<String> allExportClauses = new ArrayList<String>(libs.size()); + Set<Map.Entry<String, List<String>>> libEntries = libs.entrySet(); + for (Iterator<Map.Entry<String, List<String>>> iter = libEntries.iterator(); iter.hasNext();) { + Map.Entry<String, List<String>> element = iter.next(); + allExportClauses.addAll(element.getValue()); + } + if (devClassPath != null) { + // bug 88498 + // if there is a devClassPath defined for this plugin and the @ignoredot@ flag is true + // then we will ignore the '.' library specified in the plugin.xml + String[] ignoreDotProp = DevClassPathHelper.getDevClassPath(IGNORE_DOT, devProperties); + if (devClassPath.length > 0 && ignoreDotProp != null && ignoreDotProp.length > 0 && "true".equals(ignoreDotProp[0])) //$NON-NLS-1$ + libs.remove(DOT); + for (int i = 0; i < devClassPath.length; i++) + libs.put(devClassPath[i], allExportClauses); + } + } + + Set<String> result = new TreeSet<String>(); + Set<Map.Entry<String, List<String>>> libEntries = libs.entrySet(); + for (Iterator<Map.Entry<String, List<String>>> iter = libEntries.iterator(); iter.hasNext();) { + Map.Entry<String, List<String>> element = iter.next(); + List<String> filter = element.getValue(); + if (filter.size() == 0) //If the library is not exported, then ignore it + continue; + String libEntryText = element.getKey().trim(); + File libraryLocation; + if (libEntryText.equals(DOT)) + libraryLocation = pluginManifestLocation; + else { + // in development time, libEntries may contain absolute locations (linked folders) + File libEntryAsPath = new File(libEntryText); + libraryLocation = libEntryAsPath.isAbsolute() ? libEntryAsPath : new File(pluginManifestLocation, libEntryText); + } + Set<String> exports = null; + if (libraryLocation.exists()) { + if (libraryLocation.isFile()) + exports = filterExport(getExportsFromJAR(libraryLocation), filter); //TODO Need to handle $xx$ variables + else if (libraryLocation.isDirectory()) + exports = filterExport(getExportsFromDir(libraryLocation), filter); + } else { + List<String> expandedLibs = getLibrariesExpandingVariables(element.getKey(), false); + exports = new HashSet<String>(); + for (Iterator<String> iterator = expandedLibs.iterator(); iterator.hasNext();) { + String libName = iterator.next(); + File libFile = new File(pluginManifestLocation, libName); + if (libFile.isFile()) { + exports.addAll(filterExport(getExportsFromJAR(libFile), filter)); + } + } + } + if (exports != null) + result.addAll(exports); + } + return result; + } + + private Set<String> getExportsFromDir(File location) { + return getExportsFromDir(location, ""); //$NON-NLS-1$ + } + + private Set<String> getExportsFromDir(File location, String packageName) { + String prefix = (packageName.length() > 0) ? (packageName + '.') : ""; //$NON-NLS-1$ + String[] files = location.list(); + Set<String> exportedPaths = new HashSet<String>(); + boolean containsFile = false; + if (files != null) + for (int i = 0; i < files.length; i++) { + if (!isValidPackageName(files[i])) + continue; + File pkgFile = new File(location, files[i]); + if (pkgFile.isDirectory()) + exportedPaths.addAll(getExportsFromDir(pkgFile, prefix + files[i])); + else + containsFile = true; + } + if (containsFile) + // Allow the default package to be provided. If the default package + // contains a File then use "." as the package name to provide for default. + if (packageName.length() > 0) + exportedPaths.add(packageName); + else + exportedPaths.add(DOT); + return exportedPaths; + } + + private Set<String> getExportsFromJAR(File jarFile) { + Set<String> names = new HashSet<String>(); + ZipFile file = null; + try { + file = new ZipFile(jarFile); + } catch (IOException e) { + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_PLUGIN_LIBRARY_IGNORED, jarFile, pluginInfo.getUniqueId()); + adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null)); + return names; + } + //Run through the entries + for (Enumeration<? extends ZipEntry> entriesEnum = file.entries(); entriesEnum.hasMoreElements();) { + ZipEntry entry = entriesEnum.nextElement(); + String name = entry.getName(); + if (!isValidPackageName(name)) + continue; + int lastSlash = name.lastIndexOf("/"); //$NON-NLS-1$ + //Ignore folders that do not contain files + if (lastSlash != -1) { + if (lastSlash != name.length() - 1 && name.lastIndexOf(' ') == -1) + names.add(name.substring(0, lastSlash).replace('/', '.')); + } else { + // Allow the default package to be provided. If the default package + // contains a File then use "." as the package name to provide for default. + names.add(DOT); + } + } + try { + file.close(); + } catch (IOException e) { + // Nothing to do + } + return names; + } + + private List<String> getLibrariesExpandingVariables(String libraryPath, boolean filter) { + String var = hasPrefix(libraryPath); + if (var == null) { + List<String> returnValue = new ArrayList<String>(1); + returnValue.add(libraryPath); + return returnValue; + } + if (var.equals("ws")) { //$NON-NLS-1$ + return findWSJars(pluginManifestLocation, libraryPath, filter); + } + if (var.equals("os")) { //$NON-NLS-1$ + return findOSJars(pluginManifestLocation, libraryPath, filter); + } + return new ArrayList<String>(0); + } + + //return a String representing the string found between the $s + private String hasPrefix(String libPath) { + if (libPath.startsWith("$ws$")) //$NON-NLS-1$ + return "ws"; //$NON-NLS-1$ + if (libPath.startsWith("$os$")) //$NON-NLS-1$ + return "os"; //$NON-NLS-1$ + if (libPath.startsWith("$nl$")) //$NON-NLS-1$ + return "nl"; //$NON-NLS-1$ + return null; + } + + private boolean isValidPackageName(String name) { + if (name.indexOf(' ') > 0 || name.equalsIgnoreCase("META-INF") || name.startsWith("META-INF/")) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + return true; + } + + /** + * Parses the plugin manifest to find out: - the plug-in unique identifier - + * the plug-in version - runtime/libraries entries - the plug-in class - + * the master plugin (for a fragment) + */ + private IPluginInfo parsePluginInfo(InputStream pluginLocation) throws PluginConversionException { + InputStream input = null; + try { + input = new BufferedInputStream(pluginLocation); + return new PluginParser(adaptor, context, target).parsePlugin(input); + } catch (Exception e) { + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_PARSING_PLUGIN_MANIFEST, pluginManifestLocation); + throw new PluginConversionException(message, e); + } finally { + if (input != null) + try { + input.close(); + } catch (IOException e) { + //ignore exception + } + } + } + + public static boolean upToDate(File generationLocation, File pluginLocation, byte manifestType) { + if (!generationLocation.isFile()) + return false; + String secondLine = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(generationLocation))); + reader.readLine(); + secondLine = reader.readLine(); + } catch (IOException e) { + // not a big deal - we could not read an existing manifest + return false; + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + // ignore + } + } + String tag = GENERATED_FROM + ": "; //$NON-NLS-1$ + if (secondLine == null || !secondLine.startsWith(tag)) + return false; + + secondLine = secondLine.substring(tag.length()); + ManifestElement generatedFrom; + try { + generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, secondLine)[0]; + } catch (BundleException be) { + return false; + } + String timestampStr = generatedFrom.getValue(); + try { + return Long.parseLong(timestampStr.trim()) == getTimeStamp(pluginLocation, manifestType); + } catch (NumberFormatException nfe) { + // not a big deal - just a bogus existing manifest that will be ignored + } + return false; + } + + public static long getTimeStamp(File pluginLocation, byte manifestType) { + if ((manifestType & MANIFEST_TYPE_JAR) != 0) + return pluginLocation.lastModified(); + else if ((manifestType & MANIFEST_TYPE_PLUGIN) != 0) + return new File(pluginLocation, PLUGIN_MANIFEST).lastModified(); + else if ((manifestType & MANIFEST_TYPE_FRAGMENT) != 0) + return new File(pluginLocation, FRAGMENT_MANIFEST).lastModified(); + else if ((manifestType & MANIFEST_TYPE_BUNDLE) != 0) + return new File(pluginLocation, Constants.OSGI_BUNDLE_MANIFEST).lastModified(); + return -1; + } + + private void writeEntry(String key, String value) throws IOException { + if (value != null && value.length() > 0) { + out.write(splitOnComma(key + ": " + value)); //$NON-NLS-1$ + out.write('\n'); + } + } + + private String splitOnComma(String value) { + if (value.length() < MAXLINE || value.indexOf(LINE_SEPARATOR) >= 0) + return value; // assume the line is already split + String[] values = ManifestElement.getArrayFromList(value); + if (values == null || values.length == 0) + return value; + StringBuffer sb = new StringBuffer(value.length() + ((values.length - 1) * LIST_SEPARATOR.length())); + for (int i = 0; i < values.length - 1; i++) + sb.append(values[i]).append(LIST_SEPARATOR); + sb.append(values[values.length - 1]); + return sb.toString(); + } + + private String getStringFromArray(String[] values, String separator) { + if (values == null) + return ""; //$NON-NLS-1$ + StringBuffer result = new StringBuffer(); + for (int i = 0; i < values.length; i++) { + if (i > 0) + result.append(separator); + result.append(values[i]); + } + return result.toString(); + } + + private String getStringFromCollection(Collection<String> collection, String separator) { + StringBuffer result = new StringBuffer(); + boolean first = true; + for (Iterator<String> i = collection.iterator(); i.hasNext();) { + if (first) + first = false; + else + result.append(separator); + result.append(i.next()); + } + return result.toString(); + } + + public synchronized Dictionary<String, String> convertManifest(File pluginBaseLocation, boolean compatibility, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps) throws PluginConversionException { + long start = System.currentTimeMillis(); + if (DEBUG) + System.out.println("Convert " + pluginBaseLocation); //$NON-NLS-1$ + init(); + this.target = targetVersion == null ? TARGET32 : new Version(targetVersion); + this.devProperties = devProps; + fillPluginInfo(pluginBaseLocation); + fillManifest(compatibility, analyseJars); + if (DEBUG) + System.out.println("Time to convert manifest for: " + pluginBaseLocation + ": " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return generatedManifest; + } + + public synchronized File convertManifest(File pluginBaseLocation, File bundleManifestLocation, boolean compatibilityManifest, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps) throws PluginConversionException { + convertManifest(pluginBaseLocation, compatibilityManifest, targetVersion, analyseJars, devProps); + if (bundleManifestLocation == null) { + String cacheLocation = FrameworkProperties.getProperty(LocationManager.PROP_MANIFEST_CACHE); + bundleManifestLocation = new File(cacheLocation, pluginInfo.getUniqueId() + '_' + pluginInfo.getVersion() + ".MF"); //$NON-NLS-1$ + } + if (upToDate(bundleManifestLocation, pluginManifestLocation, manifestType)) + return bundleManifestLocation; + writeManifest(bundleManifestLocation, generatedManifest, compatibilityManifest); + return bundleManifestLocation; + } + + private String getVersionRange(String reqVersion, String matchRule) { + if (reqVersion == null) + return null; + + Version minVersion = Version.parseVersion(reqVersion); + String versionRange; + if (matchRule != null) { + if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_PERFECT)) { + versionRange = new VersionRange(minVersion, true, minVersion, true).toString(); + } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_EQUIVALENT)) { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor(), minVersion.getMinor() + 1, 0, ""), false).toString(); //$NON-NLS-1$ + } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_COMPATIBLE)) { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$ + } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)) { + // just return the reqVersion here without any version range + versionRange = reqVersion; + } else { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$ + } + } else { + versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$ + } + + StringBuffer result = new StringBuffer(); + result.append(';').append(Constants.BUNDLE_VERSION_ATTRIBUTE).append('='); + result.append('\"').append(versionRange).append('\"'); + return result.toString(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java new file mode 100644 index 000000000..1448b28ff --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java @@ -0,0 +1,710 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.adaptor; + +import java.io.InputStream; +import java.util.*; +import javax.xml.parsers.SAXParserFactory; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Version; +import org.osgi.util.tracker.ServiceTracker; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Internal class. + */ +public class PluginParser extends DefaultHandler implements IModel { + private static ServiceTracker<SAXParserFactory, SAXParserFactory> xmlTracker = null; + + private PluginInfo manifestInfo = new PluginInfo(); + private BundleContext context; + private FrameworkAdaptor adaptor; + Version target; // The targeted platform for the given manifest + static final Version TARGET21 = new Version(2, 1, 0); + + public class PluginInfo implements IPluginInfo { + String schemaVersion; + String pluginId; + String version; + String vendor; + + // an ordered list of library path names. + List<String> libraryPaths; + // TODO Should get rid of the libraries map and just have a + // list of library export statements instead. Library paths must + // preserve order. + Map<String, List<String>> libraries; //represent the libraries and their export statement + ArrayList<PluginParser.Prerequisite> requires; + private boolean requiresExpanded = false; //indicates if the requires have been processed. + boolean compatibilityFound = false; //set to true is the requirement list contain compatilibity + String pluginClass; + String masterPluginId; + String masterVersion; + String masterMatch; + private Set<String> filters; + String pluginName; + boolean singleton; + boolean fragment; + private final static String TARGET21_STRING = "2.1"; //$NON-NLS-1$ + boolean hasExtensionExtensionPoints = false; + + public boolean isFragment() { + return fragment; + } + + public String toString() { + return "plugin-id: " + pluginId + " version: " + version + " libraries: " + libraries + " class:" + pluginClass + " master: " + masterPluginId + " master-version: " + masterVersion + " requires: " + requires + " singleton: " + singleton; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ + } + + public Map<String, List<String>> getLibraries() { + if (libraries == null) + return new HashMap<String, List<String>>(0); + return libraries; + } + + public ArrayList<Prerequisite> getRequires() { + if (!TARGET21.equals(target) && schemaVersion == null && !requiresExpanded) { + requiresExpanded = true; + if (requires == null) { + requires = new ArrayList<Prerequisite>(1); + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, TARGET21_STRING, false, false, IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)); + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null)); + } else { + //Add elements on the requirement list of ui and help. + for (int i = 0; i < requires.size(); i++) { + Prerequisite analyzed = requires.get(i); + if ("org.eclipse.ui".equals(analyzed.getName())) { //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.workbench.texteditor", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.jface.text", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.editors", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.views", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.ui.ide", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + } else if ("org.eclipse.help".equals(analyzed.getName())) { //$NON-NLS-1$ + requires.add(i + 1, new Prerequisite("org.eclipse.help.base", null, true, analyzed.isExported(), null)); //$NON-NLS-1$ + } else if (PluginConverterImpl.PI_RUNTIME.equals(analyzed.getName()) && !compatibilityFound) { + requires.add(i + 1, new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, analyzed.isExported(), null)); + } + } + if (!requires.contains(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null))) { + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null)); + } + //Remove any prereq on runtime and add a prereq on runtime 2.1 + //This is used to recognize the version for which the given plugin was initially targeted. + Prerequisite runtimePrereq = new Prerequisite(PluginConverterImpl.PI_RUNTIME, null, false, false, null); + requires.remove(runtimePrereq); + requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, TARGET21_STRING, false, false, IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)); + } + } + if (requires == null) + return requires = new ArrayList<Prerequisite>(0); + + return requires; + } + + public String getMasterId() { + return masterPluginId; + } + + public String getMasterVersion() { + return masterVersion; + } + + public String getMasterMatch() { + return masterMatch; + } + + public String getPluginClass() { + return pluginClass; + } + + public String getUniqueId() { + return pluginId; + } + + public String getVersion() { + return version; + } + + public Set<String> getPackageFilters() { + return filters; + } + + public String[] getLibrariesName() { + if (libraryPaths == null) + return new String[0]; + return libraryPaths.toArray(new String[libraryPaths.size()]); + } + + public String getPluginName() { + return pluginName; + } + + public String getProviderName() { + return vendor; + } + + public boolean isSingleton() { + return singleton; + } + + public boolean hasExtensionExtensionPoints() { + return hasExtensionExtensionPoints; + } + + public String getRoot() { + return isFragment() ? FRAGMENT : PLUGIN; + } + + /* + * Provides some basic form of validation. Since plugin/fragment is the only mandatory + * attribute, it is the only one we cara about here. + */ + public String validateForm() { + if (this.pluginId == null) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_ID, getRoot()}); + if (this.pluginName == null) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_NAME, getRoot()}); + if (this.version == null) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_VERSION, getRoot()}); + if (isFragment() && this.masterPluginId == null) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), FRAGMENT_PLUGIN_ID, getRoot()}); + if (isFragment() && this.masterVersion == null) + return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), FRAGMENT_PLUGIN_VERSION, getRoot()}); + return null; + } + } + + // Current State Information + Stack<Integer> stateStack = new Stack<Integer>(); + + // Current object stack (used to hold the current object we are populating in this plugin info + Stack<Object> objectStack = new Stack<Object>(); + Locator locator = null; + + // Valid States + private static final int IGNORED_ELEMENT_STATE = 0; + private static final int INITIAL_STATE = 1; + private static final int PLUGIN_STATE = 2; + private static final int PLUGIN_RUNTIME_STATE = 3; + private static final int PLUGIN_REQUIRES_STATE = 4; + private static final int PLUGIN_EXTENSION_POINT_STATE = 5; + private static final int PLUGIN_EXTENSION_STATE = 6; + private static final int RUNTIME_LIBRARY_STATE = 7; + private static final int LIBRARY_EXPORT_STATE = 8; + private static final int PLUGIN_REQUIRES_IMPORT_STATE = 9; + private static final int FRAGMENT_STATE = 11; + + public PluginParser(FrameworkAdaptor adaptor, BundleContext context, Version target) { + super(); + this.context = context; + this.adaptor = adaptor; + this.target = target; + } + + /** + * Receive a Locator object for document events. + * + * <p> + * By default, do nothing. Application writers may override this method in + * a subclass if they wish to store the locator for use with other document + * events. + * </p> + * + * @param locator A locator for all SAX document events. + * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) + * @see org.xml.sax.Locator + */ + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + public void endDocument() { + // nothing + } + + public void endElement(String uri, String elementName, String qName) { + switch (stateStack.peek().intValue()) { + case IGNORED_ELEMENT_STATE : + stateStack.pop(); + break; + case INITIAL_STATE : + // shouldn't get here + // internalError(Policy.bind("parse.internalStack", elementName)); //$NON-NLS-1$ + break; + case PLUGIN_STATE : + case FRAGMENT_STATE : + break; + case PLUGIN_RUNTIME_STATE : + if (elementName.equals(RUNTIME)) { + stateStack.pop(); + } + break; + case PLUGIN_REQUIRES_STATE : + if (elementName.equals(PLUGIN_REQUIRES)) { + stateStack.pop(); + objectStack.pop(); + } + break; + case PLUGIN_EXTENSION_POINT_STATE : + if (elementName.equals(EXTENSION_POINT)) { + stateStack.pop(); + } + break; + case PLUGIN_EXTENSION_STATE : + if (elementName.equals(EXTENSION)) { + stateStack.pop(); + } + break; + case RUNTIME_LIBRARY_STATE : + if (elementName.equals(LIBRARY)) { + String curLibrary = (String) objectStack.pop(); + if (!curLibrary.trim().equals("")) { //$NON-NLS-1$ + @SuppressWarnings("unchecked") + List<String> exports = (List<String>) objectStack.pop(); + if (manifestInfo.libraries == null) { + manifestInfo.libraries = new HashMap<String, List<String>>(3); + manifestInfo.libraryPaths = new ArrayList<String>(3); + } + manifestInfo.libraries.put(curLibrary, exports); + manifestInfo.libraryPaths.add(curLibrary.replace('\\', '/')); + } + stateStack.pop(); + } + break; + case LIBRARY_EXPORT_STATE : + if (elementName.equals(LIBRARY_EXPORT)) { + stateStack.pop(); + } + break; + case PLUGIN_REQUIRES_IMPORT_STATE : + if (elementName.equals(PLUGIN_REQUIRES_IMPORT)) { + stateStack.pop(); + } + break; + } + } + + public void error(SAXParseException ex) { + logStatus(ex); + } + + public void fatalError(SAXParseException ex) throws SAXException { + logStatus(ex); + throw ex; + } + + public void handleExtensionPointState(String elementName, Attributes attributes) { + // nothing to do for extension-points' children + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + manifestInfo.hasExtensionExtensionPoints = true; + } + + public void handleExtensionState(String elementName, Attributes attributes) { + // nothing to do for extensions' children + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + manifestInfo.hasExtensionExtensionPoints = true; + } + + public void handleInitialState(String elementName, Attributes attributes) { + if (elementName.equals(PLUGIN)) { + stateStack.push(new Integer(PLUGIN_STATE)); + parsePluginAttributes(attributes); + } else if (elementName.equals(FRAGMENT)) { + manifestInfo.fragment = true; + stateStack.push(new Integer(FRAGMENT_STATE)); + parseFragmentAttributes(attributes); + } else { + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + } + + public void handleLibraryExportState(String elementName, Attributes attributes) { + // All elements ignored. + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + } + + public void handleLibraryState(String elementName, Attributes attributes) { + if (elementName.equals(LIBRARY_EXPORT)) { + // Change State + stateStack.push(new Integer(LIBRARY_EXPORT_STATE)); + // The top element on the stack much be a library element + String currentLib = (String) objectStack.peek(); + if (attributes == null) + return; + String maskValue = attributes.getValue("", LIBRARY_EXPORT_MASK); //$NON-NLS-1$ + // pop off the library - already in currentLib + objectStack.pop(); + @SuppressWarnings("unchecked") + List<String> exportMask = (List<String>) objectStack.peek(); + // push library back on + objectStack.push(currentLib); + //Split the export upfront + if (maskValue != null) { + StringTokenizer tok = new StringTokenizer(maskValue, ","); //$NON-NLS-1$ + while (tok.hasMoreTokens()) { + String value = tok.nextToken(); + if (!exportMask.contains(maskValue)) + exportMask.add(value.trim()); + } + } + return; + } + if (elementName.equals(LIBRARY_PACKAGES)) { + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + return; + } + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + return; + } + + public void handlePluginState(String elementName, Attributes attributes) { + if (elementName.equals(RUNTIME)) { + // We should only have one Runtime element in a plugin or fragment + Object whatIsIt = objectStack.peek(); + if ((whatIsIt instanceof PluginInfo) && ((PluginInfo) objectStack.peek()).libraries != null) { + // This is at least the 2nd Runtime element we have hit. Ignore it. + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + return; + } + stateStack.push(new Integer(PLUGIN_RUNTIME_STATE)); + // Push a new vector to hold all the library entries objectStack.push(new Vector()); + return; + } + if (elementName.equals(PLUGIN_REQUIRES)) { + stateStack.push(new Integer(PLUGIN_REQUIRES_STATE)); + // Push a new vector to hold all the prerequisites + objectStack.push(new ArrayList<String>()); + parseRequiresAttributes(attributes); + return; + } + if (elementName.equals(EXTENSION_POINT)) { + // mark the plugin as singleton and ignore all elements under extension (if there are any) + manifestInfo.singleton = true; + stateStack.push(new Integer(PLUGIN_EXTENSION_POINT_STATE)); + return; + } + if (elementName.equals(EXTENSION)) { + // mark the plugin as singleton and ignore all elements under extension (if there are any) + manifestInfo.singleton = true; + stateStack.push(new Integer(PLUGIN_EXTENSION_STATE)); + return; + } + // If we get to this point, the element name is one we don't currently accept. + // Set the state to indicate that this element will be ignored + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + + public void handleRequiresImportState(String elementName, Attributes attributes) { + // All elements ignored. + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + } + + public void handleRequiresState(String elementName, Attributes attributes) { + if (elementName.equals(PLUGIN_REQUIRES_IMPORT)) { + parsePluginRequiresImport(attributes); + return; + } + // If we get to this point, the element name is one we don't currently accept. + // Set the state to indicate that this element will be ignored + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + + public void handleRuntimeState(String elementName, Attributes attributes) { + if (elementName.equals(LIBRARY)) { + // Change State + stateStack.push(new Integer(RUNTIME_LIBRARY_STATE)); + // Process library attributes + parseLibraryAttributes(attributes); + return; + } + // If we get to this point, the element name is one we don't currently accept. + // Set the state to indicate that this element will be ignored + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + internalError(elementName); + } + + private void logStatus(SAXParseException ex) { + String name = ex.getSystemId(); + if (name == null) + name = ""; //$NON-NLS-1$ + else + name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$ + String msg; + if (name.equals("")) //$NON-NLS-1$ + msg = NLS.bind(EclipseAdaptorMsg.parse_error, ex.getMessage()); + else + msg = NLS.bind(EclipseAdaptorMsg.parse_errorNameLineColumn, new String[] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()}); + + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, msg, 0, ex, null); + adaptor.getFrameworkLog().log(entry); + } + + synchronized public PluginInfo parsePlugin(InputStream in) throws Exception { + SAXParserFactory factory = acquireXMLParsing(context); + if (factory == null) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_CONVERTER_NO_SAX_FACTORY, 0, null, null); + adaptor.getFrameworkLog().log(entry); + return null; + } + + factory.setNamespaceAware(true); + factory.setNamespaceAware(true); + try { + factory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$ + } catch (SAXException se) { + // ignore; we can still operate without string-interning + } + factory.setValidating(false); + factory.newSAXParser().parse(in, this); + return manifestInfo; + } + + public static SAXParserFactory acquireXMLParsing(BundleContext context) { + if (xmlTracker == null) { + xmlTracker = new ServiceTracker<SAXParserFactory, SAXParserFactory>(context, "javax.xml.parsers.SAXParserFactory", null); //$NON-NLS-1$ + xmlTracker.open(); + } + SAXParserFactory result = xmlTracker.getService(); + if (result != null) + return result; + // backup to using jaxp to create a new instance + return SAXParserFactory.newInstance(); + } + + public static void releaseXMLParsing() { + if (xmlTracker != null) + xmlTracker.close(); + } + + public void parseFragmentAttributes(Attributes attributes) { + // process attributes + objectStack.push(manifestInfo); + int len = attributes.getLength(); + for (int i = 0; i < len; i++) { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i).trim(); + if (attrName.equals(FRAGMENT_ID)) + manifestInfo.pluginId = attrValue; + else if (attrName.equals(FRAGMENT_NAME)) + manifestInfo.pluginName = attrValue; + else if (attrName.equals(FRAGMENT_VERSION)) + manifestInfo.version = attrValue; + else if (attrName.equals(FRAGMENT_PROVIDER)) + manifestInfo.vendor = attrValue; + else if (attrName.equals(FRAGMENT_PLUGIN_ID)) + manifestInfo.masterPluginId = attrValue; + else if (attrName.equals(FRAGMENT_PLUGIN_VERSION)) + manifestInfo.masterVersion = attrValue; + else if (attrName.equals(FRAGMENT_PLUGIN_MATCH)) + manifestInfo.masterMatch = attrValue; + } + } + + public void parseLibraryAttributes(Attributes attributes) { + // Push a vector to hold the export mask + objectStack.push(new ArrayList<String>()); + String current = attributes.getValue("", LIBRARY_NAME); //$NON-NLS-1$ + objectStack.push(current); + } + + public void parsePluginAttributes(Attributes attributes) { + // process attributes + objectStack.push(manifestInfo); + int len = attributes.getLength(); + for (int i = 0; i < len; i++) { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i).trim(); + if (attrName.equals(PLUGIN_ID)) + manifestInfo.pluginId = attrValue; + else if (attrName.equals(PLUGIN_NAME)) + manifestInfo.pluginName = attrValue; + else if (attrName.equals(PLUGIN_VERSION)) + manifestInfo.version = attrValue; + else if (attrName.equals(PLUGIN_VENDOR) || (attrName.equals(PLUGIN_PROVIDER))) + manifestInfo.vendor = attrValue; + else if (attrName.equals(PLUGIN_CLASS)) + manifestInfo.pluginClass = attrValue; + } + } + + public class Prerequisite { + String name; + String version; + boolean optional; + boolean export; + String match; + + public boolean isExported() { + return export; + } + + public String getMatch() { + return match; + } + + public String getName() { + return name; + } + + public boolean isOptional() { + return optional; + } + + public String getVersion() { + return version; + } + + public Prerequisite(String preqName, String prereqVersion, boolean isOtional, boolean isExported, String prereqMatch) { + name = preqName; + version = prereqVersion; + optional = isOtional; + export = isExported; + match = prereqMatch; + } + + public String toString() { + return name; + } + + public boolean equals(Object prereq) { + if (!(prereq instanceof Prerequisite)) + return false; + return name.equals(((Prerequisite) prereq).name); + } + + public int hashCode() { + return name.hashCode(); + } + } + + public void parsePluginRequiresImport(Attributes attributes) { + if (manifestInfo.requires == null) { + manifestInfo.requires = new ArrayList<Prerequisite>(); + // to avoid cycles + // if (!manifestInfo.pluginId.equals(PluginConverterImpl.PI_RUNTIME)) //$NON-NLS-1$ + // manifestInfo.requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, null, false, false, null)); //$NON-NLS-1$ + } + // process attributes + String plugin = attributes.getValue("", PLUGIN_REQUIRES_PLUGIN); //$NON-NLS-1$ + if (plugin == null) + return; + if (plugin.equals(PluginConverterImpl.PI_BOOT)) + return; + if (plugin.equals(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY)) + manifestInfo.compatibilityFound = true; + String version = attributes.getValue("", PLUGIN_REQUIRES_PLUGIN_VERSION); //$NON-NLS-1$ + String optional = attributes.getValue("", PLUGIN_REQUIRES_OPTIONAL); //$NON-NLS-1$ + String export = attributes.getValue("", PLUGIN_REQUIRES_EXPORT); //$NON-NLS-1$ + String match = attributes.getValue("", PLUGIN_REQUIRES_MATCH); //$NON-NLS-1$ + manifestInfo.requires.add(new Prerequisite(plugin, version, "true".equalsIgnoreCase(optional) ? true : false, "true".equalsIgnoreCase(export) ? true : false, match)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void parseRequiresAttributes(Attributes attributes) { + //Nothing to do. + } + + static String replace(String s, String from, String to) { + String str = s; + int fromLen = from.length(); + int toLen = to.length(); + int ix = str.indexOf(from); + while (ix != -1) { + str = str.substring(0, ix) + to + str.substring(ix + fromLen); + ix = str.indexOf(from, ix + toLen); + } + return str; + } + + public void startDocument() { + stateStack.push(new Integer(INITIAL_STATE)); + } + + public void startElement(String uri, String elementName, String qName, Attributes attributes) { + switch (stateStack.peek().intValue()) { + case INITIAL_STATE : + handleInitialState(elementName, attributes); + break; + case FRAGMENT_STATE : + case PLUGIN_STATE : + handlePluginState(elementName, attributes); + break; + case PLUGIN_RUNTIME_STATE : + handleRuntimeState(elementName, attributes); + break; + case PLUGIN_REQUIRES_STATE : + handleRequiresState(elementName, attributes); + break; + case PLUGIN_EXTENSION_POINT_STATE : + handleExtensionPointState(elementName, attributes); + break; + case PLUGIN_EXTENSION_STATE : + handleExtensionState(elementName, attributes); + break; + case RUNTIME_LIBRARY_STATE : + handleLibraryState(elementName, attributes); + break; + case LIBRARY_EXPORT_STATE : + handleLibraryExportState(elementName, attributes); + break; + case PLUGIN_REQUIRES_IMPORT_STATE : + handleRequiresImportState(elementName, attributes); + break; + default : + stateStack.push(new Integer(IGNORED_ELEMENT_STATE)); + } + } + + public void warning(SAXParseException ex) { + logStatus(ex); + } + + private void internalError(String elementName) { + FrameworkLogEntry error; + String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_PARSE_UNKNOWNTOP_ELEMENT, elementName); + error = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, (manifestInfo.pluginId == null ? message : "Plug-in : " + manifestInfo.pluginId + ", " + message), 0, null, null); //$NON-NLS-1$ //$NON-NLS-2$ + adaptor.getFrameworkLog().log(error); + } + + /** + * @throws SAXException + */ + public void processingInstruction(String instructionTarget, String data) throws SAXException { + // Since 3.0, a processing instruction of the form <?eclipse version="3.0"?> at + // the start of the manifest file is used to indicate the plug-in manifest + // schema version in effect. Pre-3.0 (i.e., 2.1) plug-in manifest files do not + // have one of these, and this is how we can distinguish the manifest of a + // pre-3.0 plug-in from a post-3.0 one (for compatibility tranformations). + if (instructionTarget.equalsIgnoreCase("eclipse")) { //$NON-NLS-1$ + // just the presence of this processing instruction indicates that this + // plug-in is at least 3.0 + manifestInfo.schemaVersion = "3.0"; //$NON-NLS-1$ + StringTokenizer tokenizer = new StringTokenizer(data, "=\""); //$NON-NLS-1$ + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.equalsIgnoreCase("version")) { //$NON-NLS-1$ + if (!tokenizer.hasMoreTokens()) { + break; + } + manifestInfo.schemaVersion = tokenizer.nextToken(); + break; + } + } + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java new file mode 100644 index 000000000..0ef4bffcf --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2003, 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.core.runtime.internal.adaptor; + +/** + * Internal class. + */ +public class Semaphore { + protected long notifications; + + public Semaphore(int count) { + notifications = count; + } + + /** + * Attempts to acquire this semaphore. Returns only when the semaphore has been acquired. + */ + public synchronized void acquire() { + while (true) { + if (notifications > 0) { + notifications--; + return; + } + try { + wait(); + } catch (InterruptedException e) { + //Ignore + } + } + } + + /** + * Attempts to acquire this semaphore. Returns true if it was successfully acquired, + * and false otherwise. + */ + public synchronized boolean acquire(long delay) { + long start = System.currentTimeMillis(); + long timeLeft = delay; + while (true) { + if (notifications > 0) { + notifications--; + return true; + } + if (timeLeft <= 0) + return false; + try { + wait(timeLeft); + } catch (InterruptedException e) { + //Ignore + } + timeLeft = start + delay - System.currentTimeMillis(); + } + } + + public synchronized void release() { + notifications++; + notifyAll(); + } + + // for debug only + public String toString() { + return "Semaphore(" + notifications + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java new file mode 100644 index 000000000..c197861e9 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 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.core.runtime.internal.adaptor; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import org.eclipse.osgi.framework.internal.core.BundleURLConnection; +import org.eclipse.osgi.service.urlconversion.URLConverter; +import org.eclipse.osgi.util.NLS; + +/** + * The service implementation that allows bundleresource or bundleentry + * URLs to be converted to native file URLs on the local file system. + * + * <p>Internal class.</p> + */ +public class URLConverterImpl implements URLConverter { + + /* (non-Javadoc) + * @see org.eclipse.osgi.service.urlconversion.URLConverter#toFileURL(java.net.URL) + */ + public URL toFileURL(URL url) throws IOException { + URLConnection connection = url.openConnection(); + if (connection instanceof BundleURLConnection) { + URL result = ((BundleURLConnection) connection).getFileURL(); + /* If we got a connection then we know the resource exists in + * the bundle but if connection.getFileURL returned null then there + * was a problem extracting the file to disk. See bug 259241. + **/ + if (result == null) + throw new IOException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_PLUGIN_EXTRACTION_PROBLEM, url)); + return result; + } + return url; + } + + /* (non-Javadoc) + * @see org.eclipse.osgi.service.urlconversion.URLConverter#resolve(java.net.URL) + */ + public URL resolve(URL url) throws IOException { + URLConnection connection = url.openConnection(); + if (connection instanceof BundleURLConnection) + return ((BundleURLConnection) connection).getLocalURL(); + return url; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java new file mode 100644 index 000000000..f021dfc9b --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.stats; + +import java.util.ArrayList; +import java.util.List; + +/** + * Contains information about activated bundles and acts as the main + * entry point for logging bundle activity. + */ + +public class BundleStats { + public String symbolicName; + public long id; + public int activationOrder; + private long timestamp; //timeStamp at which this bundle has been activated + private boolean duringStartup; // indicate if the bundle has been activated during startup + private long startupTime; // the time took by the bundle to startup + private long startupMethodTime; // the time took to run the startup method + + // Indicate the position of the activation trace in the file + private long traceStart = -1; + private long traceEnd = -1; + + //To keep bundle parentage + private List<BundleStats> bundlesActivated = new ArrayList<BundleStats>(3); // TODO create lazily + private BundleStats activatedBy = null; + + public BundleStats(String name, long id) { + this.symbolicName = name; + this.id = id; + } + + public long getTimestamp() { + return timestamp; + } + + public int getActivationOrder() { + return activationOrder; + } + + protected void activated(BundleStats info) { + bundlesActivated.add(info); + } + + public BundleStats getActivatedBy() { + return activatedBy; + } + + public long getId() { + return id; + } + + public String getSymbolicName() { + return symbolicName; + } + + public long getStartupTime() { + return startupTime; + } + + public long getStartupMethodTime() { + return startupMethodTime; + } + + public boolean isStartupBundle() { + return duringStartup; + } + + public int getClassLoadCount() { + if (!StatsManager.MONITOR_CLASSES) + return 0; + ClassloaderStats loader = ClassloaderStats.getLoader(symbolicName); + return loader == null ? 0 : loader.getClassLoadCount(); + } + + public long getClassLoadTime() { + if (!StatsManager.MONITOR_CLASSES) + return 0; + ClassloaderStats loader = ClassloaderStats.getLoader(symbolicName); + return loader == null ? 0 : loader.getClassLoadTime(); + } + + public List<BundleStats> getBundlesActivated() { + return bundlesActivated; + } + + public long getTraceStart() { + return traceStart; + } + + public long getTraceEnd() { + return traceEnd; + } + + protected void setTimestamp(long value) { + timestamp = value; + } + + protected void setActivationOrder(int value) { + activationOrder = value; + } + + protected void setTraceStart(long time) { + traceStart = time; + } + + protected void setDuringStartup(boolean value) { + duringStartup = value; + } + + protected void endActivation() { + startupTime = System.currentTimeMillis() - timestamp; + } + + protected void setTraceEnd(long position) { + traceEnd = position; + } + + protected void setActivatedBy(BundleStats value) { + activatedBy = value; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java new file mode 100644 index 000000000..092dd156e --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.stats; + +import java.util.ArrayList; +import java.util.List; + +/** + * Maintain statistics about a loaded class. + */ +public class ClassStats { + private String className; // fully qualified name of this class + private ClassloaderStats classloader; // the classloader that loaded this class + private int loadOrder = -1; + + private long timestamp; // time at which this class was loaded + private long timeLoading; // time to load the class + private long timeLoadingOthers = 0; // time spent loading classes which has been triggered by this class + + // parentage of classes loaded + private ClassStats loadedBy = null; // a reference to the class that loaded this class + private List<ClassStats> loaded = new ArrayList<ClassStats>(2); // a reference to the classes that this class loaded + + private boolean duringStartup; // indicate if the class was loaded during platform startup + + //information to retrieve the stacktrace from the file + private long traceStart = -1; + private long traceEnd = -1; + + public ClassStats(String name, ClassloaderStats classloader) { + className = name; + timestamp = System.currentTimeMillis(); + duringStartup = StatsManager.isBooting(); + this.classloader = classloader; + } + + public void setLoadOrder(int order) { + loadOrder = order; + } + + public void loadingDone() { + timeLoading = System.currentTimeMillis() - timestamp; + } + + public long getTimeLoading() { + return timeLoading; + } + + public long getLocalTimeLoading() { + return timeLoading - timeLoadingOthers; + } + + public void addTimeLoadingOthers(long time) { + timeLoadingOthers = timeLoadingOthers + time; + } + + public long getTraceStart() { + return traceStart; + } + + public long getTraceEnd() { + return traceEnd; + } + + public void setTraceStart(long position) { + traceStart = position; + } + + public void setTraceEnd(long position) { + traceEnd = position; + } + + public void loaded(ClassStats child) { + loaded.add(child); + } + + public void setLoadedBy(ClassStats parent) { + loadedBy = parent; + } + + public ClassStats getLoadedBy() { + return loadedBy; + } + + public List<ClassStats> getLoadedClasses() { + return loaded; + } + + public String getClassName() { + return className; + } + + public boolean isStartupClass() { + return duringStartup; + } + + public ClassloaderStats getClassloader() { + return classloader; + } + + public int getLoadOrder() { + return loadOrder; + } + + public long getTimestamp() { + return timestamp; + } + + public void toBaseClass() { + duringStartup = true; + loadOrder = -2; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java new file mode 100644 index 000000000..f34da3da3 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.stats; + +import java.io.*; +import java.util.*; + +/** + * Contains information about the classes and the bundles loaded by a given classloader. Typically there is one classloader per plugin so at levels above boot, this equates to information about + * classes and bundles in a plugin. + */ +public class ClassloaderStats { + private String id; + private long loadingTime; // time spent loading classes + /** + * classes loaded by the plugin (key: class name, value: ClassStats) + */ + private Map<String, ClassStats> classes = Collections.synchronizedMap(new HashMap<String, ClassStats>(20)); + private List<ResourceBundleStats> bundles = new ArrayList<ResourceBundleStats>(2); // bundles loaded + + private boolean keepTraces = false; // indicate whether or not the traces of classes loaded are kept + + // filters to indicate which classes we want to keep the traces + private static List<String> packageFilters = new ArrayList<String>(4); // filters on a package basis + private static Set<String> pluginFilters = new HashSet<String>(5); // filters on a plugin basis + + private static Hashtable<Thread, Stack<ClassStats>> classStacks = new Hashtable<Thread, Stack<ClassStats>>(); // represents the classes that are currently being loaded + /** + * a dictionary of the classloaderStats (key: pluginId, value: ClassloaderStats) + */ + private static Map<String, ClassloaderStats> loaders = Collections.synchronizedMap(new HashMap<String, ClassloaderStats>(20)); + public static File traceFile; + + static { + if (StatsManager.TRACE_CLASSES || StatsManager.TRACE_BUNDLES) + initializeTraceOptions(); + } + + private static void initializeTraceOptions() { + // create the trace file + String filename = StatsManager.TRACE_FILENAME; + traceFile = new File(filename); + traceFile.delete(); + + //load the filters + if (!StatsManager.TRACE_CLASSES) + return; + filename = StatsManager.TRACE_FILTERS; + if (filename == null || filename.length() == 0) + return; + try { + File filterFile = new File(filename); + System.out.print("Runtime tracing elements defined in: " + filterFile.getAbsolutePath() + "..."); //$NON-NLS-1$ //$NON-NLS-2$ + InputStream input = new FileInputStream(filterFile); + System.out.println(" Loaded."); //$NON-NLS-1$ + Properties filters = new Properties() { + private static final long serialVersionUID = 3546359543853365296L; + + public synchronized Object put(Object key, Object value) { + addFilters((String) key, (String) value); + return null; + } + }; + try { + filters.load(input); + } finally { + input.close(); + } + } catch (IOException e) { + System.out.println(" No trace filters loaded."); //$NON-NLS-1$ + } + } + + protected static void addFilters(String key, String value) { + String[] filters = StatsManager.getArrayFromList(value); + if ("plugins".equals(key)) //$NON-NLS-1$ + pluginFilters.addAll(Arrays.asList(filters)); + if ("packages".equals(key)) //$NON-NLS-1$ + packageFilters.addAll(Arrays.asList(filters)); + } + + public static void startLoadingClass(String id, String className) { + findLoader(id).startLoadClass(className); + } + + // get and create if does not exist + private static ClassloaderStats findLoader(String id) { + synchronized (loaders) { + ClassloaderStats result = loaders.get(id); + if (result == null) { + result = new ClassloaderStats(id); + loaders.put(id, result); + } + return result; + } + } + + public static synchronized Stack<ClassStats> getClassStack() { + Stack<ClassStats> result = classStacks.get(Thread.currentThread()); + if (result == null) { + result = new Stack<ClassStats>(); + classStacks.put(Thread.currentThread(), result); + } + return result; + } + + public static ClassloaderStats[] getLoaders() { + //the parameter to toArray is of size zero for thread safety, otherwise this + //could return an array with null entries if the map shrinks concurrently + return loaders.values().toArray(new ClassloaderStats[0]); + } + + public static void endLoadingClass(String id, String className, boolean success) { + findLoader(id).endLoadClass(className, success); + } + + public static void loadedBundle(String id, ResourceBundleStats info) { + findLoader(id).loadedBundle(info); + } + + public static ClassloaderStats getLoader(String id) { + return loaders.get(id); + } + + public ClassloaderStats(String id) { + this.id = id; + keepTraces = pluginFilters.contains(id); + } + + public void addBaseClasses(String[] baseClasses) { + for (int i = 0; i < baseClasses.length; i++) { + String name = baseClasses[i]; + if (classes.get(name) == null) { + ClassStats value = new ClassStats(name, this); + value.toBaseClass(); + classes.put(name, value); + } + } + } + + private void loadedBundle(ResourceBundleStats bundle) { + bundles.add(bundle); + } + + public List<ResourceBundleStats> getBundles() { + return bundles; + } + + private synchronized void startLoadClass(String name) { + getClassStack().push(findClass(name)); + } + + // internal method that return the existing classStats or creates one + private ClassStats findClass(String name) { + ClassStats result = classes.get(name); + return result == null ? new ClassStats(name, this) : result; + } + + private synchronized void endLoadClass(String name, boolean success) { + ClassStats current = getClassStack().pop(); + if (!success) + return; + if (current.getLoadOrder() >= 0) + return; + + classes.put(name, current); + current.setLoadOrder(classes.size()); + current.loadingDone(); + traceLoad(name, current); + + // is there something on the load stack. if so, link them together... + Stack<ClassStats> classStack = getClassStack(); + if (classStack.size() != 0) { + // get the time spent loading cli and subtract its load time from the class that requires loading + ClassStats previous = classStack.peek(); + previous.addTimeLoadingOthers(current.getTimeLoading()); + current.setLoadedBy(previous); + previous.loaded(current); + } else { + loadingTime = loadingTime + current.getTimeLoading(); + } + } + + private void traceLoad(String name, ClassStats target) { + // Stack trace code + if (!keepTraces) { + boolean found = false; + for (int i = 0; !found && i < packageFilters.size(); i++) + if (name.startsWith(packageFilters.get(i))) + found = true; + if (!found) + return; + } + + // Write the stack trace. The position in the file are set to the corresponding classStat object + try { + target.setTraceStart(traceFile.length()); + PrintWriter output = new PrintWriter(new FileOutputStream(traceFile.getAbsolutePath(), true)); + try { + output.println("Loading class: " + name); //$NON-NLS-1$ + output.println("Class loading stack:"); //$NON-NLS-1$ + output.println("\t" + name); //$NON-NLS-1$ + Stack<ClassStats> classStack = getClassStack(); + for (int i = classStack.size() - 1; i >= 0; i--) + output.println("\t" + classStack.get(i).getClassName()); //$NON-NLS-1$ + output.println("Stack trace:"); //$NON-NLS-1$ + new Throwable().printStackTrace(output); + } finally { + output.close(); + } + target.setTraceEnd(traceFile.length()); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + public int getClassLoadCount() { + return classes.size(); + } + + public long getClassLoadTime() { + return loadingTime; + } + + public ClassStats[] getClasses() { + //the parameter to toArray is of size zero for thread safety, otherwise this + //could return an array with null entries if the map shrinks concurrently + return classes.values().toArray(new ClassStats[0]); + } + + public String getId() { + return id; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java new file mode 100644 index 000000000..2c072f91c --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.stats; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; + +/** + * BundleStats is used to represent information about loaded bundle. A + * bundlestats instance represents only one bundle. + */ + +public class ResourceBundleStats { + private String pluginId; // the plugin loading this bundle + private String fileName; // the filename of the bundle + private int keyCount = 0; // number of keys in the bundle + private int keySize = 0; // size of the keys in the bundle + private int valueSize = 0; // size of the values in the bundle + private long hashSize = 0; // size of the hashtable + private long fileSize = 0; + + private static int sizeOf(String value) { + return 44 + (2 * value.length()); + } + + private static int sizeOf(Properties value) { + return (int) Math.round(44 + (16 + (value.size() * 1.25 * 4)) + (24 * value.size())); + } + + public ResourceBundleStats(String pluginId, String fileName, URL input) { + this.pluginId = pluginId; + this.fileName = fileName; + initialize(input); + } + + public ResourceBundleStats(String pluginId, String fileName, ResourceBundle bundle) { + this.pluginId = pluginId; + this.fileName = fileName; + initialize(bundle); + } + + /** + * Compute the size of bundle + */ + private void initialize(ResourceBundle bundle) { + for (Enumeration<String> keys = bundle.getKeys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + keySize += sizeOf(key); + valueSize += sizeOf(bundle.getString(key)); + keyCount++; + } + } + + /** + * Compute the size of stream which represents a property file + */ + private void initialize(URL url) { + InputStream stream = null; + Properties props = new Properties(); + try { + try { + stream = url.openStream(); + fileSize = stream.available(); + props.load(stream); + for (Iterator<Object> iter = props.keySet().iterator(); iter.hasNext();) { + String key = (String) iter.next(); + keySize += sizeOf(key); + valueSize += sizeOf(props.getProperty(key)); + keyCount++; + } + hashSize = sizeOf(props); + } finally { + if (stream != null) + stream.close(); + } + } catch (IOException e) { + // ignore exceptions as they will be handled when the stream + // is loaded for real. See callers. + } + } + + public long getHashSize() { + return hashSize; + } + + public int getKeyCount() { + return keyCount; + } + + public String getPluginId() { + return pluginId; + } + + public int getKeySize() { + return keySize; + } + + public int getValueSize() { + return valueSize; + } + + public long getTotalSize() { + return keySize + valueSize + hashSize; + } + + public String getFileName() { + return fileName; + } + + public long getFileSize() { + return fileSize; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java new file mode 100644 index 000000000..8356d808e --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime.internal.stats; + +import java.io.*; +import java.net.URL; +import java.util.*; +import org.eclipse.osgi.baseadaptor.HookConfigurator; +import org.eclipse.osgi.baseadaptor.HookRegistry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook; +import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry; +import org.eclipse.osgi.baseadaptor.loader.ClasspathManager; +import org.eclipse.osgi.framework.adaptor.BundleWatcher; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.debug.FrameworkDebugOptions; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.Bundle; + +public class StatsManager implements BundleWatcher, HookConfigurator, ClassLoadingStatsHook { + // This connect bundles and their info, and so allows to access the info without running through + // the bundle registry. This map only contains activated bundles. The key is the bundle Id + private Hashtable<Long, BundleStats> bundles = new Hashtable<Long, BundleStats>(20); + private Map<Thread, Stack<BundleStats>> activationStacks = new HashMap<Thread, Stack<BundleStats>>(5); + private static boolean booting = true; // the state of the platform. This value is changed by the InternalPlatform itself. + + private static StatsManager defaultInstance; + + public static boolean MONITOR_ACTIVATION = false; + public static boolean MONITOR_CLASSES = false; + public static boolean MONITOR_RESOURCES = false; + public static String TRACE_FILENAME = "runtime.traces"; //$NON-NLS-1$ + public static String TRACE_FILTERS = "trace.properties"; //$NON-NLS-1$ + public static boolean TRACE_CLASSES = false; + public static boolean TRACE_BUNDLES = false; + public static final String FRAMEWORK_SYMBOLICNAME = "org.eclipse.osgi"; //$NON-NLS-1$ + + //Option names for spies + private static final String OPTION_MONITOR_ACTIVATION = FRAMEWORK_SYMBOLICNAME + "/monitor/activation"; //$NON-NLS-1$ + private static final String OPTION_MONITOR_CLASSES = FRAMEWORK_SYMBOLICNAME + "/monitor/classes"; //$NON-NLS-1$ + private static final String OPTION_MONITOR_RESOURCES = FRAMEWORK_SYMBOLICNAME + "/monitor/resources"; //$NON-NLS-1$ + private static final String OPTION_TRACE_BUNDLES = FRAMEWORK_SYMBOLICNAME + "/trace/activation"; //$NON-NLS-1$ + private static final String OPTION_TRACE_CLASSES = FRAMEWORK_SYMBOLICNAME + "/trace/classLoading"; //$NON-NLS-1$ + private static final String OPTION_TRACE_FILENAME = FRAMEWORK_SYMBOLICNAME + "/trace/filename"; //$NON-NLS-1$ + private static final String OPTION_TRACE_FILTERS = FRAMEWORK_SYMBOLICNAME + "/trace/filters"; //$NON-NLS-1$ + + static { + setDebugOptions(); + } + + public static StatsManager getDefault() { + if (defaultInstance == null) { + defaultInstance = new StatsManager(); + defaultInstance.initialize(); + } + return defaultInstance; + } + + public static void setDebugOptions() { + FrameworkDebugOptions options = FrameworkDebugOptions.getDefault(); + // may be null if debugging is not enabled + if (options == null) + return; + MONITOR_ACTIVATION = options.getBooleanOption(OPTION_MONITOR_ACTIVATION, false); + MONITOR_CLASSES = options.getBooleanOption(OPTION_MONITOR_CLASSES, false); + MONITOR_RESOURCES = options.getBooleanOption(OPTION_MONITOR_RESOURCES, false); + TRACE_CLASSES = options.getBooleanOption(OPTION_TRACE_CLASSES, false); + TRACE_BUNDLES = options.getBooleanOption(OPTION_TRACE_BUNDLES, false); + TRACE_FILENAME = options.getOption(OPTION_TRACE_FILENAME, TRACE_FILENAME); + TRACE_FILTERS = options.getOption(OPTION_TRACE_FILTERS, TRACE_FILTERS); + } + + public static void doneBooting() { + booting = false; + } + + public static boolean isBooting() { + return booting; + } + + /** + * Returns the result of converting a list of comma-separated tokens into an array + * + * @return the array of string tokens + * @param prop the initial comma-separated string + */ + public static String[] getArrayFromList(String prop) { + return ManifestElement.getArrayFromList(prop, ","); //$NON-NLS-1$ + } + + private void initialize() { + // add the system bundle + BundleStats bundle = findBundle(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, 0); + bundle.setTimestamp(System.currentTimeMillis()); + bundle.setActivationOrder(bundles.size()); + bundle.setDuringStartup(booting); + } + + public void watchBundle(Bundle bundle, int type) { + switch (type) { + case BundleWatcher.START_ACTIVATION : + startActivation(bundle); + break; + case BundleWatcher.END_ACTIVATION : + endActivation(bundle); + break; + } + } + + public void startActivation(Bundle bundle) { + // should be called from a synchronized location to protect against concurrent updates + BundleStats info = findBundle(bundle.getSymbolicName(), bundle.getBundleId()); + info.setTimestamp(System.currentTimeMillis()); + info.setActivationOrder(bundles.size()); + info.setDuringStartup(booting); + + Stack<BundleStats> activationStack = activationStacks.get(Thread.currentThread()); + if (activationStack == null) { + activationStack = new Stack<BundleStats>(); + activationStacks.put(Thread.currentThread(), activationStack); + } + + // set the parentage of activation + if (activationStack.size() != 0) { + BundleStats activatedBy = activationStack.peek(); + activatedBy.activated(info); + info.setActivatedBy(activatedBy); + } + activationStack.push(info); + + if (TRACE_BUNDLES == true) { + traceActivate(bundle, info); + } + } + + public void endActivation(Bundle symbolicName) { + Stack<BundleStats> activationStack = activationStacks.get(Thread.currentThread()); + // should be called from a synchronized location to protect against concurrent updates + BundleStats info = activationStack.pop(); + info.endActivation(); + } + + private void traceActivate(Bundle bundle, BundleStats info) { + try { + PrintWriter output = new PrintWriter(new FileOutputStream(ClassloaderStats.traceFile.getAbsolutePath(), true)); + try { + long startPosition = ClassloaderStats.traceFile.length(); + output.println("Activating bundle: " + bundle.getSymbolicName()); //$NON-NLS-1$ + output.println("Bundle activation stack:"); //$NON-NLS-1$ + Stack<BundleStats> activationStack = activationStacks.get(Thread.currentThread()); + for (int i = activationStack.size() - 1; i >= 0; i--) + output.println("\t" + activationStack.get(i).getSymbolicName()); //$NON-NLS-1$ + output.println("Class loading stack:"); //$NON-NLS-1$ + Stack<ClassStats> classStack = ClassloaderStats.getClassStack(); + for (int i = classStack.size() - 1; i >= 0; i--) + output.println("\t" + classStack.get(i).getClassName()); //$NON-NLS-1$ + output.println("Stack trace:"); //$NON-NLS-1$ + new Throwable().printStackTrace(output); + info.setTraceStart(startPosition); + } finally { + output.close(); + info.setTraceEnd(ClassloaderStats.traceFile.length()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public BundleStats findBundle(String symbolicName, long id) { + BundleStats result = bundles.get(new Long(id)); + try { + if (result == null) { + result = new BundleStats(symbolicName, id); + bundles.put(new Long(id), result); + } + } catch (IllegalAccessError e) { + e.printStackTrace(); + } + return result; + } + + public BundleStats[] getBundles() { + return bundles.values().toArray(new BundleStats[bundles.size()]); + } + + public BundleStats getBundle(long id) { + return bundles.get(new Long(id)); + } + + /** + * @throws ClassNotFoundException + */ + public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException { + if (StatsManager.MONITOR_CLASSES) //Support for performance analysis + ClassloaderStats.startLoadingClass(getClassloaderId(manager), name); + } + + public void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) { + if (StatsManager.MONITOR_CLASSES) + ClassloaderStats.endLoadingClass(getClassloaderId(manager), name, clazz != null); + } + + public void preFindLocalResource(String name, ClasspathManager manager) { + // do nothing + } + + public void postFindLocalResource(String name, URL resource, ClasspathManager manager) { + if (StatsManager.MONITOR_RESOURCES) + if (resource != null && name.endsWith(".properties")) //$NON-NLS-1$ + ClassloaderStats.loadedBundle(getClassloaderId(manager), new ResourceBundleStats(getClassloaderId(manager), name, resource)); + return; + } + + public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { + // do nothing + } + + private String getClassloaderId(ClasspathManager loader) { + return loader.getBaseData().getSymbolicName(); + } + + public void addHooks(HookRegistry hookRegistry) { + if (Debug.MONITOR_ACTIVATION) + hookRegistry.addWatcher(StatsManager.getDefault()); + if (StatsManager.MONITOR_CLASSES || StatsManager.MONITOR_RESOURCES) + hookRegistry.addClassLoadingStatsHook(StatsManager.getDefault()); + } +} |