All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bathe.BatheBooter Maven / Gradle / Ivy

package bathe;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;


/**
 * This class takes the jar/war in the classpath, figures out what the classpath is, creates a new URL class loader,
 * loads any BatheInitializer services in the right order according to precedence and then jumps in.
 *
 * author: Richard Vowles - http://gplus.to/RichardVowles
 *
 * Thanks to Karsten Sperling for the idea of embedding the jars into subdirectories and loading them into the URL classpath from there.
 */
public class BatheBooter {
  private static final String WEB_JAR_PREFIX = "WEB-INF/jars/";
  private static final String WEB_CLASSES_PREFIX = "WEB-INF/classes/";

  private static final String MAIN_OVERRIDE = "-R";
  private static final String JUMP_CLASS = "Jump-Class";
  protected static final String BATHE_EXTERNAL_CLASSPATH = "bathe.externalClassPath";
	private static final String BATHE_IMPLEMENTATION_VERSION = "Bathe-Implementation-Version";

	protected String runnerClass;
  protected File jar;
  protected String[] passingArgs;
  protected boolean foundClassDir = false;

  protected void parseCommandLine(String[] args) {
    List appArguments = new ArrayList();

    // Process command line arguments
    for (String arg : args) {
      if (arg.startsWith(MAIN_OVERRIDE)) {
        runnerClass = arg.substring(MAIN_OVERRIDE.length());
      } else
        appArguments.add(arg);
    }

    if (runnerClass == null) {
      attemptToGetJumpClassFromManifest();
    }

    if (appArguments.size() > 0 && runnerClass == null) {
      System.err.println(
        "Usage: java -jar " + jar.getName() + " [options]\n" +
          "\n" +
          "Arguments:\n" +
          "  " + MAIN_OVERRIDE + "      specify the main class.\n" +
          "  \n" +
          "Specify Jump-Class: in your /META-INF/MANIFEST.MF to automatically define a class to run");

      System.exit(-2);
    }


    passingArgs = new String[appArguments.size()];

    appArguments.toArray(passingArgs);
  }

