/*
* HeadsUp Agile
* Copyright 2009-2012 Heads Up Development Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.headsupdev.agile.runtime;
import java.io.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Dictionary;
import org.osgi.framework.launch.*;
import org.apache.felix.main.AutoProcessor;
import org.apache.felix.framework.util.Util;
public class Main
{
/**
* The property name used to specify an URL to the system
* property file.
*/
public static final String SYSTEM_PROPERTIES_PROP = "felix.system.properties";
/**
* 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 configuration
* property file to be used for the created the framework instance.
*/
public static final String CONFIG_PROPERTIES_PROP = "felix.config.properties";
/**
* The default name used for the configuration properties file.
*/
public static final String CONFIG_PROPERTIES_FILE_VALUE = "config.properties";
/**
* The property name used to indicate we should run in debug mode
*/
public static final String DEBUG_PROP = "debug";
/**
* Name of the configuration directory.
*/
public static final String CONFIG_DIRECTORY = "conf";
private static Framework m_fwk = null;
private static boolean help = false;
private static boolean debug = false;
private static boolean verbose = false;
private static boolean restarting = true;
/**
* Start the framework instance. Load properites and install a shutdown hook.
* Wait for shutdown or restart commands and respond accordingly.
*
* @param args command line parameters, none required
* @throws Exception If an error occurs.
*/
public static void main( String[] args )
throws Exception
{
String mesg = " ";
for ( String arg : args )
{
if ( "-d".equals( arg ) || "--debug".equals( arg ) )
{
debug = true;
mesg = " (debug) ";
System.setProperty( "agile.runtime.debug", "true" );
}
else if ( "-v".equals( arg ) || "--verbose".equals( arg ) )
{
verbose = true;
mesg = "(verbose)";
System.setProperty( "agile.runtime.verbose", "true" );
}
else if ( "-c".equals( arg ) || "--color".equals( arg ) )
{
System.setProperty( "agile.runtime.color", "true" );
}
else if ( "-n".equals( arg ) || "--nocolor".equals( arg ) )
{
System.setProperty( "agile.runtime.color", "false" );
}
else if ( "-h".equals( arg ) || "--help".equals( arg ) )
{
help = true;
}
}
String defaultColor = System.getProperty( "agile.runtime.color" );
boolean useColor = defaultColor != null && defaultColor.equalsIgnoreCase( "true" );
String yellow = "";
String gray = "";
String darkGray = "";
String blank = "";
if ( useColor )
{
yellow = "\033[1;33m";
gray = "\033[1;37m";
darkGray = "\033[0;37m";
blank = "\033[0m";
}
// text from compacted "chunky" figlet font (with a cool arrowed "h"
String product1 = gray + " __ __ ";
String product2 = gray + " .---.-.-----|__| |-----.";
String product3 = gray + " | _ | _ | | | -__|";
String product4 = gray + " |___._|___ |__|__|_____|";
String product5 = gray + " |_____| ";
// Print welcome banner.
System.out.println( yellow + " /\\ __ " + product1 );
System.out.println( yellow + "| |--.-----.---.-.--| |-----.--.--.-----." + product2 );
System.out.println( yellow + "| | -__| _ | _ |__ --| | | _ |" + product3 );
System.out.println( yellow + "|__|__|_____|___._|_____|_____|_____| __|" + product4 );
System.out.println( gray + " "+mesg+ " "+yellow+" |__| " + product5 );
System.out.println( blank );
if ( help )
{
System.out.println( "Options:" );
System.out.println( " -h --help \tShow this help message and then exit" );
System.out.println( " -d --debug \tDebug mode enables the framework shell and logs debugging info" );
System.out.println( " -v --verbose\tDisplay some useful information about background tasks etc" );
System.out.println( " -c --color \toutput colour output - default if supported" );
System.out.println( " -n --nocolor\tdisable colour output" );
return;
}
// register a shutdown hook to make sure the framework is
// cleanly shutdown when the VM exits.
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 );
}
}
} );
try
{
String extraBundles = "";
if ( debug )
{
extraBundles = Main.getDebugBundles();
}
while ( restarting )
{
restarting = false;
Properties configProps = loadConfiguration();
String frameworkBundles = configProps.getProperty( "felix.auto.start.1" );
frameworkBundles = frameworkBundles + extraBundles;
configProps.setProperty( "felix.auto.start.1", frameworkBundles );
// TODO should we auto detect the framework jars here (not part of the auto start in felix)?
// 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() );
Dictionary props = new Properties();
m_fwk.getBundleContext().registerService( HeadsUpRuntime.class.getName(),
new HeadsUpRuntimeImpl(), props );
// Start the framework.
m_fwk.start();
// Wait for framework to stop to exit the VM.
m_fwk.waitForStop( 0 );
}
// remove debug bundles from cache
if ( debug )
{
Main.cleanDebug();
}
System.exit( 0 );
}
catch ( Exception ex )
{
System.err.println( "Could not create framework: " + ex );
ex.printStackTrace();
System.exit( -1 );
}
}
protected static Properties loadConfiguration()
{
// Load system properties.
Main.loadSystemProperties();
// Read configuration properties.
Properties configProps = Main.loadConfigProperties();
// If no configuration properties were found, then create
// an empty properties object.
if ( configProps == null )
{
System.err.println( "No " + CONFIG_PROPERTIES_FILE_VALUE + " found." );
configProps = new Properties();
}
// Copy framework properties from the system properties.
Main.copySystemProperties( configProps );
return configProps;
}
/**
* Force the framework to restart from scratch - reloading all configuration properties
*/
static void restart()
{
restarting = true;
stop();
}
/**
* Force the framework to shut down and exit
*/
static void stop()
{
try
{
m_fwk.stop();
}
catch ( Exception e )
{
System.err.println( "Unable to restart framework: " + e );
}
}
/**
* 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
{
URL url = Main.class.getClassLoader().getResource(
"META-INF/services/org.osgi.framework.launch.FrameworkFactory" );
if ( url != null )
{
BufferedReader br = new BufferedReader( new InputStreamReader( url.openStream() ) );
try
{
for ( String s = br.readLine(); s != null; s = br.readLine() )
{
s = s.trim();
// Try to load first non-empty, non-commented line.
if ( ( s.length() > 0 ) && ( s.charAt(0) != '#' ) )
{
return (FrameworkFactory) Class.forName( s ).newInstance();
}
}
}
finally
{
if ( br != null )
{
br.close();
}
}
}
throw new Exception( "Could not find framework factory." );
}
/**
* <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 ).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();
System.setProperty( name, Util.substVars( props.getProperty( name ), name, null, null ) );
}
}
/**
* <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>
* @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an error.
*/
public static Properties loadConfigProperties()
{
// 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 = System.getProperty( CONFIG_PROPERTIES_PROP );
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 ).toURI().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;
}
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 ) );
}
}
}
/**
* Get the list of debug bundles formatted for a felix configuration property
*
* @return the string representing a list of all bunfles needed for debugging
*/
protected static String getDebugBundles()
{
StringBuffer ret = new StringBuffer();
File debugs = new File( "debug" );
for ( File bundle : debugs.listFiles() )
{
ret.append( " \"file:debug/" );
ret.append( bundle.getName() );
ret.append( "\"" );
}
return ret.toString();
}
/**
* Cleanup after a debug running - this means removing any bundles from the cache that were loaded
* from the debug area.
*/
protected static void cleanDebug()
{
File cache = new File( "felix-cache" );
for ( File bundle : cache.listFiles() )
{
if ( !bundle.isDirectory() )
{
continue;
}
File location = new File( bundle, "bundle.location" );
if ( !location.exists() )
{
continue;
}
BufferedReader in = null;
boolean delete = false;
try
{
in = new BufferedReader( new FileReader( location ) );
String jarFile = in.readLine();
if ( jarFile.startsWith( "file:" ) )
{
jarFile = jarFile.substring( 5 );
}
if ( new File( jarFile ).getParentFile().getName().equals( "debug" ) )
{
delete = true;
}
}
catch ( IOException e )
{
e.printStackTrace();
}
finally
{
if ( in != null )
{
try
{
in.close();
}
catch ( IOException e )
{
// ignore
}
}
}
if ( delete )
{
try
{
Main.delete( bundle );
}
catch ( IOException e )
{
e.printStackTrace();
}
}
}
}
/**
* Recursively delete a directory
*
* @param dir The directory to delete
* @throws IOException thrown if any file cannot be deleted
*/
public static void delete( File dir )
throws IOException
{
if ( dir.isDirectory() )
{
for ( File file : dir.listFiles() )
{
delete( file );
}
}
if ( !dir.delete() )
{
throw new IOException( "Unable to delete" );
}
}
}