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

org.apache.twill.ext.BundledJarRunner Maven / Gradle / Ivy

The newest version!
/*
 * 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.ext;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Runs a bundled jar specified by jarPath.
 *
 * 1. Loads the bundled jar and its dependencies (in /lib) into a class loader
 * assuming the following format of the bundled jar:
 * /*.class (class files)
 * /lib/*.jar (dependencies required by the user code)
 *
 * 2. Instantiates an instance of the class {#mainClassName} and calls main({#args}) on it.
*/
public class BundledJarRunner {

  private static final Logger LOG = LoggerFactory.getLogger(BundledJarRunner.class);

  private final File jarFile;
  private final Arguments arguments;
  private Method mainMethod;

  public BundledJarRunner(File jarFile, Arguments arguments) {
    Preconditions.checkNotNull(jarFile);
    Preconditions.checkState(jarFile.exists());
    Preconditions.checkState(jarFile.canRead());
    Preconditions.checkNotNull(arguments.getMainClassName());
    Preconditions.checkNotNull(arguments.getLibFolder());

    this.jarFile = jarFile;
    this.arguments = arguments;
  }

  public void load() throws ClassNotFoundException, NoSuchMethodException,
    InstantiationException, IllegalAccessException, IOException {
    this.load(ClassLoader.getSystemClassLoader());
  }

  public void load(ClassLoader parentClassLoader) throws IOException, ClassNotFoundException,
    IllegalAccessException, InstantiationException, NoSuchMethodException {

    String mainClassName = arguments.getMainClassName();
    String libFolder = arguments.getLibFolder();

    Preconditions.checkNotNull(mainClassName);
    Preconditions.checkNotNull(libFolder);

    File inputJarFile = this.jarFile;
    File outputJarDir = Files.createTempDir();

    LOG.debug("Unpacking jar to " + outputJarDir.getAbsolutePath());
    JarFile jarFile = new JarFile(inputJarFile);
    unJar(jarFile, outputJarDir);

    LOG.debug("Loading jars into ClassLoader");
    List classPathUrls = new LinkedList();
    classPathUrls.add(inputJarFile.toURI().toURL());
    classPathUrls.addAll(getJarURLs(new File(outputJarDir, libFolder)));
    URL[] classPathUrlArray = classPathUrls.toArray(new URL[classPathUrls.size()]);

    for (URL url : classPathUrlArray) {
      LOG.debug("Loading jar: " + url.getPath());
    }

    ClassLoader classLoader = new URLClassLoader(classPathUrlArray, parentClassLoader);
    Thread.currentThread().setContextClassLoader(classLoader);

    LOG.debug("Instantiating instance of " + mainClassName);
    Class cls = classLoader.loadClass(mainClassName);
    mainMethod = cls.getMethod("main", String[].class);
  }

  public void run() throws Throwable {
    Preconditions.checkNotNull(mainMethod, "Must call load() first");
    String mainClassName = arguments.getMainClassName();
    String[] args = arguments.getMainArgs();

    try {
      LOG.info("Invoking " + mainClassName + ".main(" + Arrays.toString(args) + ")");
      mainMethod.invoke(null, new Object[] { args });
    } catch (Throwable t) {
      LOG.error("Error while trying to run " + mainClassName + " within " + jarFile.getAbsolutePath(), t);
      throw t;
    }
  }

  private void unJar(JarFile jarFile, File targetDirectory) throws IOException {
    Enumeration entries = jarFile.entries();
    while (entries.hasMoreElements()) {
      JarEntry entry = entries.nextElement();
      File output = new File(targetDirectory, entry.getName());

      if (entry.isDirectory()) {
        output.mkdirs();
      } else {
        output.getParentFile().mkdirs();

        try (
          OutputStream os = new FileOutputStream(output);
          InputStream is = jarFile.getInputStream(entry)
        ) {
          ByteStreams.copy(is, os);
        }
      }
    }
  }

