diff --git a/README.md b/README.md index 3cf7d4d1..94ace807 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,52 @@ For additional client implementations in other languages, see: For more information, see [the nailgun website](https://github.com/facebook/nailgun). +## Additional information +### Example usage + +```bash +$ ./nailgun-client/target/ng com.facebook.nailgun.examples.ThreadTest +``` + +### Logging + +To enable logging, start the server with `-Djava.util.logging.config.file="logging.properties"`. A relative file path is resolved in respect to the working directory +(i.e. system property `user.dir`). An example file is at `nailgun-server/src/main/resources/logging.properties` but will only be used if (soft-) linked appropriate. + +### Aliases + +* Aliases are loaded from `com/facebook/nailgun/builtins/builtins.properties` +* Adding an alias example: + ```bash + $ ./nailgun-client/target/ng ng-alias test com.facebook.nailgun.examples.ThreadTest + $ ./nailgun-client/target/ng test + ``` + +#### Built-in aliases + +```bash +$ ./nailgun-client/target/ng ng-alias +ng-alias com.facebook.nailgun.builtins.NGAlias + Displays and manages command aliases + +ng-cp com.facebook.nailgun.builtins.NGClasspath + Displays and manages the current system classpath + +ng-stats com.facebook.nailgun.builtins.NGServerStats + Displays nail statistics + +ng-stop com.facebook.nailgun.builtins.NGStop + Shuts down the nailgun server + +ng-version com.facebook.nailgun.builtins.NGVersion + Displays the server version number. +``` + +The alias `ng-cp` is _very_ dangerous and YOU are encouraged to set +`com.facebook.nailgun.builtins.NGClasspath.ALLOW_CLASSPATH_MODIFICATION` to +`false`. + +### Links + +* [original homepage](http://www.martiansoftware.com/nailgun/) diff --git a/logging.properties b/logging.properties new file mode 120000 index 00000000..5a9ea74e --- /dev/null +++ b/logging.properties @@ -0,0 +1 @@ +nailgun-server/src/main/resources/logging.properties \ No newline at end of file diff --git a/nailgun-examples/pom.xml b/nailgun-examples/pom.xml index e702aeab..8d4dbb0f 100644 --- a/nailgun-examples/pom.xml +++ b/nailgun-examples/pom.xml @@ -21,7 +21,7 @@ com.facebook nailgun-all - 1.0.1 + 1.0.2-SNAPSHOT diff --git a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/DumpAll.java b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/DumpAll.java index 24e1f5d7..5e696819 100644 --- a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/DumpAll.java +++ b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/DumpAll.java @@ -18,6 +18,8 @@ package com.facebook.nailgun.examples; import com.facebook.nailgun.NGContext; + +import java.io.PrintStream; import java.util.TreeSet; /** @@ -28,24 +30,26 @@ public class DumpAll { public static void nailMain(NGContext context) { - context.out.println(); - context.out.println(" context.getCommand(): " + context.getCommand()); - context.out.println(" context.getInetAddress(): " + context.getInetAddress()); - context.out.println(" context.getPort(): " + context.getPort()); - context.out.println("context.getWorkingDirectory(): " + context.getWorkingDirectory()); - context.out.println(" context.getFileSeparator(): " + context.getFileSeparator()); - context.out.println(" context.getPathSeparator(): " + context.getPathSeparator()); - - context.out.println("\ncontext.getArgs():"); + PrintStream out = context.getOut(); + out.println(); + out.println(" context.getCommand(): " + context.getCommand()); + out.println(" context.getInetAddress(): " + context.getInetAddress()); + out.println(" context.getPort(): " + context.getPort()); + out.println("context.getWorkingDirectory(): " + context.getWorkingDirectory()); + out.println(" context.getFileSeparator(): " + context.getFileSeparator()); + out.println(" context.getPathSeparator(): " + context.getPathSeparator()); + + out.println("\ncontext.getArgs():"); for (int i = 0; i < context.getArgs().length; ++i) { - context.out.println(" args[" + i + "]=" + context.getArgs()[i]); + out.println(" args[" + i + "]=" + context.getArgs()[i]); } - context.out.println("\ncontext.getEnv():"); + out.println("\ncontext.getEnv():"); TreeSet keys = new TreeSet(context.getEnv().keySet()); for (Object okey : keys) { String key = (String) okey; - context.out.println(" env[\"" + key + "\"]=" + context.getEnv().getProperty(key)); + out.println(" env[\"" + key + "\"]=" + context.getEnv().getProperty(key)); } + out.flush(); } } diff --git a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Hash.java b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Hash.java index 568e0440..be52e2f5 100644 --- a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Hash.java +++ b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Hash.java @@ -83,8 +83,9 @@ public static void nailMain(NGContext context) // display available algorithms Set algs = getCryptoImpls("MessageDigest"); for (Object alg : algs) { - context.out.println(alg); + context.getOut().println(alg); } + context.getOut().flush(); return; } @@ -92,7 +93,7 @@ public static void nailMain(NGContext context) MessageDigest md = MessageDigest.getInstance(args[0]); byte[] b = new byte[1024]; - int bytesRead = context.in.read(b); + int bytesRead = context.getIn().read(b); while (bytesRead != -1) { md.update(b, 0, bytesRead); bytesRead = System.in.read(b); @@ -105,6 +106,7 @@ public static void nailMain(NGContext context) buf.append(HEXCHARS[(result[i] >> 4) & 0x0f]); buf.append(HEXCHARS[result[i] & 0x0f]); } - context.out.println(buf); + context.getOut().println(buf); + context.getOut().flush(); } } diff --git a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Heartbeat.java b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Heartbeat.java index 92b4a5f9..545c78b9 100644 --- a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Heartbeat.java +++ b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Heartbeat.java @@ -47,7 +47,7 @@ public static void nailMain(final NGContext context) { } }); - context.addHeartbeatListener(() -> context.out.print("H")); + context.addHeartbeatListener(() -> context.getOut().print("H")); synchronized (lock) { if (!shutdown.get()) { diff --git a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Prompt.java b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Prompt.java index 2dc7b1af..101f220d 100644 --- a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Prompt.java +++ b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Prompt.java @@ -32,7 +32,8 @@ public static void nailMain(NGContext context) { if (result == null) { context.exit(1); } else { - context.out.println(result); + context.getOut().println(result); + context.getOut().flush(); } } } diff --git a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Stack.java b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Stack.java index 7d0946b8..9f3f2ab5 100644 --- a/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Stack.java +++ b/nailgun-examples/src/main/java/com/facebook/nailgun/examples/Stack.java @@ -66,10 +66,11 @@ public static void nailMain(NGContext context) throws InterruptedException { sharedStack.wait(); } if (sharedStack.size() > 0) { - context.out.println(sharedStack.pop()); + context.getOut().println(sharedStack.pop()); exitCode = 0; } } + context.getOut().flush(); context.exit(exitCode); return; } diff --git a/nailgun-server/pom.xml b/nailgun-server/pom.xml index 2f0b7b51..eb6c3158 100644 --- a/nailgun-server/pom.xml +++ b/nailgun-server/pom.xml @@ -21,7 +21,7 @@ com.facebook nailgun-all - 1.0.1 + 1.0.2-SNAPSHOT diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/AliasManager.java b/nailgun-server/src/main/java/com/facebook/nailgun/AliasManager.java index d8390b59..a5248c8a 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/AliasManager.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/AliasManager.java @@ -35,14 +35,10 @@ public class AliasManager { private Map aliases; /** Creates a new AliasManager, populating it with default Aliases. */ - public AliasManager() { + public AliasManager(ClassLoader cl) { aliases = new java.util.HashMap(); Properties props = new Properties(); - ClassLoader cl = getClass().getClassLoader(); - if (cl == null) - cl = ClassLoader.getSystemClassLoader(); // needed if nailgun classes are loaded in the boot - // classpath. try (InputStream is = cl.getResourceAsStream("com/facebook/nailgun/builtins/builtins.properties")) { props.load(is); diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGClientListener.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGClientListener.java index d0be5d4a..0baeb71c 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGClientListener.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGClientListener.java @@ -16,6 +16,7 @@ package com.facebook.nailgun; +@FunctionalInterface public interface NGClientListener { /** diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGCommunicator.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGCommunicator.java index 8038a1c3..f3f575ec 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGCommunicator.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGCommunicator.java @@ -17,7 +17,6 @@ package com.facebook.nailgun; import java.io.ByteArrayInputStream; -import java.io.Closeable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; @@ -48,7 +47,7 @@ * using underlying socket streams. Also handles client disconnect events based on client * heartbeats. */ -public class NGCommunicator implements Closeable { +public class NGCommunicator implements AutoCloseable { private static final Logger LOG = Logger.getLogger(NGCommunicator.class.getName()); private final ExecutorService orchestratorExecutor; @@ -383,6 +382,7 @@ private void stopOut() throws IOException { if (outClosed) { return; } + out.flush(); outClosed = true; LOG.log(Level.FINE, "Shutting down socket for output"); diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGConstants.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGConstants.java index ececaffa..d4d3122e 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGConstants.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGConstants.java @@ -17,8 +17,12 @@ package com.facebook.nailgun; +import java.io.File; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Properties; +import java.util.logging.Logger; /** * Just a simple holder for various NailGun-related contants. @@ -27,6 +31,8 @@ */ public class NGConstants { + private static final Logger LOG = Logger.getLogger(NGConstants.class.getName()); + /** The default NailGun port (2113) */ public static final int DEFAULT_PORT = 2113; /** The exit code sent to clients if nail completed successfully */ @@ -63,9 +69,6 @@ public class NGConstants { /** Chunk type marker for heartbeats sent to let the server know the client is still alive. */ public static final byte CHUNKTYPE_HEARTBEAT = 'H'; - /** Server version number */ - public static final String VERSION = getVersion(); - /** Expected interval between heartbeats in milliseconds. */ public static final short HEARTBEAT_INTERVAL_MILLIS = 1000; @@ -78,17 +81,38 @@ public class NGConstants { /** Maximum chunk len sent from client. */ public static final short MAXIMUM_CHUNK_LENGTH = 2048; + private static String VERSION = null; + /** Loads the version number from a file generated by Maven. */ - private static String getVersion() { - Properties props = new Properties(); - try (InputStream is = - NGConstants.class.getResourceAsStream( - "/META-INF/maven/com.facebook/nailgun-server/pom.properties")) { - props.load(is); - } catch (Throwable e) { - // In static initialization context, outputting or logging an exception is dangerous - // It smells bad, but let's ignore it + public static String getVersion() throws MalformedURLException { + if (VERSION == null) { + Properties props = new Properties(); + URL url = + NGServer.getInstance() + .getClassLoader() + .getResource("META-INF/maven/com.facebook/nailgun-server/pom.properties"); + if (url == null) { + File file = new File("target/maven-archiver/pom.properties"); + if (file.isFile()) { + url = file.toURI().toURL(); + } + } + if (url == null) { + File file = new File("nailgun-server/target/maven-archiver/pom.properties"); + if (file.isFile()) { + url = file.toURI().toURL(); + } + } + LOG.info("get version info from " + url); + try (InputStream is = url.openStream()) { + props.load(is); + } catch (Throwable e) { + // In static initialization context, outputting or logging an exception is dangerous + // It smells bad, but let's ignore it + } + VERSION = + props.getProperty("version", System.getProperty("nailgun.server.version", "[UNKNOWN]")); } - return props.getProperty("version", System.getProperty("nailgun.server.version", "[UNKNOWN]")); + return VERSION; } } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGContext.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGContext.java index b723207b..aa058822 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGContext.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGContext.java @@ -63,18 +63,22 @@ public class NGContext { private String workingDirectory = null; /** The client's stdin */ - public InputStream in = null; + private InputStream in = null; /** The client's stdout */ - public PrintStream out = null; + private PrintStream out = null; /** The client's stderr */ - public PrintStream err = null; + private PrintStream err = null; private NGCommunicator communicator = null; /** Creates a new, empty NGContext */ - public NGContext() {} + public NGContext(InputStream in, PrintStream out, PrintStream err) { + setIn(in); + setOut(out); + setErr(err); + } public void setCommunicator(NGCommunicator comm) { this.communicator = comm; @@ -123,7 +127,7 @@ public String getWorkingDirectory() { * @param in The {@link InputStream} to use as stdin for the current nail. This should be an * InputStream that ultimately reads from {@link NGInputStream}. */ - public void setIn(InputStream in) { + private void setIn(InputStream in) { this.in = in; if (!(System.in instanceof ThreadLocalInputStream)) { throw new IllegalStateException("System.in should be set by nailgun."); @@ -138,7 +142,7 @@ public void setIn(InputStream in) { * @param out The {@link PrintStream} to use as stdout for the current nail. This should be a * PrintStream that ultimately writes to {@link NGOutputStream}. */ - public void setOut(PrintStream out) { + private void setOut(PrintStream out) { this.out = out; if (!(System.out instanceof ThreadLocalPrintStream)) { throw new IllegalStateException("System.out should be set by nailgun."); @@ -153,7 +157,7 @@ public void setOut(PrintStream out) { * @param err The {@link PrintStream} to use as stderr for the current nail. This should be a * PrintStream that ultimately writes to {@link NGOutputStream}. */ - public void setErr(PrintStream err) { + private void setErr(PrintStream err) { this.err = err; if (!(System.err instanceof ThreadLocalPrintStream)) { throw new IllegalStateException("System.err should be set by nailgun."); @@ -162,6 +166,18 @@ public void setErr(PrintStream err) { tls.init(err); } + public InputStream getIn() { + return in; + } + + public PrintStream getOut() { + return out; + } + + public PrintStream getErr() { + return err; + } + void setEnv(Properties remoteEnvironment) { this.remoteEnvironment = remoteEnvironment; } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGHeartbeatListener.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGHeartbeatListener.java index 512986cc..0acbe238 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGHeartbeatListener.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGHeartbeatListener.java @@ -16,6 +16,7 @@ package com.facebook.nailgun; +@FunctionalInterface public interface NGHeartbeatListener { /** diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGInputStream.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGInputStream.java index c7ebb9fd..ca97c1aa 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGInputStream.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGInputStream.java @@ -51,6 +51,7 @@ public boolean markSupported() { } /** @see java.io.InputStream#read() */ + @Override public int read() throws IOException { // have to synchronize all one byte reads to be able to reuse internal buffer and not // recreate new buffer on heap each time @@ -60,11 +61,13 @@ public int read() throws IOException { } /** @see java.io.InputStream#read(byte[]) */ + @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** @see java.io.InputStream#read(byte[], int, int) */ + @Override public int read(byte[] b, int offset, int length) throws IOException { try { return communicator.receive(b, offset, length); diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGServer.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGServer.java index 0bacb67d..f036fcf2 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGServer.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGServer.java @@ -25,6 +25,8 @@ import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; +import java.net.URL; +import java.net.URLClassLoader; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; @@ -46,9 +48,17 @@ public class NGServer implements Runnable { private static final Logger LOG = Logger.getLogger(NGServer.class.getName()); + private static NGServer INSTANCE = null; + /** Default size for thread pool */ public static final int DEFAULT_SESSIONPOOLSIZE = 2; + public static final NGServer getInstance() { + return INSTANCE; + } + + private final URLClassLoader classLoader; + /** The address on which to listen */ private final NGListeningAddress listeningAddress; @@ -99,7 +109,7 @@ public class NGServer implements Runnable { * @param port the port on which to listen. * @param sessionPoolSize the max number of idle sessions allowed by the pool */ - public NGServer(InetAddress addr, int port, int sessionPoolSize, int timeoutMillis) { + private NGServer(InetAddress addr, int port, int sessionPoolSize, int timeoutMillis) { this(new NGListeningAddress(addr, port), sessionPoolSize, timeoutMillis); } @@ -111,7 +121,7 @@ public NGServer(InetAddress addr, int port, int sessionPoolSize, int timeoutMill * @param addr the address at which to listen, or null to bind to all local addresses * @param port the port on which to listen. */ - public NGServer(InetAddress addr, int port) { + private NGServer(InetAddress addr, int port) { this( new NGListeningAddress(addr, port), DEFAULT_SESSIONPOOLSIZE, @@ -123,7 +133,7 @@ public NGServer(InetAddress addr, int port) { * NGConstants.DEFAULT_PORT). This does not cause the server to start listening. To * do so, create a new Thread wrapping this NGServer and start it. */ - public NGServer() { + private NGServer() { this( new NGListeningAddress(null, NGConstants.DEFAULT_PORT), DEFAULT_SESSIONPOOLSIZE, @@ -141,9 +151,20 @@ public NGServer() { * disconnecting them */ public NGServer(NGListeningAddress listeningAddress, int sessionPoolSize, int timeoutMillis) { + if (INSTANCE == null) { + INSTANCE = this; + } else { + throw new IllegalStateException("NGServer singleton already initialized"); + } + Thread current = Thread.currentThread(); + ClassLoader parent = current.getContextClassLoader(); + // TODO + classLoader = new URLClassLoader(new URL[0], parent); + current.setContextClassLoader(classLoader); + this.listeningAddress = listeningAddress; - aliasManager = new AliasManager(); + aliasManager = new AliasManager(classLoader); allNailStats = new HashMap(); // allow a maximum of 10 idle threads. probably too high a number // and definitely should be configurable in the future @@ -151,6 +172,10 @@ public NGServer(NGListeningAddress listeningAddress, int sessionPoolSize, int ti heartbeatTimeoutMillis = timeoutMillis; } + public URLClassLoader getClassLoader() { + return classLoader; + } + /** * Sets a flag that determines whether Nails can be executed by class name. If this is false, * Nails can only be run via aliases (and you should probably remove ng-alias from the @@ -372,7 +397,7 @@ public void run() { // test_ng.py on *nix relies on reading this line from stdout to start connecting to server. out.println( "NGServer " - + NGConstants.VERSION + + NGConstants.getVersion() + " started on " + listeningAddress.toString() + portDescription diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGSession.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGSession.java index 8ac4d3c6..30c9ab9d 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGSession.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGSession.java @@ -17,6 +17,7 @@ package com.facebook.nailgun; +import com.facebook.nailgun.builtins.NGClasspath; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; @@ -24,6 +25,7 @@ import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketException; +import java.net.URLClassLoader; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,12 +40,14 @@ public class NGSession extends Thread { @FunctionalInterface - public interface CommnunicatorCreator { + public static interface CommnunicatorCreator { NGCommunicator get(Socket socket) throws IOException; } private static final Logger LOG = Logger.getLogger(NGSession.class.getName()); + private static final Class[] NGCLASSPATH_ARGS = new Class[] {URLClassLoader.class}; + /** The server this NGSession is working for */ private final NGServer server; /** The pool this NGSession came from, and to which it will return itself */ @@ -76,18 +80,6 @@ public interface CommnunicatorCreator { NGContext.class, }; - /** A ClassLoader that may be set by a client. Defaults to the classloader of this class. */ - public static volatile ClassLoader classLoader = - null; // initialized in the static initializer - see below - - static { - try { - classLoader = NGSession.class.getClassLoader(); - } catch (SecurityException e) { - throw e; - } - } - /** * Creates a new NGSession running for the specified NGSessionPool and NGServer. * @@ -249,12 +241,15 @@ private void runImpl(NGCommunicator comm, Socket socket) { if (alias != null) { cmdclass = alias.getAliasedClass(); } else if (server.allowsNailsByClassName()) { - cmdclass = Class.forName(cmdContext.getCommand(), true, classLoader); + cmdclass = Class.forName(cmdContext.getCommand(), true, server.getClassLoader()); } else { cmdclass = server.getDefaultNailClass(); } } catch (ClassNotFoundException ex) { - throw new NGNailNotFoundException("Nail class not found: " + cmdContext.getCommand(), ex); + NGNailNotFoundException e = + new NGNailNotFoundException("Nail class not found: " + cmdContext.getCommand(), ex); + LOG.log(Level.SEVERE, e.toString(), e); + throw e; } Object[] methodArgs = new Object[1]; @@ -264,16 +259,7 @@ private void runImpl(NGCommunicator comm, Socket socket) { .getCommandArguments() .toArray(new String[cmdContext.getCommandArguments().size()]); - boolean isStaticNail = true; // See: NonStaticNail.java - - Class[] interfaces = cmdclass.getInterfaces(); - - for (int i = 0; i < interfaces.length; i++) { - if (interfaces[i].equals(NonStaticNail.class)) { - isStaticNail = false; - break; - } - } + boolean isStaticNail = !NonStaticNail.class.isAssignableFrom(cmdclass); if (!isStaticNail) { mainMethod = cmdclass.getMethod("nailMain", new Class[] {String[].class}); @@ -281,11 +267,8 @@ private void runImpl(NGCommunicator comm, Socket socket) { } else { try { mainMethod = cmdclass.getMethod("nailMain", nailMainSignature); - NGContext context = new NGContext(); + NGContext context = new NGContext(in, out, err); context.setArgs(cmdlineArgs); - context.in = in; - context.out = out; - context.err = err; context.setCommand(cmdContext.getCommand()); context.setNGServer(server); context.setCommunicator(comm); @@ -301,8 +284,11 @@ private void runImpl(NGCommunicator comm, Socket socket) { methodArgs[0] = cmdlineArgs; } catch (NoSuchMethodException ex) { // failed to find 'main' too, so give up and throw - throw new NGNailNotFoundException( - "Can't find nailMain or main functions in " + cmdclass.getName(), ex); + NGNailNotFoundException e = + new NGNailNotFoundException( + "Can't find nailMain or main functions in " + cmdclass.getName(), ex); + LOG.log(Level.SEVERE, e.toString(), e); + throw e; } } } @@ -310,9 +296,20 @@ private void runImpl(NGCommunicator comm, Socket socket) { server.nailStarted(cmdclass); try { - mainMethod.invoke(isStaticNail ? null : cmdclass.newInstance(), methodArgs); + Object target = null; + if (!isStaticNail) { + target = cmdclass.getDeclaredConstructor().newInstance(new Object[0]); + } else if (NGClasspath.class.isAssignableFrom(cmdclass)) { + target = + cmdclass + .getDeclaredConstructor(NGCLASSPATH_ARGS) + .newInstance(server.getClassLoader()); + } + mainMethod.invoke(target, methodArgs); } catch (InvocationTargetException ite) { - throw ite.getCause(); + Throwable e = ite.getCause(); + LOG.log(Level.SEVERE, "invocation on " + mainMethod + " failed", e); + throw e; } finally { server.nailFinished(cmdclass); } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NGWin32NamedPipeSocket.java b/nailgun-server/src/main/java/com/facebook/nailgun/NGWin32NamedPipeSocket.java index 8e88bfa4..36836a37 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NGWin32NamedPipeSocket.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NGWin32NamedPipeSocket.java @@ -39,7 +39,8 @@ public class NGWin32NamedPipeSocket extends Socket { private final HANDLE readerWaitable; private final HANDLE writerWaitable; - interface CloseCallback { + @FunctionalInterface + static interface CloseCallback { void onNamedPipeSocketClose(HANDLE handle) throws IOException; } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/NonStaticNail.java b/nailgun-server/src/main/java/com/facebook/nailgun/NonStaticNail.java index 74bb3a2d..fe3b4818 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/NonStaticNail.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/NonStaticNail.java @@ -23,7 +23,8 @@ * *

Implementations of this interface MUST provide a public, no-args constructor. */ +@FunctionalInterface public interface NonStaticNail { - public void nailMain(String[] args); + void nailMain(String[] args); } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/ThreadLocalPrintStream.java b/nailgun-server/src/main/java/com/facebook/nailgun/ThreadLocalPrintStream.java index 13411c4c..78e45159 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/ThreadLocalPrintStream.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/ThreadLocalPrintStream.java @@ -32,7 +32,7 @@ class ThreadLocalPrintStream extends PrintStream { /** The PrintStreams for the various threads */ - private InheritableThreadLocal streams = null; + private InheritableThreadLocal streams = null; private PrintStream defaultPrintStream = null; @@ -44,7 +44,7 @@ class ThreadLocalPrintStream extends PrintStream { */ public ThreadLocalPrintStream(PrintStream defaultPrintStream) { super(defaultPrintStream); - streams = new InheritableThreadLocal(); + streams = new InheritableThreadLocal<>(); this.defaultPrintStream = defaultPrintStream; init(null); } @@ -65,127 +65,154 @@ void init(PrintStream streamForCurrentThread) { */ PrintStream getPrintStream() { PrintStream result = (PrintStream) streams.get(); - return ((result == null) ? defaultPrintStream : result); + if (result == null || result.checkError()) { + result = defaultPrintStream; + } + return result; } // BEGIN delegated java.io.PrintStream methods /** @see java.io.PrintStream#checkError() */ + @Override public boolean checkError() { - return (getPrintStream().checkError()); + return getPrintStream().checkError(); } /** @see java.io.PrintStream#close() */ + @Override public void close() { getPrintStream().close(); } /** @see java.io.PrintStream#flush() */ + @Override public void flush() { getPrintStream().flush(); } /** @see java.io.PrintStream#print(boolean) */ + @Override public void print(boolean b) { getPrintStream().print(b); } /** @see java.io.PrintStream#print(char) */ + @Override public void print(char c) { getPrintStream().print(c); } /** @see java.io.PrintStream#print(char[]) */ + @Override public void print(char[] s) { getPrintStream().print(s); } /** @see java.io.PrintStream#print(double) */ + @Override public void print(double d) { getPrintStream().print(d); } /** @see java.io.PrintStream#print(float) */ + @Override public void print(float f) { getPrintStream().print(f); } /** @see java.io.PrintStream#print(int) */ + @Override public void print(int i) { getPrintStream().print(i); } /** @see java.io.PrintStream#print(long) */ + @Override public void print(long l) { getPrintStream().print(l); } /** @see java.io.PrintStream#print(Object) */ + @Override public void print(Object obj) { getPrintStream().print(obj); } /** @see java.io.PrintStream#print(String) */ + @Override public void print(String s) { getPrintStream().print(s); } /** @see java.io.PrintStream#println() */ + @Override public void println() { getPrintStream().println(); } /** @see java.io.PrintStream#println(boolean) */ + @Override public void println(boolean x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(char) */ + @Override public void println(char x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(char[]) */ + @Override public void println(char[] x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(double) */ + @Override public void println(double x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(float) */ + @Override public void println(float x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(int) */ + @Override public void println(int x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(long) */ + @Override public void println(long x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(Object) */ + @Override public void println(Object x) { getPrintStream().println(x); } /** @see java.io.PrintStream#println(String) */ + @Override public void println(String x) { getPrintStream().println(x); } /** @see java.io.PrintStream#write(byte[],int,int) */ + @Override public void write(byte[] buf, int off, int len) { getPrintStream().write(buf, off, len); } /** @see java.io.PrintStream#write(int) */ + @Override public void write(int b) { getPrintStream().write(b); } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/DefaultNail.java b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/DefaultNail.java index 56843430..4c01e133 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/DefaultNail.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/DefaultNail.java @@ -32,7 +32,7 @@ public class DefaultNail { public static void nailMain(NGContext context) { - context.err.println("No such command: " + context.getCommand()); + context.getErr().println("No such command: " + context.getCommand()); context.exit(NGConstants.EXIT_NOSUCHCOMMAND); } } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGAlias.java b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGAlias.java index bc7d1215..fae60719 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGAlias.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGAlias.java @@ -72,12 +72,15 @@ public static void nailMain(NGContext context) throws ClassNotFoundException { } for (Iterator i = aliases.iterator(); i.hasNext(); ) { Alias alias = (Alias) i.next(); - context.out.println( - padl(alias.getName(), maxAliasLength) - + "\t" - + padl(alias.getAliasedClass().getName(), maxClassnameLength)); - context.out.println(padl("", maxAliasLength) + "\t" + alias.getDescription()); - context.out.println(); + context + .getOut() + .println( + padl(alias.getName(), maxAliasLength) + + "\t" + + padl(alias.getAliasedClass().getName(), maxClassnameLength)); + context.getOut().println(padl("", maxAliasLength) + "\t" + alias.getDescription()); + context.getOut().println(); + context.getOut().flush(); } } else if (args.length == 2) { server.getAliasManager().addAlias(new Alias(args[0], "", Class.forName(args[1]))); diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGClasspath.java b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGClasspath.java index 9f508599..c54d2824 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGClasspath.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGClasspath.java @@ -23,6 +23,8 @@ import java.io.File; import java.net.URL; import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Provides a means to display and add to the system classpath at runtime. If called with no @@ -36,6 +38,17 @@ */ public class NGClasspath { + // TODO: EXTREMELY DANGEROUS IF SET TO TRUE + private static final boolean ALLOW_CLASSPATH_MODIFICATION = true; + + private static final Logger LOG = Logger.getLogger(NGClasspath.class.getName()); + + private final URLClassLoader classLoader; + + public NGClasspath(URLClassLoader classLoader) { + this.classLoader = classLoader; + } + /** * Adds the specified URL (for a jar or a directory) to the System ClassLoader. This code was * written by antony_miguel and posted on @@ -47,28 +60,43 @@ public class NGClasspath { * would be that your VM is not using a URLClassLoader as the System ClassLoader. This would * result in a ClassClastException that you probably can't do much about. */ - private static void addToSystemClassLoader(URL url) throws Exception { - URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); - Class sysclass = URLClassLoader.class; - - java.lang.reflect.Method method = sysclass.getDeclaredMethod("addURL", new Class[] {URL.class}); + private void addToClassLoader(URL url) throws Exception { + // TODO: non-public method + java.lang.reflect.Method method = + classLoader.getClass().getDeclaredMethod("addURL", new Class[] {URL.class}); method.setAccessible(true); - method.invoke(sysloader, new Object[] {url}); + method.invoke(classLoader, new Object[] {url}); } - public static void nailMain(NGContext context) throws Exception { + public void nailMain(NGContext context) throws Exception { String[] args = context.getArgs(); if (args.length == 0) { - URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); - URL[] urls = sysLoader.getURLs(); + ClassLoader cl = classLoader; + URL[] urls = classLoader.getURLs(); + context.getOut().println("classloader urls:"); for (int i = 0; i < urls.length; ++i) { - context.out.println(urls[i]); + context.getOut().println("\t" + urls[i]); } + context.getOut().println("end classloader urls"); + do { + cl = cl.getParent(); + context.getOut().println("parent classloader: " + cl); + } while (cl != null); } else { - for (int i = 0; i < args.length; ++i) { - File file = new File(args[i]); - addToSystemClassLoader(file.toURL()); + if (ALLOW_CLASSPATH_MODIFICATION) { + for (int i = 0; i < args.length; ++i) { + File file = new File(args[i]); + URL url = file.toURI().toURL(); + if (file.exists()) { + addToClassLoader(url); + } else { + LOG.log(Level.WARNING, "not adding " + url + " as it does not exist"); + } + } + } else { + LOG.log(Level.SEVERE, "ng-cp classpath changes have been disabled for security"); } } + context.getOut().flush(); } } diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGServerStats.java b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGServerStats.java index cab01911..1cbe8d46 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGServerStats.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGServerStats.java @@ -41,7 +41,7 @@ public static void nailShutdown(NGServer server) { } public static void nailMain(NGContext context) { - dumpStats(context.getNGServer(), context.out); + dumpStats(context.getNGServer(), context.getOut()); } private static void dumpStats(NGServer server, java.io.PrintStream out) { diff --git a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGVersion.java b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGVersion.java index b6ff3d28..42ebcb5a 100644 --- a/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGVersion.java +++ b/nailgun-server/src/main/java/com/facebook/nailgun/builtins/NGVersion.java @@ -19,6 +19,7 @@ import com.facebook.nailgun.NGConstants; import com.facebook.nailgun.NGContext; +import java.net.MalformedURLException; /** * Displays the version of the NailGun server and exits. @@ -27,7 +28,8 @@ */ public class NGVersion { - public static void nailMain(NGContext context) { - context.out.println("NailGun server version " + NGConstants.VERSION); + public static void nailMain(NGContext context) throws MalformedURLException { + context.getOut().println("NailGun server version " + NGConstants.getVersion()); + context.getOut().flush(); } } diff --git a/nailgun-server/src/main/resources/logging.properties b/nailgun-server/src/main/resources/logging.properties new file mode 100644 index 00000000..720c9735 --- /dev/null +++ b/nailgun-server/src/main/resources/logging.properties @@ -0,0 +1,13 @@ +# http://tutorials.jenkov.com/java-logging/configuration.html +# https://docs.oracle.com/javase/7/docs/api/index.html?java/util/logging/SimpleFormatter.html +# https://stackoverflow.com/questions/54554045/using-simple-class-name-in-java-logging-formatter + +handlers=java.util.logging.ConsoleHandler +.level=FINEST +java.util.logging.ConsoleHandler.level=FINEST +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +#java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n + +sun.awt.level=WARNING +java.awt.level=WARNING +javax.level=INFO diff --git a/pom.xml b/pom.xml index ad0591b7..cdbb17a1 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.facebook nailgun-all - 1.0.1 + 1.0.2-SNAPSHOT pom