All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.karaf.main.Main Maven / Gradle / Ivy

Go to download

This bundle is the main Karaf launcher. It's responsible of the Karaf startup including the console, branding, etc bootstrap.

There is a newer version: 4.4.6
Show newest version
/*
 * 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.*;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.service.startlevel.StartLevel;

/**
 * 

* 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. *

*/ public class Main { /** * The default name used for the system properties file. */ public static final String SYSTEM_PROPERTIES_FILE_NAME = "system.properties"; /** * The default name used for the configuration properties file. */ public static final String CONFIG_PROPERTIES_FILE_NAME = "config.properties"; /** * The default name used for the startup properties file. */ public static final String STARTUP_PROPERTIES_FILE_NAME = "startup.properties"; /** * The property name prefix for the launcher's auto-install property. */ public static final String PROPERTY_AUTO_INSTALL = "karaf.auto.install"; /** * The property for auto-discovering the bundles */ public static final String PROPERTY_AUTO_START = "karaf.auto.start"; /** * The system property for specifying the Karaf home directory. The home directory * hold the binary install of Karaf. */ public static final String PROP_KARAF_HOME = "karaf.home"; /** * The environment variable for specifying the Karaf home directory. The home directory * hold the binary install of Karaf. */ public static final String ENV_KARAF_HOME = "KARAF_HOME"; /** * The system property for specifying the Karaf base directory. The base directory * holds the configuration and data for a Karaf instance. */ public static final String PROP_KARAF_BASE = "karaf.base"; /** * The environment variable for specifying the Karaf base directory. The base directory * holds the configuration and data for a Karaf instance. */ public static final String ENV_KARAF_BASE = "KARAF_BASE"; /** * The system property for specifying the Karaf data directory. The data directory * holds the bundles data and cache for a Karaf instance. */ public static final String PROP_KARAF_DATA = "karaf.data"; /** * The environment variable for specifying the Karaf data directory. The data directory * holds the bundles data and cache for a Karaf instance. */ public static final String ENV_KARAF_DATA = "KARAF_DATA"; /** * The system property for specifying the Karaf data directory. The data directory * holds the bundles data and cache for a Karaf instance. */ public static final String PROP_KARAF_INSTANCES = "karaf.instances"; /** * The system property for specifying the Karaf data directory. The data directory * holds the bundles data and cache for a Karaf instance. */ public static final String ENV_KARAF_INSTANCES = "KARAF_INSTANCES"; /** * The system property for holding the Karaf version. */ public static final String PROP_KARAF_VERSION = "karaf.version"; /** * Config property which identifies directories which contain bundles to be loaded by SMX */ public static final String BUNDLE_LOCATIONS = "bundle.locations"; /** * Config property that indicates we want to convert bundles locations to maven style urls */ public static final String PROPERTY_CONVERT_TO_MAVEN_URL = "karaf.maven.convert"; /** * If a lock should be used before starting the runtime */ public static final String PROPERTY_USE_LOCK = "karaf.lock"; /** * The lock implementation */ public static final String PROPERTY_LOCK_CLASS = "karaf.lock.class"; public static final String PROPERTY_LOCK_DELAY = "karaf.lock.delay"; public static final String PROPERTY_LOCK_LEVEL = "karaf.lock.level"; public static final String DEFAULT_REPO = "karaf.default.repository"; public static final String KARAF_FRAMEWORK = "karaf.framework"; public static final String KARAF_FRAMEWORK_FACTORY = "karaf.framework.factory"; public static final String KARAF_SHUTDOWN_TIMEOUT = "karaf.shutdown.timeout"; public static final String KARAF_SHUTDOWN_PORT = "karaf.shutdown.port"; public static final String KARAF_SHUTDOWN_HOST = "karaf.shutdown.host"; public static final String KARAF_SHUTDOWN_PORT_FILE = "karaf.shutdown.port.file"; public static final String KARAF_SHUTDOWN_COMMAND = "karaf.shutdown.command"; public static final String KARAF_SHUTDOWN_PID_FILE = "karaf.shutdown.pid.file"; public static final String DEFAULT_SHUTDOWN_COMMAND = "SHUTDOWN"; public static final String PROPERTY_LOCK_CLASS_DEFAULT = SimpleFileLock.class.getName(); public static final String INCLUDES_PROPERTY = "${includes}"; Logger LOG = Logger.getLogger(this.getClass().getName()); private File karafHome; private File karafBase; private File karafData; private File karafInstances; private Properties configProps = null; private Framework framework = null; private final String[] args; private int exitCode; private Lock lock; private int defaultStartLevel = 100; private int lockStartLevel = 1; private int lockDelay = 1000; private int shutdownTimeout = 5 * 60 * 1000; private boolean exiting = false; private ShutdownCallback shutdownCallback; public Main(String[] args) { this.args = args; } public void setShutdownCallback(ShutdownCallback shutdownCallback) { this.shutdownCallback = shutdownCallback; } public void launch() throws Exception { karafHome = Utils.getKarafHome(); karafBase = Utils.getKarafDirectory(Main.PROP_KARAF_BASE, Main.ENV_KARAF_BASE, karafHome, false, true); karafData = Utils.getKarafDirectory(Main.PROP_KARAF_DATA, Main.ENV_KARAF_DATA, new File(karafBase, "data"), true, true); karafInstances = Utils.getKarafDirectory(Main.PROP_KARAF_INSTANCES, Main.ENV_KARAF_INSTANCES, new File(karafHome, "instances"), false, false); Package p = Package.getPackage("org.apache.karaf.main"); if (p != null && p.getImplementationVersion() != null) { System.setProperty(PROP_KARAF_VERSION, p.getImplementationVersion()); } System.setProperty(PROP_KARAF_HOME, karafHome.getPath()); System.setProperty(PROP_KARAF_BASE, karafBase.getPath()); System.setProperty(PROP_KARAF_DATA, karafData.getPath()); System.setProperty(PROP_KARAF_INSTANCES, karafInstances.getPath()); // Load system properties. loadSystemProperties(karafBase); updateInstancePid(); // Read configuration properties. configProps = loadConfigProperties(); BootstrapLogManager.setProperties(configProps); LOG.addHandler(BootstrapLogManager.getDefaultHandler()); // Copy framework properties from the system properties. Main.copySystemProperties(configProps); ClassLoader classLoader = createClassLoader(configProps); processSecurityProperties(configProps); if (configProps.getProperty(Constants.FRAMEWORK_STORAGE) == null) { File storage = new File(karafData.getPath(), "cache"); try { storage.mkdirs(); } catch (SecurityException se) { throw new Exception(se.getMessage()); } configProps.setProperty(Constants.FRAMEWORK_STORAGE, storage.getAbsolutePath()); } defaultStartLevel = Integer.parseInt(configProps.getProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL)); lockStartLevel = Integer.parseInt(configProps.getProperty(PROPERTY_LOCK_LEVEL, Integer.toString(lockStartLevel))); lockDelay = Integer.parseInt(configProps.getProperty(PROPERTY_LOCK_DELAY, Integer.toString(lockDelay))); configProps.setProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL, Integer.toString(lockStartLevel)); shutdownTimeout = Integer.parseInt(configProps.getProperty(KARAF_SHUTDOWN_TIMEOUT, Integer.toString(shutdownTimeout))); // Start up the OSGI framework String factoryClass = configProps.getProperty(KARAF_FRAMEWORK_FACTORY); 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(); framework = factory.newFramework(new StringMap(configProps, false)); framework.init(); // Process properties loadStartupProperties(configProps); processAutoProperties(framework.getBundleContext()); framework.start(); // Start lock monitor new Thread() { public void run() { lock(configProps); } }.start(); } public void awaitShutdown() throws Exception { if (framework == null) { return; } while (true) { FrameworkEvent event = framework.waitForStop(0); if (event.getType() != FrameworkEvent.STOPPED_UPDATE) { return; } } } public boolean destroy() throws Exception { if (framework == null) { return true; } try { int step = 5000; // Notify the callback asap if (shutdownCallback != null) { shutdownCallback.waitingForShutdown(step); } // Stop the framework in case it's still active 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 timeout = shutdownTimeout; if (shutdownTimeout <= 0) { timeout = Integer.MAX_VALUE; } while (timeout > 0) { timeout -= step; if (shutdownCallback != null) { shutdownCallback.waitingForShutdown(step * 2); } FrameworkEvent event = framework.waitForStop(step); if (event.getType() != FrameworkEvent.WAIT_TIMEDOUT) { return true; } } return false; } finally { unlock(); } } /** *

* This method performs the main task of constructing an framework instance * and starting its execution. The following functions are performed * when invoked: *

*
    *
  1. Read the system properties file. This is a file * containing properties to be pushed into System.setProperty() * 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 -D 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 * http.proxyHost, http.proxyPort, and * http.proxyAuth. *
  2. *
  3. Perform system property variable substitution on system * properties. Any system properties in the system property * file whose value adheres to ${<system-prop-name>} * syntax will have their value substituted with the appropriate * system property value. *
  4. *
  5. Read the framework's configuration property file. 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 config.properties by default * and is located in the conf/ directory of the Felix * installation directory, which is the parent directory of the * directory containing the felix.jar file. It is possible * to use a different location for the property file by specifying * the desired URL using the felix.config.properties * system property; this should be set using the -D syntax * when executing the JVM. Refer to the * * Felix constructor documentation for more * information on the framework configuration options. *
  6. *
  7. Perform system property variable substitution on configuration * properties. Any configuration properties whose value adheres to * ${<system-prop-name>} syntax will have their value * substituted with the appropriate system property value. *
  8. *
  9. Ensure the default bundle cache has sufficient information to * initialize. 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 felix.cache.profile or felix.cache.profiledir * properties. If neither is found, the user is asked to supply a profile * name that is added to the configuration property set. See the * DefaultBundleCache * documentation for more details its configuration options. *
  10. *
  11. Creates and starts a framework instance. A * case insensitive * StringMap * is created for the configuration property file and is passed * into the framework. *
  12. *
*

* 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 * * Felix constructor documentation for more information on * framework configuration options. *

* @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; System.setProperty("karaf.restart", "false"); if (Boolean.getBoolean("karaf.restart.clean")) { File karafHome = Utils.getKarafHome(); File karafBase = Utils.getKarafDirectory(Main.PROP_KARAF_BASE, Main.ENV_KARAF_BASE, karafHome, false, true); File karafData = Utils.getKarafDirectory(Main.PROP_KARAF_DATA, Main.ENV_KARAF_DATA, new File(karafBase, "data"), true, true); Utils.deleteDirectory(karafData); } final Main main = new Main(args); try { main.launch(); } catch (Throwable ex) { main.destroy(); main.setExitCode(-1); System.err.println("Could not create framework: " + ex); ex.printStackTrace(); } try { main.awaitShutdown(); boolean stopped = main.destroy(); restart = Boolean.getBoolean("karaf.restart"); 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 occured shutting down framework: " + ex); ex.printStackTrace(); } finally { if (!restart) { System.exit(main.getExitCode()); } } } } private static void processSecurityProperties(Properties m_configProps) { String prop = m_configProps.getProperty("org.apache.karaf.security.providers"); if (prop != null) { String[] providers = prop.split(","); for (String provider : providers) { addProvider(provider); } } } private static void addProvider(String provider) { try { Security.addProvider((Provider) Class.forName(provider).newInstance()); } catch (Throwable t) { System.err.println("Unable to register security provider: " + t); } } private void updateInstancePid() { try { String instanceName = System.getProperty("karaf.name"); String pid = ManagementFactory.getRuntimeMXBean().getName(); if (pid.indexOf('@') > 0) { pid = pid.substring(0, pid.indexOf('@')); } boolean isRoot = karafHome.equals(karafBase); if (instanceName != null) { String storage = System.getProperty("karaf.instances"); if (storage == null) { throw new Exception("System property 'karaf.instances' is not set. \n" + "This property needs to be set to the full path of the instance.properties file."); } File storageFile = new File(storage); File propertiesFile = new File(storageFile, "instance.properties"); Properties props = new Properties(); if (propertiesFile.exists()) { FileInputStream fis = new FileInputStream(propertiesFile); props.load(fis); int count = Integer.parseInt(props.getProperty("count")); for (int i = 0; i < count; i++) { String name = props.getProperty("item." + i + ".name"); if (name.equals(instanceName)) { props.setProperty("item." + i + ".pid", pid); FileOutputStream fos = new FileOutputStream(propertiesFile); props.store(fos, null); fis.close(); fos.close(); return; } } fis.close(); if (!isRoot) { throw new Exception("Instance " + instanceName + " not found"); } } else if (isRoot) { if (!propertiesFile.getParentFile().exists()) { try { propertiesFile.getParentFile().mkdirs(); } catch (SecurityException se) { throw new Exception(se.getMessage()); } } props.setProperty("count", "1"); props.setProperty("item.0.name", instanceName); props.setProperty("item.0.loc", karafHome.getAbsolutePath()); props.setProperty("item.0.pid", pid); props.setProperty("item.0.root", "true"); FileOutputStream fos = new FileOutputStream(propertiesFile); props.store(fos, null); fos.close(); } } } catch (Exception e) { System.err.println("Unable to update instance pid: " + e.getMessage()); } } /** *

* Processes the auto-install and auto-start properties from the * specified configuration properties. * * @param context the system bundle context */ private void processAutoProperties(BundleContext context) { // Check if we want to convert URLs to maven style boolean convertToMavenUrls = Boolean.parseBoolean(configProps.getProperty(PROPERTY_CONVERT_TO_MAVEN_URL, "true")); // Retrieve the Start Level service, since it will be needed // to set the start level of the installed bundles. StartLevel sl = (StartLevel) context.getService( context.getServiceReference(org.osgi.service.startlevel.StartLevel.class.getName())); // Set the default bundle start level int ibsl = 60; try { String str = configProps.getProperty("karaf.startlevel.bundle"); if (str != null) { ibsl = Integer.parseInt(str); } } catch (Throwable t) { } sl.setInitialBundleStartLevel(ibsl); // If we have a clean state, install everything if (framework.getBundleContext().getBundles().length == 1) { // The auto-install property specifies a space-delimited list of // bundle URLs to be automatically installed into each new profile; // the start level to which the bundles are assigned is specified by // appending a ".n" to the auto-install property name, where "n" is // the desired start level for the list of bundles. autoInstall(PROPERTY_AUTO_INSTALL, context, sl, convertToMavenUrls, false); // The auto-start property specifies a space-delimited list of // bundle URLs to be automatically installed and started into each // new profile; the start level to which the bundles are assigned // is specified by appending a ".n" to the auto-start property name, // where "n" is the desired start level for the list of bundles. // The following code starts bundles in one pass, installing bundles // for a given level, then starting them, then moving to the next level. autoInstall(PROPERTY_AUTO_START, context, sl, convertToMavenUrls, true); } } private List autoInstall(String propertyPrefix, BundleContext context, StartLevel sl, boolean convertToMavenUrls, boolean start) { Map autoStart = new TreeMap(); List bundles = new ArrayList(); for (Object o : configProps.keySet()) { String key = (String) o; // Ignore all keys that are not the auto-start property. if (!key.startsWith(propertyPrefix)) { continue; } // If the auto-start property does not have a start level, // then assume it is the default bundle start level, otherwise // parse the specified start level. int startLevel = sl.getInitialBundleStartLevel(); if (!key.equals(propertyPrefix)) { try { startLevel = Integer.parseInt(key.substring(key.lastIndexOf('.') + 1)); } catch (NumberFormatException ex) { System.err.println("Invalid property: " + key); } } autoStart.put(startLevel, configProps.getProperty(key)); } for (Integer startLevel : autoStart.keySet()) { StringTokenizer st = new StringTokenizer(autoStart.get(startLevel), "\" ", true); if (st.countTokens() > 0) { String location; do { location = nextLocation(st); if (location != null) { try { String[] parts = convertToMavenUrlsIfNeeded(location, convertToMavenUrls); Bundle b = context.installBundle(parts[0], new URL(parts[1]).openStream()); sl.setBundleStartLevel(b, startLevel); bundles.add(b); } catch (Exception ex) { System.err.println("Error installing bundle " + location + ": " + ex); } } } while (location != null); } } // Now loop through and start the installed bundles. if (start) { for (Bundle b : bundles) { try { String fragmentHostHeader = (String) b.getHeaders().get(Constants.FRAGMENT_HOST); if (fragmentHostHeader == null || fragmentHostHeader.trim().length() == 0) { b.start(); } } catch (Exception ex) { System.err.println("Error starting bundle " + b.getSymbolicName() + ": " + ex); } } } return bundles; } private static String[] convertToMavenUrlsIfNeeded(String location, boolean convertToMavenUrls) { String[] parts = location.split("\\|"); if (convertToMavenUrls) { String[] p = parts[1].split("/"); if (p.length >= 4 && p[p.length-1].startsWith(p[p.length-3] + "-" + p[p.length-2])) { String artifactId = p[p.length-3]; String version = p[p.length-2]; String classifier; String type; String artifactIdVersion = artifactId + "-" + version; StringBuffer sb = new StringBuffer(); if (p[p.length-1].charAt(artifactIdVersion.length()) == '-') { classifier = p[p.length-1].substring(artifactIdVersion.length() + 1, p[p.length-1].lastIndexOf('.')); } else { classifier = null; } type = p[p.length-1].substring(p[p.length-1].lastIndexOf('.') + 1); sb.append("mvn:"); for (int j = 0; j < p.length - 3; j++) { if (j > 0) { sb.append('.'); } sb.append(p[j]); } sb.append('/').append(artifactId).append('/').append(version); if (!"jar".equals(type) || classifier != null) { sb.append('/'); if (!"jar".equals(type)) { sb.append(type); } if (classifier != null) { sb.append('/').append(classifier); } } parts[1] = parts[0]; parts[0] = sb.toString(); } else { parts[1] = parts[0]; } } else { parts[1] = parts[0]; } return parts; } private static String nextLocation(StringTokenizer st) { String retVal = null; if (st.countTokens() > 0) { String tokenList = "\" "; StringBuffer tokBuf = new StringBuffer(10); String tok; boolean inQuote = false; boolean tokStarted = false; boolean exit = false; while ((st.hasMoreTokens()) && (!exit)) { tok = st.nextToken(tokenList); if (tok.equals("\"")) { inQuote = !inQuote; if (inQuote) { tokenList = "\""; } else { tokenList = "\" "; } } else if (tok.equals(" ")) { if (tokStarted) { retVal = tokBuf.toString(); tokStarted = false; tokBuf = new StringBuffer(10); exit = true; } } else { tokStarted = true; tokBuf.append(tok.trim()); } } // Handle case where end of token stream and // still got data if ((!exit) && (tokStarted)) { retVal = tokBuf.toString(); } } return retVal; } /** *

* Loads the properties in the system property file associated with the * framework installation into System.setProperty(). These properties * are not directly used by the framework in anyway. By default, the system * property file is located in the conf/ directory of the Felix * installation directory and is called "system.properties". The * installation directory of Felix is assumed to be the parent directory of * the felix.jar file as found on the system class path property. * The precise file from which to load system properties can be set by * initializing the "felix.system.properties" system property to an * arbitrary URL. *

* * @param karafBase the karaf base folder */ protected static void loadSystemProperties(File karafBase) { // The system properties file is either specified by a system // property or it is in the same directory as the Felix JAR file. // Try to load it from one of these places. // See if the property URL was specified as a property. URL propURL; try { File file = new File(new File(karafBase, "etc"), SYSTEM_PROPERTIES_FILE_NAME); propURL = file.toURI().toURL(); } catch (MalformedURLException ex) { System.err.print("Main: " + ex); return; } // Read the properties file. Properties props = new Properties(); InputStream is = null; try { is = propURL.openConnection().getInputStream(); props.load(is); is.close(); } catch (FileNotFoundException ex) { // Ignore file not found. } catch (Exception ex) { System.err.println( "Main: Error loading system properties from " + propURL); System.err.println("Main: " + ex); try { if (is != null) is.close(); } catch (IOException ex2) { // Nothing we can do. } return; } // Perform variable substitution on specified properties. for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = System.getProperty(name, props.getProperty(name)); System.setProperty(name, substVars(value, name, null, null)); } } /** *

* Loads the configuration properties in the configuration property file * associated with the framework installation; these properties * are accessible to the framework and to bundles and are intended * for configuration purposes. By default, the configuration property * file is located in the conf/ directory of the Felix * installation directory and is called "config.properties". * The installation directory of Felix is assumed to be the parent * directory of the felix.jar file as found on the system class * path property. The precise file from which to load configuration * properties can be set by initializing the "felix.config.properties" * system property to an arbitrary URL. *

* * @return A Properties instance or null if there was an error. * @throws Exception if something wrong occurs */ private Properties loadConfigProperties() throws Exception { // See if the property URL was specified as a property. URL configPropURL; try { File etcFolder = new File(karafBase, "etc"); if (!etcFolder.exists()) { throw new FileNotFoundException("etc folder not found: " + etcFolder.getAbsolutePath()); } File file = new File(etcFolder, CONFIG_PROPERTIES_FILE_NAME); configPropURL = file.toURI().toURL(); } catch (MalformedURLException ex) { System.err.print("Main: " + ex); return null; } Properties configProps = loadPropertiesFile(configPropURL, false); // Perform variable substitution for system properties. for (Enumeration e = configProps.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); configProps.setProperty(name, substVars(configProps.getProperty(name), name, null, configProps)); } return configProps; } private void loadStartupProperties(Properties configProps) throws Exception { // The config properties file is either specified by a system // property or it is in the conf/ directory of the Felix // installation directory. Try to load it from one of these // places. List bundleDirs = new ArrayList(); // See if the property URL was specified as a property. URL startupPropURL; File etcFolder = new File(karafBase, "etc"); if (!etcFolder.exists()) { throw new FileNotFoundException("etc folder not found: " + etcFolder.getAbsolutePath()); } File file = new File(etcFolder, STARTUP_PROPERTIES_FILE_NAME); startupPropURL = file.toURI().toURL(); Properties startupProps = loadPropertiesFile(startupPropURL, true); String defaultRepo = System.getProperty(DEFAULT_REPO, "system"); if (karafBase.equals(karafHome)) { File systemRepo = new File(karafHome, defaultRepo); if (!systemRepo.exists()) { throw new FileNotFoundException("system repo not found: " + systemRepo.getAbsolutePath()); } bundleDirs.add(systemRepo); } else { File baseSystemRepo = new File(karafBase, defaultRepo); File homeSystemRepo = new File(karafHome, defaultRepo); if (!baseSystemRepo.exists() && !homeSystemRepo.exists()) { throw new FileNotFoundException("system repos not found: " + baseSystemRepo.getAbsolutePath() + " " + homeSystemRepo.getAbsolutePath()); } bundleDirs.add(baseSystemRepo); bundleDirs.add(homeSystemRepo); } String locations = configProps.getProperty(BUNDLE_LOCATIONS); if (locations != null) { StringTokenizer st = new StringTokenizer(locations, "\" ", true); if (st.countTokens() > 0) { String location; do { location = nextLocation(st); if (location != null) { File f; if (karafBase.equals(karafHome)) { f = new File(karafHome, location); } else { f = new File(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); } } // Mutate properties Main.processConfigurationProperties(configProps, startupProps, bundleDirs); } protected static Properties loadPropertiesFile(URL configPropURL, boolean failIfNotFound) throws Exception { // Read the properties file. Properties configProps = new Properties(); InputStream is = null; try { is = configPropURL.openConnection().getInputStream(); configProps.load(is); is.close(); } catch (FileNotFoundException ex) { if (failIfNotFound) { throw ex; } } catch (Exception ex) { System.err.println("Error loading config properties from " + configPropURL); System.err.println("Main: " + ex); return configProps; } finally { try { if (is != null) { is.close(); } } catch (IOException ex2) { // Nothing we can do. } } String includes = configProps.getProperty(INCLUDES_PROPERTY); if (includes != null) { StringTokenizer st = new StringTokenizer(includes, "\" ", true); if (st.countTokens() > 0) { String location; do { location = nextLocation(st); if (location != null) { URL url = new URL(configPropURL, location); Properties props = loadPropertiesFile(url, true); configProps.putAll(props); } } while (location != null); } configProps.remove(INCLUDES_PROPERTY); } for (Enumeration e = configProps.propertyNames(); e.hasMoreElements();) { Object key = e.nextElement(); if (key instanceof String) { String v = configProps.getProperty((String) key); configProps.put(key, v.trim()); } } return configProps; } protected static void copySystemProperties(Properties configProps) { for (Enumeration e = System.getProperties().propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); if (key.startsWith("felix.") || key.startsWith("karaf.") || key.startsWith("org.osgi.framework.")) { configProps.setProperty(key, System.getProperty(key)); } } } private ClassLoader createClassLoader(Properties configProps) throws Exception { String framework = configProps.getProperty(KARAF_FRAMEWORK); if (framework == null) { throw new IllegalArgumentException("Property " + KARAF_FRAMEWORK + " must be set in the etc/" + CONFIG_PROPERTIES_FILE_NAME + " configuration file"); } String bundle = configProps.getProperty(KARAF_FRAMEWORK + "." + framework); if (bundle == null) { throw new IllegalArgumentException("Property " + KARAF_FRAMEWORK + "." + framework + " must be set in the etc/" + CONFIG_PROPERTIES_FILE_NAME + " configuration file"); } File bundleFile = new File(karafBase, bundle); if (!bundleFile.exists()) { bundleFile = new File(karafHome, bundle); } if (!bundleFile.exists()) { throw new FileNotFoundException(bundleFile.getAbsolutePath()); } List urls = new ArrayList(); urls.add( bundleFile.toURI().toURL() ); File[] libs = new File(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()); } /** * Process properties to customize default felix behavior * * @param configProps properties loaded from etc/config.properties * @param startupProps properties loaded from etc/startup.properties * @param bundleDirs location to load bundles from (usually system/) */ private static void processConfigurationProperties(Properties configProps, Properties startupProps, List bundleDirs) throws Exception { if (bundleDirs == null) { return; } boolean hasErrors = false; if ("all".equals(configProps.getProperty(PROPERTY_AUTO_START, "").trim())) { configProps.remove(PROPERTY_AUTO_START); ArrayList jars = new ArrayList(); // We should start all the bundles in the system dir. for (File bundleDir : bundleDirs) { findJars(bundleDir, jars); } StringBuffer sb = new StringBuffer(); for (File jar : jars) { try { sb.append("\"").append(jar.toURI().toURL().toString()).append("\" "); } catch (MalformedURLException e) { System.err.print("Ignoring " + jar.toString() + " (" + e + ")"); } } configProps.setProperty(PROPERTY_AUTO_START, sb.toString()); } else if (STARTUP_PROPERTIES_FILE_NAME.equals(configProps.getProperty(PROPERTY_AUTO_START, "").trim())) { configProps.remove(PROPERTY_AUTO_START); // We should start the bundles in the startup.properties file. HashMap levels = new HashMap(); for (Object o : startupProps.keySet()) { String name = (String) o; File file = findFile(bundleDirs, name); if (file != null) { Integer level; try { level = new Integer(startupProps.getProperty(name).trim()); } catch (NumberFormatException e1) { System.err.print("Ignoring " + file.toString() + " (run level must be an integer)"); continue; } StringBuffer sb = levels.get(level); if (sb == null) { sb = new StringBuffer(256); levels.put(level, sb); } try { sb.append("\"").append(file.toURI().toURL().toString()).append("|").append(name).append("\" "); } catch (MalformedURLException e) { System.err.print("Ignoring " + file.toString() + " (" + e + ")"); } } else { System.err.println("Bundle listed in " + STARTUP_PROPERTIES_FILE_NAME + " configuration not found: " + name); hasErrors = true; } } for (Map.Entry entry : levels.entrySet()) { configProps.setProperty(PROPERTY_AUTO_START + "." + entry.getKey(), entry.getValue().toString()); } } if (hasErrors) { throw new Exception("Aborting due to missing startup bundles"); } } private static File findFile(List bundleDirs, String name) { for (File bundleDir : bundleDirs) { File file = findFile(bundleDir, name); if (file != null) { return file; } } return null; } private static File findFile(File dir, String name) { File theFile = new File(dir, name); if (theFile.exists() && !theFile.isDirectory()) { return theFile; } for (File file : dir.listFiles()) { if (file.isDirectory()) { return findFile(file, name); } } return null; } private static void findJars(File dir, ArrayList jars) { for (File file : dir.listFiles()) { if (file.isDirectory()) { findJars(file, jars); } else { if (file.toString().endsWith(".jar")) { jars.add(file); } } } } private static final String DELIM_START = "${"; private static final String DELIM_STOP = "}"; /** *

* This method performs property variable substitution on the * specified value. If the specified value contains the syntax * ${<prop-name>}, where <prop-name> * refers to either a configuration property or a system property, * then the corresponding property value is substituted for the variable * placeholder. Multiple variable placeholders may exist in the * specified value as well as nested variable placeholders, which * are substituted from inner most to outer most. Configuration * properties override system properties. *

* * @param val The string on which to perform property substitution. * @param currentKey The key of the property being evaluated used to * detect cycles. * @param cycleMap Map of variable references used to detect nested cycles. * @param configProps Set of configuration properties. * @return The value of the specified string after system property substitution. * @throws IllegalArgumentException If there was a syntax error in the * property placeholder syntax or a recursive variable reference. */ public static String substVars(String val, String currentKey, Map cycleMap, Properties configProps) throws IllegalArgumentException { // If there is currently no cycle map, then create // one for detecting cycles for this invocation. if (cycleMap == null) { cycleMap = new HashMap(); } // Put the current key in the cycle map. cycleMap.put(currentKey, currentKey); // Assume we have a value that is something like: // "leading ${foo.${bar}} middle ${baz} trailing" // Find the first ending '}' variable delimiter, which // will correspond to the first deepest nested variable // placeholder. int stopDelim = val.indexOf(DELIM_STOP); // Find the matching starting "${" variable delimiter // by looping until we find a start delimiter that is // greater than the stop delimiter we have found. int startDelim = val.indexOf(DELIM_START); while (stopDelim >= 0) { int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length()); if ((idx < 0) || (idx > stopDelim)) { break; } else if (idx < stopDelim) { startDelim = idx; } } // If we do not have a start or stop delimiter, then just // return the existing value. if ((startDelim < 0) && (stopDelim < 0)) { return val; } // At this point, we found a stop delimiter without a start, // so throw an exception. else if (((startDelim < 0) || (startDelim > stopDelim)) && (stopDelim >= 0)) { throw new IllegalArgumentException( "stop delimiter with no start delimiter: " + val); } // At this point, we have found a variable placeholder so // we must perform a variable substitution on it. // Using the start and stop delimiter indices, extract // the first, deepest nested variable placeholder. String variable = val.substring(startDelim + DELIM_START.length(), stopDelim); // Verify that this is not a recursive variable reference. if (cycleMap.get(variable) != null) { throw new IllegalArgumentException( "recursive variable reference: " + variable); } // Get the value of the deepest nested variable placeholder. // Try to configuration properties first. String substValue = (configProps != null) ? configProps.getProperty(variable, null) : null; if (substValue == null) { // Ignore unknown property values. substValue = System.getProperty(variable, ""); } // Remove the found variable from the cycle map, since // it may appear more than once in the value and we don't // want such situations to appear as a recursive reference. cycleMap.remove(variable); // Append the leading characters, the substituted value of // the variable, and the trailing characters to get the new // value. val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length(), val.length()); // Now perform substitution again, since there could still // be substitutions to make. val = substVars(val, currentKey, cycleMap, configProps); // Return the value. return val; } /** * 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; } public void lock(Properties props) { try { if (Boolean.parseBoolean(props.getProperty(PROPERTY_USE_LOCK, "true"))) { doLock(props); } else { setStartLevel(defaultStartLevel); } } catch (Exception e) { e.printStackTrace(); } } private void doLock(Properties props) throws Exception { String clz = props.getProperty(PROPERTY_LOCK_CLASS, PROPERTY_LOCK_CLASS_DEFAULT); lock = (Lock) Class.forName(clz).getConstructor(Properties.class).newInstance(props); boolean lockLogged = false; setStartLevel(lockStartLevel); while (!exiting) { if (lock.lock()) { if (lockLogged) { LOG.info("Lock acquired."); } setupShutdown(props); setStartLevel(defaultStartLevel); for (;;) { if (!lock.isAlive()) { break; } Thread.sleep(lockDelay); } if (framework.getState() == Bundle.ACTIVE && !exiting) { LOG.info("Lost the lock, stopping this instance ..."); setStartLevel(lockStartLevel); } } else if (!lockLogged) { LOG.info("Waiting for the lock ..."); lockLogged = true; } Thread.sleep(lockDelay); } } public void unlock() throws Exception { if (lock != null) { lock.release(); } } protected void setStartLevel(int level) throws Exception { BundleContext ctx = framework.getBundleContext(); ServiceReference[] refs = ctx.getServiceReferences(StartLevel.class.getName(), null); StartLevel sl = (StartLevel) ctx.getService(refs[0]); sl.setStartLevel(level); } private Random random = null; private ServerSocket shutdownSocket; protected void setupShutdown(Properties props) { writePid(props); try { int port = Integer.parseInt(props.getProperty(KARAF_SHUTDOWN_PORT, "0")); String host = props.getProperty(KARAF_SHUTDOWN_HOST, "localhost"); String portFile = props.getProperty(KARAF_SHUTDOWN_PORT_FILE); final String shutdown = props.getProperty(KARAF_SHUTDOWN_COMMAND, DEFAULT_SHUTDOWN_COMMAND); if (port >= 0) { shutdownSocket = new ServerSocket(port, 1, InetAddress.getByName(host)); if (port == 0) { port = shutdownSocket.getLocalPort(); } if (portFile != null) { Writer w = new OutputStreamWriter(new FileOutputStream(portFile)); w.write(Integer.toString(port)); w.close(); } Thread thread = new ShutdownSocketThread(shutdown); thread.setDaemon(true); thread.start(); } } catch (Exception e) { e.printStackTrace(); } } private void writePid(Properties props) { try { String pidFile = props.getProperty(KARAF_SHUTDOWN_PID_FILE); if (pidFile != null) { RuntimeMXBean rtb = ManagementFactory.getRuntimeMXBean(); String processName = rtb.getName(); Pattern pattern = Pattern.compile("^([0-9]+)@.+$", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(processName); if (matcher.matches()) { int pid = Integer.parseInt(matcher.group(1)); Writer w = new OutputStreamWriter(new FileOutputStream(pidFile)); w.write(Integer.toString(pid)); w.close(); } } } catch (Exception e) { e.printStackTrace(); } } private class ShutdownSocketThread extends Thread { private final String shutdown; public ShutdownSocketThread(String shutdown) { this.shutdown = shutdown; } public void run() { try { while (true) { // Wait for the next connection Socket socket = null; InputStream stream = null; try { socket = shutdownSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (AccessControlException ace) { LOG.log(Level.WARNING, "Karaf shutdown socket: security exception: " + ace.getMessage(), ace); continue; } catch (IOException e) { LOG.log(Level.SEVERE, "Karaf shutdown socket: accept: ", e); System.exit(1); } // Read a set of characters from the socket StringBuilder command = new StringBuilder(); int expected = 1024; // Cut off to avoid DoS attack while (expected < shutdown.length()) { if (random == null) { random = new Random(); } expected += (random.nextInt() % 1024); } while (expected > 0) { int ch; try { ch = stream.read(); } catch (IOException e) { LOG.log(Level.WARNING, "Karaf shutdown socket: read: ", e); ch = -1; } if (ch < 32) { // Control character or EOF terminates loop break; } command.append((char) ch); expected--; } // Close the socket now that we are done with it try { socket.close(); } catch (IOException e) { // Ignore } // Match against our command string boolean match = command.toString().equals(shutdown); if (match) { LOG.log(Level.INFO, "Karaf shutdown socket: received shutdown command. Stopping framework..."); framework.stop(); break; } else { LOG.log(Level.WARNING, "Karaf shutdown socket: Invalid command '" + command.toString() + "' received"); } } } catch (Exception e) { e.printStackTrace(); } finally { try { shutdownSocket.close(); } catch (IOException e) { // Ignore } } } } }