/*******************************************************************************
* Copyright (c) 2012- 2016 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
* DISID Corporation, S.L - Spring Roo maintainer
*******************************************************************************/
package org.springframework.roo.shell.eclipse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.felix.framework.util.Util;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
/**
* Loads Roo via Felix.
* <p>
* This class is based on Apache Felix's org.apache.felix.main.Main class.
* <p>
* For maximum compatibility with Felix, this class has minimal changes from the
* original. All changes are noted with a
* "**** CHANGE FROM ORIGINAL FELIX VERSION ****" comment.
* @author Christian Dupuis
* @author Juan Carlos GarcĂa
* @since 1.1.0
*/
public class Main {
/**
* Switch for specifying bundle directory.
**/
public static final String BUNDLE_DIR_SWITCH = "-b";
/**
* Name of the configuration directory.
*/
public static final String CONFIG_DIRECTORY = "conf";
/**
* The default name used for the configuration properties file.
**/
public static final String CONFIG_PROPERTIES_FILE_VALUE = "config.properties";
/**
* The property name used to specify an URL to the configuration property
* file to be used for the created the framework instance.
**/
public static final String CONFIG_PROPERTIES_PROP = "felix.config.properties";
/**
* The property name used to specify whether the launcher should install a
* shutdown hook.
**/
public static final String SHUTDOWN_HOOK_PROP = "felix.shutdown.hook";
/**
* The default name used for the system properties file.
**/
public static final String SYSTEM_PROPERTIES_FILE_VALUE = "system.properties";
/**
* The property name used to specify an URL to the system property file.
**/
public static final String SYSTEM_PROPERTIES_PROP = "felix.system.properties";
/**
* Version of Roo bundle cache. STS auto installs bundles into Roo and if bundles in STS change, e.g. due to an STS update, the cache needs to be cleaned.
*/
private static final int CACHE_VERSION = 2;
private Framework m_fwk = null;
/**
* <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>Examine and verify command-line arguments.</b></i> The launcher
* accepts a "<tt>-b</tt>" command line switch to set the bundle auto-deploy
* directory and a single argument to set the bundle cache directory.</li>
* <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>. Generally speaking, the framework does not use
* system properties at all.</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. If the <tt>config.properties</tt> file cannot be
* found, then default values are used for all configuration properties.
* Refer to the <a href="Felix.html#Felix(java.util.Map)"> <tt>Felix</tt>
* </a> constructor documentation for more information on framework
* configuration properties.</li>
* <li><i><b>Copy configuration properties specified as system properties
* into the set of configuration properties.</b></i> Even though the Felix
* framework does not consult system properties for configuration
* information, sometimes it is convenient to specify them on the command
* line when launching Felix. To make this possible, the Felix launcher
* copies any configuration properties specified as system properties into
* the set of configuration properties passed into Felix.</li>
* <li><i><b>Add shutdown hook.</b></i> To make sure the framework shutdowns
* cleanly, the launcher installs a shutdown hook; this can be disabled with
* the <tt>felix.shutdown.hook</tt> configuration property.</li>
* <li><i><b>Create and initialize a framework instance.</b></i> The OSGi
* standard <tt>FrameworkFactory</tt> is retrieved from
* <tt>META-INF/services</tt> and used to create a framework instance with
* the configuration properties.</li>
* <li><i><b>Auto-deploy bundles.</b></i> All bundles in the auto-deploy
* directory are deployed into the framework instance.</li>
* <li><i><b>Start the framework.</b></i> The framework is started and the
* launcher thread waits for the framework to shutdown.</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 a some means to interact with the
* framework; this is generally done by bundles in the auto-deploy directory
* or specifying an "auto-start" property in the configuration property
* file. If no bundles providing a means to interact with the framework 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.
* </p>
* <p>
* The launcher provides two ways to deploy bundles into a framework at
* startup, which have associated configuration properties:
* </p>
* <ul>
* <li>Bundle auto-deploy - Automatically deploys all bundles from a
* specified directory, controlled by the following configuration
* properties:
* <ul>
* <li><tt>felix.auto.deploy.dir</tt> - Specifies the auto-deploy directory
* from which bundles are automatically deploy at framework startup. The
* default is the <tt>bundle/</tt> directory of the current directory.</li>
* <li><tt>felix.auto.deploy.action</tt> - Specifies the auto-deploy actions
* to be found on bundle JAR files found in the auto-deploy directory. The
* possible actions are <tt>install</tt>, <tt>update</tt>, <tt>start</tt>,
* and <tt>uninstall</tt>. If no actions are specified, then the auto-deploy
* directory is not processed. There is no default value for this property.</li>
* </ul>
* </li>
* <li>Bundle auto-properties - Configuration properties which specify URLs
* to bundles to install/start:
* <ul>
* <li><tt>felix.auto.install.N</tt> - Space-delimited list of bundle URLs
* to automatically install when the framework is started, where <tt>N</tt>
* is the start level into which the bundle will be installed (e.g.,
* felix.auto.install.2).</li>
* <li><tt>felix.auto.start.N</tt> - Space-delimited list of bundle URLs to
* automatically install and start when the framework is started, where
* <tt>N</tt> is the start level into which the bundle will be installed
* (e.g., felix.auto.start.2).</li>
* </ul>
* </li>
* </ul>
* <p>
* These properties should be specified in the <tt>config.properties</tt> so
* that they can be processed by the launcher during the framework startup
* process.
* </p>
* @param args Accepts arguments to set the auto-deploy directory and/or the
* bundle cache directory.
* @throws Exception If an error occurs.
**/
public Framework start(String projectDir, String bundleDir, String cacheDir, String configProperties, String rooVersion) throws Exception {
// Read configuration properties.
Map<String, String> configProps = new HashMap<String, String>();
((Map)configProps).putAll(Main.loadConfigProperties(configProperties));
configProps.put("felix.service.urlhandlers", "false");
configProps.put("roo.working.directory", projectDir);
// If there is a passed in bundle auto-deploy directory, then
// that overwrites anything in the config file.
if (bundleDir != null) {
configProps.put(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY, bundleDir);
}
// If there is a passed in bundle cache directory, then
// that overwrites anything in the config file.
if (cacheDir != null) {
configProps.put(Constants.FRAMEWORK_STORAGE, cacheDir);
}
configProps.put("org.osgi.framework.system.packages.extra", "org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins,org.w3c.dom.traversal");
configProps.put("roobot.index.dowload", "false");
if (shouldCleanCache(cacheDir, rooVersion)) {
configProps.put("org.osgi.framework.storage.clean", "onFirstInit");
}
// If enabled, register a shutdown hook to make sure the framework is
// cleanly shutdown when the VM exits.
String enableHook = configProps.get(SHUTDOWN_HOOK_PROP);
if ((enableHook == null) || !enableHook.equalsIgnoreCase("false")) {
Runtime.getRuntime().addShutdownHook(new Thread("Felix Shutdown Hook") {
public void run() {
try {
if (m_fwk != null) {
m_fwk.stop();
m_fwk.waitForStop(0);
}
}
catch (Exception ex) {
System.err.println("Error stopping framework: " + ex);
}
}
});
}
// Print welcome banner. // **** CHANGE FROM ORIGINAL FELIX VERSION ****
// System.out.println("\nWelcome to Felix");
// System.out.println("================\n");
// **** END OF CHANGE FROM ORIGINAL FELIX VERSION ****
// try {
// Create an instance of the framework.
FrameworkFactory factory = getFrameworkFactory();
m_fwk = factory.newFramework(configProps);
// Initialize the framework, but don't start it yet.
m_fwk.init();
// Use the system bundle context to process the auto-deploy
// and auto-install/auto-start properties.
AutoProcessor.process(configProps, m_fwk.getBundleContext(), rooVersion);
// Start the framework.
m_fwk.start();
return m_fwk;
// }
// catch (Exception ex) {
// System.err.println("Could not create framework: " + ex);
// ex.printStackTrace();
// // System.exit(-1);
// }
//
// return null;
}
private boolean shouldCleanCache(String cacheDir, String rooVersion) throws FileNotFoundException {
Properties props = new Properties();
// cache directory to Spring Roo 2.0+ versions
String cacheName = "sts-cache";
// cache directory to Spring Roo 1.x versions
if(rooVersion.startsWith("1")){
int index = cacheDir.lastIndexOf("sts-cache");
if (index > -1) {
cacheName = cacheDir.subSequence(index, cacheDir.length()).toString();
} else {
cacheName = "sts-cache";
}
}
// check version
// store file in cache directories parent since the cache will get deleted
File versionFile = new File(new File(cacheDir).getParentFile(), "." + cacheName + "-version");
if (versionFile.exists()) {
try {
FileInputStream in = new FileInputStream(versionFile);
try {
props.load(in);
} finally {
in.close();
}
int version = Integer.parseInt(props.getProperty("version"));
if (version == CACHE_VERSION) {
// nothing to do
return false;
}
} catch (Exception e) {
// ignore
}
}
// update version
try {
FileOutputStream out = new FileOutputStream(versionFile);
props.setProperty("version", Integer.toString(CACHE_VERSION));
try {
props.store(out, "Auto generated by SpringSource Tool Suite");
} finally {
out.close();
}
} catch (Exception e) {
// ignore
}
return true;
}
public static void copySystemProperties(Properties configProps) {
for (Enumeration e = System.getProperties().propertyNames(); e.hasMoreElements();) {
String key = (String) e.nextElement();
if (key.startsWith("felix.") || key.startsWith("org.osgi.framework.")) {
configProps.setProperty(key, System.getProperty(key));
}
}
}
/**
* <p>
* 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 <tt>conf/</tt> directory of the Felix installation
* directory and is called "<tt>config.properties</tt>". The installation
* directory of Felix is assumed to be the parent directory of the
* <tt>felix.jar</tt> file as found on the system class path property. The
* precise file from which to load configuration properties can be set by
* initializing the "<tt>felix.config.properties</tt>" system property to an
* arbitrary URL.
* </p>
* @param configProperties
* @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an
* error.
**/
public static Properties loadConfigProperties(String configProperties) {
// 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.
// See if the property URL was specified as a property.
URL propURL = null;
String custom = configProperties;
if (custom != null) {
try {
propURL = new URL(custom);
}
catch (MalformedURLException ex) {
System.err.print("Main: " + ex);
return null;
}
}
else {
// Determine where the configuration directory is by figuring
// out where felix.jar is located on the system class path.
File confDir = null;
String classpath = System.getProperty("java.class.path");
int index = classpath.toLowerCase().indexOf("felix.jar");
int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
if (index >= start) {
// Get the path of the felix.jar file.
String jarLocation = classpath.substring(start, index);
// Calculate the conf directory based on the parent
// directory of the felix.jar directory.
confDir = new File(new File(new File(jarLocation).getAbsolutePath()).getParent(), CONFIG_DIRECTORY);
}
else {
// Can't figure it out so use the current directory as default.
confDir = new File(System.getProperty("user.dir"), CONFIG_DIRECTORY);
}
try {
propURL = new File(confDir, CONFIG_PROPERTIES_FILE_VALUE).toURL();
}
catch (MalformedURLException ex) {
System.err.print("Main: " + ex);
return null;
}
}
// Read the properties file.
Properties props = new Properties();
InputStream is = null;
try {
// Try to load config.properties.
is = propURL.openConnection().getInputStream();
props.load(is);
is.close();
}
catch (Exception ex) {
// Try to close input stream if we have one.
try {
if (is != null)
is.close();
}
catch (IOException ex2) {
// Nothing we can do.
}
return null;
}
// Perform variable substitution for system properties.
for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
String name = (String) e.nextElement();
props.setProperty(name, Util.substVars(props.getProperty(name), name, null, props));
}
return props;
}
/**
* <p>
* Loads the properties in the system property file associated with the
* framework installation into <tt>System.setProperty()</tt>. These
* properties are not directly used by the framework in anyway. By default,
* the system property file is located in the <tt>conf/</tt> directory of
* the Felix installation directory and is called "
* <tt>system.properties</tt>". The installation directory of Felix is
* assumed to be the parent directory of the <tt>felix.jar</tt> file as
* found on the system class path property. The precise file from which to
* load system properties can be set by initializing the "
* <tt>felix.system.properties</tt>" system property to an arbitrary URL.
* </p>
**/
public static void loadSystemProperties() {
// 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 = null;
String custom = System.getProperty(SYSTEM_PROPERTIES_PROP);
if (custom != null) {
try {
propURL = new URL(custom);
}
catch (MalformedURLException ex) {
System.err.print("Main: " + ex);
return;
}
}
else {
// Determine where the configuration directory is by figuring
// out where felix.jar is located on the system class path.
File confDir = null;
String classpath = System.getProperty("java.class.path");
int index = classpath.toLowerCase().indexOf("felix.jar");
int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
if (index >= start) {
// Get the path of the felix.jar file.
String jarLocation = classpath.substring(start, index);
// Calculate the conf directory based on the parent
// directory of the felix.jar directory.
confDir = new File(new File(new File(jarLocation).getAbsolutePath()).getParent(), CONFIG_DIRECTORY);
}
else {
// Can't figure it out so use the current directory as default.
confDir = new File(System.getProperty("user.dir"), CONFIG_DIRECTORY);
}
try {
propURL = new File(confDir, SYSTEM_PROPERTIES_FILE_VALUE).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();
System.setProperty(name, Util.substVars(props.getProperty(name), name, null, null));
}
}
/**
* Simple method to parse META-INF/services file for framework factory.
* Currently, it assumes the first non-commented line is the class name of
* the framework factory implementation.
* @return The created <tt>FrameworkFactory</tt> instance.
* @throws Exception if any errors occur.
**/
private static FrameworkFactory getFrameworkFactory() throws Exception {
return (FrameworkFactory) Class.forName("org.apache.felix.framework.FrameworkFactory").newInstance();
}
}