Java tutorial
/* * Copyright (C) 2012 Google Inc. * * Licensed 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 interactivespaces.launcher.bootstrap; import interactivespaces.system.core.configuration.ConfigurationProvider; import interactivespaces.system.core.configuration.CoreConfiguration; import interactivespaces.system.core.container.ContainerCustomizerProvider; import interactivespaces.system.core.container.ContainerFilesystemLayout; import interactivespaces.system.core.container.InteractiveSpacesStartLevel; import interactivespaces.system.core.container.SimpleContainerCustomizerProvider; import interactivespaces.system.core.logging.LoggingProvider; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.SynchronousBundleListener; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.Thread.UncaughtExceptionHandler; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; /** * The boostrapper for Interactive Spaces. * * @author Keith M. Hughes */ public class InteractiveSpacesFrameworkBootstrap { /** * Configuration parameter to specify if startup order of bundles should be logged. */ public static final String CONFIG_PROPERTY_STARTUP_LOGGING = "interactivespaces.logging.container.startup"; /** * Configuration parameter value to specify if startup order of bundles should be logged. */ public static final String CONFIG_PROPERTY_VALUE_STARTUP_LOGGING = "true"; /** * The argument for saying the container should run with no shell access. */ public static final String ARGS_NOSHELL = "--noshell"; /** * Command line argument prefix for specifying a specific runtime path. This should match the value of * {@code InteractiveSpacesLauncher.COMMAND_LINE_RUNTIME_PREFIX}, but can't be a shared variable because of package * dependency considerations. */ public static final String ARGS_RUNTIME_PREFIX = "--runtime="; /** * Command line argument prefix for specifying a specific config path. This should match the value of * {@code InteractiveSpacesLauncher.COMMAND_LINE_CONFIG_PREFIX}, but can't be a shared variable because of package * dependency considerations. */ public static final String ARGS_CONFIG_PREFIX = "--config="; /** * External packages loaded from the Interactive Spaces system folder. */ public static final String[] PACKAGES_SYSTEM_EXTERNAL = new String[] { "org.apache.commons.logging; version=1.1.1", "org.apache.commons.logging.impl; version=1.1.1", "javax.transaction; version=1.1.0", "javax.transaction.xa; version=1.1.0", "javax.transaction", "javax.transaction.xa" }; /** * Packages loaded from the Interactive Spaces system folder that are part of Interactive Spaces. */ public static final String[] PACKAGES_SYSTEM_INTERACTIVESPACES = new String[] { "interactivespaces.system.core.logging", "interactivespaces.system.core.configuration", "interactivespaces.system.core.container" }; /** * The Jar Manifest property that gives the Interactive Spaces version. */ public static final String MANIFEST_PROPERTY_INTERACTIVESPACES_VERSION = "Bundle-Version"; /** * The folder where Interactive Spaces will cache OSGi plugins. This is relative to the run folder. */ public static final String FOLDER_PLUGINS_CACHE = "plugins-cache"; /** * Where the OSGi framework launcher can be found. */ public static final String OSGI_FRAMEWORK_LAUNCH_FRAMEWORK_FACTORY = "META-INF/services/org.osgi.framework.launch.FrameworkFactory"; /** * Bundle manifest header indicating the start level to use. */ public static final String BUNDLE_MANIFEST_START_LEVEL_HEADER = "InteractiveSpaces-StartLevel"; /** * The OSGI framework which has been started. */ private Framework framework; /** * All bundles installed. */ private final Set<Bundle> bundles = new HashSet<Bundle>(); /** * The initial set of bundles to load. */ private List<File> initialBundles; /** * Whether or not the OSGi shell is needed. */ private boolean needShell = true; /** * Logging provider for the container. */ private Log4jLoggingProvider loggingProvider; /** * The configuration provider for the container. */ private FileConfigurationProvider configurationProvider; /** * The container customizer provider for the container. */ private SimpleContainerCustomizerProvider containerCustomizerProvider; /** * The base install folder for Interactive Spaces. */ private File baseInstallFolder; /** * The root runtime directory for this container. May be the same as the base install folder, but can be independently * controlled to allow for multiple runtime instances. */ private File runtimeFolder; /** * The root config directory for this container. */ private File configFolder; /** * The OSGi bundle context for the OSGi framework bundle. */ private BundleContext rootBundleContext; /** * The start level for the OSGi framework. */ private FrameworkStartLevel frameworkStartLevel; /** * Boot the framework. * * @param args * the arguments to be passed to the bootstrap */ public void boot(List<String> args) { baseInstallFolder = new File(".").getAbsoluteFile().getParentFile(); // Set default values for various directories. runtimeFolder = baseInstallFolder; configFolder = new File(baseInstallFolder, ContainerFilesystemLayout.FOLDER_DEFAULT_CONFIG); processCommandLineArgs(args); initialBundles = new ArrayList<File>(); getBootstrapBundleJars(new File(baseInstallFolder, ContainerFilesystemLayout.FOLDER_SYSTEM_BOOTSTRAP)); try { setupShutdownHandler(); setupExceptionHandler(); loadStartupFolder(); createCoreServices(args); if (initialBundles.isEmpty()) { throw new RuntimeException("No bootstrap bundles to install."); } File environmentFolder = new File(configFolder, ContainerFilesystemLayout.FOLDER_CONFIG_ENVIRONMENT); ExtensionsReader extensionsReader = new ExtensionsReader(loggingProvider.getLog()); extensionsReader.processExtensionFiles(environmentFolder); createFramework(extensionsReader); registerCoreServices(); loadClasses(extensionsReader.getLoadClasses()); addContainerPathBundles(initialBundles, extensionsReader.getContainerPath()); framework.start(); startBundles(initialBundles); frameworkStartLevel.setStartLevel(InteractiveSpacesStartLevel.STARTUP_LEVEL_LAST.getStartLevel()); framework.waitForStop(0); System.exit(0); } catch (Throwable ex) { if (loggingProvider != null && loggingProvider.getLog() != null) { loggingProvider.getLog().error("Error starting framework", ex); } else { System.err.println("Error starting framework"); ex.printStackTrace(); } System.exit(1); } } /** * Process the command line arguments for this container. * * @param args * the list of command line arguments */ private void processCommandLineArgs(List<String> args) { for (String arg : args) { if (arg.equals(ARGS_NOSHELL)) { needShell = false; } else if (arg.startsWith(ARGS_RUNTIME_PREFIX)) { runtimeFolder = new File(arg.substring(ARGS_RUNTIME_PREFIX.length())); } else if (arg.startsWith(ARGS_CONFIG_PREFIX)) { configFolder = new File(arg.substring(ARGS_CONFIG_PREFIX.length())); } } } /** * Load the contents of the startup folder, which contains additional resources for the container. * * @throws Exception * could not create the startup folder or load it */ private void loadStartupFolder() throws Exception { File startupFolder = new File(baseInstallFolder, ContainerFilesystemLayout.FOLDER_USER_BOOTSTRAP); if (startupFolder.exists()) { if (startupFolder.isFile()) { throw new Exception(String.format("User bootstrap folder %s is a file not a folder.", startupFolder.getAbsolutePath())); } } else if (!startupFolder.mkdirs()) { throw new Exception( String.format("Cannot create user bootstrap folder %s.", startupFolder.getAbsolutePath())); } getBootstrapBundleJars(startupFolder); } /** * Set up the default exception handler. */ private void setupExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { loggingProvider.getLog().error(String.format("Caught uncaught exception from thread %s", t), e); } }); } /** * Create the core services to the base bundle which are platform dependent. * * @param args * the list of command line arguments */ public void createCoreServices(List<String> args) { loggingProvider = new Log4jLoggingProvider(); loggingProvider.configure(runtimeFolder); configurationProvider = new FileConfigurationProvider(baseInstallFolder, configFolder, loggingProvider.getLog()); configurationProvider.load(); containerCustomizerProvider = new SimpleContainerCustomizerProvider(args, true); } /** * Register all bootstrap core services with the container. */ public void registerCoreServices() { rootBundleContext.registerService(LoggingProvider.class.getName(), loggingProvider, null); rootBundleContext.registerService(ConfigurationProvider.class.getName(), configurationProvider, null); rootBundleContext.registerService(ContainerCustomizerProvider.class.getName(), containerCustomizerProvider, null); } /** * Start all bundles. * * @param jars * the jars to start as OSGi bundles * * @throws BundleException * something happened while starting bundles that could not be recovered from */ private void startBundles(List<File> jars) throws BundleException { for (File bundleFile : jars) { String bundleUri = bundleFile.getAbsoluteFile().toURI().toString(); try { Bundle bundle = rootBundleContext.installBundle(bundleUri); String symbolicName = bundle.getSymbolicName(); if (symbolicName != null) { InteractiveSpacesStartLevel startLevel = InteractiveSpacesStartLevel.STARTUP_LEVEL_DEFAULT; if (symbolicName.equals("interactivespaces.master.webapp")) { startLevel = InteractiveSpacesStartLevel.STARTUP_LEVEL_LAST; } else if (symbolicName.equals("interactivespaces.master")) { startLevel = InteractiveSpacesStartLevel.STARTUP_LEVEL_PENULTIMATE; } else { String interactiveSpacesStartLevel = bundle.getHeaders() .get(BUNDLE_MANIFEST_START_LEVEL_HEADER); if (interactiveSpacesStartLevel != null) { startLevel = InteractiveSpacesStartLevel.valueOf(interactiveSpacesStartLevel); } } if (startLevel != InteractiveSpacesStartLevel.STARTUP_LEVEL_DEFAULT) { bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel.getStartLevel()); } bundles.add(bundle); } else { logBadBundle(bundleUri, new Exception("No symbolic name")); } } catch (Exception e) { logBadBundle(bundleUri, e); } } // Start all installed non-fragment bundles. for (final Bundle bundle : bundles) { if (!isFragment(bundle)) { // TODO(keith): See if way to start up shell from property // since we may want it for remote access. String symbolicName = bundle.getSymbolicName(); if (symbolicName.equals("org.apache.felix.gogo.shell") && !needShell) { continue; } startBundle(bundle); } } } /** * Log that we had a bad bundle. * * @param bundleUri * URI for the bundle * @param e * triggering exception */ private void logBadBundle(String bundleUri, Exception e) { loggingProvider.getLog().error(String.format( "Bundle %s is not an OSGi bundle, skipping during Interactive Spaces startup", bundleUri), e); } /** * Start a particular bundle. * * @param bundle * the bundle to start */ private void startBundle(Bundle bundle) { try { bundle.start(); } catch (Exception e) { loggingProvider.getLog().error(String.format("Error while starting bundle %s", bundle.getLocation()), e); } } /** * Create, configure, and start the OSGi framework instance. * * @param extensionsReader * the reader for extensions files * * @throws Exception * unable to create and/or start the framework */ private void createFramework(ExtensionsReader extensionsReader) throws Exception { Map<String, String> m = new HashMap<String, String>(); m.put(Constants.FRAMEWORK_STORAGE_CLEAN, "onFirstInit"); String delegations = getClassloaderDelegations(); if (delegations != null) { loggingProvider.getLog().info(String.format("Delegations %s", delegations)); m.put(Constants.FRAMEWORK_BOOTDELEGATION, delegations); } List<String> extraPackages = new ArrayList<String>(); for (String pckage : PACKAGES_SYSTEM_EXTERNAL) { extraPackages.add(pckage); } for (String pckage : PACKAGES_SYSTEM_INTERACTIVESPACES) { extraPackages.add(pckage); } extraPackages.addAll(extensionsReader.getPackages()); loadLibraries(extensionsReader.getLoadLibraries()); StringBuilder packages = new StringBuilder(); String separator = ""; for (String extraPackage : extraPackages) { packages.append(separator).append(extraPackage); separator = ", "; } m.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, packages.toString()); m.put(CoreConfiguration.CONFIGURATION_INTERACTIVESPACES_BASE_INSTALL_DIR, baseInstallFolder.getAbsolutePath()); m.put(CoreConfiguration.CONFIGURATION_INTERACTIVESPACES_RUNTIME_DIR, runtimeFolder.getAbsolutePath()); m.put(CoreConfiguration.CONFIGURATION_INTERACTIVESPACES_VERSION, getInteractiveSpacesVersion()); m.putAll(configurationProvider.getInitialConfiguration()); File pluginsCacheFolder = new File( new File(runtimeFolder, ContainerFilesystemLayout.FOLDER_INTERACTIVESPACES_RUN), FOLDER_PLUGINS_CACHE); m.put(Constants.FRAMEWORK_STORAGE, pluginsCacheFolder.getCanonicalPath()); framework = getFrameworkFactory().newFramework(m); frameworkStartLevel = framework.adapt(FrameworkStartLevel.class); framework.init(); rootBundleContext = framework.getBundleContext(); if (CONFIG_PROPERTY_VALUE_STARTUP_LOGGING.equals(m.get(CONFIG_PROPERTY_STARTUP_LOGGING))) { rootBundleContext.addBundleListener(new SynchronousBundleListener() { @Override public void bundleChanged(BundleEvent event) { try { if (event.getType() == BundleEvent.STARTED) { Bundle bundle = event.getBundle(); loggingProvider.getLog() .info(String.format("Bundle %s:%s started with start level %d", bundle.getSymbolicName(), bundle.getVersion(), bundle.adapt(BundleStartLevel.class).getStartLevel())); } } catch (Exception e) { loggingProvider.getLog().error("Exception while responding to bundle change events", e); } } }); } } /** * Load a collection of libraries. * * @param libraries * the libraries to load */ private void loadLibraries(List<String> libraries) { for (String library : libraries) { loggingProvider.getLog().info(String.format("Loading system library %s", library)); System.loadLibrary(library); } } /** * Load a collection of classes. * * @param classes * the classes to load */ private void loadClasses(List<String> classes) { for (String className : classes) { loggingProvider.getLog().info(String.format("Loading class %s", className)); try { Class<?> clazz = InteractiveSpacesFrameworkBootstrap.class.getClassLoader().loadClass(className); Object obj = clazz.newInstance(); rootBundleContext.registerService(obj.getClass().getName(), obj, null); } catch (Exception e) { loggingProvider.getLog().error(String.format("Error while creating class %s", className), e); } } } /** * Add in all container path entries from the extensions files as long as the files actually exist. * * @param initialBundles * the initial bundles list * @param containerPath * the elements to be on the container classpath. */ private void addContainerPathBundles(List<File> initialBundles, List<String> containerPath) { for (String containerBundlePath : containerPath) { File bundleFile = new File(containerBundlePath); if (bundleFile.isFile()) { initialBundles.add(bundleFile); } else { loggingProvider.getLog() .warn(String.format("Container path file %s is not a file", containerBundlePath)); } } } /** * Set up a shutdown hook which will stop the framework when the VM shuts down. */ private void setupShutdownHandler() { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { if (framework != null) { framework.stop(); framework.waitForStop(0); } } catch (Exception ex) { loggingProvider.getLog().error("Error stopping framework", ex); } } }); } /** * Get all jars from the bootstrap folder. * * @param folder * the folder containing the bootstrap bundles */ private void getBootstrapBundleJars(File folder) { // Look in the specified bundle directory to create a list // of all JAR files to install. File[] files = folder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { String filename = name.toLowerCase(); return filename.endsWith(".jar") || filename.endsWith(".war"); } }); for (File f : files) { initialBundles.add(f.getAbsoluteFile()); } } /** * Is the bundle a fragment host? * * @param bundle * the bundle to check * * @return {@code true} if the bundle is a fragment host */ private boolean isFragment(Bundle bundle) { return bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null; } /** * Simple method to parse META-INF/services file for framework factory. Currently, it assumes the first non-commented * line is the class nodeName of the framework factory implementation. * * @return the created <tt>FrameworkFactory</tt> instance * * @throws Exception * if any errors occur. **/ private FrameworkFactory getFrameworkFactory() throws Exception { // using the ServiceLoader to get a factory. ClassLoader classLoader = InteractiveSpacesFrameworkBootstrap.class.getClassLoader(); URL url = classLoader.getResource(OSGI_FRAMEWORK_LAUNCH_FRAMEWORK_FACTORY); if (url != null) { BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream())); try { for (String s = br.readLine(); s != null; s = br.readLine()) { // Try to load first non-empty, non-commented line. s = s.trim(); if (!s.isEmpty() && s.charAt(0) != '#') { return (FrameworkFactory) classLoader.loadClass(s).newInstance(); } } } finally { if (br != null) { br.close(); } } } throw new Exception("Could not find framework factory."); } /** * Get the list of packages which must be delegated to the boot classloader. * * <p> * The bootloader loads all classes in the java install. This covers things like the javax classes which are not * automatically exposed through the OSGi bundle classloaders. * * @return a properly formated string for the delegation classpath, or {@code null} if there are no packages to be * delegated */ private String getClassloaderDelegations() { File delegation = new File(baseInstallFolder, "lib/system/java/delegations.conf"); if (delegation.exists()) { StringBuilder builder = new StringBuilder(); String separator = ""; BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(delegation)); String line; while ((line = reader.readLine()) != null) { if (!line.trim().isEmpty()) { builder.append(separator).append(line); separator = ","; } } return builder.toString(); } catch (Exception e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // Don't care. Closing. } } } } return null; } /** * Get the Interactive Spaces version from the JAR manifest. * * @return The interactive spaces version */ private String getInteractiveSpacesVersion() { String classContainer = getClass().getProtectionDomain().getCodeSource().getLocation().toString(); InputStream in = null; try { URL manifestUrl = new URL("jar:" + classContainer + "!/META-INF/MANIFEST.MF"); in = manifestUrl.openStream(); Manifest manifest = new Manifest(in); Attributes attributes = manifest.getMainAttributes(); return attributes.getValue(MANIFEST_PROPERTY_INTERACTIVESPACES_VERSION); } catch (IOException ex) { return null; } finally { if (in != null) { try { in.close(); } catch (IOException e) { // Don't care } } } } }