
uk.org.retep.kernel.Bootstrap Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2008, Peter T Mount
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the retep.org.uk nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package uk.org.retep.kernel;
// Implementation note: this class cannot access anything outside of what is
// available to just the JVM...
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Semaphore;
/**
* This class runs before the Kernel. It configures core system features like
* Log4J, and the relevant ClassLoaders
* @author peter
*/
public class Bootstrap
{
private static final Bootstrap instance = new Bootstrap();
private List arguments = new ArrayList();
private String applicationName;
private Thread bootStrapThread;
private File libDir;
private File appDir;
private File homeDir;
private File etcDir;
private File data1Dir;
private File data2Dir;
private ClassLoader kernelLoader;
private ClassLoader appLoader;
private Class> kernelClass;
private Object kernel;
private Semaphore semaphore;
public static Bootstrap getInstance()
{
return instance;
}
private Bootstrap()
{
}
public String getApplicationName()
{
return applicationName;
}
public void setApplicationName( final String applicationName )
{
this.applicationName = applicationName;
// On Linux and Darwin, applicationName is just that, but on Win32
// when run from inside Cygwin it's the full path to the .exe so
// we have to do an inline StringUtils.basename() in that case
if( File.separatorChar == '\\' )
{
final int i = applicationName.lastIndexOf( '\\' );
if( i > -1 )
{
this.applicationName = applicationName.substring( i + 1 );
}
}
}
private URL getJarURL( Class> clazz )
throws MalformedURLException
{
URL classURL = clazz.getResource( clazz.getSimpleName() + ".class" );
if( classURL == null )
{
return null;
}
String path = classURL.toString();
int i = path.indexOf( '!' );
if( i > -1 )
{
path = path.substring( 0, i );
}
if( path.startsWith( "jar:" ) )
{
path = path.substring( 4 );
}
return new URL( path );
}
private File getJarFile( Class> clazz )
{
try
{
URL jarURL = getJarURL( clazz );
if( jarURL != null )
{
return new File( jarURL.getPath() ).getAbsoluteFile();
}
return null;
}
catch( MalformedURLException ex )
{
ex.printStackTrace();
return null;
}
}
public void bootstrap()
{
try
{
final long startTime = System.currentTimeMillis();
final Date applicationStartDate = new Date( startTime );
if( bootStrapThread == null )
{
bootStrapThread = Thread.currentThread();
}
else
{
throw new SecurityException(
"Only the BootStrap Thread may call this method" );
}
// Where the core jar is located
homeDir = getJarFile( getClass() ).getParentFile().getParentFile();
libDir = new File( homeDir, "lib" );
appDir = new File( libDir, applicationName );
etcDir = new File( homeDir, "etc" );
data1Dir = new File( homeDir, "data" );
data2Dir = new File( data1Dir, applicationName );
data1Dir = new File( data1Dir, "common" );
// Create the Kernel ClassLoader
kernelLoader = createClassLoader( libDir, null );
// Now the application ClassLoader
appLoader = createClassLoader( appDir, kernelLoader );
// Terracotta classloader support
final boolean terracottaPresent = TerracottaSupport.terracottaPresent();
if( terracottaPresent )
{
TerracottaSupport.initClassLoader( kernelLoader, "kernel" );
TerracottaSupport.initClassLoader( appLoader, applicationName );
}
// Create the Kernel using the application's ClassLoader
kernelClass = kernelLoader.loadClass( "uk.org.retep.kernel.Kernel" );
kernel = kernelClass.getMethod( "getInstance" ).invoke( null );
// inject various parameters
inject( "bootStrapThread", bootStrapThread );
inject( "applicationName", applicationName );
inject( "applicationStartDate", applicationStartDate );
inject( "kernelClassLoader", kernelLoader );
inject( "applicationClassLoader", appLoader );
inject( "homeDirectory", homeDir );
inject( "configDirectory", etcDir );
inject( "arguments", Collections.unmodifiableList( arguments ) );
inject( "dataDirectory", data2Dir.exists() ? data2Dir : data1Dir );
// Load build time configuration
final Properties props = getProperties();
inject( "kernelVersion", props.getProperty( "kernel.version" ) );
// Set the GlobalThreadPool to run with the appLoader
final Class> globalThreadPool = kernelLoader.loadClass(
"uk.org.retep.util.thread.GlobalThreadPool" );
final Method setContextClassLoader = globalThreadPool.getMethod(
"setContextClassLoader", ClassLoader.class );
setContextClassLoader.invoke( null, appLoader );
// Semaphore used to terminate the main thread
semaphore = new Semaphore( 0 );
inject( "bootstrapSemaphore", semaphore );
// Start the kernel
final Method bootstrap = kernelClass.getDeclaredMethod( "bootstrap" );
bootstrap.setAccessible( true );
bootstrap.invoke( kernel );
// Now wait on the semaphore
semaphore.acquire();
}
catch( Throwable t )
{
if( t instanceof InvocationTargetException )
{
t = t.getCause();
}
// Some voodo, remove all traces of Reflection, hiding how the bootstrap works
Throwable t1 = t;
while( t1 != null )
{
final List ste = Arrays.asList(
t1.getStackTrace() );
final Iterator it = ste.iterator();
while( it.hasNext() )
{
final StackTraceElement s = it.next();
if( s.getClassName().contains( "reflect" ) )
{
try
{
it.remove();
}
catch( UnsupportedOperationException uoe )
{
// Ignore
}
}
}
t1.setStackTrace( ste.toArray(
new StackTraceElement[ ste.size() ] ) );
t1 = t.getCause();
}
t.printStackTrace();
System.exit( 1 );
}
}
private Properties getProperties()
throws IOException
{
final InputStream is = getClass().getResourceAsStream(
"bootstrap.properties" );
try
{
final Properties props = new Properties();
props.load( is );
return props;
}
finally
{
is.close();
}
}
private ClassLoader createClassLoader( final File dir,
final ClassLoader parent )
throws MalformedURLException
{
// Get a list of files
final File[] files = dir.listFiles( new FileFilter()
{
@Override
public boolean accept( final File file )
{
final String name = file.getName();
return file.isFile() &&
name.endsWith( ".jar" );
}
} );
final URL urls[] = new URL[ files.length ];
for( int i = 0; i < files.length; i++ )
{
urls[i] = files[i].toURI().toURL();
}
if( parent == null )
{
return new URLClassLoader( urls );
}
else
{
return new URLClassLoader( urls, parent );
}
}
private void inject( final String field, final Object value )
{
try
{
final Field f = kernelClass.getDeclaredField( field );
f.setAccessible( true );
f.set( kernel, value );
}
catch( Exception ex )
{
throw new RuntimeException( "Failed to inject " + field, ex );
}
}
/**
* Used by JNI to add a command line argument
* @param arg command line argument
*/
public void addArgument( final String arg )
{
arguments.add( arg );
}
/**
* Proxy isRestart() to the kernel instance. This is called by JNI
* @return
*/
public boolean isRestart()
{
if( kernel == null )
{
return false;
}
try
{
return (Boolean) kernel.getClass().getMethod( "isRestart" ).invoke(
kernel );
}
catch( Exception ex )
{
return false;
}
}
}