/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.launcher; import com.bc.ceres.core.runtime.RuntimeConfig; import com.bc.ceres.core.runtime.RuntimeConfigException; import com.bc.ceres.core.runtime.internal.DefaultRuntimeConfig; import com.bc.ceres.launcher.internal.BootstrapClasspathFactory; import com.bc.ceres.launcher.internal.BruteForceClasspathFactory; import java.io.File; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.StringTokenizer; /** * A launcher for applications based on the Ceres runtime. * * @see RuntimeConfig * @see ClasspathFactory */ public final class Launcher { private RuntimeConfig runtimeConfig; private ClasspathFactory classpathFactory; /** * Launches the application with a default {@link RuntimeConfig} and a {@link ClasspathFactory} * based on the <code>${ceres.context}.mainClass</code> property. If the main class is * {@code "com.bc.ceres.core.runtime.RuntimeLauncher"}, then a minimal classpath which at least includes the * <code>ceres-core</code> library is used. Otherwise all directories, JARs and ZIPs found in * the home directory will be added to the classpath. * * @param args the command-line arguments */ public static void main(String[] args) { try { Launcher launcher = createDefaultLauncher(); launcher.launch(args); } catch (Throwable e) { System.err.println("Error: " + e.getMessage()); e.printStackTrace(System.err); System.exit(1); } } /** * Creates a default launcher. * * @return a default launcher. * @throws RuntimeConfigException if the default configuration is invalid */ public static Launcher createDefaultLauncher() throws RuntimeConfigException { RuntimeConfig runtimeConfig = new DefaultRuntimeConfig(); ClasspathFactory classpathFactory; if (runtimeConfig.isUsingModuleRuntime()) { classpathFactory = new BootstrapClasspathFactory(runtimeConfig); } else { classpathFactory = new BruteForceClasspathFactory(runtimeConfig); } return new Launcher(runtimeConfig, classpathFactory); } /** * Constructs a new launcher. * * @param runtimeConfig the runtime configuration * @param classpathFactory the classpath factory */ public Launcher(RuntimeConfig runtimeConfig, ClasspathFactory classpathFactory) { this.runtimeConfig = runtimeConfig; this.classpathFactory = classpathFactory; trace("Configuration type: " + this.runtimeConfig.getClass().getName()); trace("Classpath type: " + this.classpathFactory.getClass().getName()); } public RuntimeConfig getRuntimeConfig() { return runtimeConfig; } public ClasspathFactory getClasspathFactory() { return classpathFactory; } public ClassLoader createClassLoader() throws RuntimeConfigException { ClassLoader classLoader = getClass().getClassLoader(); URL[] defaultClasspath = createDefaultClasspath(); if (defaultClasspath.length > 0) { classLoader = new URLClassLoader(defaultClasspath, classLoader); } URL[] mainClasspath = createMainClasspath(); if (defaultClasspath.length > 0) { classLoader = new URLClassLoader(mainClasspath, classLoader); } traceClassLoader("classLoader", classLoader); return classLoader; } URL[] createDefaultClasspath() throws RuntimeConfigException { return classpathFactory.createClasspath(); } URL[] createMainClasspath() { ArrayList<URL> urlList = new ArrayList<URL>(16); String paths = runtimeConfig.getMainClassPath(); if (paths != null) { StringTokenizer st = new StringTokenizer(paths, File.pathSeparator); while (st.hasMoreTokens()) { String path = st.nextToken().trim(); try { URL url = new File(path).toURI().toURL(); urlList.add(url); } catch (MalformedURLException e) { trace(MessageFormat.format("Invalid classpath entry: {0}", path)); } } } return urlList.toArray(new URL[urlList.size()]); } public void launch(String[] args) throws Exception { ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); ClassLoader classLoader = createClassLoader(); try { Thread.currentThread().setContextClassLoader(classLoader); Class<?> mainClass = classLoader.loadClass(runtimeConfig.getMainClassName()); if (runtimeConfig.isUsingModuleRuntime()) { Method mainMethod = mainClass.getMethod("launch", new Class[]{RuntimeConfig.class, ClassLoader.class, String[].class}); mainMethod.invoke(null, new Object[]{runtimeConfig, classLoader, args}); } else { Method mainMethod = mainClass.getMethod("main", new Class[]{String[].class}); mainMethod.invoke(null, new Object[]{args}); } } finally { Thread.currentThread().setContextClassLoader(oldContextClassLoader); } } private void trace(String msg) { if (runtimeConfig.isDebug()) { runtimeConfig.getLogger().info(String.format("ceres-launcher: %s", msg)); } } // do not delete, useful for debugging private void traceClassLoader(String name, ClassLoader classLoader) { trace("============================================================================="); trace(name + ".class = " + classLoader.getClass()); if (classLoader instanceof URLClassLoader) { URL[] classpath = ((URLClassLoader) classLoader).getURLs(); for (int i = 0; i < classpath.length; i++) { trace(name + ".url[" + i + "] = " + classpath[i]); } } if (classLoader.getParent() != null) { traceClassLoader(name + ".parent", classLoader.getParent()); } else { trace(name + ".parent = null"); } } }