diff options
Diffstat (limited to 'bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java')
-rw-r--r-- | bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java new file mode 100644 index 000000000..71fe5a28a --- /dev/null +++ b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java @@ -0,0 +1,498 @@ +/******************************************************************************* + * 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.osgi.internal.debug; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import org.eclipse.osgi.internal.framework.EquinoxConfiguration; +import org.eclipse.osgi.service.debug.*; +import org.osgi.framework.*; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * The DebugOptions implementation class that allows accessing the list of debug options specified + * for the application as well as creating {@link DebugTrace} objects for the purpose of having + * dynamic enablement of debug tracing. + * + * @since 3.1 + */ +public class FrameworkDebugOptions implements DebugOptions, ServiceTrackerCustomizer<DebugOptionsListener, DebugOptionsListener> { + + private static final String OSGI_DEBUG = "osgi.debug"; //$NON-NLS-1$ + private static final String OSGI_DEBUG_VERBOSE = "osgi.debug.verbose"; //$NON-NLS-1$ + public static final String PROP_TRACEFILE = "osgi.tracefile"; //$NON-NLS-1$ + /** The default name of the .options file if loading when the -debug command-line argument is used */ + private static final String OPTIONS = ".options"; //$NON-NLS-1$ + + /** monitor used to lock the options maps */ + private final Object lock = new Object(); + /** A current map of all the options with values set */ + private Properties options = null; + /** A map of all the disabled options with values set at the time debug was disabled */ + private Properties disabledOptions = null; + /** A cache of all of the bundles <code>DebugTrace</code> in the format <key,value> --> <bundle name, DebugTrace> */ + protected final Map<String, DebugTrace> debugTraceCache = new HashMap<String, DebugTrace>(); + /** The File object to store messages. This value may be null. */ + protected File outFile = null; + /** Is verbose debugging enabled? Changing this value causes a new tracing session to start. */ + protected boolean verboseDebug = true; + private final EquinoxConfiguration environmentInfo; + private volatile BundleContext context; + private volatile ServiceTracker<DebugOptionsListener, DebugOptionsListener> listenerTracker; + + public FrameworkDebugOptions(EquinoxConfiguration environmentInfo) { + this.environmentInfo = environmentInfo; + // check if verbose debugging was set during initialization. This needs to be set even if debugging is disabled + this.verboseDebug = Boolean.valueOf(environmentInfo.getConfiguration(OSGI_DEBUG_VERBOSE, Boolean.TRUE.toString())).booleanValue(); + // if no debug option was specified, don't even bother to try. + // Must ensure that the options slot is null as this is the signal to the + // platform that debugging is not enabled. + String debugOptionsFilename = environmentInfo.getConfiguration(OSGI_DEBUG); + if (debugOptionsFilename == null) + return; + options = new Properties(); + URL optionsFile; + if (debugOptionsFilename.length() == 0) { + // default options location is user.dir (install location may be r/o so + // is not a good candidate for a trace options that need to be updatable by + // by the user) + String userDir = System.getProperty("user.dir").replace(File.separatorChar, '/'); //$NON-NLS-1$ + if (!userDir.endsWith("/")) //$NON-NLS-1$ + userDir += "/"; //$NON-NLS-1$ + debugOptionsFilename = new File(userDir, OPTIONS).toString(); + } + optionsFile = buildURL(debugOptionsFilename, false); + if (optionsFile == null) { + System.out.println("Unable to construct URL for options file: " + debugOptionsFilename); //$NON-NLS-1$ + return; + } + System.out.print("Debug options:\n " + optionsFile.toExternalForm()); //$NON-NLS-1$ + try { + InputStream input = optionsFile.openStream(); + try { + options.load(input); + System.out.println(" loaded"); //$NON-NLS-1$ + } finally { + input.close(); + } + } catch (FileNotFoundException e) { + System.out.println(" not found"); //$NON-NLS-1$ + } catch (IOException e) { + System.out.println(" did not parse"); //$NON-NLS-1$ + e.printStackTrace(System.out); + } + // trim off all the blanks since properties files don't do that. + for (Object key : options.keySet()) { + options.put(key, ((String) options.get(key)).trim()); + } + } + + public void start(BundleContext bc) { + this.context = bc; + listenerTracker = new ServiceTracker<DebugOptionsListener, DebugOptionsListener>(bc, DebugOptionsListener.class.getName(), this); + listenerTracker.open(); + } + + public void stop(BundleContext bc) { + listenerTracker.close(); + listenerTracker = null; + this.context = null; + } + + @SuppressWarnings("deprecation") + private static URL buildURL(String spec, boolean trailingSlash) { + if (spec == null) + return null; + boolean isFile = spec.startsWith("file:"); //$NON-NLS-1$ + try { + if (isFile) + return adjustTrailingSlash(new File(spec.substring(5)).toURL(), trailingSlash); + return new URL(spec); + } catch (MalformedURLException e) { + // if we failed and it is a file spec, there is nothing more we can do + // otherwise, try to make the spec into a file URL. + if (isFile) + return null; + try { + return adjustTrailingSlash(new File(spec).toURL(), trailingSlash); + } catch (MalformedURLException e1) { + return null; + } + } + } + + private static URL adjustTrailingSlash(URL url, boolean trailingSlash) throws MalformedURLException { + String file = url.getFile(); + if (trailingSlash == (file.endsWith("/"))) //$NON-NLS-1$ + return url; + file = trailingSlash ? file + "/" : file.substring(0, file.length() - 1); //$NON-NLS-1$ + return new URL(url.getProtocol(), url.getHost(), file); + } + + /** + * @see DebugOptions#getBooleanOption(String, boolean) + */ + public boolean getBooleanOption(String option, boolean defaultValue) { + String optionValue = getOption(option); + return optionValue != null ? optionValue.equalsIgnoreCase("true") : defaultValue; //$NON-NLS-1$ + } + + /** + * @see DebugOptions#getOption(String) + */ + public String getOption(String option) { + return getOption(option, null); + } + + /** + * @see DebugOptions#getOption(String, String) + */ + public String getOption(String option, String defaultValue) { + synchronized (lock) { + if (options != null) { + return options.getProperty(option, defaultValue); + } + } + return defaultValue; + } + + /** + * @see DebugOptions#getIntegerOption(String, int) + */ + public int getIntegerOption(String option, int defaultValue) { + String value = getOption(option); + try { + return value == null ? defaultValue : Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public Map<String, String> getOptions() { + Map<String, String> snapShot = new HashMap<String, String>(); + synchronized (lock) { + if (options != null) + snapShot.putAll((Map) options); + else if (disabledOptions != null) + snapShot.putAll((Map) disabledOptions); + } + return snapShot; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#getAllOptions() + */ + String[] getAllOptions() { + + String[] optionsArray = null; + synchronized (lock) { + if (options != null) { + optionsArray = new String[options.size()]; + final Iterator<Map.Entry<Object, Object>> entrySetIterator = options.entrySet().iterator(); + int i = 0; + while (entrySetIterator.hasNext()) { + Map.Entry<Object, Object> entry = entrySetIterator.next(); + optionsArray[i] = ((String) entry.getKey()) + "=" + ((String) entry.getValue()); //$NON-NLS-1$ + i++; + } + } + } + if (optionsArray == null) { + optionsArray = new String[1]; // TODO this is strange; null is the only element so we can print null in writeSession + } + return optionsArray; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#removeOption(java.lang.String) + */ + public void removeOption(String option) { + if (option == null) + return; + String fireChangedEvent = null; + synchronized (lock) { + if (options != null && options.remove(option) != null) { + fireChangedEvent = getSymbolicName(option); + } + } + // Send the options change event outside the sync block + if (fireChangedEvent != null) { + optionsChanged(fireChangedEvent); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#setOption(java.lang.String, java.lang.String) + */ + public void setOption(String option, String value) { + + if (option == null || value == null) { + throw new IllegalArgumentException("The option and value must not be null."); //$NON-NLS-1$ + } + String fireChangedEvent = null; + value = value != null ? value.trim() : null; + synchronized (lock) { + if (options != null) { + // get the current value + String currentValue = options.getProperty(option); + + if (currentValue != null) { + if (!currentValue.equals(value)) { + fireChangedEvent = getSymbolicName(option); + } + } else { + if (value != null) { + fireChangedEvent = getSymbolicName(option); + } + } + if (fireChangedEvent != null) { + options.put(option, value); + } + } + } + // Send the options change event outside the sync block + if (fireChangedEvent != null) { + optionsChanged(fireChangedEvent); + } + } + + private String getSymbolicName(String option) { + int firstSlashIndex = option.indexOf("/"); //$NON-NLS-1$ + if (firstSlashIndex > 0) + return option.substring(0, firstSlashIndex); + return null; + } + + @SuppressWarnings("cast") + public void setOptions(Map<String, String> ops) { + if (ops == null) + throw new IllegalArgumentException("The options must not be null."); //$NON-NLS-1$ + Properties newOptions = new Properties(); + for (Iterator<Map.Entry<String, String>> entries = ops.entrySet().iterator(); entries.hasNext();) { + Map.Entry<String, String> entry = entries.next(); + if (!(entry.getKey() instanceof String) || !(entry.getValue() instanceof String)) + throw new IllegalArgumentException("Option keys and values must be of type String: " + entry.getKey() + "=" + entry.getValue()); //$NON-NLS-1$ //$NON-NLS-2$ + newOptions.put(entry.getKey(), entry.getValue().trim()); + } + Set<String> fireChangesTo = null; + + synchronized (lock) { + if (options == null) { + disabledOptions = newOptions; + // no events to fire + return; + } + fireChangesTo = new HashSet<String>(); + // first check for removals + for (Iterator<Object> keys = options.keySet().iterator(); keys.hasNext();) { + String key = (String) keys.next(); + if (!newOptions.containsKey(key)) { + String symbolicName = getSymbolicName(key); + if (symbolicName != null) + fireChangesTo.add(symbolicName); + } + } + // now check for changes to existing values + for (Iterator<Map.Entry<Object, Object>> newEntries = newOptions.entrySet().iterator(); newEntries.hasNext();) { + Map.Entry<Object, Object> entry = newEntries.next(); + String existingValue = (String) options.get(entry.getKey()); + if (!entry.getValue().equals(existingValue)) { + String symbolicName = getSymbolicName((String) entry.getKey()); + if (symbolicName != null) + fireChangesTo.add(symbolicName); + } + } + // finally set the actual options + options = newOptions; + } + if (fireChangesTo != null) + for (Iterator<String> iChanges = fireChangesTo.iterator(); iChanges.hasNext();) + optionsChanged(iChanges.next()); + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#isDebugEnabled() + */ + public boolean isDebugEnabled() { + synchronized (lock) { + return options != null; + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#setDebugEnabled() + */ + public void setDebugEnabled(boolean enabled) { + boolean fireChangedEvent = false; + synchronized (lock) { + if (enabled) { + if (options != null) + return; + // notify the trace that a new session is started + EclipseDebugTrace.newSession = true; + + // enable platform debugging - there is no .options file + environmentInfo.setConfiguration(OSGI_DEBUG, ""); //$NON-NLS-1$ + if (disabledOptions != null) { + options = disabledOptions; + disabledOptions = null; + // fire changed event to indicate some options were re-enabled + fireChangedEvent = true; + } else { + options = new Properties(); + } + } else { + if (options == null) + return; + // disable platform debugging. + environmentInfo.clearConfiguration(OSGI_DEBUG); + if (options.size() > 0) { + // Save the current options off in case debug is re-enabled + disabledOptions = options; + // fire changed event to indicate some options were disabled + fireChangedEvent = true; + } + options = null; + } + } + if (fireChangedEvent) { + // (Bug 300911) need to fire event to listeners that options have been disabled + optionsChanged("*"); //$NON-NLS-1$ + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#createTrace(java.lang.String) + */ + public final DebugTrace newDebugTrace(String bundleSymbolicName) { + + return this.newDebugTrace(bundleSymbolicName, null); + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#createTrace(java.lang.String, java.lang.Class) + */ + public final DebugTrace newDebugTrace(String bundleSymbolicName, Class<?> traceEntryClass) { + + DebugTrace debugTrace = null; + synchronized (debugTraceCache) { + debugTrace = debugTraceCache.get(bundleSymbolicName); + if (debugTrace == null) { + debugTrace = new EclipseDebugTrace(bundleSymbolicName, this, traceEntryClass); + debugTraceCache.put(bundleSymbolicName, debugTrace); + } + } + return debugTrace; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#getFile() + */ + public final File getFile() { + + return this.outFile; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#setFile(java.io.File) + */ + public synchronized void setFile(final File traceFile) { + + this.outFile = traceFile; + environmentInfo.setConfiguration(PROP_TRACEFILE, this.outFile.getAbsolutePath()); + // the file changed so start a new session + EclipseDebugTrace.newSession = true; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#getVerbose() + */ + boolean isVerbose() { + + return this.verboseDebug; + } + + EquinoxConfiguration getConfiguration() { + return this.environmentInfo; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.service.debug.DebugOptions#setVerbose(boolean) + */ + public synchronized void setVerbose(final boolean verbose) { + + this.verboseDebug = verbose; + // the verbose flag changed so start a new session + EclipseDebugTrace.newSession = true; + } + + /** + * Notifies the trace listener for the specified bundle that its option-path has changed. + * @param bundleSymbolicName The bundle of the owning trace listener to notify. + */ + private void optionsChanged(String bundleSymbolicName) { + // use osgi services to get the listeners + BundleContext bc = context; + if (bc == null) + return; + // do not use the service tracker because that is only used to call all listeners initially when they are registered + // here we only want the services with the specified name. + ServiceReference<?>[] listenerRefs = null; + try { + listenerRefs = bc.getServiceReferences(DebugOptionsListener.class.getName(), "(" + DebugOptions.LISTENER_SYMBOLICNAME + "=" + bundleSymbolicName + ")"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ + } catch (InvalidSyntaxException e) { + // consider logging; should not happen + } + if (listenerRefs == null) + return; + for (int i = 0; i < listenerRefs.length; i++) { + DebugOptionsListener service = (DebugOptionsListener) bc.getService(listenerRefs[i]); + if (service == null) + continue; + try { + service.optionsChanged(this); + } catch (Throwable t) { + // TODO consider logging + } finally { + bc.ungetService(listenerRefs[i]); + } + } + } + + public DebugOptionsListener addingService(ServiceReference<DebugOptionsListener> reference) { + DebugOptionsListener listener = context.getService(reference); + listener.optionsChanged(this); + return listener; + } + + public void modifiedService(ServiceReference<DebugOptionsListener> reference, DebugOptionsListener service) { + // nothing + } + + public void removedService(ServiceReference<DebugOptionsListener> reference, DebugOptionsListener service) { + context.ungetService(reference); + } +}
\ No newline at end of file |