From 276485e1de9099bbfae093078f9e69fc65104114 Mon Sep 17 00:00:00 2001 From: Allen Wittenauer Date: Thu, 5 Feb 2015 11:31:40 -0800 Subject: [PATCH] HADOOP-9044. add FindClass main class to provide classpath checking of installations (Steve Loughran via aw) --- .../org/apache/hadoop/util/FindClass.java | 388 ++++++++++++++++++ .../org/apache/hadoop/util/TestFindClass.java | 218 ++++++++++ 2 files changed, 606 insertions(+) create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/FindClass.java create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestFindClass.java diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/FindClass.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/FindClass.java new file mode 100644 index 00000000000..b7feb22d34d --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/FindClass.java @@ -0,0 +1,388 @@ +/* + * 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.hadoop.util; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.conf.Configured; +import org.apache.hadoop.util.StringUtils; +import org.apache.hadoop.util.Tool; +import org.apache.hadoop.util.ToolRunner; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.net.URL; +import java.security.CodeSource; + +/** + * This entry point exists for diagnosing classloader problems: + * is a class or resource present -and if so, where? + * + *

+ * Actions + *

+ *

+ * + * It returns an error code if a class/resource cannot be loaded/found + * -and optionally a class may be requested as being loaded. + * The latter action will call the class's constructor -it must support an + * empty constructor); any side effects from the + * constructor or static initializers will take place. + * + * All error messages are printed to {@link System#out}; errors + * to {@link System#err}. + * + */ +@SuppressWarnings("UseOfSystemOutOrSystemErr") +public final class FindClass extends Configured implements Tool { + + /** + * create command: {@value} + */ + public static final String A_CREATE = "create"; + + /** + * Load command: {@value} + */ + public static final String A_LOAD = "load"; + + /** + * Command to locate a resource: {@value} + */ + public static final String A_RESOURCE = "locate"; + + /** + * Command to locate and print a resource: {@value} + */ + public static final String A_PRINTRESOURCE = "print"; + + /** + * Exit code when the operation succeeded: {@value} + */ + public static final int SUCCESS = 0; + + /** + * generic error {@value} + */ + protected static final int E_GENERIC = 1; + + /** + * usage error -bad arguments or similar {@value} + */ + protected static final int E_USAGE = 2; + + /** + * class or resource not found {@value} + */ + protected static final int E_NOT_FOUND = 3; + + /** + * class load failed {@value} + */ + protected static final int E_LOAD_FAILED = 4; + + /** + * class creation failed {@value} + */ + protected static final int E_CREATE_FAILED = 5; + + /** + * Output stream. Defaults to {@link System#out} + */ + private static PrintStream stdout = System.out; + + /** + * Error stream. Defaults to {@link System#err} + */ + private static PrintStream stderr = System.err; + + /** + * Empty constructor; passes a new Configuration + * object instance to its superclass's constructor + */ + public FindClass() { + super(new Configuration()); + } + + /** + * Create a class with a specified configuration + * @param conf configuration + */ + public FindClass(Configuration conf) { + super(conf); + } + + /** + * Change the output streams to be something other than the + * System.out and System.err streams + * @param out new stdout stream + * @param err new stderr stream + */ + @VisibleForTesting + public static void setOutputStreams(PrintStream out, PrintStream err) { + stdout = out; + stderr = err; + } + + /** + * Get a class fromt the configuration + * @param name the class name + * @return the class + * @throws ClassNotFoundException if the class was not found + * @throws Error on other classloading problems + */ + private Class getClass(String name) throws ClassNotFoundException { + return getConf().getClassByName(name); + } + + /** + * Get the resource + * @param name resource name + * @return URL or null for not found + */ + private URL getResource(String name) { + return getConf().getResource(name); + } + + /** + * Load a resource + * @param name resource name + * @return the status code + */ + private int loadResource(String name) { + URL url = getResource(name); + if (url == null) { + err("Resource not found: %s", name); + return E_NOT_FOUND; + } + out("%s: %s", name, url); + return SUCCESS; + } + + /** + * Dump a resource to out + * @param name resource name + * @return the status code + */ + @SuppressWarnings("NestedAssignment") + private int dumpResource(String name) { + URL url = getResource(name); + if (url == null) { + err("Resource not found:" + name); + return E_NOT_FOUND; + } + try { + //open the resource + InputStream instream = url.openStream(); + //read it in and print + int data; + while (-1 != (data = instream.read())) { + stdout.print((char) data); + } + //end of file + stdout.print('\n'); + return SUCCESS; + } catch (IOException e) { + printStack(e, "Failed to read resource %s at URL %s", name, url); + return E_LOAD_FAILED; + } + } + + /** + * print something to stderr + * @param s string to print + */ + private static void err(String s, Object... args) { + stderr.format(s, args); + stderr.print('\n'); + } + + /** + * print something to stdout + * @param s string to print + */ + private static void out(String s, Object... args) { + stdout.format(s, args); + stdout.print('\n'); + } + + /** + * print a stack trace with text + * @param e the exception to print + * @param text text to print + */ + private static void printStack(Throwable e, String text, Object... args) { + err(text, args); + e.printStackTrace(stderr); + } + + /** + * Loads the class of the given name + * @param name classname + * @return outcome code + */ + private int loadClass(String name) { + try { + Class clazz = getClass(name); + loadedClass(name, clazz); + return SUCCESS; + } catch (ClassNotFoundException e) { + printStack(e, "Class not found " + name); + return E_NOT_FOUND; + } catch (Exception e) { + printStack(e, "Exception while loading class " + name); + return E_LOAD_FAILED; + } catch (Error e) { + printStack(e, "Error while loading class " + name); + return E_LOAD_FAILED; + } + } + + /** + * Log that a class has been loaded, and where from. + * @param name classname + * @param clazz class + */ + private void loadedClass(String name, Class clazz) { + out("Loaded %s as %s", name, clazz); + CodeSource source = clazz.getProtectionDomain().getCodeSource(); + URL url = source.getLocation(); + out("%s: %s", name, url); + } + + /** + * Create an instance of a class + * @param name classname + * @return the outcome + */ + private int createClassInstance(String name) { + try { + Class clazz = getClass(name); + loadedClass(name, clazz); + Object instance = clazz.newInstance(); + try { + //stringify + out("Created instance " + instance.toString()); + } catch (Exception e) { + //catch those classes whose toString() method is brittle, but don't fail the probe + printStack(e, + "Created class instance but the toString() operator failed"); + } + return SUCCESS; + } catch (ClassNotFoundException e) { + printStack(e, "Class not found " + name); + return E_NOT_FOUND; + } catch (Exception e) { + printStack(e, "Exception while creating class " + name); + return E_CREATE_FAILED; + } catch (Error e) { + printStack(e, "Exception while creating class " + name); + return E_CREATE_FAILED; + } + } + + /** + * Run the class/resource find or load operation + * @param args command specific arguments. + * @return the outcome + * @throws Exception if something went very wrong + */ + @Override + public int run(String[] args) throws Exception { + if (args.length != 2) { + return usage(args); + } + String action = args[0]; + String name = args[1]; + int result; + if (A_LOAD.equals(action)) { + result = loadClass(name); + } else if (A_CREATE.equals(action)) { + //first load to separate load errors from create + result = loadClass(name); + if (result == SUCCESS) { + //class loads, so instantiate it + result = createClassInstance(name); + } + } else if (A_RESOURCE.equals(action)) { + result = loadResource(name); + } else if (A_PRINTRESOURCE.equals(action)) { + result = dumpResource(name); + } else { + result = usage(args); + } + return result; + } + + /** + * Print a usage message + * @param args the command line arguments + * @return an exit code + */ + private int usage(String[] args) { + err( + "Usage : [load | create] "); + err( + " [locate | print] ]"); + err("The return codes are:"); + explainResult(SUCCESS, + "The operation was successful"); + explainResult(E_GENERIC, + "Something went wrong"); + explainResult(E_USAGE, + "This usage message was printed"); + explainResult(E_NOT_FOUND, + "The class or resource was not found"); + explainResult(E_LOAD_FAILED, + "The class was found but could not be loaded"); + explainResult(E_CREATE_FAILED, + "The class was loaded, but an instance of it could not be created"); + return E_USAGE; + } + + /** + * Explain an error code as part of the usage + * @param errorcode error code returned + * @param text error text + */ + private void explainResult(int errorcode, String text) { + err(" %2d -- %s ", errorcode , text); + } + + /** + * Main entry point. + * Runs the class via the {@link ToolRunner}, then + * exits with an appropriate exit code. + * @param args argument list + */ + public static void main(String[] args) { + try { + int result = ToolRunner.run(new FindClass(), args); + System.exit(result); + } catch (Exception e) { + printStack(e, "Running FindClass"); + System.exit(E_GENERIC); + } + } +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestFindClass.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestFindClass.java new file mode 100644 index 00000000000..28389c27d54 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestFindClass.java @@ -0,0 +1,218 @@ +/* + * 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.hadoop.util; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import junit.framework.Assert; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.util.FindClass; +import org.apache.hadoop.util.ToolRunner; +import org.junit.Test; + +/** + * Test the find class logic + */ +public class TestFindClass extends Assert { + private static final Log LOG = LogFactory.getLog(TestFindClass.class); + + public static final String LOG4J_PROPERTIES = "log4j.properties"; + + /** + * Run the tool runner instance + * @param expected expected return code + * @param args a list of arguments + * @throws Exception on any falure that is not handled earlier + */ + private void run(int expected, String... args) throws Exception { + int result = ToolRunner.run(new FindClass(), args); + assertEquals(expected, result); + } + + @Test + public void testUsage() throws Throwable { + run(FindClass.E_USAGE, "org.apache.hadoop.util.TestFindClass"); + } + + @Test + public void testFindsResource() throws Throwable { + run(FindClass.SUCCESS, + FindClass.A_RESOURCE, "org/apache/hadoop/util/TestFindClass.class"); + } + + @Test + public void testFailsNoSuchResource() throws Throwable { + run(FindClass.E_NOT_FOUND, + FindClass.A_RESOURCE, + "org/apache/hadoop/util/ThereIsNoSuchClass.class"); + } + + @Test + public void testLoadFindsSelf() throws Throwable { + run(FindClass.SUCCESS, + FindClass.A_LOAD, "org.apache.hadoop.util.TestFindClass"); + } + + @Test + public void testLoadFailsNoSuchClass() throws Throwable { + run(FindClass.E_NOT_FOUND, + FindClass.A_LOAD, "org.apache.hadoop.util.ThereIsNoSuchClass"); + } + + @Test + public void testLoadWithErrorInStaticInit() throws Throwable { + run(FindClass.E_LOAD_FAILED, + FindClass.A_LOAD, + "org.apache.hadoop.util.TestFindClass$FailInStaticInit"); + } + + @Test + public void testCreateHandlesBadToString() throws Throwable { + run(FindClass.SUCCESS, + FindClass.A_CREATE, + "org.apache.hadoop.util.TestFindClass$BadToStringClass"); + } + + @Test + public void testCreatesClass() throws Throwable { + run(FindClass.SUCCESS, + FindClass.A_CREATE, "org.apache.hadoop.util.TestFindClass"); + } + + @Test + public void testCreateFailsInStaticInit() throws Throwable { + run(FindClass.E_LOAD_FAILED, + FindClass.A_CREATE, + "org.apache.hadoop.util.TestFindClass$FailInStaticInit"); + } + + @Test + public void testCreateFailsInConstructor() throws Throwable { + run(FindClass.E_CREATE_FAILED, + FindClass.A_CREATE, + "org.apache.hadoop.util.TestFindClass$FailInConstructor"); + } + + @Test + public void testCreateFailsNoEmptyConstructor() throws Throwable { + run(FindClass.E_CREATE_FAILED, + FindClass.A_CREATE, + "org.apache.hadoop.util.TestFindClass$NoEmptyConstructor"); + } + + @Test + public void testLoadPrivateClass() throws Throwable { + run(FindClass.SUCCESS, + FindClass.A_LOAD, "org.apache.hadoop.util.TestFindClass$PrivateClass"); + } + + @Test + public void testCreateFailsPrivateClass() throws Throwable { + run(FindClass.E_CREATE_FAILED, + FindClass.A_CREATE, + "org.apache.hadoop.util.TestFindClass$PrivateClass"); + } + + @Test + public void testCreateFailsInPrivateConstructor() throws Throwable { + run(FindClass.E_CREATE_FAILED, + FindClass.A_CREATE, + "org.apache.hadoop.util.TestFindClass$PrivateConstructor"); + } + + @Test + public void testLoadFindsLog4J() throws Throwable { + run(FindClass.SUCCESS, FindClass.A_RESOURCE, LOG4J_PROPERTIES); + } + + @SuppressWarnings("UseOfSystemOutOrSystemErr") + @Test + public void testPrintLog4J() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(baos); + FindClass.setOutputStreams(out, System.err); + run(FindClass.SUCCESS, FindClass.A_PRINTRESOURCE, LOG4J_PROPERTIES); + //here the content should be done + out.flush(); + String body = baos.toString("UTF8"); + LOG.info(LOG4J_PROPERTIES + " =\n" + body); + assertTrue(body.contains("Apache")); + } + + + /** + * trigger a divide by zero fault in the static init + */ + public static class FailInStaticInit { + static { + int x = 0; + int y = 1 / x; + } + } + + /** + * trigger a divide by zero fault in the constructor + */ + public static class FailInConstructor { + public FailInConstructor() { + int x = 0; + int y = 1 / x; + } + } + + /** + * A class with no parameterless constructor -expect creation to fail + */ + public static class NoEmptyConstructor { + public NoEmptyConstructor(String text) { + } + } + + /** + * This has triggers an NPE in the toString() method; checks the logging + * code handles this. + */ + public static class BadToStringClass { + public BadToStringClass() { + } + + @Override + public String toString() { + throw new NullPointerException("oops"); + } + } + + /** + * This has a private constructor + * -creating it will trigger an IllegalAccessException + */ + public static class PrivateClass { + private PrivateClass() { + } + } + + /** + * This has a private constructor + * -creating it will trigger an IllegalAccessException + */ + public static class PrivateConstructor { + private PrivateConstructor() { + } + } +}