blob: d757bcf11ca315fe42d5e947b9129bb4f78b1c71 [file] [log] [blame]
/*******************************************************************************
* 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.internal.registry.osgi;
import java.io.*;
import java.net.URL;
import java.util.*;
import org.eclipse.core.internal.registry.ExtensionRegistry;
import org.eclipse.core.internal.registry.RegistryMessages;
import org.eclipse.core.internal.runtime.ResourceTranslator;
import org.eclipse.core.internal.runtime.RuntimeLog;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
/**
* A listener for bundle events. When a bundles come and go we look to see
* if there are any extensions or extension points and update the registry accordingly.
* Using a Synchronous listener here is important. If the
* bundle activator code tries to access the registry to get its extension
* points, we need to ensure that they are in the registry before the
* bundle start is called. By listening sync we are able to ensure that
* happens.
*/
public class EclipseBundleListener implements SynchronousBundleListener {
private static final String PLUGIN_MANIFEST = "plugin.xml"; //$NON-NLS-1$
private static final String FRAGMENT_MANIFEST = "fragment.xml"; //$NON-NLS-1$
private ExtensionRegistry registry;
private RegistryStrategyOSGI strategy;
private Object token;
private HashMap dynamicAddStateStamps = new HashMap();
private long currentStateStamp[] = new long[] {0};
public EclipseBundleListener(ExtensionRegistry registry, Object key, RegistryStrategyOSGI strategy) {
this.registry = registry;
this.token = key;
this.strategy = strategy;
}
public void bundleChanged(BundleEvent event) {
/* Only should listen for RESOLVED and UNRESOLVED events.
*
* When a bundle is updated the Framework will publish an UNRESOLVED and
* then a RESOLVED event which should cause the bundle to be removed
* and then added back into the registry.
*
* When a bundle is uninstalled the Framework should publish an UNRESOLVED
* event and then an UNINSTALLED event so the bundle will have been removed
* by the UNRESOLVED event before the UNINSTALLED event is published.
*
* When a bundle is refreshed from PackageAdmin an UNRESOLVED event will be
* published which will remove the bundle from the registry. If the bundle
* can be RESOLVED after a refresh then a RESOLVED event will be published
* which will add the bundle back. This is required because the classloader
* will have been refreshed for the bundle so all extensions and extension
* points for the bundle must be refreshed.
*/
Bundle bundle = event.getBundle();
switch (event.getType()) {
case BundleEvent.RESOLVED :
synchronized (currentStateStamp) {
long newStateStamp = registry.computeState();
if (currentStateStamp[0] != newStateStamp) {
// new state stamp; clear the dynamicaddStateStamps
currentStateStamp[0] = newStateStamp;
dynamicAddStateStamps.clear();
}
}
addBundle(bundle, true);
break;
case BundleEvent.UNRESOLVED :
removeBundle(bundle);
break;
}
}
public void processBundles(Bundle[] bundles) {
for (int i = 0; i < bundles.length; i++) {
if (isBundleResolved(bundles[i]))
addBundle(bundles[i], false);
else
removeBundle(bundles[i]);
}
}
private boolean isBundleResolved(Bundle bundle) {
return (bundle.getState() & (Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING | Bundle.STOPPING)) != 0;
}
private void removeBundle(Bundle bundle) {
long timestamp = 0;
if (strategy.checkContributionsTimestamp()) {
URL pluginManifest = getExtensionURL(bundle, false);
if (pluginManifest != null)
timestamp = strategy.getExtendedTimestamp(bundle, pluginManifest);
}
registry.remove(Long.toString(bundle.getBundleId()), timestamp);
}
static public URL getExtensionURL(Bundle bundle, boolean report) {
// bail out if system bundle
if (bundle.getBundleId() == 0)
return null;
// bail out if the bundle does not have a symbolic name
if (bundle.getSymbolicName() == null)
return null;
boolean isFragment = OSGIUtils.getDefault().isFragment(bundle);
String manifestName = isFragment ? FRAGMENT_MANIFEST : PLUGIN_MANIFEST;
URL extensionURL = bundle.getEntry(manifestName);
if (extensionURL == null)
return null;
// If the bundle is not a singleton, then it is not added
if (!isSingleton(bundle)) {
if (report && !isGeneratedManifest(bundle)) {
String message = NLS.bind(RegistryMessages.parse_nonSingleton, bundle.getSymbolicName());
RuntimeLog.log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, message, null));
}
return null;
}
if (!isFragment)
return extensionURL;
// If the bundle is a fragment being added to a non singleton host, then it is not added
Bundle[] hosts = OSGIUtils.getDefault().getHosts(bundle);
if (hosts == null)
return null; // should never happen?
if (isSingleton(hosts[0]))
return extensionURL;
if (report) {
// if the host is not a singleton we always report the error; even if the host has a generated manifest
String message = NLS.bind(RegistryMessages.parse_nonSingletonFragment, bundle.getSymbolicName(), hosts[0].getSymbolicName());
RuntimeLog.log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, message, null));
}
return null;
}
private static boolean isGeneratedManifest(Bundle bundle) {
return bundle.getHeaders("").get("Generated-from") != null; //$NON-NLS-1$ //$NON-NLS-2$
}
private void addBundle(Bundle bundle, boolean checkNLSFragments) {
if (checkNLSFragments)
checkForNLSFragment(bundle);
// if the given bundle already exists in the registry then return.
// note that this does not work for update cases.
IContributor contributor = ContributorFactoryOSGi.createContributor(bundle);
if (registry.hasContributor(contributor))
return;
URL pluginManifest = getExtensionURL(bundle, true);
if (pluginManifest == null)
return;
InputStream is;
try {
is = new BufferedInputStream(pluginManifest.openStream());
} catch (IOException ex) {
is = null;
}
if (is == null)
return;
ResourceBundle translationBundle = null;
try {
translationBundle = ResourceTranslator.getResourceBundle(bundle);
} catch (MissingResourceException e) {
//Ignore the exception
}
long timestamp = 0;
if (strategy.checkContributionsTimestamp())
timestamp = strategy.getExtendedTimestamp(bundle, pluginManifest);
registry.addContribution(is, contributor, true, pluginManifest.getPath(), translationBundle, token, timestamp);
}
private void checkForNLSFragment(Bundle bundle) {
if (!OSGIUtils.getDefault().isFragment(bundle)) {
// only need to worry about fragments
synchronized (currentStateStamp) {
// mark this host as processed for the current state stamp.
dynamicAddStateStamps.put(Long.toString(bundle.getBundleId()), new Long(currentStateStamp[0]));
}
return;
}
Bundle[] hosts = OSGIUtils.getDefault().getHosts(bundle);
if (hosts == null)
return;
// check to see if the hosts should be refreshed because the fragment contains NLS properties files.
for (int i = 0; i < hosts.length; i++)
checkForNLSFiles(hosts[i], bundle);
}
private void checkForNLSFiles(Bundle host, Bundle fragment) {
String hostID = Long.toString(host.getBundleId());
synchronized (currentStateStamp) {
Long hostStateStamp = (Long) dynamicAddStateStamps.get(hostID);
if (hostStateStamp != null && currentStateStamp[0] == hostStateStamp.longValue())
return; // already processed this host
}
Bundle[] fragments = OSGIUtils.getDefault().getFragments(host);
boolean refresh = false;
// check host first
if (hasNLSFilesFor(host, fragment)) {
refresh = true;
} else {
// check the fragment provides NLS for other fragments of this host
for (int i = 0; i < fragments.length && !refresh; i++) {
if (fragment.equals(fragments[i]))
continue; // skip fragment that was just resolved; it will be added in by the caller
if (hasNLSFilesFor(fragments[i], fragment)) {
refresh = true;
}
}
}
if (refresh) {
// force the host and fragments to be removed and added back
removeBundle(host);
addBundle(host, false);
for (int i = 0; i < fragments.length; i++) {
if (fragment.equals(fragments[i]))
continue; // skip fragment that was just resolved; it will be added in by the caller
removeBundle(fragments[i]);
addBundle(fragments[i], false);
}
synchronized (currentStateStamp) {
// mark this host as processed for the current state stamp.
dynamicAddStateStamps.put(hostID, new Long(currentStateStamp[0]));
}
}
}
private boolean hasNLSFilesFor(Bundle target, Bundle fragment) {
if (!registry.hasContributor(Long.toString(target.getBundleId())))
return false;
// get the base localization path from the target
Dictionary targetHeaders = target.getHeaders(""); //$NON-NLS-1$
String localization = (String) targetHeaders.get(Constants.BUNDLE_LOCALIZATION);
if (localization == null)
// localization may be empty in which case we should check the default
localization = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
// we do a simple check to make sure the default nls path exists in the target;
// this is for performance reasons, but I'm not sure it is valid because a target could ship without the default nls properties file but this seems very unlikely
URL baseNLS = target.getEntry(localization + ".properties"); //$NON-NLS-1$
if (baseNLS == null)
return false;
int lastSlash = localization.lastIndexOf('/');
if (lastSlash == localization.length() - 1)
return false; // just to be safe
String baseDir = lastSlash < 0 ? "" : localization.substring(0, lastSlash); //$NON-NLS-1$
String filePattern = (lastSlash < 0 ? localization : localization.substring(lastSlash + 1)) + "_*.properties"; //$NON-NLS-1$
Enumeration nlsFiles = fragment.findEntries(baseDir, filePattern, false);
return nlsFiles != null;
}
private static boolean isSingleton(Bundle bundle) {
Dictionary allHeaders = bundle.getHeaders(""); //$NON-NLS-1$
String symbolicNameHeader = (String) allHeaders.get(Constants.BUNDLE_SYMBOLICNAME);
try {
if (symbolicNameHeader != null) {
ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameHeader);
if (symbolicNameElements.length > 0) {
String singleton = symbolicNameElements[0].getDirective(Constants.SINGLETON_DIRECTIVE);
if (singleton == null)
singleton = symbolicNameElements[0].getAttribute(Constants.SINGLETON_DIRECTIVE);
if (!"true".equalsIgnoreCase(singleton)) { //$NON-NLS-1$
String manifestVersion = (String) allHeaders.get(org.osgi.framework.Constants.BUNDLE_MANIFESTVERSION);
if (manifestVersion == null) {//the header was not defined for previous versions of the bundle
//3.0 bundles without a singleton attributes are still being accepted
if (OSGIUtils.getDefault().getBundle(symbolicNameElements[0].getValue()) == bundle)
return true;
}
return false;
}
}
}
} catch (BundleException e1) {
//This can't happen because the fwk would have rejected the bundle
}
return true;
}
}