  protected void attemptToGetJumpClassFromManifest() {
    try {
      Enumeration resources = Thread.currentThread().getContextClassLoader()
        .getResources("META-INF/MANIFEST.MF");

      while (resources.hasMoreElements()) {
        Manifest manifest = new Manifest(resources.nextElement().openStream());

        Attributes attr = manifest.getMainAttributes();
        String jumpGate = attr.getValue(JUMP_CLASS);

        if (jumpGate != null) {

          runnerClass = jumpGate;

	        System.setProperty(BATHE_IMPLEMENTATION_VERSION, attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION));

          break;
        }
      }

    } catch (IOException iex) {

    }

  }

  public static void main(String []args) throws IOException {
    new BatheBooter().run(args);
  }

  public void run(String []args) throws IOException {
    // Find the WAR and load the corresponding properties
    jar = getRunFile();

    parseCommandLine(args);

    List jarOffsets = determineJarOffsets(jar);
    URLClassLoader loader = createUrlClassLoader(jar, jarOffsets);

    Thread.currentThread().setContextClassLoader(loader);

    runWithLoader(loader, jar, runnerClass, passingArgs);

	  loader.close(); // supported in 1.7
  }

	public void runWithLoader(URLClassLoader loader, File runnable, String runnerClass, String[] args) throws IOException {
		ClassLoader localLoader = loader == null ? Thread.currentThread().getContextClassLoader() : loader;

		try {
			args = new BatheInitializerProcessor().process(args, runnerClass, localLoader);

			// Start the application
			exec(localLoader, runnable, runnerClass, args);
		} finally {
			Thread.currentThread().setContextClassLoader(null);
		}
	}


	private boolean tryRunMethod(Class runner, File runnable, String[] args) throws InvocationTargetException, IllegalAccessException {
		try {
			Method method = runner.getMethod("run", File.class, String[].class);

			method.invoke(null, runnable, args);
		} catch (NoSuchMethodException e) {
			return false;
		}

		return true;
	}

	private boolean tryMainMethod(Class runner, String[] args) throws InvocationTargetException, IllegalAccessException {
		try {
			Method method = runner.getMethod("main", String[].class);

			// force to Object so it doesn't  try to use ...
			method.invoke(null, (Object)args);
		} catch (NoSuchMethodException e) {
			return false;
		}

		return true;
	}

	/**
   * Runs the main runner class in the specified class loader, passing in
   * the WAR being run as well as the specified command line arguments.
   */
  public void exec(ClassLoader loader, File runnable, String runnerClass, String[] args) {
    try {
      Class runner = Class.forName(runnerClass, true, loader);

	    if (!tryRunMethod(runner, runnable, args)) {
		    if (!tryMainMethod(runner, args)) {
			    throw new RuntimeException("Cannot find run or main method in " + runnerClass);
		    }
	    }

    } catch (ClassNotFoundException e) {
      throw new RuntimeException("The class you are trying to run can not be found on the classpath: " + runnerClass + ", " + loader.toString(), e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Run method needs to be public and static", e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) throw (RuntimeException) cause;
      throw new RuntimeException("Unhandled exception in WebApp runner", e);
    }
  }

  /**
   * Creates a URL class loader out of the specified library offsets within the war file
   */
  protected URLClassLoader createUrlClassLoader(File war, List libraries) {
    List urls;

    try {
      urls = new ArrayList();

	    // external classpaths first
	    String externalClasspath = System.getProperty(BATHE_EXTERNAL_CLASSPATH);
	    if (externalClasspath != null) {
		    for(String cp : externalClasspath.split(",")) {
			    urls.add(new URL(cp.trim()));
		    }
	    }

	    // now internal classpaths
	    String jarPrefix = "jar:" + war.toURI().toString() + "!/";

      if (foundClassDir)
        urls.add(new URL(jarPrefix + WEB_CLASSES_PREFIX));

      for (String jar : libraries) {
	      // the trailing / is extremely important, if it isn't there, it treats it as a JarLoader and fails
	      // to load it as it changes the url to jar:jar:file:!/blah!/
        URL newLibraryOffset = new URL(jarPrefix + WEB_JAR_PREFIX + jar + "/" );

        urls.add(newLibraryOffset);
      }

    } catch (MalformedURLException e) {
      throw new RuntimeException("Unable to create JAR/WAR class path", e);
    }

    URL[] cp = new URL[urls.size()];
    return new URLClassLoader(urls.toArray(cp), ClassLoader.getSystemClassLoader());
  }

  /**
   *
   * Extracts out the jars that are embedded in subdirectories and sets them up in a list to allow priority sorting and
   * construction of a URLClassPath.
   *
   */
  protected List determineJarOffsets(File war) {
    try {
      List jars = new ArrayList<>();

      JarFile archive = new JarFile(war);

      int webJarLength = WEB_JAR_PREFIX.length();

      try {
        Enumeration i = archive.entries();

        while (i.hasMoreElements()) {
          JarEntry entry = i.nextElement();
          String name = entry.getName();

          //stop problem where the WEB_JAR_PREFIX/WEB_CLASSES_PREFIX is included as an entry
          if (name.equals(WEB_JAR_PREFIX) || name.equals(WEB_CLASSES_PREFIX)) continue;

          if (name.startsWith(WEB_JAR_PREFIX)) {
            String partName = name.substring(webJarLength);

            String jarDir = partName.substring(0, partName.indexOf('/'));

            if (!jars.contains(jarDir)) {
              jars.add(jarDir);
            }
          } else if (name.startsWith(WEB_CLASSES_PREFIX)) {
            foundClassDir = true;
          }
        }
      } finally {
        archive.close();
      }
      return Collections.unmodifiableList(jars);
    } catch (IOException e) {
      throw new RuntimeException("Unable to determine the extracted jar files in the jar/war", e);
    }
  }

  /**
   * This may end up needing to be more sophisticated if there is more than one jar on the command line. We would need to looking through
   * for the one with this class in it.
   *
   * @return The main file we are running
   */
  protected File getRunFile() {
    for (URL url : getSystemClassPath()) {
      if ("file".equals(url.getProtocol()) && (url.getPath().endsWith(".war") || url.getPath().endsWith(".jar"))) {
        try {
          return new File(url.toURI());
        } catch (URISyntaxException e) {
          /* ignored */
        }
      }
    }
    throw new RuntimeException("Cannot get the runnable artifact");
  }

  /**
   * Returns the URLs that make up the system class path.
   */
  protected URL[] getSystemClassPath() {
    ClassLoader loader = ClassLoader.getSystemClassLoader();
    if (!(loader instanceof URLClassLoader))
      throw new RuntimeException("System class loader does not expose classpath URLs");
    return ((URLClassLoader) loader).getURLs();
  }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy