blob: 4b8641e4b3eb9556f3e7be24e09d0b09a7f225d0 [file] [log] [blame]
/*
* 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<URL> classPathUrls = new LinkedList<URL>();
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<JarEntry> 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<URL> getJarURLs(File dir) throws MalformedURLException {
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
List<URL> urls = new LinkedList<URL>();
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:"
+ " <jarFileName> <libFolder> <mainClassName>");
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);
}
}
}
}