/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2008-2010 Sun Microsystems, Inc. */ package org.opends.server.util.cli; import static org.opends.messages.AdminToolMessages.*; import static org.opends.messages.DSConfigMessages.*; import static org.opends.messages.QuickSetupMessages.*; import static org.opends.messages.UtilityMessages.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.Reader; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import javax.naming.NamingException; import javax.naming.NoPermissionException; import javax.naming.ldap.InitialLdapContext; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManager; import org.opends.admin.ads.ServerDescriptor; import org.opends.admin.ads.util.ApplicationTrustManager; import org.opends.admin.ads.util.ConnectionUtils; import org.opends.admin.ads.util.OpendsCertificateException; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import org.opends.quicksetup.util.PlainTextProgressMessageFormatter; import org.opends.quicksetup.util.ProgressMessageFormatter; import org.opends.quicksetup.util.Utils; import org.opends.server.protocols.ldap.LDAPResultCode; import org.opends.server.tools.ClientException; import org.opends.server.types.NullOutputStream; import org.opends.server.util.PasswordReader; import org.opends.server.util.SetupUtils; /** * This class provides an abstract base class which can be used as the * basis of a console-based application. */ public abstract class ConsoleApplication { /** * A null reader. */ private static final class NullReader extends Reader { /** * {@inheritDoc} */ @Override public void close() throws IOException { // Do nothing. } /** * {@inheritDoc} */ @Override public int read(char[] cbuf, int off, int len) throws IOException { return -1; } } // The error stream which this application should use. private final PrintStream err; // The input stream reader which this application should use. private final BufferedReader in; // The output stream which this application should use. private final PrintStream out; /** * The maximum number of times we try to confirm. */ protected final static int CONFIRMATION_MAX_TRIES = 5; private static final String COMMENT_SHELL_UNIX = "# "; private static final String COMMENT_BATCH_WINDOWS = "rem "; /** * The String used to write comments in a shell (or batch) script. */ protected static final String SHELL_COMMENT_SEPARATOR = SetupUtils.isWindows() ? COMMENT_BATCH_WINDOWS : COMMENT_SHELL_UNIX; /** * Creates a new console application instance. * * @param in * The application input stream. * @param out * The application output stream. * @param err * The application error stream. */ protected ConsoleApplication(BufferedReader in, PrintStream out, PrintStream err) { if (in != null) { this.in = in; } else { this.in = new BufferedReader(new NullReader()); } if (out != null) { this.out = out; } else { this.out = NullOutputStream.printStream(); } if (err != null) { this.err = out; } else { this.err = NullOutputStream.printStream(); } } /** * Creates a new console application instance. * * @param in * The application input stream. * @param out * The application output stream. * @param err * The application error stream. */ protected ConsoleApplication(InputStream in, OutputStream out, OutputStream err) { if (in != null) { this.in = new BufferedReader(new InputStreamReader(in)); } else { this.in = new BufferedReader(new NullReader()); } if (out != null) { this.out = new PrintStream(out); } else { this.out = NullOutputStream.printStream(); } if (err != null) { this.err = new PrintStream(err); } else { this.err = NullOutputStream.printStream(); } } /** * Interactively confirms whether a user wishes to perform an * action. If the application is non-interactive, then the provided * default is returned automatically. * * @param prompt * The prompt describing the action. * @param defaultValue * The default value for the confirmation message. This * will be returned if the application is non-interactive * or if the user just presses return. * @return Returns true if the user wishes the action * to be performed, or false if they refused, * or if an exception occurred. * @throws CLIException * If the user's response could not be read from the * console for some reason. */ public final boolean confirmAction(Message prompt, final boolean defaultValue) throws CLIException { if (!isInteractive()) { return defaultValue; } final Message yes = INFO_GENERAL_YES.get(); final Message no = INFO_GENERAL_NO.get(); final Message errMsg = ERR_CONSOLE_APP_CONFIRM.get(yes, no); prompt = INFO_MENU_PROMPT_CONFIRM.get(prompt, yes, no, defaultValue ? yes : no); ValidationCallback validator = new ValidationCallback() { public Boolean validate(ConsoleApplication app, String input) { String ninput = input.toLowerCase().trim(); if (ninput.length() == 0) { return defaultValue; } else if (no.toString().toLowerCase().startsWith(ninput)) { return false; } else if (yes.toString().toLowerCase().startsWith(ninput)) { return true; } else { // Try again... app.println(); app.println(errMsg); app.println(); } return null; } }; return readValidatedInput(prompt, validator, CONFIRMATION_MAX_TRIES); } /** * Gets the application error stream. * * @return Returns the application error stream. */ public final PrintStream getErrorStream() { return err; } /** * Gets the application input stream. * * @return Returns the application input stream. */ public final BufferedReader getInputStream() { return in; } /** * Gets the application output stream. * * @return Returns the application output stream. */ public final PrintStream getOutputStream() { return out; } /** * Indicates whether or not the user has requested advanced mode. * * @return Returns true if the user has requested * advanced mode. */ public abstract boolean isAdvancedMode(); /** * Indicates whether or not the user has requested interactive * behavior. * * @return Returns true if the user has requested * interactive behavior. */ public abstract boolean isInteractive(); /** * Indicates whether or not this console application is running in * its menu-driven mode. This can be used to dictate whether output * should go to the error stream or not. In addition, it may also * dictate whether or not sub-menus should display a cancel option * as well as a quit option. * * @return Returns true if this console application * is running in its menu-driven mode. */ public abstract boolean isMenuDrivenMode(); /** * Indicates whether or not the user has requested quiet output. * * @return Returns true if the user has requested * quiet output. */ public abstract boolean isQuiet(); /** * Indicates whether or not the user has requested script-friendly * output. * * @return Returns true if the user has requested * script-friendly output. */ public abstract boolean isScriptFriendly(); /** * Indicates whether or not the user has requested verbose output. * * @return Returns true if the user has requested * verbose output. */ public abstract boolean isVerbose(); /** * 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() { Message msg = INFO_MENU_PROMPT_RETURN_TO_CONTINUE.get(); try { readLineOfInput(msg); } catch (CLIException e) { // Ignore the exception - applications don't care. } } /** * Displays a blank line to the error stream. */ public final void println() { err.println(); } /** * Displays a message to the error stream. * * @param msg * The message. */ public final void println(Message msg) { err.println(wrapText(msg, MAX_LINE_WIDTH)); } /** * Displays a message to the error stream. * * @param msg * The message. */ public final void print(Message msg) { err.print(wrapText(msg, MAX_LINE_WIDTH)); } /** * Displays a blank line to the output stream if we are not in quiet mode. */ public final void printlnProgress() { if (!isQuiet()) { out.println(); } } /** * Displays a message to the output stream if we are not in quiet mode. * * @param msg * The message. */ public final void printProgress(Message msg) { if (!isQuiet()) { out.print(msg); } } /** * Displays a message to the error stream indented by the specified * number of columns. * * @param msg * The message. * @param indent * The number of columns to indent. */ public final void println(Message msg, int indent) { err.println(wrapText(msg, MAX_LINE_WIDTH, indent)); } /** * Displays a message to the error stream if verbose mode is * enabled. * * @param msg * The verbose message. */ public final void printVerboseMessage(Message msg) { if (isVerbose() || isInteractive()) { err.println(wrapText(msg, MAX_LINE_WIDTH)); } } /** * Interactively retrieves a line of input from the console. * * @param prompt * The prompt. * @return Returns the line of input, or null if the * end of input has been reached. * @throws CLIException * If the line of input could not be retrieved for some * reason. */ public final String readLineOfInput(Message prompt) throws CLIException { if (prompt != null) { err.print(wrapText(prompt, MAX_LINE_WIDTH)); err.print(" "); } try { String s = in.readLine(); if (s == null) { throw CLIException .adaptInputException(new EOFException("End of input")); } else { return s; } } catch (IOException e) { throw CLIException.adaptInputException(e); } } /** * Commodity method that 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 null if * there should not be a default and the user must * explicitly provide a value. * * @throws CLIException * If the line of input could not be retrieved for some * reason. * @return The string value read from the user. */ public String readInput(Message prompt, String defaultValue) throws CLIException { while (true) { if (defaultValue != null) { prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), defaultValue); } String response = readLineOfInput(prompt); if ("".equals(response)) { if (defaultValue == null) { println(INFO_ERROR_EMPTY_RESPONSE.get()); } else { return defaultValue; } } else { return response; } } } /** * Commodity method that 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). * If an error occurs a message will be logged to the provided logger. * * @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 null if * there should not be a default and the user must * explicitly provide a value. * * @param logger the Logger to be used to log the error message. * @return The string value read from the user. */ public String readInput(Message prompt, String defaultValue, Logger logger) { String s = defaultValue; try { s = readInput(prompt, defaultValue); } catch (CLIException ce) { logger.log(Level.WARNING, "Error reading input: "+ce, ce); } return s; } /** * Interactively retrieves a password from the console. * * @param prompt * The password prompt. * @return Returns the password. * @throws CLIException * If the password could not be retrieved for some reason. */ public final String readPassword(Message prompt) throws CLIException { err.print(wrapText(prompt + " ", MAX_LINE_WIDTH)); char[] pwChars; try { pwChars = PasswordReader.readPassword(); } catch (Exception e) { throw CLIException.adaptInputException(e); } return new String(pwChars); } /** * Commodity method that interactively retrieves a password from the * console. If there is an error an error message is logged to the provided * Logger and null is returned. * * @param prompt * The password prompt. * @param logger the Logger to be used to log the error message. * @return Returns the password. */ protected final String readPassword(Message prompt, Logger logger) { String pwd = null; try { pwd = readPassword(prompt); } catch (CLIException ce) { logger.log(Level.WARNING, "Error reading input: "+ce, ce); } return pwd; } /** * Interactively retrieves a port value from the console. * * @param prompt * The port prompt. * @param defaultValue * The port default value. * @return Returns the port. * @throws CLIException * If the port could not be retrieved for some reason. */ public final int readPort(Message prompt, final int defaultValue) throws CLIException { ValidationCallback callback = new ValidationCallback() { public Integer validate(ConsoleApplication app, String input) throws CLIException { String ninput = input.trim(); if (ninput.length() == 0) { return defaultValue; } else { try { int i = Integer.parseInt(ninput); if (i < 1 || i > 65535) { throw new NumberFormatException(); } return i; } catch (NumberFormatException e) { // Try again... app.println(); app.println(ERR_LDAP_CONN_BAD_PORT_NUMBER.get(ninput)); app.println(); return null; } } } }; if (defaultValue != -1) { prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), String.valueOf(defaultValue)); } return readValidatedInput(prompt, callback, CONFIRMATION_MAX_TRIES); } /** * Returns a message object for the given NamingException. * @param ne the NamingException. * @param hostPort the hostPort representation of the server we were * contacting when the NamingException occurred. * @return a message object for the given NamingException. */ protected Message getMessageForException(NamingException ne, String hostPort) { return Utils.getMessageForException(ne, hostPort); } /** * Commodity method used to repeatidly ask the user to provide a port value. * @param prompt the prompt message. * @param defaultValue the default value of the port to be proposed to the * user. * @param logger the logger where the errors will be written. * @return the port value provided by the user. */ protected int askPort(Message prompt, int defaultValue, Logger logger) { int port = -1; while (port == -1) { try { port = readPort(prompt, defaultValue); } catch (CLIException ce) { port = -1; logger.log(Level.WARNING, "Error reading input: "+ce, ce); } } return port; } /** * Interactively prompts for user input and continues until valid * input is provided. * * @param * The type of decoded user input. * @param prompt * The interactive prompt which should be displayed on each * input attempt. * @param validator * An input validator responsible for validating and * decoding the user's response. * @return Returns the decoded user's response. * @throws CLIException * If an unexpected error occurred which prevented * validation. */ public final T readValidatedInput(Message prompt, ValidationCallback validator) throws CLIException { while (true) { String response = readLineOfInput(prompt); T value = validator.validate(this, response); if (value != null) { return value; } } } /** * Interactively prompts for user input and continues until valid * input is provided. * * @param * The type of decoded user input. * @param prompt * The interactive prompt which should be displayed on each * input attempt. * @param validator * An input validator responsible for validating and * decoding the user's response. * @param maxTries * The maximum number of tries that we can make. * @return Returns the decoded user's response. * @throws CLIException * If an unexpected error occurred which prevented * validation or if the maximum number of tries was reached. */ public final T readValidatedInput(Message prompt, ValidationCallback validator, int maxTries) throws CLIException { int nTries = 0; while (nTries < maxTries) { String response = readLineOfInput(prompt); T value = validator.validate(this, response); if (value != null) { return value; } nTries++; } throw new CLIException(ERR_TRIES_LIMIT_REACHED.get(maxTries)); } /** * Commodity method that interactively confirms whether a user wishes to * perform an action. If the application is non-interactive, then the provided * default is returned automatically. If there is an error an error message * is logged to the provided Logger and the defaul value is returned. * * @param prompt * The prompt describing the action. * @param defaultValue * The default value for the confirmation message. This * will be returned if the application is non-interactive * or if the user just presses return. * @param logger the Logger to be used to log the error message. * @return Returns true if the user wishes the action * to be performed, or false if they refused. * @throws CLIException if the user did not provide valid answer after * a certain number of tries * (ConsoleApplication.CONFIRMATION_MAX_TRIES) */ protected final boolean askConfirmation(Message prompt, boolean defaultValue, Logger logger) throws CLIException { boolean v = defaultValue; boolean done = false; int nTries = 0; while (!done && (nTries < CONFIRMATION_MAX_TRIES)) { nTries++; try { v = confirmAction(prompt, defaultValue); done = true; } catch (CLIException ce) { if (ce.getMessageObject().getDescriptor().equals( ERR_CONFIRMATION_TRIES_LIMIT_REACHED) || ce.getMessageObject().getDescriptor().equals( ERR_TRIES_LIMIT_REACHED)) { throw ce; } logger.log(Level.WARNING, "Error reading input: "+ce, ce); // Try again... println(); } } if (!done) { // This means we reached the maximum number of tries throw new CLIException(ERR_CONFIRMATION_TRIES_LIMIT_REACHED.get( CONFIRMATION_MAX_TRIES)); } return v; } /** * Returns an InitialLdapContext using the provided parameters. We try * to guarantee that the connection is able to read the configuration. * @param host the host name. * @param port the port to connect. * @param useSSL whether to use SSL or not. * @param useStartTLS whether to use StartTLS or not. * @param bindDn the bind dn to be used. * @param pwd the password. * @param connectTimeout the timeout in milliseconds to connect to the server. * @param trustManager the trust manager. * @return an InitialLdapContext connected. * @throws NamingException if there was an error establishing the connection. */ protected InitialLdapContext createAdministrativeContext(String host, int port, boolean useSSL, boolean useStartTLS, String bindDn, String pwd, int connectTimeout, ApplicationTrustManager trustManager) throws NamingException { InitialLdapContext ctx; String ldapUrl = ConnectionUtils.getLDAPUrl(host, port, useSSL); if (useSSL) { ctx = Utils.createLdapsContext(ldapUrl, bindDn, pwd, connectTimeout, null, trustManager); } else if (useStartTLS) { ctx = Utils.createStartTLSContext(ldapUrl, bindDn, pwd, connectTimeout, null, trustManager, null); } else { ctx = Utils.createLdapContext(ldapUrl, bindDn, pwd, connectTimeout, null); } if (!ConnectionUtils.connectedAsAdministrativeUser(ctx)) { throw new NoPermissionException( ERR_NOT_ADMINISTRATIVE_USER.get().toString()); } return ctx; } /** * Creates an Initial LDAP Context interacting with the user if the * application is interactive. * @param ci the LDAPConnectionConsoleInteraction object that is assumed * to have been already run. * @return the initial LDAP context or null if the user did * not accept to trust the certificates. * @throws ClientException if there was an error establishing the connection. */ protected InitialLdapContext createInitialLdapContextInteracting( LDAPConnectionConsoleInteraction ci) throws ClientException { return createInitialLdapContextInteracting(ci, isInteractive() && ci.isTrustStoreInMemory()); } /** * Creates an Initial LDAP Context interacting with the user if the * application is interactive. * @param ci the LDAPConnectionConsoleInteraction object that is assumed * to have been already run. * @param promptForCertificate whether we should prompt for the certificate * or not. * @return the initial LDAP context or null if the user did * not accept to trust the certificates. * @throws ClientException if there was an error establishing the connection. */ protected InitialLdapContext createInitialLdapContextInteracting( LDAPConnectionConsoleInteraction ci, boolean promptForCertificate) throws ClientException { // Interact with the user though the console to get // LDAP connection information String hostName = ConnectionUtils.getHostNameForLdapUrl(ci.getHostName()); Integer portNumber = ci.getPortNumber(); String bindDN = ci.getBindDN(); String bindPassword = ci.getBindPassword(); TrustManager trustManager = ci.getTrustManager(); KeyManager keyManager = ci.getKeyManager(); InitialLdapContext ctx; if (ci.useSSL()) { String ldapsUrl = "ldaps://" + hostName + ":" + portNumber; while (true) { try { ctx = ConnectionUtils.createLdapsContext(ldapsUrl, bindDN, bindPassword, ci.getConnectTimeout(), null, trustManager, keyManager); ctx.reconnect(null); break; } catch (NamingException e) { if (promptForCertificate) { OpendsCertificateException oce = getCertificateRootException(e); if (oce != null) { String authType = null; if (trustManager instanceof ApplicationTrustManager) { ApplicationTrustManager appTrustManager = (ApplicationTrustManager)trustManager; authType = appTrustManager.getLastRefusedAuthType(); } if (ci.checkServerCertificate(oce.getChain(), authType, hostName)) { // If the certificate is trusted, update the trust manager. trustManager = ci.getTrustManager(); // Try to connect again. continue; } else { // Assume user canceled. return null; } } } if (e.getCause() != null) { if (!isInteractive() && !ci.isTrustAll()) { if (getCertificateRootException(e) != null || (e.getCause() instanceof SSLHandshakeException)) { Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT_NOT_TRUSTED.get( hostName, String.valueOf(portNumber)); throw new ClientException( LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); } } if (e.getCause() instanceof SSLException) { Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT_WRONG_PORT.get( hostName, String.valueOf(portNumber)); throw new ClientException( LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); } } String hostPort = ServerDescriptor.getServerRepresentation(hostName, portNumber); Message message = Utils.getMessageForException(e, hostPort); throw new ClientException( LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); } } } else if (ci.useStartTLS()) { String ldapUrl = "ldap://" + hostName + ":" + portNumber; while (true) { try { ctx = ConnectionUtils.createStartTLSContext(ldapUrl, bindDN, bindPassword, ConnectionUtils.getDefaultLDAPTimeout(), null, trustManager, keyManager, null); ctx.reconnect(null); break; } catch (NamingException e) { if (promptForCertificate) { OpendsCertificateException oce = getCertificateRootException(e); if (oce != null) { String authType = null; if (trustManager instanceof ApplicationTrustManager) { ApplicationTrustManager appTrustManager = (ApplicationTrustManager)trustManager; authType = appTrustManager.getLastRefusedAuthType(); } if (ci.checkServerCertificate(oce.getChain(), authType, hostName)) { // If the certificate is trusted, update the trust manager. trustManager = ci.getTrustManager(); // Try to connect again. continue ; } else { // Assume user cancelled. return null; } } else { Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( hostName, String.valueOf(portNumber)); throw new ClientException( LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); } } Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( hostName, String.valueOf(portNumber)); throw new ClientException( LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); } } } else { String ldapUrl = "ldap://" + hostName + ":" + portNumber; while (true) { try { ctx = ConnectionUtils.createLdapContext(ldapUrl, bindDN, bindPassword, ConnectionUtils.getDefaultLDAPTimeout(), null); ctx.reconnect(null); break; } catch (NamingException e) { Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( hostName, String.valueOf(portNumber)); throw new ClientException( LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); } } } return ctx; } /** * Returns the message to be displayed in the file with the equivalent * command-line with information about the current time. * @return the message to be displayed in the file with the equivalent * command-line with information about the current time. */ protected String getCurrentOperationDateMessage() { String date = formatDateTimeStringForEquivalentCommand(new Date()); return INFO_OPERATION_START_TIME_MESSAGE.get(date). toString(); } /** * Formats a Date to String representation in "dd/MMM/yyyy:HH:mm:ss Z". * * @param date to format; null if date is null * @return string representation of the date */ protected String formatDateTimeStringForEquivalentCommand(Date date) { String timeStr = null; if (date != null) { SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_LOCAL_TIME); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); timeStr = dateFormat.format(date); } return timeStr; } /** * Prompts the user to give the Global Administrator UID. * @param defaultValue the default value that will be proposed in the prompt * message. * @param logger the Logger to be used to log the error message. * @return the Global Administrator UID as provided by the user. */ protected String askForAdministratorUID(String defaultValue, Logger logger) { String s = defaultValue; try { s = readInput(INFO_ADMINISTRATOR_UID_PROMPT.get(), defaultValue); } catch (CLIException ce) { logger.log(Level.WARNING, "Error reading input: "+ce, ce); } return s; } /** * Prompts the user to give the Global Administrator password. * @param logger the Logger to be used to log the error message. * @return the Global Administrator password as provided by the user. */ protected String askForAdministratorPwd(Logger logger) { String pwd = readPassword(INFO_ADMINISTRATOR_PWD_PROMPT.get(), logger); return pwd; } /** * The default period time used to write points in the output. */ protected static final long DEFAULT_PERIOD_TIME = 3000; /** * Class used to add points periodically to the end of the output. * */ protected class PointAdder implements Runnable { private Thread t; private boolean stopPointAdder; private boolean pointAdderStopped; private long periodTime = DEFAULT_PERIOD_TIME; private boolean isError; private ProgressMessageFormatter formatter; /** * Default constructor. * Creates a PointAdder that writes to the standard output with the default * period time. */ public PointAdder() { this(DEFAULT_PERIOD_TIME, false, new PlainTextProgressMessageFormatter()); } /** * Default constructor. * @param periodTime the time between printing two points. * @param isError whether the points must be printed in error stream * or output stream. * @param formatter the text formatter. */ public PointAdder(long periodTime, boolean isError, ProgressMessageFormatter formatter) { this.periodTime = periodTime; this.isError = isError; this.formatter = formatter; } /** * Starts the PointAdder: points are added at the end of the logs * periodically. */ public void start() { MessageBuilder mb = new MessageBuilder(); mb.append(formatter.getSpace()); for (int i=0; i< 5; i++) { mb.append(formatter.getFormattedPoint()); } if (isError) { print(mb.toMessage()); } else { printProgress(mb.toMessage()); } t = new Thread(this); t.start(); } /** * Stops the PointAdder: points are no longer added at the end of the logs * periodically. */ public synchronized void stop() { stopPointAdder = true; while (!pointAdderStopped) { try { t.interrupt(); // To allow the thread to set the boolean. Thread.sleep(100); } catch (Throwable t) { } } } /** * {@inheritDoc} */ public void run() { while (!stopPointAdder) { try { Thread.sleep(periodTime); if (isError) { print(formatter.getFormattedPoint()); } else { printProgress(formatter.getFormattedPoint()); } } catch (Throwable t) { } } pointAdderStopped = true; } } private OpendsCertificateException getCertificateRootException(Throwable t) { OpendsCertificateException oce = null; while (t != null && oce == null) { t = t.getCause(); if (t instanceof OpendsCertificateException) { oce = (OpendsCertificateException)t; } } return oce; } }