de.tsl2.nano.core.AppLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.core Show documentation
Show all versions of tsl2.nano.core Show documentation
TSL2 Framework Core (Main-Loader, Environment, Logging, Classloading, Crypting, PKI, HttpClient, ManagedException, Progress, System-Execution, CPU/Profiling, Compatibility-Layer, Messaging, Updater)
The newest version!
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: ts
* created on: 11.09.2013
*
* Copyright: (c) Thomas Schneider 2013, all rights reserved
*/
package de.tsl2.nano.core;
import java.io.File;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.security.Permission;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.jar.Attributes;
import org.apache.commons.logging.Log;
import de.tsl2.nano.core.classloader.NetworkClassLoader;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.CollectionUtil;
import de.tsl2.nano.core.util.ConcurrentUtil;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
/**
* Provides an Application Starter with an own extended classloader and a convenience to handle call arguments (the
* {@link Argumentator}. The goal is to create the new classloader before loading any classes by the parent classloader.
* thats, why this class uses as less dependencies/imports as possible!
*
* Use:
*
*
* - Extend the AppLoader and create the main method, creating a new instance of your extended loader and calling {@link #start(String[])}.
* - Override {@link #getManual()} to have a check and print of application usage
* - Override {@link #createEnvironment(Argumentator)} to interpret arguments and set application environment (see {@link ENV}).
*
* Example:
*
* public class Loader extends AppLoader {
* public static void main(String[] args) {
* new Loader().start("mypackagepath.MyMainClass", null, args);
* }
* }
*
*
* ATTENTION: your extension should do not more than that - to have as less dependencies as possible!
*
*
* The main arguments will be concatenated through the following rule:
* 1. META-INF/MANIFEST.MF:Main-Arguments
* 2. System.getProperties("apploader.args")
* 3. main(args) the real command line arguments
*
*
* The environment directory is defined in the first main args, if args.length > 1. If you set the system property
* 'env.user.home', the user.home will be used as parent directory for the environment.
*
* @author ts
* @version $Revision$
*/
public class AppLoader {
static Log LOG = LogFactory.getLog(AppLoader.class);
private static final String KEY_ISNESTEDJAR = "nano.apploader.isnestedjar";
/**
* provides a map containing argument names (map-keys) and their description (map-values).
*
* @return map describing all possible main application arguments. Used by {@link Argumentator}.
*/
protected Map getManual() {
// don't use MapUtil.asMap() to use only core-classes
Map map = new LinkedHashMap();
map.put("usage", getClass().getSimpleName() + " [environment name or path] {} [arguments]");
return map;
}
/**
* createEnvironment
*
* @param environment environment name/path
* @param args console call arguments (see {@link Argumentator}) to be interpreted.
*/
protected Object createEnvironment(String environment, Argumentator args) {
System.setProperty(environment, environment + "/");
//we don't want to have a direct dependency to the environment class!
return callENV("create", environment);
}
public static Object callENV(String fctName, Object... args) {
return BeanClass.createBeanClass("de.tsl2.nano.core.ENV").callMethod(null,
fctName,
Argumentator.getArgumentClasses(String.class, args),
args);
}
/**
* extracts mainclass and environment from args and delegates to {@link #start(String, String, String[])}.
*
* @param args main args (must not be null!)
*/
public void start(String[] args) {
String mainclass;
String mainmethod;
String environment;
String[] nargs;
if (args.length == 0) {
System.out.println(
"AppLoader needs at least one parameter!\n " +
"syntax: AppLoader [environment-dir(default:'.' + main-class + '.environment')] [method-if-not-main] [args...]"
+
"Tip: it is possible to add 'Main-Arguments' to the META-INF/MANIFEST file.");
return;
} else if (args.length == 1) {
//use the mainclass name as environment name
environment = getFileSystemPrefix() + "." + StringUtil.substring(args[0], ".", null, true).toLowerCase();
mainclass = args[0];
mainmethod = "main";
nargs = new String[0];
} else {
//if a full path was given, use that directly
int processed = 1;
if (args[0].startsWith("/") || args[0].contains(":")) {
environment = args[0];
mainclass = args[processed++];
} else {
environment = getFileSystemPrefix() + "." + StringUtil.substring(args[0], ".", null, true).toLowerCase();
mainclass = args[0];
}
mainmethod = args.length > 2 ? args[processed++] : "main";
nargs = new String[args.length - processed];
System.arraycopy(args, processed, nargs, 0, nargs.length);
}
start(mainclass, mainmethod, environment, nargs);
}
/**
* delegates to {@link #start(String, String, String, String[])}
*
* @param mainclass main-class to start it's main method with new classloader. must not be null!
* @param args main args (must not be null!)
*/
public void start(String mainclass, String[] args) {
start(mainclass, null, null, args);
}
/**
* starts application loading. will be called by the static main method to work inside an extended class instance.
*
* @param mainclass main-class to start it's main method with new classloader. must not be null!
* @param environment (optional, default: see #getDefaultEnvPath()) environment name or path
* @param args main args (must not be null!)
*/
public void start(String mainclass, String mainmethod, String environment, String[] args) {
try {
/*
* check and use the AppLoaders main arguments
*/
if (isHelpRequest(args)) {
printHelp(mainclass);
}
if (environment == null) {
if (args.length > 0) {
environment = args[0];
String[] nargs = new String[args.length - 1];
System.arraycopy(args, 1, nargs, 0, nargs.length);
args = nargs;
} else {
environment = getFileSystemPrefix() + getDefaultEnvPath(mainclass);
}
}
environment = FileUtil.getURIFile(environment).getPath();
if (mainmethod == null) {
mainmethod = "main";
}
LogFactory.setLogFile(environment + "/apploader.log");
LOG.info("\n#############################################################"
+ "\nAppLoader preparing launch for:\n mainclass : "
+ mainclass
+ "\n mainmethod: "
+ mainmethod
+ "\n args : "
+ StringUtil.toString(args, 200)
+ "\n"
+ " environment: "
+ environment
+ "\n"
+ "#############################################################\n");
//TODO: should be removed after resolving access problems
noSecurity();
useUTF8();
/*
* create the classloader to be used by the new application
*/
FileUtil.userDirFile(environment).mkdirs();
NetworkClassLoader networkClassLoader = provideClassloader(environment);
BeanClass> bc = BeanClass.createBeanClass(mainclass);
/*
* now, we can load the environment with properties and services
*/
createEnvironment(environment, new Argumentator(bc.getName(), getManual(), args));
/*
* start the jar path checker thread
*/
//don't use the wrong classloaders Environment!
int deltaTime = (Integer) BeanClass.createBeanClass(ENV.class.getName()).callMethod(null, "get",
new Class[] { String.class, Object.class }, "jar.checker.deltatime", 1000);
networkClassLoader.startPathChecker(environment, deltaTime);
/*
* prepare cleaning the Apploader
*/
ConcurrentUtil.startDaemon("apploader-clean", new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOG = null;
//stop old logfactory instance
LogFactory.stop();
}
}
});
/*
* finally, the application will be started inside the
* new environment and classloader
*/
bc.callMethod(null, mainmethod, new Class[] { String[].class }, new Object[] { args });
} catch (Throwable ex) {
//main exception catching: log the exception before exiting!
ex.printStackTrace();
LOG.error(ex);
}
}
/**
* internal hack to guarantee utf-8
*/
public static void useUTF8() {
useCharset("UTF-8");
}
public static void useCp1252() {
useCharset("Cp1252");
}
protected static void useCharset(String encoding) {
try {
System.setProperty("file.encoding", encoding);
Field charset = Charset.class.getDeclaredField("defaultCharset");
charset.setAccessible(true);
charset.set(null,null);
} catch (Exception e) {
System.err.println(e.toString());
System.out.println("continuing after " + encoding + " error, followed by html rendering problems...");
}
}
/**
* getDefaultEnvPath
*
* @param mainclass
* @return '.' + main-class-name + '.environment'
*/
private String getDefaultEnvPath(String mainclass) {
return "." + StringUtil.substring(mainclass, ".", null, true).toLowerCase() + ".environment";
}
/**
* isHelpRequest
*
* @param args arguments to check
* @return true, if arguments describe a help request
*/
protected boolean isHelpRequest(String[] args) {
return args.length == 0 || args[0].matches(".*(\\?|help|man)");
}
/**
* printHelp
*
* @param name
* @param args
*/
protected void printHelp(String name) {
Argumentator.printManual(name, getManual(), System.out, 80);
}
/**
* main - to be delegated by your extension
*
* @param args console call arguments
*/
public static void main(String[] args) {
String sa = System.getProperty("apploader.args");
String[] sargs = sa != null ? sa.split(",") : new String[0];
args =
CollectionUtil.concat(String[].class, getArgumentsFromManifest(), sargs, args);
new AppLoader().start(args);
}
protected static Attributes getManifestAttributes() {
return Argumentator.readManifest();
}
static String[] getArgumentsFromManifest() {
String argss = getManifestAttributes().getValue("Main-Arguments");
return argss != null ? argss.split("\\s") : new String[0];
}
/**
* creates and sets a new extended classloader for the current threads context
*
* @param environment name/path to be added to the classloader
*/
protected NetworkClassLoader provideClassloader(String environment) {
environment = FileUtil.getURIFile(environment).getPath();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
String classPath = System.getProperty("java.class.path");
/*
* there are two mechanisms: loading from jar, or loading in an IDE from classpath.
* 1. loading from a jar-file, the previous classloader must not be given to the new one!
* so we have to add the jar-file itself ('java.classpath') and the user.dir to the path.
* 2. loading from IDE-classpath, we have to use the parent classloader
*/
//perhaps on application servers, the following property is set
String mngt = System.getProperty("javax.management.builder.initial");
//e.g. the JNLPClassLoader is of type URLClassLoader
ClassLoader cl =
/*contextClassLoader instanceof URLClassLoader || */classPath.contains(File.pathSeparator) || mngt != null || isDalvik()
? contextClassLoader : null;
NetworkClassLoader nestedLoader = new NetworkClassLoader(cl, NetworkClassLoader.REGEX_EXCLUDE);
nestedLoader.setEnvironment(environment);
if (cl == null) {
LOG.info("discarding boot classloader " + contextClassLoader);
nestedLoader.addFile(classPath);
// String configDir = System.getProperty("user.dir") + "/" + environment + "/";
// nestedLoader.addLibraryPath(new File(configDir).getAbsolutePath());
System.setProperty(KEY_ISNESTEDJAR, Boolean.toString(true));
} else {
System.setProperty(KEY_ISNESTEDJAR, Boolean.toString(false));
}
//set the classes directory before the root config directory!
File binDir = new File(environment + "/" + NetworkClassLoader.DEFAULT_BIN_DIR);
binDir.mkdirs();
nestedLoader.addLibraryPath(binDir.getPath());
nestedLoader.addLibraryPath(new File(environment).getPath());
System.out.println("resetting current thread classloader " + contextClassLoader + " with " + nestedLoader);
Thread.currentThread().setContextClassLoader(nestedLoader);
return nestedLoader;
}
/**
* convenience to insert new args to the given args
*
* @param args standard arguments
* @param preArgs arguments to be inserted before.
* @return new String[] holding preArgs + args
*/
protected String[] extendArgs(String[] args, String... preArgs) {
// don't use CollectionUtil.concat(String[].class, preArgs, args) to import only nano-core classes
String[] newArgs = new String[args.length + preArgs.length];
System.arraycopy(preArgs, 0, newArgs, 0, preArgs.length);
System.arraycopy(args, 0, newArgs, preArgs.length, args.length);
return newArgs;
}
public static String getFileSystemPrefix() {
if (System.getProperty("env.user.home") != null && !isDalvik())
return System.getProperty("user.home") + "/";
//on dalvik systems, the MainActivity.onCreate() should set the system property
return isDalvik() ? System.getProperty("android.sdcard.path", "/mnt/sdcard/") : isUnix() ?
/*&& new File("/opt").canWrite() ? "/opt/"*/ /*System.getProperty("user.home") + "/"*/ "" : "";
}
public static String getJavaVersion() {
return System.getProperty("java.specification.version");
}
public static boolean hasCompiler() {
return System.getProperty("java.compiler") != null;
}
public static boolean isJRE() {
return !hasCompiler();
}
/**
* isDalvik
*
* @return true if this app is started on android
*/
public static final boolean isDalvik() {
return System.getProperty("java.vm.specification.name").startsWith("Dalvik");
}
public static final boolean isJdkOracle() {
return System.getProperty("java.vm.vendor").contains("Oracle");
}
public static final boolean isOpenJDK() {
return System.getProperty("java.vm.name").contains("OpenJDK");
}
public static final boolean isUnix() {
//we distinguish only between windows or not --> unix
return !isWindows();
}
public static final boolean isWindows() {
//we distinguish only between windows or not --> unix
return System.getProperty("os.name").toLowerCase().contains("windows");
}
public static final boolean isUnixFS() {
//TODO: eval the real file-system
return File.pathSeparatorChar == ':';
}
public static boolean isNestingJar() {
return Boolean.getBoolean(KEY_ISNESTEDJAR);
}
/**
* only for testing
*
* tries to remove any security manager and, additionally sets a policy with all permissions. will be used as
* workaaround inside a container - but will normally not work.
*/
protected static void noSecurity() {
try {
LOG.info("resetting security manager and policies to enable all-permissions");
//first, set the permission to reset the security manager.
// Policy policy = Policy.getInstance(AllPermission.class.getName(), null);
Policy policy = new Policy() {
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {
return true;
}
@Override
public String toString() {
return Util.toString(Policy.class, "all-permissions");
}
};
Policy.setPolicy(policy);
//try to reset the securiy manager
System.setSecurityManager(null);
} catch (Exception e) {
//if it doesn't work, ignore that. the security manager will throw its security exception on actions without permission.
LOG.info("couldn't set all permissions. failure: " + e.toString());
}
}
}