| /* |
| * 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.accumulo.start; |
| |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.ServiceLoader; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarFile; |
| |
| import org.apache.accumulo.start.classloader.AccumuloClassLoader; |
| import org.apache.accumulo.start.spi.KeywordExecutable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class Main { |
| |
| private static final Logger log = LoggerFactory.getLogger(Main.class); |
| private static ClassLoader classLoader; |
| private static Class<?> vfsClassLoader; |
| private static Map<String,KeywordExecutable> servicesMap; |
| |
| public static void main(final String[] args) { |
| try { |
| // Preload classes that cause a deadlock between the ServiceLoader and the DFSClient when |
| // using |
| // the VFSClassLoader with jars in HDFS. |
| ClassLoader loader = getClassLoader(); |
| Class<?> confClass = null; |
| try { |
| confClass = |
| AccumuloClassLoader.getClassLoader().loadClass("org.apache.hadoop.conf.Configuration"); |
| } catch (ClassNotFoundException e) { |
| log.error("Unable to find Hadoop Configuration class on classpath, check configuration.", |
| e); |
| System.exit(1); |
| } |
| Object conf = null; |
| try { |
| conf = confClass.newInstance(); |
| } catch (Exception e) { |
| log.error("Error creating new instance of Hadoop Configuration", e); |
| System.exit(1); |
| } |
| try { |
| Method getClassByNameOrNullMethod = |
| conf.getClass().getMethod("getClassByNameOrNull", String.class); |
| getClassByNameOrNullMethod.invoke(conf, "org.apache.hadoop.mapred.JobConf"); |
| getClassByNameOrNullMethod.invoke(conf, "org.apache.hadoop.mapred.JobConfigurable"); |
| } catch (Exception e) { |
| log.error("Error pre-loading JobConf and JobConfigurable classes, VFS classloader with " |
| + "system classes in HDFS may not work correctly", e); |
| System.exit(1); |
| } |
| |
| if (args.length == 0) { |
| printUsage(); |
| System.exit(1); |
| } |
| |
| // determine whether a keyword was used or a class name, and execute it with the remaining |
| // args |
| String keywordOrClassName = args[0]; |
| KeywordExecutable keywordExec = getExecutables(loader).get(keywordOrClassName); |
| if (keywordExec != null) { |
| execKeyword(keywordExec, stripArgs(args, 1)); |
| } else { |
| execMainClassName(keywordOrClassName, stripArgs(args, 1)); |
| } |
| |
| } catch (Throwable t) { |
| log.error("Uncaught exception", t); |
| System.exit(1); |
| } |
| } |
| |
| public static synchronized ClassLoader getClassLoader() { |
| if (classLoader == null) { |
| try { |
| ClassLoader clTmp = |
| (ClassLoader) getVFSClassLoader().getMethod("getClassLoader").invoke(null); |
| classLoader = clTmp; |
| Thread.currentThread().setContextClassLoader(classLoader); |
| } catch (ClassNotFoundException | IOException | IllegalAccessException |
| | IllegalArgumentException | InvocationTargetException | NoSuchMethodException |
| | SecurityException e) { |
| log.error("Problem initializing the class loader", e); |
| System.exit(1); |
| } |
| } |
| return classLoader; |
| } |
| |
| public static synchronized Class<?> getVFSClassLoader() |
| throws IOException, ClassNotFoundException { |
| if (vfsClassLoader == null) { |
| Thread.currentThread().setContextClassLoader(AccumuloClassLoader.getClassLoader()); |
| Class<?> vfsClassLoaderTmp = AccumuloClassLoader.getClassLoader() |
| .loadClass("org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader"); |
| vfsClassLoader = vfsClassLoaderTmp; |
| } |
| return vfsClassLoader; |
| } |
| |
| private static void execKeyword(final KeywordExecutable keywordExec, final String[] args) { |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| keywordExec.execute(args); |
| } catch (Exception e) { |
| die(e); |
| } |
| } |
| }; |
| startThread(r, keywordExec.keyword()); |
| } |
| |
| private static void execMainClassName(final String className, final String[] args) { |
| Class<?> classWithMain = null; |
| try { |
| classWithMain = getClassLoader().loadClass(className); |
| } catch (ClassNotFoundException cnfe) { |
| System.out.println("Classname " + className |
| + " not found. Please make sure you use the wholly qualified package name."); |
| System.exit(1); |
| } |
| execMainClass(classWithMain, args); |
| } |
| |
| public static void execMainClass(final Class<?> classWithMain, final String[] args) { |
| Method main = null; |
| try { |
| main = classWithMain.getMethod("main", args.getClass()); |
| } catch (Throwable t) { |
| log.error("Could not run main method on '" + classWithMain.getName() + "'.", t); |
| } |
| if (main == null || !Modifier.isPublic(main.getModifiers()) |
| || !Modifier.isStatic(main.getModifiers())) { |
| System.out.println(classWithMain.getName() |
| + " must implement a public static void main(String args[]) method"); |
| System.exit(1); |
| } |
| final Method finalMain = main; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| final Object thisIsJustOneArgument = args; |
| finalMain.invoke(null, thisIsJustOneArgument); |
| } catch (InvocationTargetException e) { |
| if (e.getCause() != null) { |
| die(e.getCause()); |
| } else { |
| // Should never happen, but check anyway. |
| die(e); |
| } |
| } catch (Exception e) { |
| die(e); |
| } |
| } |
| }; |
| startThread(r, classWithMain.getName()); |
| } |
| |
| public static String[] stripArgs(final String[] originalArgs, int numToStrip) { |
| int newSize = originalArgs.length - numToStrip; |
| String newArgs[] = new String[newSize]; |
| System.arraycopy(originalArgs, numToStrip, newArgs, 0, newSize); |
| return newArgs; |
| } |
| |
| private static void startThread(final Runnable r, final String name) { |
| Thread t = new Thread(r, name); |
| t.setContextClassLoader(getClassLoader()); |
| t.start(); |
| } |
| |
| /** |
| * Print a stack trace to stderr and exit with a non-zero status. |
| * |
| * @param t |
| * The {@link Throwable} containing a stack trace to print. |
| */ |
| private static void die(final Throwable t) { |
| log.error("Thread '" + Thread.currentThread().getName() + "' died.", t); |
| System.exit(1); |
| } |
| |
| public static void printUsage() { |
| TreeSet<String> keywords = new TreeSet<>(getExecutables(getClassLoader()).keySet()); |
| |
| // jar is a special case, because it has arguments |
| keywords.remove("jar"); |
| keywords.add("jar <jar> [<main class>] args"); |
| |
| String prefix = ""; |
| String kwString = ""; |
| for (String kw : keywords) { |
| kwString += prefix + kw; |
| prefix = " | "; |
| } |
| System.out.println("accumulo " + kwString + " | <accumulo class> args"); |
| } |
| |
| public static synchronized Map<String,KeywordExecutable> getExecutables(final ClassLoader cl) { |
| if (servicesMap == null) { |
| servicesMap = checkDuplicates(ServiceLoader.load(KeywordExecutable.class, cl)); |
| } |
| return servicesMap; |
| } |
| |
| public static Map<String,KeywordExecutable> |
| checkDuplicates(final Iterable<? extends KeywordExecutable> services) { |
| TreeSet<String> blacklist = new TreeSet<>(); |
| TreeMap<String,KeywordExecutable> results = new TreeMap<>(); |
| for (KeywordExecutable service : services) { |
| String keyword = service.keyword(); |
| if (blacklist.contains(keyword)) { |
| // subsequent times a duplicate is found, just warn and exclude it |
| warnDuplicate(service); |
| } else if (results.containsKey(keyword)) { |
| // the first time a duplicate is found, blacklist it and warn |
| blacklist.add(keyword); |
| warnDuplicate(results.remove(keyword)); |
| warnDuplicate(service); |
| } else { |
| // first observance of this keyword, so just add it to the list |
| results.put(service.keyword(), service); |
| } |
| } |
| return Collections.unmodifiableSortedMap(results); |
| } |
| |
| private static void warnDuplicate(final KeywordExecutable service) { |
| log.warn("Ambiguous duplicate binding for keyword '" + service.keyword() + "' found: " |
| + service.getClass().getName()); |
| } |
| |
| // feature: will work even if main class isn't in the JAR |
| public static Class<?> loadClassFromJar(final String[] args, final JarFile f, |
| final ClassLoader cl) throws ClassNotFoundException, IOException { |
| ClassNotFoundException explicitNotFound = null; |
| if (args.length >= 2) { |
| try { |
| return cl.loadClass(args[1]); // jar-file main-class |
| } catch (ClassNotFoundException cnfe) { |
| // assume this is the first argument, look for main class in JAR manifest |
| explicitNotFound = cnfe; |
| } |
| } |
| String mainClass = f.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); |
| if (mainClass == null) { |
| if (explicitNotFound != null) { |
| throw explicitNotFound; |
| } |
| throw new ClassNotFoundException( |
| "No main class was specified, and the JAR manifest does not specify one"); |
| } |
| return cl.loadClass(mainClass); |
| } |
| |
| } |