summaryrefslogtreecommitdiffstats
path: root/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java')
-rw-r--r--gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java469
1 files changed, 469 insertions, 0 deletions
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
new file mode 100644
index 0000000000..31dfae7264
--- /dev/null
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -0,0 +1,469 @@
+package com.google.gerrit.launcher;
+
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed 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.
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/** Main class for a JAR file to run code from "WEB-INF/lib". */
+public final class GerritLauncher {
+ private static final String pkg = "com.google.gerrit.pgm";
+ public static final String NOT_ARCHIVED = "NOT_ARCHIVED";
+
+ public static void main(final String argv[]) throws Exception {
+ System.exit(mainImpl(argv));
+ }
+
+ private static int mainImpl(final String argv[]) throws Exception {
+ if (argv.length == 0) {
+ File me;
+ try {
+ me = getDistributionArchive();
+ } catch (FileNotFoundException e) {
+ me = null;
+ }
+
+ String jar = me != null ? me.getName() : "gerrit.war";
+ System.err.println("Gerrit Code Review " + getVersion(me));
+ System.err.println("usage: java -jar " + jar + " command [ARG ...]");
+ System.err.println();
+ System.err.println("The most commonly used commands are:");
+ System.err.println(" init Initialize a Gerrit installation");
+ System.err.println(" daemon Run the Gerrit network daemons");
+ System.err.println(" gsql Run the interactive query console");
+ System.err.println(" version Display the build version number");
+ System.err.println();
+ System.err.println(" ls List files available for cat");
+ System.err.println(" cat FILE Display a file from the archive");
+ System.err.println();
+ return 1;
+ }
+
+ // Special cases, a few global options actually are programs.
+ //
+ if ("-v".equals(argv[0]) || "--version".equals(argv[0])) {
+ argv[0] = "version";
+ } else if ("-p".equals(argv[0]) || "--cat".equals(argv[0])) {
+ argv[0] = "cat";
+ } else if ("-l".equals(argv[0]) || "--ls".equals(argv[0])) {
+ argv[0] = "ls";
+ }
+
+ // Run the application class
+ //
+ final ClassLoader cl = libClassLoader();
+ Thread.currentThread().setContextClassLoader(cl);
+ return invokeProgram(cl, argv);
+ }
+
+ private static String getVersion(final File me) {
+ if (me == null) {
+ return "";
+ }
+
+ try {
+ final JarFile jar = new JarFile(me);
+ try {
+ Manifest mf = jar.getManifest();
+ Attributes att = mf.getMainAttributes();
+ String val = att.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+ return val != null ? val : "";
+ } finally {
+ jar.close();
+ }
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private static int invokeProgram(final ClassLoader loader,
+ final String[] origArgv) throws Exception {
+ String name = origArgv[0];
+ final String[] argv = new String[origArgv.length - 1];
+ System.arraycopy(origArgv, 1, argv, 0, argv.length);
+
+ Class<?> clazz;
+ try {
+ try {
+ clazz = Class.forName(pkg + "." + name, true, loader);
+ } catch (ClassNotFoundException cnfe) {
+ if (name.equals(name.toLowerCase())) {
+ String first = name.substring(0, 1).toUpperCase();
+ String cn = first + name.substring(1);
+ clazz = Class.forName(pkg + "." + cn, true, loader);
+ } else {
+ throw cnfe;
+ }
+ }
+ } catch (ClassNotFoundException cnfe) {
+ System.err.println("fatal: unknown command " + name);
+ System.err.println(" (no " + pkg + "." + name + ")");
+ return 1;
+ }
+
+ final Method main;
+ try {
+ main = clazz.getMethod("main", argv.getClass());
+ } catch (SecurityException e) {
+ System.err.println("fatal: unknown command " + name);
+ return 1;
+ } catch (NoSuchMethodException e) {
+ System.err.println("fatal: unknown command " + name);
+ return 1;
+ }
+
+ final Object res;
+ try {
+ if ((main.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
+ res = main.invoke(null, new Object[] {argv});
+ } else {
+ res = main.invoke(clazz.newInstance(), new Object[] {argv});
+ }
+ } catch (InvocationTargetException ite) {
+ if (ite.getCause() instanceof Exception) {
+ throw (Exception) ite.getCause();
+ } else if (ite.getCause() instanceof Error) {
+ throw (Error) ite.getCause();
+ } else {
+ throw ite;
+ }
+ }
+ if (res instanceof Number) {
+ return ((Number) res).intValue();
+ } else {
+ return 0;
+ }
+ }
+
+ private static ClassLoader libClassLoader() throws IOException {
+ final File path;
+ try {
+ path = getDistributionArchive();
+ } catch (FileNotFoundException e) {
+ if (NOT_ARCHIVED == e.getMessage()) {
+ // Assume the CLASSPATH was made complete by the calling process,
+ // as we are likely being run from within a developer's IDE.
+ //
+ return GerritLauncher.class.getClassLoader();
+ }
+ throw e;
+ }
+
+ final ArrayList<URL> jars = new ArrayList<URL>();
+ try {
+ final ZipFile zf = new ZipFile(path);
+ try {
+ final Enumeration<? extends ZipEntry> e = zf.entries();
+ while (e.hasMoreElements()) {
+ final ZipEntry ze = e.nextElement();
+ if (ze.isDirectory()) {
+ continue;
+ }
+
+ if (ze.getName().startsWith("WEB-INF/lib/")) {
+ final File tmp = createTempFile(safeName(ze), ".jar");
+ final FileOutputStream out = new FileOutputStream(tmp);
+ try {
+ final InputStream in = zf.getInputStream(ze);
+ try {
+ final byte[] buf = new byte[4096];
+ int n;
+ while ((n = in.read(buf, 0, buf.length)) > 0) {
+ out.write(buf, 0, n);
+ }
+ } finally {
+ in.close();
+ }
+ } finally {
+ out.close();
+ }
+ jars.add(tmp.toURI().toURL());
+ }
+ }
+ } finally {
+ zf.close();
+ }
+ } catch (IOException e) {
+ throw new IOException("Cannot obtain libraries from " + path, e);
+ }
+
+ if (jars.isEmpty()) {
+ return GerritLauncher.class.getClassLoader();
+ }
+ return new URLClassLoader(jars.toArray(new URL[jars.size()]));
+ }
+
+ private static String safeName(final ZipEntry ze) {
+ // Try to derive the name of the temporary file so it
+ // doesn't completely suck. Best if we can make it
+ // match the name it was in the archive.
+ //
+ String name = ze.getName();
+ if (0 <= name.lastIndexOf('/')) {
+ name = name.substring(name.lastIndexOf('/') + 1);
+ }
+ if (0 <= name.lastIndexOf('.')) {
+ name = name.substring(0, name.lastIndexOf('.'));
+ }
+ if (name.isEmpty()) {
+ name = "code";
+ }
+ return name;
+ }
+
+ private static File myArchive;
+
+ /**
+ * Locate the JAR/WAR file we were launched from.
+ *
+ * @return local path of the Gerrit WAR file.
+ * @throws FileNotFoundException if the code cannot guess the location.
+ */
+ public static File getDistributionArchive() throws FileNotFoundException {
+ if (myArchive == null) {
+ myArchive = locateMyArchive();
+ }
+ return myArchive;
+ }
+
+ private static File locateMyArchive() throws FileNotFoundException {
+ final ClassLoader myCL = GerritLauncher.class.getClassLoader();
+ final String myName =
+ GerritLauncher.class.getName().replace('.', '/') + ".class";
+
+ final URL myClazz = myCL.getResource(myName);
+ if (myClazz == null) {
+ throw new FileNotFoundException("Cannot find JAR: no " + myName);
+ }
+
+ // ZipFile may have the path of our JAR hiding within itself.
+ //
+ try {
+ Field nameField = ZipFile.class.getDeclaredField("name");
+ nameField.setAccessible(true);
+
+ JarFile jar = ((JarURLConnection) myClazz.openConnection()).getJarFile();
+ File path = new File((String) nameField.get(jar));
+ if (path.isFile()) {
+ return path;
+ }
+ } catch (Exception e) {
+ // Nope, that didn't work. Try a different method.
+ //
+ }
+
+ // Maybe this is a local class file, running under a debugger?
+ //
+ if ("file".equals(myClazz.getProtocol())) {
+ final File path = new File(myClazz.getPath());
+ if (path.isFile() && path.getParentFile().isDirectory()) {
+ throw new FileNotFoundException(NOT_ARCHIVED);
+ }
+ }
+
+ // The CodeSource might be able to give us the source as a stream.
+ // If so, copy it to a local file so we have random access to it.
+ //
+ final CodeSource src =
+ GerritLauncher.class.getProtectionDomain().getCodeSource();
+ if (src != null) {
+ try {
+ final InputStream in = src.getLocation().openStream();
+ try {
+ final File tmp = createTempFile("gerrit_", ".zip");
+ final FileOutputStream out = new FileOutputStream(tmp);
+ try {
+ final byte[] buf = new byte[4096];
+ int n;
+ while ((n = in.read(buf, 0, buf.length)) > 0) {
+ out.write(buf, 0, n);
+ }
+ } finally {
+ out.close();
+ }
+ return tmp;
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ // Nope, that didn't work.
+ //
+ }
+ }
+
+ throw new FileNotFoundException("Cannot find local copy of JAR");
+ }
+
+ private static boolean temporaryDirectoryFound;
+ private static File temporaryDirectory;
+
+ /**
+ * Creates a temporary file within the application's unpack location.
+ * <p>
+ * The launcher unpacks the nested JAR files into a temporary directory,
+ * allowing the classes to be loaded from local disk with standard Java APIs.
+ * This method constructs a new temporary file in the same directory.
+ * <p>
+ * The method first tries to create {@code prefix + suffix} within the
+ * directory under the assumption that a given {@code prefix + suffix}
+ * combination is made at most once per JVM execution. If this fails (e.g. the
+ * named file already exists) a mangled unique name is used and returned
+ * instead, with the unique string appearing between the prefix and suffix.
+ * <p>
+ * Files created by this method will be automatically deleted by the JVM when
+ * it terminates. If the returned file is converted into a directory by the
+ * caller, the caller must arrange for the contents to be deleted before the
+ * directory is.
+ * <p>
+ * If supported by the underlying operating system, the temporary directory
+ * which contains these temporary files is accessible only by the user running
+ * the JVM.
+ *
+ * @param prefix prefix of the file name.
+ * @param suffix suffix of the file name.
+ * @return the path of the temporary file. The returned object exists in the
+ * filesystem as a file; caller may need to delete and recreate as a
+ * directory if a directory was preferred.
+ * @throws IOException the file could not be created.
+ */
+ public static File createTempFile(String prefix, String suffix)
+ throws IOException {
+ if (!temporaryDirectoryFound) {
+ final File d = File.createTempFile("gerrit_", "_app", tmproot());
+ if (d.delete() && d.mkdir()) {
+ // Try to lock the directory down to be accessible by us.
+ // We first have to remove all permissions, then add back
+ // only the owner permissions.
+ //
+ d.setWritable(false, false /* all */);
+ d.setReadable(false, false /* all */);
+ d.setExecutable(false, false /* all */);
+
+ d.setWritable(true, true /* owner only */);
+ d.setReadable(true, true /* owner only */);
+ d.setExecutable(true, true /* owner only */);
+
+ d.deleteOnExit();
+ temporaryDirectory = d;
+ }
+ temporaryDirectoryFound = true;
+ }
+
+ if (temporaryDirectory != null) {
+ // If we have a private directory and this name has not yet
+ // been used within the private directory, create it as-is.
+ //
+ final File tmp = new File(temporaryDirectory, prefix + suffix);
+ if (tmp.createNewFile()) {
+ tmp.deleteOnExit();
+ return tmp;
+ }
+ }
+
+ if (!prefix.endsWith("_")) {
+ prefix += "_";
+ }
+
+ final File tmp = File.createTempFile(prefix, suffix, temporaryDirectory);
+ tmp.deleteOnExit();
+ return tmp;
+ }
+
+ private static File tmproot() {
+ // Try to find the user's home directory. If we can't find it
+ // return null so the JVM's default temporary directory is used
+ // instead. This is probably /tmp or /var/tmp.
+ //
+ String userHome = System.getProperty("user.home");
+ if (userHome == null || "".equals(userHome)) {
+ userHome = System.getenv("HOME");
+ if (userHome == null || "".equals(userHome)) {
+ System.err.println("warning: cannot determine home directory");
+ System.err.println("warning: using system temporary directory instead");
+ return null;
+ }
+ }
+
+ // Ensure the home directory exists. If it doesn't, try to make it.
+ //
+ final File home = new File(userHome);
+ if (!home.exists()) {
+ if (home.mkdirs()) {
+ System.err.println("warning: created " + home.getAbsolutePath());
+ } else {
+ System.err.println("warning: " + home.getAbsolutePath() + " not found");
+ System.err.println("warning: using system temporary directory instead");
+ return null;
+ }
+ }
+
+ // Use $HOME/.gerritcodereview/tmp for our temporary file area.
+ //
+ final File tmp = new File(new File(home, ".gerritcodereview"), "tmp");
+ if (!tmp.exists() && !tmp.mkdirs()) {
+ System.err.println("warning: cannot create " + tmp.getAbsolutePath());
+ System.err.println("warning: using system temporary directory instead");
+ return null;
+ }
+
+ // Try to clean up any stale empty directories. Assume any empty
+ // directory that is older than 7 days is one of these dead ones
+ // that we can clean up.
+ //
+ final File[] tmpEntries = tmp.listFiles();
+ if (tmpEntries != null) {
+ final long now = System.currentTimeMillis();
+ final long expired = now - MILLISECONDS.convert(7, DAYS);
+ for (final File tmpEntry : tmpEntries) {
+ if (tmpEntry.isDirectory() && tmpEntry.lastModified() < expired) {
+ final String[] all = tmpEntry.list();
+ if (all == null || all.length == 0) {
+ tmpEntry.delete();
+ }
+ }
+ }
+ }
+
+ try {
+ return tmp.getCanonicalFile();
+ } catch (IOException e) {
+ return tmp;
+ }
+ }
+
+ private GerritLauncher() {
+ }
+}