type) {
- if (type.equals(InvocationContext.class)) {
- return type.cast(invocationContext);
- } else {
- return null;
- }
- }
- };
- try {
- real = (PipeCommand)invoker.invoke(resolver, CRaSHCommand.this);
- }
- catch (org.crsh.cli.SyntaxException e) {
- throw new org.crsh.command.SyntaxException(e.getMessage());
- } catch (InvocationException e) {
- throw toScript(e.getCause());
- }
-
- //
+ // It's a pipe command
+ if (ret instanceof PipeCommand) {
+ real = (PipeCommand)ret;
real.doOpen(invocationContext);
+ } else {
+ if (ret != null) {
+ peekContext().getWriter().print(ret);
+ }
}
-
- public void provide(Object element) throws IOException {
+ }
+ public void provide(Object element) throws IOException {
+ if (real != null) {
real.provide(element);
+ } else {
+ // We just drop the elements
}
-
- public void flush() throws IOException {
+ }
+ public void flush() throws IOException {
+ if (real != null) {
real.flush();
+ } else {
+ peekContext().flush();
}
-
- public void close() {
+ }
+ public void close() throws IOException {
+ if (real != null) {
try {
real.close();
}
finally {
popContext();
}
+ } else {
+ InvocationContext> context = popContext();
+ context.close();
}
- };
- }
+ CRaSHCommand.this.unmatched = null;
+ }
+ };
}
}
diff --git a/shell/core/src/main/java/org/crsh/command/ClassDispatcher.java b/shell/core/src/main/java/org/crsh/command/ClassDispatcher.java
index 66d47c30c..2dac341d5 100644
--- a/shell/core/src/main/java/org/crsh/command/ClassDispatcher.java
+++ b/shell/core/src/main/java/org/crsh/command/ClassDispatcher.java
@@ -24,6 +24,7 @@
import groovy.lang.MissingPropertyException;
import org.codehaus.groovy.runtime.InvokerInvocationException;
import org.crsh.io.Consumer;
+import org.crsh.util.Safe;
import java.io.IOException;
import java.util.ArrayList;
@@ -83,7 +84,6 @@ Object dispatch(String methodName, Object[] arguments) {
//
try {
pipe.fire();
- pipe.close();
return null;
}
catch (ScriptException e) {
@@ -94,6 +94,9 @@ Object dispatch(String methodName, Object[] arguments) {
throw e;
}
}
+ finally {
+ Safe.close(pipe);
+ }
}
private PipeCommandProxy, Object> resolvePipe(String name, Object[] args, boolean piped) {
diff --git a/shell/core/src/main/java/org/crsh/command/CommandContext.java b/shell/core/src/main/java/org/crsh/command/CommandContext.java
index 72c0002b2..1ae71e5e5 100644
--- a/shell/core/src/main/java/org/crsh/command/CommandContext.java
+++ b/shell/core/src/main/java/org/crsh/command/CommandContext.java
@@ -20,12 +20,14 @@
import org.crsh.shell.InteractionContext;
+import java.io.Closeable;
+
/**
* The command context provides the services for invoking a command.
*
* @author Julien Viet
*/
-public interface CommandContext extends InteractionContext
, RuntimeContext {
+public interface CommandContext
extends InteractionContext
, RuntimeContext, Closeable {
boolean isPiped();
diff --git a/shell/core/src/main/java/org/crsh/command/InnerInvocationContext.java b/shell/core/src/main/java/org/crsh/command/InnerInvocationContext.java
index a2a93f153..8ae16d5da 100644
--- a/shell/core/src/main/java/org/crsh/command/InnerInvocationContext.java
+++ b/shell/core/src/main/java/org/crsh/command/InnerInvocationContext.java
@@ -123,6 +123,10 @@ public void flush() throws IOException {
consumer.flush();
}
+ public void close() throws IOException {
+ // Nothing to do
+ }
+
public Map getSession() {
return outter.getSession();
}
diff --git a/shell/core/src/main/java/org/crsh/command/InvocationContextImpl.java b/shell/core/src/main/java/org/crsh/command/InvocationContextImpl.java
index a37908b78..081cf02c1 100644
--- a/shell/core/src/main/java/org/crsh/command/InvocationContextImpl.java
+++ b/shell/core/src/main/java/org/crsh/command/InvocationContextImpl.java
@@ -121,6 +121,10 @@ public void flush() throws IOException {
commandContext.flush();
}
+ public void close() throws IOException {
+ commandContext.close();
+ }
+
public Map getSession() {
return commandContext.getSession();
}
diff --git a/shell/core/src/main/java/org/crsh/command/PipeCommandProxy.java b/shell/core/src/main/java/org/crsh/command/PipeCommandProxy.java
index 10369c445..0d0daf9a3 100644
--- a/shell/core/src/main/java/org/crsh/command/PipeCommandProxy.java
+++ b/shell/core/src/main/java/org/crsh/command/PipeCommandProxy.java
@@ -70,7 +70,7 @@ public void flush() throws ScriptException, IOException {
}
}
- public void close() throws ScriptException {
+ public void close() throws ScriptException, IOException {
delegate.close();
if (next != null && next instanceof PipeCommand) {
((PipeCommand)next).close();
diff --git a/shell/core/src/main/java/org/crsh/io/Producer.java b/shell/core/src/main/java/org/crsh/io/Producer.java
index 9a7b5d94c..add7dee1d 100644
--- a/shell/core/src/main/java/org/crsh/io/Producer.java
+++ b/shell/core/src/main/java/org/crsh/io/Producer.java
@@ -20,6 +20,7 @@
package org.crsh.io;
import java.io.Closeable;
+import java.io.IOException;
/**
* A producer that produces elements in a specific consumer.
@@ -46,6 +47,6 @@ public interface Producer> extends Closeable {
/**
* Close the producer.
*/
- void close();
+ void close() throws IOException;
}
diff --git a/shell/core/src/main/java/org/crsh/processor/jline/JLineProcessor.java b/shell/core/src/main/java/org/crsh/processor/jline/JLineProcessor.java
index 098196c08..719154332 100644
--- a/shell/core/src/main/java/org/crsh/processor/jline/JLineProcessor.java
+++ b/shell/core/src/main/java/org/crsh/processor/jline/JLineProcessor.java
@@ -66,21 +66,35 @@ public void run() {
loop();
}
- private void loop() {
+ private String readLine() {
+ StringBuilder buffer = new StringBuilder();
+ String prompt = getPrompt();
+ writer.println();
+ writer.flush();
while (true) {
- String prompt = getPrompt();
- String line;
try {
- writer.println();
- writer.flush();
- if ((line = reader.readLine(prompt)) == null) {
- break;
+ String chunk;
+ if ((chunk = reader.readLine(prompt)) == null) {
+ return null;
+ }
+ if (chunk.length() > 0 && chunk.charAt(chunk.length() - 1) == '\\') {
+ prompt = "> ";
+ buffer.append(chunk, 0, chunk.length() - 1);
+ } else {
+ buffer.append(chunk);
+ return buffer.toString();
}
}
catch (IOException e) {
// What should we do other than that ?
- break;
+ return null;
}
+ }
+ }
+
+ private void loop() {
+ while (true) {
+ String line = readLine();
//
ShellProcess process = shell.createProcess(line);
diff --git a/shell/core/src/main/java/org/crsh/processor/term/ProcessContext.java b/shell/core/src/main/java/org/crsh/processor/term/ProcessContext.java
index ed9270493..95efa40c6 100644
--- a/shell/core/src/main/java/org/crsh/processor/term/ProcessContext.java
+++ b/shell/core/src/main/java/org/crsh/processor/term/ProcessContext.java
@@ -70,6 +70,7 @@ public String getProperty(String name) {
public String readLine(String msg, boolean echo) {
try {
processor.term.provide(Text.create(msg));
+ processor.term.flush();
}
catch (IOException e) {
return null;
@@ -137,7 +138,7 @@ public void end(ShellResponse response) {
switch (processor.status) {
case PROCESSING:
if (response instanceof ShellResponse.Close) {
- runnable = processor.CLOSE;
+ runnable = processor.CLOSE_TASK;
processor.status = Status.CLOSED;
} else if (response instanceof ShellResponse.Cancelled) {
runnable = Processor.NOOP;
diff --git a/shell/core/src/main/java/org/crsh/processor/term/Processor.java b/shell/core/src/main/java/org/crsh/processor/term/Processor.java
index bb8d12fd2..bd7df8f41 100644
--- a/shell/core/src/main/java/org/crsh/processor/term/Processor.java
+++ b/shell/core/src/main/java/org/crsh/processor/term/Processor.java
@@ -42,6 +42,9 @@
public final class Processor implements Runnable, Consumer {
+ /** . */
+ private static final Text CONTINUE_PROMPT = Text.create("> ");
+
/** . */
static final Runnable NOOP = new Runnable() {
public void run() {
@@ -49,21 +52,21 @@ public void run() {
};
/** . */
- final Runnable WRITE_PROMPT = new Runnable() {
+ final Runnable WRITE_PROMPT_TASK = new Runnable() {
public void run() {
writePromptFlush();
}
};
/** . */
- final Runnable CLOSE = new Runnable() {
+ final Runnable CLOSE_TASK = new Runnable() {
public void run() {
close();
}
};
/** . */
- private final Runnable READ_TERM = new Runnable() {
+ private final Runnable READ_TERM_TASK = new Runnable() {
public void run() {
readTerm();
}
@@ -96,6 +99,9 @@ public void run() {
/** . */
private final CloseableList listeners;
+ /** . */
+ private final StringBuffer lineBuffer = new StringBuffer();
+
public Processor(Term term, Shell shell) {
this.term = term;
this.shell = shell;
@@ -154,7 +160,7 @@ boolean iterate() throws InterruptedException, IOException {
}
case PROCESSING:
case CANCELLING:
- runnable = READ_TERM;
+ runnable = READ_TERM_TASK;
break;
case CLOSED:
return false;
@@ -170,7 +176,6 @@ boolean iterate() throws InterruptedException, IOException {
return true;
}
- // We assume this is called under lock synchronization
ProcessContext peekProcess() {
while (true) {
synchronized (lock) {
@@ -181,13 +186,27 @@ ProcessContext peekProcess() {
complete(((TermEvent.Complete)event).getLine());
} else {
String line = ((TermEvent.ReadLine)event).getLine().toString();
- if (line.length() > 0) {
- term.addToHistory(line);
+ if (line.endsWith("\\")) {
+ lineBuffer.append(line, 0, line.length() - 1);
+ try {
+ term.provide(CONTINUE_PROMPT);
+ term.flush();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ lineBuffer.append(line);
+ String command = lineBuffer.toString();
+ lineBuffer.setLength(0);
+ if (command.length() > 0) {
+ term.addToHistory(command);
+ }
+ ShellProcess process = shell.createProcess(command);
+ current = new ProcessContext(this, process);
+ status = Status.PROCESSING;
+ return current;
}
- ShellProcess process = shell.createProcess(line);
- current = new ProcessContext(this, process);
- status = Status.PROCESSING;
- return current;
}
} else {
break;
@@ -203,13 +222,14 @@ ProcessContext peekProcess() {
/** . */
private final Object termLock = new Object();
- private boolean reading = false;
+ /** . */
+ private boolean termReading = false;
void readTerm() {
//
synchronized (termLock) {
- if (reading) {
+ if (termReading) {
try {
termLock.wait();
return;
@@ -218,7 +238,7 @@ void readTerm() {
throw new AssertionError(e);
}
} else {
- reading = true;
+ termReading = true;
}
}
@@ -241,7 +261,7 @@ public void run() {
};
}
else if (status == Status.AVAILABLE) {
- runnable = WRITE_PROMPT;
+ runnable = WRITE_PROMPT_TASK;
} else {
runnable = NOOP;
}
@@ -258,7 +278,7 @@ public void run() {
}
};
} else if (status != Status.CLOSED) {
- runnable = CLOSE;
+ runnable = CLOSE_TASK;
} else {
runnable = NOOP;
}
@@ -279,7 +299,7 @@ public void run() {
}
finally {
synchronized (termLock) {
- reading = false;
+ termReading = false;
termLock.notifyAll();
}
}
diff --git a/shell/core/src/main/java/org/crsh/shell/impl/command/CRaSHSession.java b/shell/core/src/main/java/org/crsh/shell/impl/command/CRaSHSession.java
index dc4d73c14..0d6e8e208 100644
--- a/shell/core/src/main/java/org/crsh/shell/impl/command/CRaSHSession.java
+++ b/shell/core/src/main/java/org/crsh/shell/impl/command/CRaSHSession.java
@@ -148,7 +148,7 @@ private String eval(String name, String def) {
ClassLoader previous = setCRaSHLoader();
try {
GroovyShell shell = getGroovyShell();
- Object ret = shell.evaluate("return " + name + ";");
+ Object ret = shell.getContext().getVariable(name);
if (ret instanceof Closure) {
log.log(Level.FINEST, "Invoking " + name + " closure");
Closure c = (Closure)ret;
diff --git a/shell/core/src/main/java/org/crsh/shell/impl/command/Pipe.java b/shell/core/src/main/java/org/crsh/shell/impl/command/Pipe.java
index 6d406f2ab..e7c3b1c7d 100644
--- a/shell/core/src/main/java/org/crsh/shell/impl/command/Pipe.java
+++ b/shell/core/src/main/java/org/crsh/shell/impl/command/Pipe.java
@@ -99,7 +99,7 @@ public void flush() throws IOException {
command.flush();
}
- public void close() throws ScriptException {
+ public void close() throws ScriptException, IOException {
command.close();
}
}
diff --git a/shell/core/src/main/java/org/crsh/shell/impl/command/PipeFilter.java b/shell/core/src/main/java/org/crsh/shell/impl/command/PipeFilter.java
index a0104a804..bfea7e015 100644
--- a/shell/core/src/main/java/org/crsh/shell/impl/command/PipeFilter.java
+++ b/shell/core/src/main/java/org/crsh/shell/impl/command/PipeFilter.java
@@ -107,7 +107,7 @@ public void open(CommandContext consumer) {
}
public void close() {
- Safe.close((Closeable)context);
+ Safe.close(context);
}
}
@@ -158,8 +158,8 @@ public void flush() throws ScriptException, IOException {
ca.flush();
}
- public void close() throws ScriptException {
- ((Pipe)context).close();
+ public void close() throws ScriptException, IOException {
+ context.close();
}
}
@@ -194,8 +194,8 @@ public void flush() throws IOException {
context.flush();
}
- public void close() {
- ((Pipe)context).close();
+ public void close() throws IOException {
+ context.close();
}
}
}
diff --git a/shell/core/src/main/java/org/crsh/shell/impl/command/PipeLine.java b/shell/core/src/main/java/org/crsh/shell/impl/command/PipeLine.java
index 86da45dab..f4d4fd863 100644
--- a/shell/core/src/main/java/org/crsh/shell/impl/command/PipeLine.java
+++ b/shell/core/src/main/java/org/crsh/shell/impl/command/PipeLine.java
@@ -105,7 +105,7 @@ public void flush() throws IOException {
current.flush();
}
- public void close() {
+ public void close() throws IOException {
current.close();
}
}
diff --git a/shell/core/src/main/java/org/crsh/text/formatter/ThreadRenderable.java b/shell/core/src/main/java/org/crsh/text/formatter/ThreadRenderable.java
index ba5fe51e6..f0efb8ca7 100644
--- a/shell/core/src/main/java/org/crsh/text/formatter/ThreadRenderable.java
+++ b/shell/core/src/main/java/org/crsh/text/formatter/ThreadRenderable.java
@@ -79,6 +79,7 @@ public Renderer renderer(Iterator stream) {
Thread.sleep(100);
}
catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
// Resample
diff --git a/shell/core/src/main/java/org/crsh/text/ui/EvalElement.java b/shell/core/src/main/java/org/crsh/text/ui/EvalElement.java
index 3ad1b0571..2c09e53f7 100644
--- a/shell/core/src/main/java/org/crsh/text/ui/EvalElement.java
+++ b/shell/core/src/main/java/org/crsh/text/ui/EvalElement.java
@@ -143,6 +143,10 @@ public void flush() throws IOException {
renderers.add(i);
}
}
+
+ public void close() throws IOException {
+ // Nothing to do, except maybe release resources (and also prevent to do any other operation)
+ }
};
if (cmd instanceof CRaSHCommand) {
diff --git a/shell/core/src/main/resources/crash/commands/base/jdbc.groovy b/shell/core/src/main/resources/crash/commands/base/jdbc.groovy
index ec0788faf..ed11f4b0a 100644
--- a/shell/core/src/main/resources/crash/commands/base/jdbc.groovy
+++ b/shell/core/src/main/resources/crash/commands/base/jdbc.groovy
@@ -77,13 +77,19 @@ class jdbc implements Completer{
// We use this trick to work around the fact that the DriverManager#getConnection will not
// use the thread context classloader because of the nasty DriverManager#getCallerClassLoader method
- def getConnection = DriverManager.class.getDeclaredMethod("getConnection", String.class, Properties.class, ClassLoader.class)
- if (!getConnection.accessible)
- getConnection.accessible = true
-
- //
try {
- connection = getConnection.invoke(null, connectionString, props, Thread.currentThread().getContextClassLoader());
+ try {
+ def getConnection = DriverManager.class.getDeclaredMethod("getConnection", String.class, Properties.class, ClassLoader.class);
+ getConnection.setAccessible(true);
+ connection = getConnection.invoke(null, connectionString, props, Thread.currentThread().getContextClassLoader());
+ }
+ catch (NoSuchMethodException ignore) {
+ // JDK8 does not have this method instead it has the same method but with Class as last argument
+ // that we must invoke with null
+ def getConnection = DriverManager.class.getDeclaredMethod("getConnection", String.class, Properties.class, Class.class);
+ getConnection.setAccessible(true);
+ connection = getConnection.invoke(null, connectionString, props, null);
+ }
}
catch (InvocationTargetException ite) {
throw ite.cause;
diff --git a/shell/core/src/main/resources/crash/crash.properties b/shell/core/src/main/resources/crash/crash.properties
index fa74253bd..cd18b6f23 100644
--- a/shell/core/src/main/resources/crash/crash.properties
+++ b/shell/core/src/main/resources/crash/crash.properties
@@ -5,6 +5,12 @@ crash.vfs.refresh_period=1
crash.ssh.port=2000
#crash.ssh.keypath=/path/to/the/key/file
+# The idle-timeout for ssh sessions in milliseconds
+crash.ssh.idle-timeout=600000
+
+# The authentication timeout for ssh sessions in milliseconds
+crash.ssh.auth-timeout=600000
+
# Telnet configuration
crash.telnet.port=5000
diff --git a/shell/core/src/test/java/org/crsh/processor/term/ProcessorTestCase.java b/shell/core/src/test/java/org/crsh/processor/term/ProcessorTestCase.java
index f483888cc..1b0a3ca6e 100644
--- a/shell/core/src/test/java/org/crsh/processor/term/ProcessorTestCase.java
+++ b/shell/core/src/test/java/org/crsh/processor/term/ProcessorTestCase.java
@@ -28,6 +28,7 @@
import org.crsh.cli.impl.Delimiter;
import org.crsh.cli.spi.Completion;
import org.crsh.shell.Shell;
+import org.crsh.shell.ShellProcess;
import org.crsh.shell.ShellProcessContext;
import org.crsh.shell.ShellResponse;
import org.crsh.term.console.ConsoleTerm;
@@ -35,6 +36,8 @@
import org.crsh.text.CLS;
import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -251,6 +254,37 @@ public CompletionMatch complete(String prefix) {
controller.connector.assertChars("ba").assertFlush();
}
+ public void testMultiLine() throws Exception {
+ final LinkedList requests = new LinkedList();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Controller controller = create(new BaseShell(BaseProcessFactory.ECHO) {
+ @Override
+ public ShellProcess createProcess(String request) {
+ return new BaseProcess(request) {
+ @Override
+ protected ShellResponse execute(String request) {
+ requests.add(request);
+ latch.countDown();
+ return super.execute(request);
+ }
+ };
+ }
+ });
+ controller.assertStart();
+
+ //
+ controller.connector.append("a\\\r\n");
+ controller.connector.assertChars("a").assertFlush();
+ controller.connector.assertChars("\\").assertFlush();
+ controller.connector.assertCRLF().assertFlush().assertChars("> ").assertFlush();
+ assertEquals(Collections.emptyList(), requests);
+ controller.connector.append("b\r\n");
+ controller.connector.assertChars("b").assertFlush();
+ controller.connector.assertCRLF().assertFlush();
+ latch.await(5, TimeUnit.SECONDS);
+ assertEquals(Collections.singletonList("ab"), requests);
+ }
+
public void testCLS() throws Exception {
Controller controller = create(new BaseShell(new BaseProcessFactory() {
@Override
diff --git a/shell/core/src/test/java/org/crsh/shell/Commands.java b/shell/core/src/test/java/org/crsh/shell/Commands.java
index bd3e808b0..411eab129 100644
--- a/shell/core/src/test/java/org/crsh/shell/Commands.java
+++ b/shell/core/src/test/java/org/crsh/shell/Commands.java
@@ -169,6 +169,22 @@ public void provide(String element) throws ScriptException, IOException {
}
}
+ public static class IsClosed extends CRaSHCommand {
+
+ /** . */
+ public static final AtomicInteger closed = new AtomicInteger();
+
+ @Command
+ public org.crsh.command.PipeCommand