Skip to content
This repository was archived by the owner on Jul 6, 2023. It is now read-only.

Commit 63e57dd

Browse files
committed
Forward merge resolutions
1 parent 2230ee5 commit 63e57dd

File tree

7 files changed

+223
-88
lines changed

7 files changed

+223
-88
lines changed

build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ allprojects {
2929

3030
repositories {
3131
jcenter()
32-
maven {
33-
url "http://m2.neo4j.org/content/repositories/snapshots"
34-
}
32+
mavenCentral()
3533
}
3634
}
3735

@@ -78,4 +76,5 @@ ext {
7876
jlineVersion = '2.14.6'
7977
mockitoVersion = '1.9.5'
8078
systemRulesVersion = '1.19.0'
79+
commonsIoVersion = '2.6'
8180
}

cypher-shell/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dependencies {
4444
compile("jline:jline:$jlineVersion") {
4545
exclude(group: 'junit', module: 'junit')
4646
}
47+
compile group: 'commons-io', name: 'commons-io', version: "$commonsIoVersion"
4748
testCompile "junit:junit:$junitVersion"
4849
testCompile "org.mockito:mockito-core:$mockitoVersion"
4950
testCompile "com.github.stefanbirkner:system-rules:$systemRulesVersion"

cypher-shell/src/integration-test/java/org/neo4j/shell/MainIntegrationTest.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,22 @@ private static class ShellAndConnection
5555
private ConnectionConfig connectionConfig;
5656
private CypherShell shell;
5757
private Main main;
58+
private PrintStream printStream;
59+
private InputStream inputStream;
5860

5961
@Before
6062
public void setup() {
6163
// given
62-
InputStream inputStream = new ByteArrayInputStream( inputString.getBytes() );
64+
inputStream = new ByteArrayInputStream(inputString.getBytes());
6365

6466
baos = new ByteArrayOutputStream();
65-
PrintStream ps = new PrintStream( baos );
67+
printStream = new PrintStream(baos);
6668

67-
main = new Main( inputStream, ps );
69+
main = new Main(inputStream, printStream);
6870

6971
CliArgs cliArgs = new CliArgs();
7072
cliArgs.setUsername("", "");
71-
cliArgs.setPassword( "", "" );
73+
cliArgs.setPassword("", "");
7274

7375
ShellAndConnection sac = getShell( cliArgs );
7476
shell = sac.shell;
@@ -90,25 +92,42 @@ public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
9092
assertEquals( format( "username: neo4j%npassword: ***%n" ), baos.toString() );
9193
assertEquals("neo4j", connectionConfig.username());
9294
assertEquals("neo", connectionConfig.password());
95+
96+
String out = baos.toString();
97+
assertEquals(String.format( "username: neo4j%npassword: ***%n" ), out);
9398
}
9499

95100
@Test
96-
public void promptsSilentlyOnWrongAuthenticationIfOutputRedirected() throws Exception {
101+
public void doesNotPromptToStdOutOnWrongAuthenticationIfOutputRedirected() throws Exception {
97102
// when
98103
assertEquals("", connectionConfig.username());
99104
assertEquals("", connectionConfig.password());
100105

101-
main.connectMaybeInteractively(shell, connectionConfig, true, false);
102-
103-
// then
104-
// should be connected
105-
assertTrue(shell.isConnected());
106-
// should have prompted silently and set the username and password
107-
assertEquals("neo4j", connectionConfig.username());
108-
assertEquals("neo", connectionConfig.password());
109-
110-
String out = baos.toString();
111-
assertEquals( "", out );
106+
// Redirect System.in and System.out
107+
InputStream stdIn = System.in;
108+
PrintStream stdOut = System.out;
109+
System.setIn(inputStream);
110+
System.setOut(printStream);
111+
112+
// Create a Main with the standard in and out
113+
try {
114+
Main realMain = new Main(stdIn, stdOut);
115+
realMain.connectMaybeInteractively(shell, connectionConfig, true, false);
116+
117+
// then
118+
// should be connected
119+
assertTrue(shell.isConnected());
120+
// should have prompted silently and set the username and password
121+
assertEquals("neo4j", connectionConfig.username());
122+
assertEquals("neo", connectionConfig.password());
123+
124+
String out = baos.toString();
125+
assertEquals("", out);
126+
} finally {
127+
// Restore in and out
128+
System.setIn(stdIn);
129+
System.setOut(stdOut);
130+
}
112131
}
113132

114133
@Test

cypher-shell/src/main/java/org/neo4j/shell/Main.java

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class Main {
2626
static final String NEO_CLIENT_ERROR_SECURITY_UNAUTHORIZED = "Neo.ClientError.Security.Unauthorized";
2727
private final InputStream in;
2828
private final PrintStream out;
29+
private final boolean hasSpecialInteractiveOutputStream;
2930

3031
public static void main(String[] args) {
3132
CliArgs cliArgs = CliArgHelper.parse(args);
@@ -41,15 +42,27 @@ public static void main(String[] args) {
4142
}
4243

4344
private Main() {
44-
this(System.in, System.out);
45+
this(System.in, System.out, false);
4546
}
4647

4748
/**
4849
* For testing purposes
4950
*/
5051
Main(final InputStream in, final PrintStream out) {
52+
this(in, out, true);
53+
}
54+
55+
private Main(final InputStream in, final PrintStream out, final boolean hasSpecialInteractiveOutputStream ) {
5156
this.in = in;
5257
this.out = out;
58+
this.hasSpecialInteractiveOutputStream = hasSpecialInteractiveOutputStream;
59+
}
60+
61+
/**
62+
* Delegate for testing purposes
63+
*/
64+
private OutputStream getOutputStreamForInteractivePrompt() {
65+
return hasSpecialInteractiveOutputStream ? this.out : ShellRunner.getOutputStreamForInteractivePrompt();
5366
}
5467

5568
void startShell(@Nonnull CliArgs cliArgs) {
@@ -102,37 +115,48 @@ void connectMaybeInteractively(@Nonnull CypherShell shell,
102115
boolean outputInteractive)
103116
throws Exception {
104117

105-
OutputStream outputStream = outputInteractive ? out : new ThrowawayOutputStream();
118+
boolean didPrompt = false;
119+
120+
// Prompt directly in interactive mode if user provided username but not password
121+
if (inputInteractive && !connectionConfig.username().isEmpty() && connectionConfig.password().isEmpty()) {
122+
promptForUsernameAndPassword(connectionConfig, outputInteractive);
123+
didPrompt = true;
124+
}
125+
126+
try {
127+
// Try to connect
128+
shell.connect(connectionConfig);
129+
} catch (AuthenticationException e) {
130+
// Fail if we already prompted,
131+
// or do not have interactive input,
132+
// or already tried with both username and password
133+
if (didPrompt || !inputInteractive || (!connectionConfig.username().isEmpty() && !connectionConfig.password().isEmpty())) {
134+
throw e;
135+
}
136+
137+
// Otherwise we prompt for username and password, and try to connect again
138+
promptForUsernameAndPassword(connectionConfig, outputInteractive);
139+
shell.connect(connectionConfig);
140+
}
141+
}
106142

107-
ConsoleReader consoleReader = new ConsoleReader(in, outputStream);
143+
private void promptForUsernameAndPassword(ConnectionConfig connectionConfig, boolean outputInteractive) throws Exception {
144+
OutputStream promptOutputStream = getOutputStreamForInteractivePrompt();
145+
ConsoleReader consoleReader = new ConsoleReader(in, promptOutputStream);
108146
// Disable expansion of bangs: !
109147
consoleReader.setExpandEvents(false);
110148
// Ensure Reader does not handle user input for ctrl+C behaviour
111149
consoleReader.setHandleUserInterrupt(false);
112150

113151
try {
114-
shell.connect(connectionConfig);
115-
} catch (AuthenticationException e) {
116-
// Only prompt for username/password if they weren't used
117-
if (!connectionConfig.username().isEmpty() && !connectionConfig.password().isEmpty()) {
118-
throw e;
152+
if (connectionConfig.username().isEmpty()) {
153+
String username = outputInteractive ?
154+
promptForNonEmptyText("username", consoleReader, null) :
155+
promptForText("username", consoleReader, null);
156+
connectionConfig.setUsername(username);
119157
}
120-
// else need to prompt for username and password
121-
if (inputInteractive) {
122-
if (connectionConfig.username().isEmpty()) {
123-
String username = outputInteractive ?
124-
promptForNonEmptyText("username", consoleReader, null) :
125-
promptForText("username", consoleReader, null);
126-
connectionConfig.setUsername(username);
127-
}
128-
if (connectionConfig.password().isEmpty()) {
129-
connectionConfig.setPassword(promptForText("password", consoleReader, '*'));
130-
}
131-
// try again
132-
shell.connect(connectionConfig);
133-
} else {
134-
// Can't prompt because input has been redirected
135-
throw e;
158+
if (connectionConfig.password().isEmpty()) {
159+
connectionConfig.setPassword(promptForText("password", consoleReader, '*'));
136160
}
137161
} finally {
138162
consoleReader.close();
@@ -176,14 +200,6 @@ private String promptForText(@Nonnull String prompt, @Nonnull ConsoleReader cons
176200
if (line == null) {
177201
throw new CommandException("No text could be read, exiting...");
178202
}
179-
180203
return line;
181204
}
182-
183-
private static class ThrowawayOutputStream extends OutputStream {
184-
@Override
185-
public void write( int b )
186-
{
187-
}
188-
}
189205
}

cypher-shell/src/main/java/org/neo4j/shell/ShellRunner.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.neo4j.shell;
22

3+
import org.apache.commons.io.output.NullOutputStream;
4+
import org.apache.commons.io.output.WriterOutputStream;
5+
36
import org.neo4j.shell.cli.CliArgs;
47
import org.neo4j.shell.cli.FileHistorian;
58
import org.neo4j.shell.cli.InteractiveShellRunner;
@@ -13,8 +16,11 @@
1316
import java.io.File;
1417
import java.io.FileInputStream;
1518
import java.io.FileNotFoundException;
19+
import java.io.FileOutputStream;
1620
import java.io.IOException;
1721
import java.io.InputStream;
22+
import java.io.OutputStream;
23+
import java.nio.charset.Charset;
1824

1925
import static org.fusesource.jansi.internal.CLibrary.STDIN_FILENO;
2026
import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO;
@@ -128,11 +134,38 @@ static boolean isOutputInteractive() {
128134
*/
129135
static InputStream getInputStream(CliArgs cliArgs) throws FileNotFoundException
130136
{
131-
if ( cliArgs.getInputFilename() == null)
137+
if ( cliArgs.getInputFilename() == null )
132138
{
133139
return System.in;
140+
}
141+
else
142+
{
143+
return new BufferedInputStream( new FileInputStream( new File( cliArgs.getInputFilename() ) ) );
144+
}
145+
}
146+
147+
static OutputStream getOutputStreamForInteractivePrompt() {
148+
if (isWindows()) {
149+
// Output will never be a TTY on windows and it isatty seems to be able to block forever on Windows so avoid
150+
// calling it.
151+
if (System.console() != null) {
152+
return new WriterOutputStream(System.console().writer(), Charset.defaultCharset());
153+
}
134154
} else {
135-
return new BufferedInputStream( new FileInputStream( new File(cliArgs.getInputFilename()) ));
155+
try {
156+
if (1 == isatty(STDOUT_FILENO)) {
157+
return System.out;
158+
} else {
159+
return new FileOutputStream(new File("/dev/tty"));
160+
}
161+
} catch (Throwable ignored) {
162+
// system is not using libc (like Alpine Linux)
163+
// Fallback to checking stdin OR stdout
164+
if (System.console() != null) {
165+
return new WriterOutputStream(System.console().writer(), Charset.defaultCharset());
166+
}
167+
}
136168
}
169+
return new NullOutputStream();
137170
}
138171
}

0 commit comments

Comments
 (0)