opendj-cli/src/main/java/com/forgerock/opendj/cli/CliConstants.java
@@ -31,6 +31,9 @@ */ public final class CliConstants { /** Default value for LDAP connection timeout. */ public static final int DEFAULT_LDAP_CONNECT_TIMEOUT = 30000; /** Default value for incrementing port number. */ public static final int PORT_INCREMENT = 1000; opendj-cli/src/main/java/com/forgerock/opendj/cli/ClientException.java
New file @@ -0,0 +1,108 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2014 ForgeRock AS */ package com.forgerock.opendj.cli; import static com.forgerock.opendj.cli.CliMessages.ERR_CONSOLE_INPUT_ERROR; import org.forgerock.i18n.LocalizableException; import org.forgerock.i18n.LocalizableMessage; /** * This class defines an exception that may be thrown if a local problem occurs in a Directory Server client. */ public class ClientException extends Exception implements LocalizableException { /** * The serial version identifier required to satisfy the compiler because this class extends * <CODE>java.lang.Exception</CODE>, which implements the <CODE>java.io.Serializable</CODE> interface. This value * was generated using the <CODE>serialver</CODE> command-line utility included with the Java SDK. */ private static final long serialVersionUID = 1384120263337669664L; /** The return code. */ private ReturnCode returnCode; /** The message linked to that exception. */ private final LocalizableMessage message; /** * Adapts any exception that may have occurred whilst reading input from the * console. * * @param cause * The exception that occurred whilst reading input from the * console. * @return Returns a new CLI exception describing a problem that occurred * whilst reading input from the console. */ public static ClientException adaptInputException(final Throwable cause) { return new ClientException(ReturnCode.ERROR_USER_DATA, ERR_CONSOLE_INPUT_ERROR.get(cause.getMessage()), cause); } /** * Creates a new client exception with the provided message. * * @param exitCode * The exit code that may be used if the client considers this to be a fatal problem. * @param message * The message that explains the problem that occurred. */ public ClientException(ReturnCode exitCode, LocalizableMessage message) { super(message.toString()); this.returnCode = exitCode; this.message = message; } /** * Creates a new client exception with the provided message and root cause. * * @param exitCode * The exit code that may be used if the client considers this to be a fatal problem. * @param message * The message that explains the problem that occurred. * @param cause * The exception that was caught to trigger this exception. */ public ClientException(ReturnCode exitCode, LocalizableMessage message, Throwable cause) { super(message.toString(), cause); this.returnCode = exitCode; this.message = message; } /** * Retrieves the exit code that the client may use if it considers this to be a fatal problem. * * @return The exit code that the client may use if it considers this to be a fatal problem. */ public int getReturnCode() { return returnCode.get(); } @Override public LocalizableMessage getMessageObject() { return message; } } opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
@@ -34,7 +34,6 @@ import static com.forgerock.opendj.cli.Utils.wrapText; import java.io.BufferedReader; import java.io.Closeable; import java.io.Console; import java.io.EOFException; import java.io.IOException; @@ -45,11 +44,9 @@ import org.forgerock.i18n.LocalizableMessage; /** * This class provides an abstract base class which can be used as the basis of * a console-based application. * This class provides an abstract base class which can be used as the basis of a console-based application. */ public abstract class ConsoleApplication { private final PrintStream err; private final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); @@ -57,6 +54,8 @@ private final PrintStream out; private final PrintStream err; private final Console console = System.console(); /** @@ -80,27 +79,6 @@ } /** * Closes the provided {@code Closeable}s if they are not {@code null}. * * @param closeables * The closeables to be closed. */ public final void closeIfNotNull(Closeable... closeables) { if (closeables == null) { return; } for (Closeable closeable : closeables) { if (closeable != null) { try { closeable.close(); } catch (Exception ignored) { // Do nothing. } } } } /** * Returns the application error stream. * * @return The application error stream. @@ -128,8 +106,8 @@ } /** * Indicates whether or not the user has requested interactive behavior. The * default implementation returns {@code true}. * Indicates whether or not the user has requested interactive behavior. The default implementation returns * {@code true}. * * @return {@code true} if the user has requested interactive behavior. */ @@ -138,8 +116,7 @@ } /** * Indicates whether or not the user has requested quiet output. The default * implementation returns {@code false}. * Indicates whether or not the user has requested quiet output. The default implementation returns {@code false}. * * @return {@code true} if the user has requested quiet output. */ @@ -148,8 +125,8 @@ } /** * Indicates whether or not the user has requested script-friendly output. * The default implementation returns {@code false}. * Indicates whether or not the user has requested script-friendly output. The default implementation returns * {@code false}. * * @return {@code true} if the user has requested script-friendly output. */ @@ -158,8 +135,7 @@ } /** * Indicates whether or not the user has requested verbose output. The * default implementation returns {@code false}. * Indicates whether or not the user has requested verbose output. The default implementation returns {@code false}. * * @return {@code true} if the user has requested verbose output. */ @@ -168,16 +144,15 @@ } /** * Interactively prompts the user to press return to continue. This method * should be called in situations where a user needs to be given a chance to * read some documentation before continuing (continuing may cause the * Interactively prompts the user to press return to continue. This method should be called in situations where a * user needs to be given a chance to read some documentation before continuing (continuing may cause the * documentation to be scrolled out of view). */ public final void pressReturnToContinue() { final LocalizableMessage msg = INFO_MENU_PROMPT_RETURN_TO_CONTINUE.get(); try { readLineOfInput(msg); } catch (final CLIException e) { } catch (final ClientException e) { // Ignore the exception - applications don't care. } } @@ -189,14 +164,14 @@ * The message. */ public final void errPrint(final LocalizableMessage msg) { err.print(wrapText(msg, MAX_LINE_WIDTH)); getErrStream().print(wrap(msg)); } /** * Displays a blank line to the error stream. */ public final void errPrintln() { err.println(); getErrStream().println(); } /** @@ -206,12 +181,11 @@ * The message. */ public final void errPrintln(final LocalizableMessage msg) { err.println(wrapText(msg, MAX_LINE_WIDTH)); getErrStream().println(wrap(msg)); } /** * Displays a message to the error stream indented by the specified number * of columns. * Displays a message to the error stream indented by the specified number of columns. * * @param msg * The message. @@ -219,7 +193,7 @@ * The number of columns to indent. */ public final void errPrintln(final LocalizableMessage msg, final int indent) { err.println(wrapText(msg, MAX_LINE_WIDTH, indent)); getErrStream().println(wrapText(msg, MAX_LINE_WIDTH, indent)); } /** @@ -229,8 +203,8 @@ * The verbose message. */ public final void errPrintVerboseMessage(final LocalizableMessage msg) { if (isVerbose() || isInteractive()) { err.println(wrapText(msg, MAX_LINE_WIDTH)); if (isVerbose()) { getErrStream().println(wrap(msg)); } } @@ -241,7 +215,7 @@ * The message. */ public final void print(final LocalizableMessage msg) { out.print(wrapText(msg, MAX_LINE_WIDTH)); out.print(wrap(msg)); } /** @@ -258,12 +232,11 @@ * The message. */ public final void println(final LocalizableMessage msg) { out.println(wrapText(msg, MAX_LINE_WIDTH)); out.println(wrap(msg)); } /** * Displays a message to the output stream indented by the specified number * of columns. * Displays a message to the output stream indented by the specified number of columns. * * @param msg * The message. @@ -282,27 +255,24 @@ */ public final void printVerboseMessage(final LocalizableMessage msg) { if (isVerbose() || isInteractive()) { out.println(wrapText(msg, MAX_LINE_WIDTH)); out.println(wrap(msg)); } } /** * Interactively prompts (on error output) the user to provide a string * value. Any non-empty string will be allowed (the empty string will * indicate that the default should be used, if there is one). * Interactively prompts (on error output) the user to provide a string value. Any non-empty string will be allowed * (the empty string will indicate that the default should be used, if there is one). * * @param prompt * The prompt to present to the user. * @param defaultValue * The default value to assume if the user presses ENTER without * typing anything, or {@code null} if there should not be a * default and the user must explicitly provide a value. * @throws CLIException * The default value to assume if the user presses ENTER without typing anything, or {@code null} if * there should not be a default and the user must explicitly provide a value. * @throws ClientException * If the line of input could not be retrieved for some reason. * @return The string value read from the user. */ public final String readInput(LocalizableMessage prompt, final String defaultValue) throws CLIException { public final String readInput(LocalizableMessage prompt, final String defaultValue) throws ClientException { while (true) { if (defaultValue != null) { prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), defaultValue); @@ -327,13 +297,13 @@ * @param prompt * The password prompt. * @return The password. * @throws CLIException * @throws ClientException * If the password could not be retrieved for some reason. */ public final char[] readPassword(final LocalizableMessage prompt) throws CLIException { public final char[] readPassword(final LocalizableMessage prompt) throws ClientException { if (console != null) { if (prompt != null) { err.print(wrapText(prompt, MAX_LINE_WIDTH)); err.print(wrap(prompt)); err.print(" "); } try { @@ -343,7 +313,7 @@ } return password; } catch (final Throwable e) { throw CLIException.adaptInputException(e); throw ClientException.adaptInputException(e); } } else { // FIXME: should go direct to char[] and avoid the String. @@ -357,23 +327,48 @@ * @param prompt * The prompt. * @return The line of input. * @throws CLIException * @throws ClientException * If the line of input could not be retrieved for some reason. */ private final String readLineOfInput(final LocalizableMessage prompt) throws CLIException { private final String readLineOfInput(final LocalizableMessage prompt) throws ClientException { if (prompt != null) { err.print(wrapText(prompt, MAX_LINE_WIDTH)); err.print(wrap(prompt)); err.print(" "); } try { final String s = reader.readLine(); if (s == null) { throw CLIException.adaptInputException(new EOFException("End of input")); throw ClientException.adaptInputException(new EOFException("End of input")); } else { return s; } } catch (final IOException e) { throw CLIException.adaptInputException(e); throw ClientException.adaptInputException(e); } } /** * Inserts line breaks into the provided buffer to wrap text at no more than the specified column width (80). * * @param msg * The message to wrap. * @return The wrapped message. */ private String wrap(final LocalizableMessage msg) { return wrapText(msg, MAX_LINE_WIDTH); } /** * Returns the error stream. Effectively, when an application is in "interactive mode" all the informations should * be written in the stdout. * * @return The error stream that should be used with this application. */ private PrintStream getErrStream() { if (isInteractive()) { return out; } else { return err; } } opendj-cli/src/main/java/com/forgerock/opendj/cli/Utils.java
@@ -332,14 +332,15 @@ /** * Checks that the java version. * * @throws CLIException * @throws ClientException * If the java version we are running on is not compatible. */ public static void checkJavaVersion() throws CLIException { public static void checkJavaVersion() throws ClientException { final String version = System.getProperty("java.specification.version"); if (!(Float.valueOf(version) >= 1.6)) { final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; throw new CLIException(ERR_INCOMPATIBLE_JAVA_VERSION.get("1.6", version, javaBin), null); throw new ClientException(ReturnCode.JAVA_VERSION_INCOMPATIBLE, ERR_INCOMPATIBLE_JAVA_VERSION.get("1.6", version, javaBin), null); } } opendj-cli/src/test/java/com/forgerock/opendj/cli/ConsoleApplicationTestCase.java
@@ -33,6 +33,8 @@ import org.testng.annotations.Test; import static org.fest.assertions.Assertions.assertThat; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; /** * Unit tests for the console application class. @@ -80,8 +82,12 @@ return interactive; } public void setVerbose(boolean verbose) { this.verbose = verbose; public void setVerbose(boolean v) { verbose = v; } public void setInteractive(boolean inter) { interactive = inter; } } @@ -99,7 +105,7 @@ public void testWriteLineInErrorStream() throws UnsupportedEncodingException { final LocalizableMessage msg = LocalizableMessage.raw("Language is the source of misunderstandings."); final MockConsoleApplication ca = MockConsoleApplication.getDefault(); ca.errPrint(msg); ca.errPrintln(msg); assertThat(ca.getOut()).isEmpty(); assertThat(ca.getErr()).contains(msg.toString()); } @@ -134,4 +140,50 @@ assertThat(ca.getOut()).isEmpty(); assertThat(ca.getErr()).contains(msg.toString()); } /** * In non interactive applications, standard messages should be displayed in the stdout(info) and errors to the * stderr (warnings, errors). * * @throws UnsupportedEncodingException */ @Test() public void testNonInteractiveApplicationShouldNotStdoutErrors() throws UnsupportedEncodingException { final LocalizableMessage msg = LocalizableMessage.raw("Language is the source of misunderstandings."); final LocalizableMessage msg2 = LocalizableMessage .raw("If somebody wants a sheep, that is a proof that one exists."); final MockConsoleApplication ca = MockConsoleApplication.getDefault(); assertFalse(ca.isInteractive()); ca.errPrintln(msg); assertThat(ca.getOut()).isEmpty(); assertThat(ca.getErr()).contains(msg.toString()); ca.println(msg2); assertThat(ca.getOut()).contains(msg2.toString()); assertThat(ca.getErr()).doesNotContain(msg2.toString()); } /** * If an application is interactive, all messages should be redirect to the stdout. (info, warnings, errors). * * @throws UnsupportedEncodingException */ @Test() public void testInteractiveApplicationShouldStdoutErrors() throws UnsupportedEncodingException { final LocalizableMessage msg = LocalizableMessage.raw("Language is the source of misunderstandings."); final LocalizableMessage msg2 = LocalizableMessage .raw("If somebody wants a sheep, that is a proof that one exists."); final MockConsoleApplication ca = MockConsoleApplication.getDefault(); assertFalse(ca.isInteractive()); ca.setInteractive(true); assertTrue(ca.isInteractive()); ca.errPrintln(msg); assertThat(ca.getOut()).contains(msg.toString()); assertThat(ca.getErr()).isEmpty(); ca.println(msg2); assertThat(ca.getOut()).contains(msg2.toString()); assertThat(ca.getErr()).isEmpty(); } } opendj-cli/src/test/java/com/forgerock/opendj/cli/UtilsTestCase.java
@@ -29,8 +29,8 @@ public class UtilsTestCase extends CliTestCase { @Test(expectedExceptions = CLIException.class) public void testInvalidJavaVersion() throws CLIException { @Test(expectedExceptions = ClientException.class) public void testInvalidJavaVersion() throws ClientException { final String original = System.getProperty("java.specification.version"); System.setProperty("java.specification.version", "1.5"); try { @@ -41,7 +41,7 @@ } @Test() public void testValidJavaVersion() throws CLIException { public void testValidJavaVersion() throws ClientException { Utils.checkJavaVersion(); } }