  private List getJarURLs(File dir) throws MalformedURLException {
    File[] files = dir.listFiles(new FilenameFilter() {
      @Override
      public boolean accept(File dir, String name) {
        return name.endsWith(".jar");
      }
    });
    List urls = new LinkedList();

    if (files != null) {
      for (File file : files) {
        urls.add(file.toURI().toURL());
      }
    } else {
      LOG.warn("No jar files found in " + dir.getAbsolutePath());
    }

    return urls;
  }

  /**
   * Contains runtime arguments for {@link org.apache.twill.ext.BundledJarRunner}.
   */
  public static class Arguments {

    /**
     * Filename of the bundled jar, as specified in the TwillSpecification local files.
     */
    private final String jarFileName;

    /**
     * Class name of the class having the main() that is to be called.
     */
    private final String mainClassName;

    /**
     * Arguments to pass the main() of the class specified by mainClassName.
     */
    private final String[] mainArgs;

    /**
     * Folder within the bundled jar containing the jar dependencies.
     */
    private final String libFolder;

    public Arguments(String jarFileName, String libFolder, String mainClassName, String[] mainArgs) {
      this.jarFileName = jarFileName;
      this.libFolder = libFolder;
      this.mainClassName = mainClassName;
      this.mainArgs = mainArgs;
    }

    public static Arguments fromArray(String[] args) {
      Preconditions.checkArgument(args.length >= 3, "Requires at least 3 arguments:"
        + "   ");

      Builder builder = new Builder();
      builder.setJarFileName(args[0]);
      builder.setLibFolder(args[1]);
      builder.setMainClassName(args[2]);
      builder.setMainArgs(Arrays.copyOfRange(args, 3, args.length));

      return builder.createArguments();
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }

      if (o == null || getClass() != o.getClass()) {
        return false;
      }

      Arguments arguments = (Arguments) o;
      return Objects.equal(jarFileName, arguments.jarFileName)
        && Objects.equal(libFolder, arguments.libFolder)
        && Arrays.deepEquals(mainArgs, arguments.mainArgs)
        && Objects.equal(mainClassName, arguments.mainClassName);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(jarFileName, mainClassName, mainArgs, libFolder);
    }

    public String[] toArray() {
      String[] result = new String[3 + mainArgs.length];
      result[0] = jarFileName;
      result[1] = libFolder;
      result[2] = mainClassName;
      for (int i = 0; i < mainArgs.length; i++) {
        result[3 + i] = mainArgs[i];
      }

      return result;
    }

    public String getJarFileName() {
      return jarFileName;
    }

    public String getLibFolder() {
      return libFolder;
    }

    public String getMainClassName() {
      return mainClassName;
    }

    public String[] getMainArgs() {
      return mainArgs;
    }

    /**
     * Builder for {@link org.apache.twill.ext.BundledJarRunner.Arguments}.
     */
    public static class Builder {
      private String jarFileName;
      private String libFolder;
      private String mainClassName;
      private String[] mainArgs;

      public Builder() {}

      public Builder setJarFileName(String jarFileName) {
        this.jarFileName = jarFileName;
        return this;
      }

      public Builder setLibFolder(String libFolder) {
        this.libFolder = libFolder;
        return this;
      }

      public Builder setMainClassName(String mainClassName) {
        this.mainClassName = mainClassName;
        return this;
      }

      public Builder setMainArgs(String[] mainArgs) {
        this.mainArgs = mainArgs;
        return this;
      }

      public Builder from(Arguments arguments) {
        this.jarFileName = arguments.getJarFileName();
        this.libFolder = arguments.getLibFolder();
        this.mainClassName = arguments.getMainClassName();
        this.mainArgs = arguments.getMainArgs();
        return this;
      }

      public Arguments createArguments() {
        return new Arguments(jarFileName, libFolder, mainClassName, mainArgs);
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy