/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.twill.launcher; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; /** * A launcher for application from a archive jar. * This class should have no dependencies on any library except the J2SE one. * This class should not import any thing except java.* */ public final class TwillLauncher { private static final int TEMP_DIR_ATTEMPTS = 20; /** * Main method to unpackage a jar and run the mainClass.main() method. * @param args args[0] is the path to jar file, args[1] is the class name of the mainClass. * The rest of args will be passed the mainClass unmodified. */ public static void main(String[] args) throws Exception { if (args.length < 3) { System.out.println("Usage: java " + TwillLauncher.class.getName() + " [jarFile] [mainClass] [use_classpath]"); return; } File file = new File(args[0]); final File targetDir = createTempDir("twill.launcher"); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.out.println("Cleanup directory " + targetDir); deleteDir(targetDir); } }); System.out.println("UnJar " + file + " to " + targetDir); unJar(file, targetDir); // Create ClassLoader URLClassLoader classLoader = createClassLoader(targetDir, Boolean.parseBoolean(args[2])); Thread.currentThread().setContextClassLoader(classLoader); System.out.println("Launch class with classpath: " + Arrays.toString(classLoader.getURLs())); Class<?> mainClass = classLoader.loadClass(args[1]); Method mainMethod = mainClass.getMethod("main", String[].class); String[] arguments = Arrays.copyOfRange(args, 3, args.length); System.out.println("Launching main: " + mainMethod + " " + Arrays.toString(arguments)); mainMethod.invoke(mainClass, new Object[]{arguments}); System.out.println("Main class completed."); System.out.println("Launcher completed"); } /** * This method is copied from Guava Files.createTempDir(). */ private static File createTempDir(String prefix) throws IOException { File baseDir = new File(System.getProperty("java.io.tmpdir")); if (!baseDir.isDirectory() && !baseDir.mkdirs()) { throw new IOException("Tmp directory not exists: " + baseDir.getAbsolutePath()); } String baseName = prefix + "-" + System.currentTimeMillis() + "-"; for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { File tempDir = new File(baseDir, baseName + counter); if (tempDir.mkdir()) { return tempDir; } } throw new IOException("Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')'); } private static void unJar(File jarFile, File targetDir) throws IOException { JarInputStream jarInput = new JarInputStream(new FileInputStream(jarFile)); try { JarEntry jarEntry = jarInput.getNextJarEntry(); while (jarEntry != null) { File target = new File(targetDir, jarEntry.getName()); if (jarEntry.isDirectory()) { target.mkdirs(); } else { target.getParentFile().mkdirs(); copy(jarInput, target); } jarEntry = jarInput.getNextJarEntry(); } } finally { jarInput.close(); } } private static void copy(InputStream is, File file) throws IOException { byte[] buf = new byte[8192]; OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); try { int len = is.read(buf); while (len != -1) { os.write(buf, 0, len); len = is.read(buf); } } finally { os.close(); } } private static URLClassLoader createClassLoader(File dir, boolean useClassPath) { try { List<URL> urls = new ArrayList<URL>(); urls.add(dir.toURI().toURL()); urls.add(new File(dir, "classes").toURI().toURL()); urls.add(new File(dir, "resources").toURI().toURL()); File libDir = new File(dir, "lib"); File[] files = libDir.listFiles(); if (files != null) { for (File file : files) { if (file.getName().endsWith(".jar")) { urls.add(file.toURI().toURL()); } } } if (useClassPath) { InputStream is = ClassLoader.getSystemResourceAsStream("classpath"); if (is != null) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); String line = reader.readLine(); if (line != null) { for (String path : line.split(":")) { urls.addAll(getClassPaths(path)); } } } finally { is.close(); } } } return new URLClassLoader(urls.toArray(new URL[0])); } catch (Exception e) { throw new IllegalStateException(e); } } private static Collection<URL> getClassPaths(String path) throws MalformedURLException { String classpath = expand(path); if (classpath.endsWith("/*")) { // Grab all .jar files File dir = new File(classpath.substring(0, classpath.length() - 2)); File[] files = dir.listFiles(); if (files == null || files.length == 0) { return singleItem(dir.toURI().toURL()); } List<URL> result = new ArrayList<URL>(files.length); for (File file : files) { if (file.getName().endsWith(".jar")) { result.add(file.toURI().toURL()); } } return result; } else { return singleItem(new File(classpath).toURI().toURL()); } } private static Collection<URL> singleItem(URL url) { List<URL> result = new ArrayList<URL>(1); result.add(url); return result; } private static String expand(String value) { String result = value; for (Map.Entry<String, String> entry : System.getenv().entrySet()) { result = result.replace("$" + entry.getKey(), entry.getValue()); result = result.replace("${" + entry.getKey() + "}", entry.getValue()); } return result; } private static void deleteDir(File dir) { File[] files = dir.listFiles(); if (files == null || files.length == 0) { dir.delete(); return; } for (File file : files) { deleteDir(file); } dir.delete(); } }