blob: 856f447e8d63420e1eb5b71674c05872f5b22200 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.core.osgi;
import java.beans.Introspector;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.core.startup.CoreBridge;
import org.netbeans.core.startup.Main;
import org.netbeans.core.startup.RunLevel;
import org.netbeans.core.startup.Splash;
import org.openide.modules.ModuleInstall;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbCollections;
import org.openide.util.SharedClassObject;
import org.openide.util.lookup.Lookups;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.launch.Framework;
import org.osgi.service.url.AbstractURLStreamHandlerService;
import org.osgi.service.url.URLConstants;
import org.osgi.service.url.URLStreamHandlerService;
/**
* Initializes critical NetBeans infrastructure inside an OSGi container.
*/
public class Activator implements BundleActivator, SynchronousBundleListener {
private static final Logger LOG = Logger.getLogger(Activator.class.getName());
public Activator() {}
/** Bundles which have been loaded or are in line to be loaded. */
private DependencyQueue<String,Bundle> queue;
private BundleContext context;
private Framework framework;
public @Override void start(final BundleContext context) throws Exception {
if (System.getProperty("netbeans.home") != null) {
throw new IllegalStateException("Should not be run from inside regular NetBeans module system");
}
String storage = context.getProperty(Constants.FRAMEWORK_STORAGE);
if (storage != null) {
System.setProperty("netbeans.user", storage);
}
System.setProperty("TopSecurityManager.disable", "true");
NbBundle.setBranding(System.getProperty("branding.token"));
OSGiMainLookup.initialize(context);
queue = new DependencyQueue<String,Bundle>();
this.context = context;
framework = ((Framework) context.getBundle(0));
if (framework.getState() == Bundle.STARTING) {
LOG.fine("framework still starting");
final AtomicReference<FrameworkListener> frameworkListener = new AtomicReference<FrameworkListener>();
frameworkListener.set(new FrameworkListener() {
public @Override void frameworkEvent(FrameworkEvent event) {
if (event.getType() == FrameworkEvent.STARTED) {
// System.err.println("framework started");
context.removeFrameworkListener(frameworkListener.get());
context.addBundleListener(Activator.this);
processLoadedBundles();
}
}
});
context.addFrameworkListener(frameworkListener.get());
} else {
LOG.fine("framework already started");
context.addBundleListener(this);
processLoadedBundles();
}
}
private void processLoadedBundles() {
List<Bundle> toLoad = new ArrayList<Bundle>();
for (Bundle b : context.getBundles()) {
if (b.getState() == Bundle.ACTIVE) {
Dictionary<?,?> headers = b.getHeaders();
toLoad.addAll(queue.offer(b, provides(headers), requires(headers), needs(headers)));
}
}
// System.err.println("processing already loaded bundles: " + toLoad);
load(toLoad);
}
public @Override void stop(BundleContext context) throws Exception {}
public @Override void bundleChanged(BundleEvent event) {
Bundle bundle = event.getBundle();
switch (event.getType()) {
case BundleEvent.STARTED:
// System.err.println("started " + bundle.getSymbolicName());
Dictionary<?,?> headers = bundle.getHeaders();
load(queue.offer(bundle, provides(headers), requires(headers), needs(headers)));
break;
case BundleEvent.STOPPED:
// System.err.println("stopped " + bundle.getSymbolicName());
if (framework.getState() == Bundle.STOPPING) {
// System.err.println("fwork stopping during " + bundle.getSymbolicName());
// ActiveQueue.stop();
} else {
unload(queue.retract(bundle));
}
break;
}
}
static Set<String> provides(Dictionary<?,?> headers) {
Set<String> deps = new TreeSet<String>(splitTokens((String) headers.get("OpenIDE-Module-Provides")));
String name = (String) headers.get(Constants.BUNDLE_SYMBOLICNAME);
if (name != null) {
name = name.replaceFirst(";.+", "");
deps.add("cnb." + name);
if (name.equals("org.openide.modules")) {
CoreBridge.defineOsTokens(deps);
}
}
return deps;
}
static Set<String> requires(Dictionary<?,?> headers) {
Set<String> deps = new TreeSet<String>();
String v = (String) headers.get(Constants.REQUIRE_BUNDLE);
if (v != null) {
// PackageAdmin.getRequiredBundles is not suitable for this - it is backwards.
// XXX try to follow the spec more closely; this will work at least for headers created by MakeOSGi:
for (String item : v.split(", ")) {
deps.add("cnb." + item.replaceFirst(";.+", ""));
}
}
// XXX also check for BUNDLE_SYMBOLICNAME_ATTRIBUTE in IMPORT_PACKAGE (though not currently used by MakeOSGi)
deps.addAll(splitTokens((String) headers.get("OpenIDE-Module-Requires")));
return deps;
}
static Set<String> needs(Dictionary<?,?> headers) {
return splitTokens((String) headers.get("OpenIDE-Module-Needs"));
}
private static Set<String> splitTokens(String tokens) {
if (tokens == null) {
return Collections.emptySet();
}
Set<String> split = new TreeSet<String>(Arrays.asList(tokens.split("[, ]+")));
split.remove("");
return split;
}
static final Map<Bundle,ModuleInstall> installers = new HashMap<Bundle,ModuleInstall>();
private void load(List<Bundle> bundles) {
if (bundles.isEmpty() || bundles.size() == 1 && bundles.iterator().next().getBundleId() == 0) {
return;
}
LOG.log(Level.FINE, "loading: {0}", bundles);
OSGiMainLookup.bundlesAdded(bundles);
boolean showWindowSystem = false; // trigger for showing main window and setting up related GUI elements
boolean loadServicesFolder = false;
for (Bundle bundle : bundles) {
registerUrlProtocolHandlers(bundle);
if (bundle.getSymbolicName().equals("org.netbeans.core")) { // NOI18N
loadServicesFolder = true;
} else if (bundle.getSymbolicName().equals("org.netbeans.bootstrap")) { // NOI18N
System.setProperty("netbeans.buildnumber", bundle.getVersion().getQualifier()); // NOI18N
} else if (bundle.getSymbolicName().equals("org.netbeans.core.windows")) { // NOI18N
showWindowSystem = true;
}
}
OSGiRepository.DEFAULT.addLayersFor(bundles);
if (loadServicesFolder) {
OSGiMainLookup.loadServicesFolder();
}
if (showWindowSystem) {
Splash.getInstance().setRunning(true);
Main.initUICustomizations();
}
for (Bundle bundle : bundles) {
ModuleInstall mi = installerFor(bundle);
if (mi != null) {
installers.put(bundle, mi);
LOG.log(Level.FINE, "restored: {0}", bundle.getSymbolicName());
mi.restored();
}
}
ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class);
final Lookup start = Lookups.metaInfServices(l, "META-INF/namedservices/Modules/Start/");
// NbStartStop not quite appropriate here; will not properly handle multiple enable/disable cycles
// (but it does run them in parallel, which may be desirable)
for (Runnable r : start.lookupAll(Runnable.class)) {
if (bundles.contains(FrameworkUtil.getBundle(r.getClass()))) {
LOG.log(Level.FINE, "starting {0}", r.getClass().getName());
r.run();
}
}
if (showWindowSystem) {
// XXX set ${jdk.home}?
List<String> bisp = new ArrayList<String>(Arrays.asList(Introspector.getBeanInfoSearchPath()));
bisp.add("org.netbeans.beaninfo"); // NOI18N
Introspector.setBeanInfoSearchPath(bisp.toArray(new String[bisp.size()]));
CoreBridge.getDefault().registerPropertyEditors();
}
for (RunLevel rl : Lookup.getDefault().lookupAll(RunLevel.class)) {
rl.run();
}
if (showWindowSystem) {
Splash.getInstance().setRunning(false);
}
}
private void unload(List<Bundle> bundles) {
if (bundles.isEmpty()) {
return;
}
LOG.log(Level.FINE, "unloading: {0}", bundles);
ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class);
final Lookup stop = Lookups.metaInfServices(l, "META-INF/namedservices/Modules/Stop/");
for (Callable<?> r : stop.lookupAll(Callable.class)) {
if (bundles.contains(FrameworkUtil.getBundle(r.getClass()))) {
try {
if (!((Boolean) r.call())) {
LOG.log(Level.WARNING, "ignoring false return value from {0}", r.getClass().getName());
}
} catch (Exception x) {
LOG.log(Level.WARNING, null, x);
}
}
}
for (Runnable r : stop.lookupAll(Runnable.class)) {
if (bundles.contains(FrameworkUtil.getBundle(r.getClass()))) {
r.run();
}
}
for (Bundle bundle : bundles) {
ModuleInstall mi = installers.remove(bundle);
if (mi != null) {
LOG.log(Level.FINE, "uninstalled: {0}", bundle.getSymbolicName());
mi.uninstalled();
}
}
OSGiRepository.DEFAULT.removeLayersFor(bundles);
OSGiMainLookup.bundlesRemoved(bundles);
}
private static ModuleInstall installerFor(Bundle b) {
if (b.getSymbolicName().equals("org.netbeans.modules.autoupdate.ui")) { // NOI18N
// Won't work anyway, so don't even try.
return null;
}
String respath = b.getHeaders().get("OpenIDE-Module-Install");
if (respath != null) {
String fqn = respath.replaceFirst("[.]class$", "").replace('/', '.');
try {
return SharedClassObject.findObject(((Class<?>) b.loadClass(fqn)).asSubclass(ModuleInstall.class), true);
} catch (Exception x) { // CNFE, CCE, ...
LOG.log(Level.WARNING, "Could not load " + fqn, x);
return null;
}
}
return null;
}
private void registerUrlProtocolHandlers(final Bundle bundle) {
Enumeration<?> e = bundle.getEntryPaths("META-INF/namedservices/URLStreamHandler/");
if (e != null) {
for (String path : NbCollections.iterable(NbCollections.checkedEnumerationByFilter(e, String.class, true))) {
URL entry = bundle.getEntry(path + "java.net.URLStreamHandler");
if (entry != null) {
String protocol = path.replaceAll("^META-INF/namedservices/URLStreamHandler/|/$", "");
try {
InputStream is = entry.openStream();
try {
BufferedReader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
String line;
while ((line = r.readLine()) != null) {
if (!line.isEmpty() && !line.startsWith("#")) {
final String fqn = line;
Dictionary<String,Object> props = new Hashtable<String,Object>();
props.put(URLConstants.URL_HANDLER_PROTOCOL, protocol);
class Svc extends AbstractURLStreamHandlerService {
public @Override URLConnection openConnection(final URL u) throws IOException {
try {
URLStreamHandler handler = (URLStreamHandler) bundle.loadClass(fqn).newInstance();
Method openConnection = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class);
openConnection.setAccessible(true);
return (URLConnection) openConnection.invoke(handler, u);
} catch (Exception x) {
throw (IOException) new IOException(x.toString()).initCause(x);
}
}
}
BundleContext context = bundle.getBundleContext();
if (context != null) {
context.registerService(URLStreamHandlerService.class.getName(), new Svc(), props);
} else {
LOG.log(Level.WARNING, "no context for {0} in state {1}", new Object[] {bundle.getSymbolicName(), bundle.getState()});
}
}
}
} finally {
is.close();
}
} catch (Exception x) {
LOG.log(Level.WARNING, "Could not load protocol handler for " + protocol + " from " + bundle.getSymbolicName(), x);
}
}
}
}
}
}