/* * 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.apache.karaf.main; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.security.Provider; import java.security.Security; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.felix.utils.properties.Properties; import org.apache.karaf.info.ServerInfo; import org.apache.karaf.main.internal.Systemd; import org.apache.karaf.main.lock.Lock; import org.apache.karaf.main.lock.LockCallBack; import org.apache.karaf.main.lock.NoLock; import org.apache.karaf.main.util.ArtifactResolver; import org.apache.karaf.main.util.BootstrapLogManager; import org.apache.karaf.main.util.SimpleMavenResolver; import org.apache.karaf.main.util.Utils; import org.apache.karaf.util.config.PropertiesLoader; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; /** * <p> * This class is the default way to instantiate and execute the framework. It is not * intended to be the only way to instantiate and execute the framework; rather, it is * one example of how to do so. When embedding the framework in a host application, * this class can serve as a simple guide of how to do so. It may even be * worthwhile to reuse some of its property handling capabilities. This class * is completely static and is only intended to start a single instance of * the framework. * </p> */ public class Main { /** * The default name used for the startup properties file. */ public static final String STARTUP_PROPERTIES_FILE_NAME = "startup.properties"; Logger LOG = Logger.getLogger(this.getClass().getName()); private ConfigProperties config; private Framework framework = null; private final String[] args; private int exitCode; private ShutdownCallback shutdownCallback; private KarafActivatorManager activatorManager; private Lock lock; private KarafLockCallback lockCallback; private boolean exiting; /** * <p> * This method performs the main task of constructing an framework instance * and starting its execution. The following functions are performed * when invoked: * </p> * <ol> * <li><i><b>Read the system properties file.</b></i> This is a file * containing properties to be pushed into <tt>System.setProperty()</tt> * before starting the framework. This mechanism is mainly shorthand * for people starting the framework from the command line to avoid having * to specify a bunch of <tt>-D</tt> system property definitions. * The only properties defined in this file that will impact the framework's * behavior are the those concerning setting HTTP proxies, such as * <tt>http.proxyHost</tt>, <tt>http.proxyPort</tt>, and * <tt>http.proxyAuth</tt>. * </li> * <li><i><b>Perform system property variable substitution on system * properties.</b></i> Any system properties in the system property * file whose value adheres to <tt>${<system-prop-name>}</tt> * syntax will have their value substituted with the appropriate * system property value. * </li> * <li><i><b>Read the framework's configuration property file.</b></i> This is * a file containing properties used to configure the framework * instance and to pass configuration information into * bundles installed into the framework instance. The configuration * property file is called <tt>config.properties</tt> by default * and is located in the <tt>conf/</tt> directory of the Felix * installation directory, which is the parent directory of the * directory containing the <tt>felix.jar</tt> file. It is possible * to use a different location for the property file by specifying * the desired URL using the <tt>felix.config.properties</tt> * system property; this should be set using the <tt>-D</tt> syntax * when executing the JVM. Refer to the * <tt>Felix</tt> constructor documentation for more * information on the framework configuration options. * </li> * <li><i><b>Perform system property variable substitution on configuration * properties.</b></i> Any configuration properties whose value adheres to * <tt>${<system-prop-name>}</tt> syntax will have their value * substituted with the appropriate system property value. * </li> * <li><i><b>Ensure the default bundle cache has sufficient information to * initialize.</b></i> The default implementation of the bundle cache * requires either a profile name or a profile directory in order to * start. The configuration properties are checked for at least one * of the <tt>felix.cache.profile</tt> or <tt>felix.cache.profiledir</tt> * properties. If neither is found, the user is asked to supply a profile * name that is added to the configuration property set. See the * <a href="cache/DefaultBundleCache.html"><tt>DefaultBundleCache</tt></a> * documentation for more details its configuration options. * </li> * <li><i><b>Creates and starts a framework instance.</b></i> A * case insensitive * <a href="util/StringMap.html"><tt>StringMap</tt></a> * is created for the configuration property file and is passed * into the framework. * </li> * </ol> * <p> * It should be noted that simply starting an instance of the framework is not enough * to create an interactive session with it. It is necessary to install * and start bundles that provide an interactive impl; this is generally * done by specifying an "auto-start" property in the framework configuration * property file. If no interactive impl bundles are installed or if * the configuration property file cannot be found, the framework will appear to * be hung or deadlocked. This is not the case, it is executing correctly, * there is just no way to interact with it. Refer to the * <tt>Felix</tt> constructor documentation for more information on * framework configuration options. * </p> * @param args An array of arguments, all of which are ignored. * @throws Exception If an error occurs. **/ public static void main(String[] args) throws Exception { while (true) { boolean restart = false; boolean restartJvm = false; // karaf.restart.jvm take priority over karaf.restart System.setProperty("karaf.restart.jvm", "false"); System.setProperty("karaf.restart", "false"); final Main main = new Main(args); try { main.launch(); } catch (Throwable ex) { // Also log to sytem.err in case logging is not yet initialized System.err.println(ex.getMessage()); main.LOG.log(Level.SEVERE, "Could not launch framework", ex); main.destroy(); main.setExitCode(-1); } try { main.awaitShutdown(); boolean stopped = main.destroy(); restart = Boolean.getBoolean("karaf.restart"); restartJvm = Boolean.getBoolean("karaf.restart.jvm"); main.updateInstancePidAfterShutdown(); if (!stopped) { if (restart) { System.err.println("Timeout waiting for framework to stop. Restarting now."); } else { System.err.println("Timeout waiting for framework to stop. Exiting VM."); main.setExitCode(-3); } } } catch (Throwable ex) { main.setExitCode(-2); System.err.println("Error occurred shutting down framework: " + ex); ex.printStackTrace(); } finally { if (restartJvm) { System.exit(10); } else if (!restart) { System.exit(main.getExitCode()); } else { System.gc(); } } } } public Main(String[] args) { this.args = args; } public void setShutdownCallback(ShutdownCallback shutdownCallback) { this.shutdownCallback = shutdownCallback; } public void updateInstancePidAfterShutdown() throws Exception { if (config == null) { config = new ConfigProperties(); } InstanceHelper.updateInstancePid(config.karafHome, config.karafBase, false); } public void launch() throws Exception { if (config == null) { config = new ConfigProperties(); } config.performInit(); if (config.delayConsoleStart) { System.out.println(config.startupMessage); } String log4jConfigPath = System.getProperty("karaf.etc") + "/org.ops4j.pax.logging.cfg"; BootstrapLogManager.setProperties(config.props, log4jConfigPath); lockCallback = new KarafLockCallback(); InstanceHelper.updateInstancePid(config.karafHome, config.karafBase, true); BootstrapLogManager.configureLogger(LOG); for (String provider : config.securityProviders) { addSecurityProvider(provider); } List<File> bundleDirs = getBundleRepos(); ArtifactResolver resolver = new SimpleMavenResolver(bundleDirs); // Start up the OSGI framework ClassLoader classLoader = createClassLoader(resolver); FrameworkFactory factory = loadFrameworkFactory(classLoader); framework = factory.newFramework(config.props); setLogger(); framework.init(); framework.getBundleContext().addFrameworkListener(lockCallback); framework.start(); FrameworkStartLevel sl = framework.adapt(FrameworkStartLevel.class); sl.setInitialBundleStartLevel(config.defaultBundleStartlevel); // If we have a clean state, install everything if (framework.getBundleContext().getBundles().length == 1) { LOG.info("Installing and starting initial bundles"); File startupPropsFile = new File(config.karafEtc, STARTUP_PROPERTIES_FILE_NAME); List<BundleInfo> bundles = readBundlesFromStartupProperties(startupPropsFile); installAndStartBundles(resolver, framework.getBundleContext(), bundles); LOG.info("All initial bundles installed and set to start"); } ServerInfo serverInfo = new ServerInfoImpl(args, config); framework.getBundleContext().registerService(ServerInfo.class, serverInfo, null); activatorManager = new KarafActivatorManager(classLoader, framework); activatorManager.startKarafActivators(); setStartLevel(config.lockStartLevel); // Progress bar if (config.delayConsoleStart) { new StartupListener(LOG, framework.getBundleContext()); } monitor(); registerSignalHandler(); watchdog(); } /* * Hack to set felix logger * KARAF-3706: disable the logger related code to avoid the exception * It needs to be revisited when the FELIX-4871 is fixed. */ private void setLogger() { try { if (framework.getClass().getName().startsWith("org.apache.felix.")) { Field field = framework.getClass().getDeclaredField("m_logger"); field.setAccessible(true); Object logger = field.get(framework); Method method = logger.getClass().getDeclaredMethod("setLogger", Object.class); method.setAccessible(true); method.invoke(logger, new Object() { public void log(int level, String message, Throwable exception) { Level lvl; switch (level) { case 1: lvl = Level.SEVERE; break; case 2: lvl = Level.WARNING; break; case 3: lvl = Level.INFO; break; case 4: lvl = Level.FINE; break; default: lvl = Level.FINEST; break; } Logger.getLogger("Felix").log(lvl, message, exception); } }); } } catch (Throwable t) { t.printStackTrace(); } } private void registerSignalHandler() { if (!Boolean.valueOf(System.getProperty("karaf.handle.sigterm", "true"))) { return; } try { final Class<?> signalClass = Class.forName("sun.misc.Signal"); final Class<?> signalHandlerClass = Class.forName("sun.misc.SignalHandler"); Object signalHandler = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { signalHandlerClass }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Main.this.destroy(); return null; } } ); signalClass.getMethod("handle", signalClass, signalHandlerClass).invoke( null, signalClass.getConstructor(String.class).newInstance("TERM"), signalHandler ); } catch (Exception e) { } } private void monitor() { new Thread("Karaf Lock Monitor Thread") { public void run() { try { doMonitor(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } private void doMonitor() throws Exception { lock = createLock(); File dataDir = new File(System.getProperty(ConfigProperties.PROP_KARAF_DATA)); while (!exiting) { if (lock.lock()) { lockCallback.lockAquired(); for (;;) { if (!dataDir.isDirectory()) { LOG.info("Data directory does not exist anymore, halting"); framework.stop(); System.exit(-1); return; } if (!lock.isAlive() || exiting) { break; } Thread.sleep(config.lockDelay); } if (!exiting) { lockCallback.lockLost(); } } else { if (config.lockSlaveBlock) { LOG.log(Level.SEVERE, "Can't lock, and lock is exclusive"); System.err.println("Can't lock (another instance is running), and lock is exclusive"); System.exit(5); } else { lockCallback.waitingForLock(); } } Thread.sleep(config.lockDelay); } } Lock getLock() { return lock; } private void watchdog() { if(Boolean.getBoolean("karaf.systemd.enabled")) { new Thread("Karaf Systemd Watchdog Thread") { public void run() { try { doWatchdog(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } } private void doWatchdog() throws Exception { Systemd systemd = new Systemd(); long timeout = systemd.getWatchdogTimeout(TimeUnit.MILLISECONDS); int code; while (!exiting && timeout > 0) { code = systemd.notifyWatchdog(); if(code < 0) { System.err.println("Systemd sd_notify failed with error code: " + code); break; } Thread.sleep(timeout / 2); } } private ClassLoader createClassLoader(ArtifactResolver resolver) throws Exception { List<URL> urls = new ArrayList<URL>(); urls.add(resolver.resolve(config.frameworkBundle).toURL()); File[] libs = new File(config.karafHome, "lib").listFiles(); if (libs != null) { for (File f : libs) { if (f.isFile() && f.canRead() && f.getName().endsWith(".jar")) { urls.add(f.toURI().toURL()); } } } return new URLClassLoader(urls.toArray(new URL[urls.size()]), Main.class.getClassLoader()); } private FrameworkFactory loadFrameworkFactory(ClassLoader classLoader) throws Exception { String factoryClass = config.frameworkFactoryClass; if (factoryClass == null) { InputStream is = classLoader.getResourceAsStream("META-INF/services/" + FrameworkFactory.class.getName()); BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); factoryClass = br.readLine(); br.close(); } FrameworkFactory factory = (FrameworkFactory) classLoader.loadClass(factoryClass).newInstance(); return factory; } private Lock createLock() { if (!config.useLock) { return new NoLock(); } try { return (Lock) Lock.class.getClassLoader().loadClass(config.lockClass).getConstructor(Properties.class).newInstance(config.props); } catch (Exception e) { if (e instanceof InvocationTargetException){ throw new RuntimeException("Exception instantiating lock class " + config.lockClass + "\n" + ((InvocationTargetException)e).getTargetException().getMessage(), e); }else{ throw new RuntimeException("Exception instantiating lock class " + config.lockClass, e); } } } private static void addSecurityProvider(String provider) { try { Security.addProvider((Provider) Class.forName(provider).newInstance()); } catch (Throwable t) { System.err.println("Unable to register security provider: " + t); } } public List<BundleInfo> readBundlesFromStartupProperties(File startupPropsFile) { Properties startupProps = PropertiesLoader.loadPropertiesOrFail(startupPropsFile); List<BundleInfo> bundeList = new ArrayList<BundleInfo>(); for (String key : startupProps.keySet()) { try { BundleInfo bi = new BundleInfo(); bi.uri = new URI(key); String startlevelSt = startupProps.getProperty(key).trim(); bi.startLevel = new Integer(startlevelSt); bundeList.add(bi); } catch (Exception e) { throw new RuntimeException("Error loading startup bundle list from " + startupPropsFile + " at " + key, e); } } return bundeList; } private void installAndStartBundles(ArtifactResolver resolver, BundleContext context, List<BundleInfo> bundles) { for (BundleInfo bundleInfo : bundles) { try { Bundle b; if (bundleInfo.uri.toString().startsWith("reference:file:")) { URI temp = URI.create(bundleInfo.uri.toString().substring("reference:file:".length())); URI resolvedURI = resolver.resolve(temp); URI finalUri = URI.create("reference:file:" + config.karafBase.toURI().relativize(resolvedURI)); b = context.installBundle(finalUri.toString()); } else { URI resolvedURI = resolver.resolve(bundleInfo.uri); b = context.installBundle(bundleInfo.uri.toString(), resolvedURI.toURL().openStream()); } b.adapt(BundleStartLevel.class).setStartLevel(bundleInfo.startLevel); if (isNotFragment(b)) { b.start(); } } catch (Exception e) { throw new RuntimeException("Error installing bundle listed in " + STARTUP_PROPERTIES_FILE_NAME + " with url: " + bundleInfo.uri + " and startlevel: " + bundleInfo.startLevel, e); } } } private boolean isNotFragment(Bundle b) { String fragmentHostHeader = (String) b.getHeaders().get(Constants.FRAGMENT_HOST); return fragmentHostHeader == null || fragmentHostHeader.trim().length() == 0; } private List<File> getBundleRepos() { List<File> bundleDirs = new ArrayList<File>(); File homeSystemRepo = new File(config.karafHome, config.defaultRepo); if (!homeSystemRepo.isDirectory()) { throw new RuntimeException("system repo folder not found: " + homeSystemRepo.getAbsolutePath()); } bundleDirs.add(homeSystemRepo); File baseSystemRepo = new File(config.karafBase, config.defaultRepo); if (baseSystemRepo.isDirectory() && !baseSystemRepo.equals(homeSystemRepo)) { bundleDirs.add(baseSystemRepo); } String locations = config.bundleLocations; if (locations != null) { StringTokenizer st = new StringTokenizer(locations, "\" ", true); if (st.countTokens() > 0) { String location; do { location = Utils.nextLocation(st); if (location != null) { File f; if (config.karafBase.equals(config.karafHome)) { f = new File(config.karafHome, location); } else { f = new File(config.karafBase, location); } if (f.exists() && f.isDirectory()) { bundleDirs.add(f); } else { System.err.println("Bundle location " + location + " does not exist or is not a directory."); } } } while (location != null); } } return bundleDirs; } /** * Retrieve the arguments used when launching Karaf * * @return the arguments of the main karaf process */ public String[] getArgs() { return args; } public int getExitCode() { return exitCode; } public void setExitCode(int exitCode) { this.exitCode = exitCode; } public Framework getFramework() { return framework; } protected void setStartLevel(int level) { framework.adapt(FrameworkStartLevel.class).setStartLevel(level); } public ConfigProperties getConfig() { return config; } public void setConfig(ConfigProperties config) { this.config = config; } public void awaitShutdown() throws Exception { if (framework == null) { return; } while (true) { FrameworkEvent event = framework.waitForStop(0); if (event.getType() == FrameworkEvent.STOPPED_UPDATE) { if (lock != null) { lock.release(); } while (framework.getState() != Bundle.STARTING && framework.getState() != Bundle.ACTIVE) { Thread.sleep(10); } monitor(); } else { return; } } } public boolean destroy() throws Exception { if (framework == null) { return true; } try { int timeout = config.shutdownTimeout; if (config.shutdownTimeout <= 0) { timeout = Integer.MAX_VALUE; } if (shutdownCallback != null) { shutdownCallback.waitingForShutdown(timeout); } exiting = true; if (framework.getState() == Bundle.ACTIVE || framework.getState() == Bundle.STARTING) { new Thread() { public void run() { try { framework.stop(); } catch (BundleException e) { System.err.println("Error stopping karaf: " + e.getMessage()); } } }.start(); } int step = 5000; while (timeout > 0) { timeout -= step; FrameworkEvent event = framework.waitForStop(step); if (event.getType() != FrameworkEvent.WAIT_TIMEDOUT) { if (activatorManager != null) { activatorManager.stopKarafActivators(); } return true; } } return false; } finally { if (lock != null) { exiting = true; lock.release(); } } } private final class KarafLockCallback implements LockCallBack, FrameworkListener { private Object startLevelLock = new Object(); @Override public void lockLost() { if (framework.getState() == Bundle.ACTIVE) { LOG.warning("Lock lost. Setting startlevel to " + config.lockStartLevel); synchronized (startLevelLock) { setStartLevel(config.lockStartLevel); // we have to wait for the start level to be reduced here because // if the lock is regained before the start level is fully changed // things may not come up as expected LOG.fine("Waiting for start level change to complete..."); try { startLevelLock.wait(config.shutdownTimeout); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void lockAquired() { LOG.info("Lock acquired. Setting startlevel to " + config.defaultStartLevel); InstanceHelper.setupShutdown(config, framework); setStartLevel(config.defaultStartLevel); } @Override public void waitingForLock() { LOG.fine("Waiting for the lock ..."); } @Override public void frameworkEvent(FrameworkEvent event) { if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED) { synchronized (startLevelLock) { LOG.fine("Start level change complete."); startLevelLock.notifyAll(); } } } } }