001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2007-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.tools.status;
028
029import static com.forgerock.opendj.cli.ArgumentConstants.*;
030import static com.forgerock.opendj.cli.CliMessages.*;
031import static com.forgerock.opendj.cli.Utils.*;
032
033import static org.forgerock.util.Utils.*;
034import static org.opends.messages.AdminToolMessages.*;
035import static org.opends.messages.QuickSetupMessages.INFO_ERROR_READING_SERVER_CONFIGURATION;
036import static org.opends.messages.QuickSetupMessages.INFO_NOT_AVAILABLE_LABEL;
037
038import java.io.File;
039import java.io.InputStream;
040import java.io.OutputStream;
041import java.io.PrintStream;
042import java.net.URI;
043import java.security.GeneralSecurityException;
044import java.security.cert.CertificateException;
045import java.security.cert.X509Certificate;
046import java.util.HashSet;
047import java.util.Set;
048import java.util.TreeSet;
049import java.util.concurrent.TimeUnit;
050
051import javax.naming.AuthenticationException;
052import javax.naming.NamingException;
053import javax.naming.ldap.InitialLdapContext;
054import javax.net.ssl.KeyManager;
055import javax.net.ssl.SSLException;
056import javax.net.ssl.TrustManager;
057
058import org.forgerock.i18n.LocalizableMessage;
059import org.forgerock.i18n.LocalizableMessageBuilder;
060import org.forgerock.i18n.slf4j.LocalizedLogger;
061import org.forgerock.opendj.config.LDAPProfile;
062import org.forgerock.opendj.config.client.ManagementContext;
063import org.forgerock.opendj.config.client.ldap.LDAPManagementContext;
064import org.forgerock.opendj.config.server.ConfigException;
065import org.forgerock.opendj.ldap.AuthorizationException;
066import org.forgerock.opendj.ldap.Connection;
067import org.forgerock.opendj.ldap.LDAPConnectionFactory;
068import org.forgerock.opendj.ldap.LDAPOptions;
069import org.forgerock.opendj.ldap.LdapException;
070import org.forgerock.opendj.ldap.ResultCode;
071import org.forgerock.opendj.ldap.SSLContextBuilder;
072import org.forgerock.opendj.ldap.TrustManagers;
073import org.opends.admin.ads.util.ApplicationTrustManager;
074import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
075import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
076import org.opends.guitools.controlpanel.datamodel.BaseDNTableModel;
077import org.opends.guitools.controlpanel.datamodel.ConfigReadException;
078import org.opends.guitools.controlpanel.datamodel.ConnectionHandlerDescriptor;
079import org.opends.guitools.controlpanel.datamodel.ConnectionHandlerTableModel;
080import org.opends.guitools.controlpanel.datamodel.ConnectionProtocolPolicy;
081import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
082import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
083import org.opends.guitools.controlpanel.util.ControlPanelLog;
084import org.opends.guitools.controlpanel.util.Utilities;
085import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
086import org.opends.server.types.DN;
087import org.opends.server.types.InitializationException;
088import org.opends.server.types.NullOutputStream;
089import org.opends.server.types.OpenDsException;
090import org.opends.server.util.BuildVersion;
091import org.opends.server.util.StaticUtils;
092import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
093
094import com.forgerock.opendj.cli.ArgumentException;
095import com.forgerock.opendj.cli.CliConstants;
096import com.forgerock.opendj.cli.ClientException;
097import com.forgerock.opendj.cli.ConsoleApplication;
098import com.forgerock.opendj.cli.ReturnCode;
099import com.forgerock.opendj.cli.TableBuilder;
100import com.forgerock.opendj.cli.TextTablePrinter;
101
102/**
103 * The class used to provide some CLI interface to display status.
104 * This class basically is in charge of parsing the data provided by the
105 * user in the command line.
106 */
107public class StatusCli extends ConsoleApplication
108{
109  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
110
111  private boolean displayMustAuthenticateLegend;
112  private boolean displayMustStartLegend;
113
114  /** Prefix for log files. */
115  public static final String LOG_FILE_PREFIX = "opendj-status-";
116  /** Suffix for log files. */
117  public static final String LOG_FILE_SUFFIX = ".log";
118
119  private ApplicationTrustManager interactiveTrustManager;
120  private boolean useInteractiveTrustManager;
121
122  /** The argument parser. */
123  private StatusCliArgumentParser argParser;
124
125  /**
126   * Constructor for the status cli object.
127   *
128   * @param out
129   *          The print stream to use for standard output.
130   * @param err
131   *          The print stream to use for standard error.
132   * @param in
133   *          The input stream to use for standard input.
134   */
135  public StatusCli(PrintStream out, PrintStream err, InputStream in)
136  {
137    super(out, err);
138  }
139
140  /**
141   * The main method for the status CLI tool.
142   *
143   * @param args The command-line arguments provided to this program.
144   */
145
146  public static void main(String[] args)
147  {
148    int retCode = mainCLI(args, true, System.out, System.err, System.in);
149    if(retCode != 0)
150    {
151      System.exit(retCode);
152    }
153  }
154
155  /**
156   * Parses the provided command-line arguments and uses that information to
157   * run the status tool.
158   *
159   * @param args the command-line arguments provided to this program.
160   *
161   * @return The return code.
162   */
163
164  public static int mainCLI(String[] args)
165  {
166    return mainCLI(args, true, System.out, System.err, System.in);
167  }
168
169  /**
170   * Parses the provided command-line arguments and uses that information to run
171   * the status tool.
172   *
173   * @param args
174   *          The command-line arguments provided to this program.
175   * @param initializeServer
176   *          Indicates whether to initialize the server.
177   * @param outStream
178   *          The output stream to use for standard output, or {@code null}
179   *          if standard output is not needed.
180   * @param errStream
181   *          The output stream to use for standard error, or {@code null}
182   *          if standard error is not needed.
183   * @param inStream
184   *          The input stream to use for standard input.
185   * @return The return code.
186   */
187  public static int mainCLI(String[] args, boolean initializeServer,
188      OutputStream outStream, OutputStream errStream, InputStream inStream)
189  {
190    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
191    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
192
193    try {
194      ControlPanelLog.initLogFileHandler(
195              File.createTempFile(LOG_FILE_PREFIX, LOG_FILE_SUFFIX));
196      ControlPanelLog.initPackage("org.opends.server.tools.status");
197    } catch (Throwable t) {
198      System.err.println("Unable to initialize log");
199      t.printStackTrace();
200    }
201
202    final StatusCli statusCli = new StatusCli(out, err, inStream);
203    return statusCli.execute(args);
204  }
205
206  /**
207   * Parses the provided command-line arguments and uses that information to run
208   * the status CLI.
209   *
210   * @param args
211   *          The command-line arguments provided to this program.
212   * @return The return code of the process.
213   */
214  public int execute(String[] args) {
215    argParser = new StatusCliArgumentParser(StatusCli.class.getName());
216    try {
217      argParser.initializeGlobalArguments(getOutputStream());
218    } catch (ArgumentException ae) {
219      println(ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
220      return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
221    }
222
223    try
224    {
225      argParser.getSecureArgsList().initArgumentsWithConfiguration();
226    }
227    catch (ConfigException ce)
228    {
229      // Ignore.
230    }
231
232    // Validate user provided data
233    try {
234      argParser.parseArguments(args);
235    } catch (ArgumentException ae) {
236      argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
237      return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
238    }
239
240    //  If we should just display usage or version information,
241    // then print it and exit.
242    if (argParser.usageOrVersionDisplayed()) {
243      return ReturnCode.SUCCESS.get();
244    }
245
246    // Checks the version - if upgrade required, the tool is unusable
247    try
248    {
249      BuildVersion.checkVersionMismatch();
250    }
251    catch (InitializationException e)
252    {
253      println(e.getMessageObject());
254      return 1;
255    }
256
257    int v = argParser.validateGlobalOptions(getErrorStream());
258    if (v != ReturnCode.SUCCESS.get()) {
259      println(LocalizableMessage.raw(argParser.getUsage()));
260      return v;
261    }
262
263    final ControlPanelInfo controlInfo = ControlPanelInfo.getInstance();
264    controlInfo.setTrustManager(getTrustManager());
265    controlInfo.setConnectTimeout(argParser.getConnectTimeout());
266    controlInfo.regenerateDescriptor();
267
268    if (controlInfo.getServerDescriptor().getStatus() == ServerDescriptor.ServerStatus.STARTED)
269    {
270      String bindDn = null;
271      String bindPwd = null;
272
273      ManagementContext mContext = null;
274
275      // This is done because we do not need to ask the user about these
276      // parameters. We force their presence in the
277      // LDAPConnectionConsoleInteraction, this done, it will not prompt
278      // the user for them.
279      final SecureConnectionCliArgs secureArgsList = argParser.getSecureArgsList();
280      controlInfo.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
281      int port = CliConstants.DEFAULT_ADMINISTRATION_CONNECTOR_PORT;
282      controlInfo.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
283      String ldapUrl = controlInfo.getURLToConnect();
284      try
285      {
286        final URI uri = new URI(ldapUrl);
287        port = uri.getPort();
288      }
289      catch (Throwable t)
290      {
291        logger.error(LocalizableMessage.raw("Error parsing url: " + ldapUrl));
292      }
293      secureArgsList.hostNameArg.setPresent(true);
294      secureArgsList.portArg.setPresent(true);
295      secureArgsList.hostNameArg.addValue(secureArgsList.hostNameArg.getDefaultValue());
296      secureArgsList.portArg.addValue(Integer.toString(port));
297      // We already know if SSL or StartTLS can be used.  If we cannot
298      // use them we will not propose them in the connection parameters
299      // and if none of them can be used we will just not ask for the
300      // protocol to be used.
301      final LDAPConnectionConsoleInteraction ci =
302          new LDAPConnectionConsoleInteraction(this, argParser.getSecureArgsList());
303      try
304      {
305        ci.run(false);
306      }
307      catch (ArgumentException e)
308      {
309        argParser.displayMessageAndUsageReference(getErrStream(), e.getMessageObject());
310        return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
311      }
312      try
313      {
314        if (argParser.isInteractive())
315        {
316          bindDn = ci.getBindDN();
317          bindPwd = ci.getBindPassword();
318        }
319        else
320        {
321          bindDn = argParser.getBindDN();
322          bindPwd = argParser.getBindPassword();
323        }
324        if (bindPwd != null && !bindPwd.isEmpty())
325        {
326          mContext = getManagementContextFromConnection(ci);
327          interactiveTrustManager = ci.getTrustManager();
328          controlInfo.setTrustManager(interactiveTrustManager);
329          useInteractiveTrustManager = true;
330        }
331      } catch (ClientException e) {
332        println(e.getMessageObject());
333        return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
334      } finally {
335        closeSilently(mContext);
336      }
337
338      if (mContext != null)
339      {
340        InitialLdapContext ctx = null;
341        try {
342          ctx = Utilities.getAdminDirContext(controlInfo, bindDn, bindPwd);
343          controlInfo.setDirContext(ctx);
344          controlInfo.regenerateDescriptor();
345          writeStatus(controlInfo);
346
347          if (!controlInfo.getServerDescriptor().getExceptions().isEmpty()) {
348            return ReturnCode.ERROR_INITIALIZING_SERVER.get();
349          }
350        } catch (NamingException ne) {
351          // This should not happen but this is useful information to
352          // diagnose the error.
353          println();
354          println(INFO_ERROR_READING_SERVER_CONFIGURATION.get(ne));
355          return ReturnCode.ERROR_INITIALIZING_SERVER.get();
356        } catch (ConfigReadException cre) {
357          // This should not happen but this is useful information to
358          // diagnose the error.
359          println();
360          println(cre.getMessageObject());
361          return ReturnCode.ERROR_INITIALIZING_SERVER.get();
362        } finally {
363          StaticUtils.close(ctx);
364        }
365      } else {
366        // The user did not provide authentication: just display the
367        // information we can get reading the config file.
368        writeStatus(controlInfo);
369        return ReturnCode.ERROR_USER_CANCELLED.get();
370      }
371    } else {
372      writeStatus(controlInfo);
373    }
374
375    return ReturnCode.SUCCESS.get();
376  }
377
378  private void writeStatus(ControlPanelInfo controlInfo)
379  {
380    if (controlInfo.getServerDescriptor() == null)
381    {
382      controlInfo.regenerateDescriptor();
383    }
384    writeStatus(controlInfo.getServerDescriptor());
385    int period = argParser.getRefreshPeriod();
386    boolean first = true;
387    while (period > 0)
388    {
389      long timeToSleep = period * 1000;
390      if (!first)
391      {
392        long t1 = System.currentTimeMillis();
393        controlInfo.regenerateDescriptor();
394        long t2 = System.currentTimeMillis();
395
396        timeToSleep = timeToSleep - t2 + t1;
397      }
398
399      if (timeToSleep > 0)
400      {
401        StaticUtils.sleep(timeToSleep);
402      }
403      println();
404      println(LocalizableMessage.raw("          ---------------------"));
405      println();
406      writeStatus(controlInfo.getServerDescriptor());
407      first = false;
408    }
409  }
410
411  private void writeStatus(ServerDescriptor desc)
412  {
413    LocalizableMessage[] labels =
414      {
415        INFO_SERVER_STATUS_LABEL.get(),
416        INFO_CONNECTIONS_LABEL.get(),
417        INFO_HOSTNAME_LABEL.get(),
418        INFO_ADMINISTRATIVE_USERS_LABEL.get(),
419        INFO_INSTALLATION_PATH_LABEL.get(),
420        INFO_OPENDS_VERSION_LABEL.get(),
421        INFO_JAVA_VERSION_LABEL.get(),
422        INFO_CTRL_PANEL_ADMIN_CONNECTOR_LABEL.get()
423      };
424    int labelWidth = 0;
425    LocalizableMessage title = INFO_SERVER_STATUS_TITLE.get();
426    if (!isScriptFriendly())
427    {
428      for (LocalizableMessage label : labels)
429      {
430        labelWidth = Math.max(labelWidth, label.length());
431      }
432      println();
433      println(centerTitle(title));
434    }
435    writeStatusContents(desc, labelWidth);
436    writeCurrentConnectionContents(desc, labelWidth);
437    if (!isScriptFriendly())
438    {
439      println();
440    }
441
442    title = INFO_SERVER_DETAILS_TITLE.get();
443    if (!isScriptFriendly())
444    {
445      println(centerTitle(title));
446    }
447    writeHostnameContents(desc, labelWidth);
448    writeAdministrativeUserContents(desc, labelWidth);
449    writeInstallPathContents(desc, labelWidth);
450    boolean sameInstallAndInstance = desc.sameInstallAndInstance();
451    if (!sameInstallAndInstance)
452    {
453      writeInstancePathContents(desc, labelWidth);
454    }
455    writeVersionContents(desc, labelWidth);
456    writeJavaVersionContents(desc, labelWidth);
457    writeAdminConnectorContents(desc, labelWidth);
458    if (!isScriptFriendly())
459    {
460      println();
461    }
462
463    writeListenerContents(desc);
464    if (!isScriptFriendly())
465    {
466      println();
467    }
468
469    writeBaseDNContents(desc);
470
471    writeErrorContents(desc);
472
473    if (!isScriptFriendly())
474    {
475      if (displayMustStartLegend)
476      {
477        println();
478        println(INFO_NOT_AVAILABLE_SERVER_DOWN_CLI_LEGEND.get());
479      }
480      else if (displayMustAuthenticateLegend)
481      {
482        println();
483        println(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LEGEND.get());
484      }
485    }
486    println();
487  }
488
489  /**
490   * Writes the status contents displaying with what is specified in the
491   * provided ServerDescriptor object.
492   *
493   * @param desc
494   *          The ServerStatusDescriptor object.
495   */
496  private void writeStatusContents(ServerDescriptor desc, int maxLabelWidth)
497  {
498    writeLabelValue(INFO_SERVER_STATUS_LABEL.get(), getStatus(desc).toString(), maxLabelWidth);
499  }
500
501  private LocalizableMessage getStatus(ServerDescriptor desc)
502  {
503    switch (desc.getStatus())
504    {
505    case STARTED:
506      return INFO_SERVER_STARTED_LABEL.get();
507    case STOPPED:
508      return INFO_SERVER_STOPPED_LABEL.get();
509    case STARTING:
510      return INFO_SERVER_STARTING_LABEL.get();
511    case STOPPING:
512      return INFO_SERVER_STOPPING_LABEL.get();
513    case NOT_CONNECTED_TO_REMOTE:
514      return INFO_SERVER_NOT_CONNECTED_TO_REMOTE_STATUS_LABEL.get();
515    case UNKNOWN:
516      return INFO_SERVER_UNKNOWN_STATUS_LABEL.get();
517    default:
518      throw new IllegalStateException("Unknown status: "+desc.getStatus());
519    }
520  }
521
522  /**
523   * Writes the current connection contents displaying with what is specified in
524   * the provided ServerDescriptor object.
525   *
526   * @param desc
527   *          The ServerDescriptor object.
528   */
529  private void writeCurrentConnectionContents(ServerDescriptor desc, int maxLabelWidth)
530  {
531    writeLabelValue(INFO_CONNECTIONS_LABEL.get(), getNbConnection(desc), maxLabelWidth);
532  }
533
534  private String getNbConnection(ServerDescriptor desc)
535  {
536    if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
537    {
538      final int nConn = desc.getOpenConnections();
539      if (nConn >= 0)
540      {
541        return String.valueOf(nConn);
542      }
543      else if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
544      {
545        return getNotAvailableBecauseAuthenticationIsRequiredText();
546      }
547      else
548      {
549        return getNotAvailableText();
550      }
551    }
552    return getNotAvailableBecauseServerIsDownText();
553  }
554
555  /**
556   * Writes the host name contents.
557   *
558   * @param desc
559   *          The ServerDescriptor object.
560   * @param maxLabelWidth
561   *          The maximum label width of the left label.
562   */
563  private void writeHostnameContents(ServerDescriptor desc, int maxLabelWidth)
564  {
565    writeLabelValue(INFO_HOSTNAME_LABEL.get(), desc.getHostname(), maxLabelWidth);
566  }
567
568  /**
569   * Writes the administrative user contents displaying with what is specified
570   * in the provided ServerStatusDescriptor object.
571   *
572   * @param desc
573   *          The ServerStatusDescriptor object.
574   * @param maxLabelWidth
575   *          The maximum label width of the left label.
576   */
577  private void writeAdministrativeUserContents(ServerDescriptor desc, int maxLabelWidth)
578  {
579    Set<DN> administrators = desc.getAdministrativeUsers();
580    if (!administrators.isEmpty())
581    {
582      TreeSet<DN> ordered = new TreeSet<>(administrators);
583      for (DN dn : ordered)
584      {
585        writeLabelValue(INFO_ADMINISTRATIVE_USERS_LABEL.get(), dn.toString(), maxLabelWidth);
586      }
587    }
588    else
589    {
590      writeLabelValue(INFO_ADMINISTRATIVE_USERS_LABEL.get(), getErrorText(desc), maxLabelWidth);
591    }
592  }
593
594  private String getErrorText(ServerDescriptor desc)
595  {
596    if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED
597        && (!desc.isAuthenticated() || !desc.getExceptions().isEmpty()))
598    {
599      return getNotAvailableBecauseAuthenticationIsRequiredText();
600    }
601    return getNotAvailableText();
602  }
603
604  /**
605   * Writes the install path contents displaying with what is specified in the
606   * provided ServerDescriptor object.
607   *
608   * @param desc
609   *          The ServerDescriptor object.
610   * @param maxLabelWidth
611   *          The maximum label width of the left label.
612   */
613  private void writeInstallPathContents(ServerDescriptor desc, int maxLabelWidth)
614  {
615    writeLabelValue(INFO_INSTALLATION_PATH_LABEL.get(), desc.getInstallPath(), maxLabelWidth);
616  }
617
618  /**
619   * Writes the instance path contents displaying with what is specified in the
620   * provided ServerDescriptor object.
621   *
622   * @param desc
623   *          The ServerDescriptor object.
624   * @param maxLabelWidth
625   *          The maximum label width of the left label.
626   */
627  private void writeInstancePathContents(ServerDescriptor desc, int maxLabelWidth)
628  {
629    writeLabelValue(INFO_CTRL_PANEL_INSTANCE_PATH_LABEL.get(), desc.getInstancePath(), maxLabelWidth);
630  }
631
632  /**
633   * Updates the server version contents displaying with what is specified in
634   * the provided ServerDescriptor object. This method must be called from the
635   * event thread.
636   *
637   * @param desc
638   *          The ServerDescriptor object.
639   */
640  private void writeVersionContents(ServerDescriptor desc, int maxLabelWidth)
641  {
642    writeLabelValue(INFO_OPENDS_VERSION_LABEL.get(), desc.getOpenDSVersion(), maxLabelWidth);
643  }
644
645  /**
646   * Updates the java version contents displaying with what is specified in the
647   * provided ServerDescriptor object. This method must be called from the event
648   * thread.
649   *
650   * @param desc
651   *          The ServerDescriptor object.
652   * @param maxLabelWidth
653   *          The maximum label width of the left label.
654   */
655  private void writeJavaVersionContents(ServerDescriptor desc, int maxLabelWidth)
656  {
657    writeLabelValue(INFO_JAVA_VERSION_LABEL.get(), getJavaVersion(desc), maxLabelWidth);
658  }
659
660  private String getJavaVersion(ServerDescriptor desc)
661  {
662    if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
663    {
664      if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
665      {
666        return getNotAvailableBecauseAuthenticationIsRequiredText();
667      }
668      return desc.getJavaVersion();
669    }
670    return getNotAvailableBecauseServerIsDownText();
671  }
672
673  /**
674   * Updates the admin connector contents displaying with what is specified in
675   * the provided ServerDescriptor object. This method must be called from the
676   * event thread.
677   *
678   * @param desc
679   *          The ServerDescriptor object.
680   * @param maxLabelWidth
681   *          The maximum label width of the left label.
682   */
683  private void writeAdminConnectorContents(ServerDescriptor desc, int maxLabelWidth)
684  {
685    ConnectionHandlerDescriptor adminConnector = desc.getAdminConnector();
686    LocalizableMessage text = adminConnector != null
687        ? INFO_CTRL_PANEL_ADMIN_CONNECTOR_DESCRIPTION.get(adminConnector.getPort())
688        : INFO_NOT_AVAILABLE_SHORT_LABEL.get();
689    writeLabelValue(INFO_CTRL_PANEL_ADMIN_CONNECTOR_LABEL.get(), text.toString(), maxLabelWidth);
690  }
691
692  /**
693   * Writes the listeners contents displaying with what is specified in the
694   * provided ServerDescriptor object.
695   *
696   * @param desc
697   *          The ServerDescriptor object.
698   */
699  private void writeListenerContents(ServerDescriptor desc)
700  {
701    if (!isScriptFriendly())
702    {
703      LocalizableMessage title = INFO_LISTENERS_TITLE.get();
704      println(centerTitle(title));
705    }
706
707    Set<ConnectionHandlerDescriptor> allHandlers = desc.getConnectionHandlers();
708    if (allHandlers.isEmpty())
709    {
710      if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
711      {
712        if (!desc.isAuthenticated())
713        {
714          println(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LABEL.get());
715        }
716        else
717        {
718          println(INFO_NO_LISTENERS_FOUND.get());
719        }
720      }
721      else
722      {
723        println(INFO_NO_LISTENERS_FOUND.get());
724      }
725    }
726    else
727    {
728      ConnectionHandlerTableModel connHandlersTableModel =
729        new ConnectionHandlerTableModel(false);
730      connHandlersTableModel.setData(allHandlers);
731      writeConnectionHandlersTableModel(connHandlersTableModel, desc);
732    }
733  }
734
735  /**
736   * Writes the base DN contents displaying with what is specified in the
737   * provided ServerDescriptor object.
738   *
739   * @param desc
740   *          The ServerDescriptor object.
741   */
742  private void writeBaseDNContents(ServerDescriptor desc)
743  {
744    LocalizableMessage title = INFO_DATABASES_TITLE.get();
745    if (!isScriptFriendly())
746    {
747      println(centerTitle(title));
748    }
749
750    Set<BaseDNDescriptor> replicas = new HashSet<>();
751    Set<BackendDescriptor> bs = desc.getBackends();
752    for (BackendDescriptor backend: bs)
753    {
754      if (!backend.isConfigBackend())
755      {
756        replicas.addAll(backend.getBaseDns());
757      }
758    }
759    if (replicas.isEmpty())
760    {
761      if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
762      {
763        if (!desc.isAuthenticated())
764        {
765          println(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LABEL.get());
766        }
767        else
768        {
769          println(INFO_NO_DBS_FOUND.get());
770        }
771      }
772      else
773      {
774        println(INFO_NO_DBS_FOUND.get());
775      }
776    }
777    else
778    {
779      BaseDNTableModel baseDNTableModel = new BaseDNTableModel(true, false);
780      baseDNTableModel.setData(replicas, desc.getStatus(), desc.isAuthenticated());
781
782      writeBaseDNTableModel(baseDNTableModel, desc);
783    }
784  }
785
786  /**
787   * Writes the error label contents displaying with what is specified in the
788   * provided ServerDescriptor object.
789   *
790   * @param desc
791   *          The ServerDescriptor object.
792   */
793  private void writeErrorContents(ServerDescriptor desc)
794  {
795    for (OpenDsException ex : desc.getExceptions())
796    {
797      LocalizableMessage errorMsg = ex.getMessageObject();
798      if (errorMsg != null)
799      {
800        println();
801        println(errorMsg);
802      }
803    }
804  }
805
806  /**
807   * Returns the not available text explaining that the data is not available
808   * because the server is down.
809   *
810   * @return the text.
811   */
812  private String getNotAvailableBecauseServerIsDownText()
813  {
814    displayMustStartLegend = true;
815    return INFO_NOT_AVAILABLE_SERVER_DOWN_CLI_LABEL.get().toString();
816  }
817
818  /**
819   * Returns the not available text explaining that the data is not available
820   * because authentication is required.
821   *
822   * @return the text.
823   */
824  private String getNotAvailableBecauseAuthenticationIsRequiredText()
825  {
826    displayMustAuthenticateLegend = true;
827    return INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LABEL.get().toString();
828  }
829
830  /**
831   * Returns the not available text explaining that the data is not available.
832   *
833   * @return the text.
834   */
835  private String getNotAvailableText()
836  {
837    return INFO_NOT_AVAILABLE_LABEL.get().toString();
838  }
839
840  /**
841   * Writes the contents of the provided table model simulating a table layout
842   * using text.
843   *
844   * @param tableModel
845   *          The connection handler table model.
846   * @param desc
847   *          The Server Status descriptor.
848   */
849  private void writeConnectionHandlersTableModel(
850      ConnectionHandlerTableModel tableModel,
851      ServerDescriptor desc)
852  {
853    if (isScriptFriendly())
854    {
855      for (int i=0; i<tableModel.getRowCount(); i++)
856      {
857        // Get the host name, it can be multivalued.
858        String[] hostNames = getHostNames(tableModel, i);
859        for (String hostName : hostNames)
860        {
861          println(LocalizableMessage.raw("-"));
862          for (int j=0; j<tableModel.getColumnCount(); j++)
863          {
864            LocalizableMessageBuilder line = new LocalizableMessageBuilder();
865            line.append(tableModel.getColumnName(j)).append(": ");
866            if (j == 0)
867            {
868              // It is the hostName
869              line.append(getCellValue(hostName, desc));
870            }
871            else
872            {
873              line.append(getCellValue(tableModel.getValueAt(i, j), desc));
874            }
875            println(line.toMessage());
876          }
877        }
878      }
879    }
880    else
881    {
882      TableBuilder table = new TableBuilder();
883      for (int i=0; i< tableModel.getColumnCount(); i++)
884      {
885        table.appendHeading(LocalizableMessage.raw(tableModel.getColumnName(i)));
886      }
887      for (int i=0; i<tableModel.getRowCount(); i++)
888      {
889        // Get the host name, it can be multivalued.
890        String[] hostNames = getHostNames(tableModel, i);
891        for (String hostName : hostNames)
892        {
893          table.startRow();
894          for (int j=0; j<tableModel.getColumnCount(); j++)
895          {
896            if (j == 0)
897            {
898              // It is the hostName
899              table.appendCell(getCellValue(hostName, desc));
900            }
901            else
902            {
903              table.appendCell(getCellValue(tableModel.getValueAt(i, j), desc));
904            }
905          }
906        }
907      }
908      TextTablePrinter printer = new TextTablePrinter(getOutputStream());
909      printer.setColumnSeparator(LIST_TABLE_SEPARATOR);
910      table.print(printer);
911    }
912  }
913
914  private String[] getHostNames(ConnectionHandlerTableModel tableModel, int row)
915  {
916   String v = (String)tableModel.getValueAt(row, 0);
917   String htmlTag = "<html>";
918   if (v.toLowerCase().startsWith(htmlTag))
919   {
920     v = v.substring(htmlTag.length());
921   }
922   return v.split("<br>");
923  }
924
925  private String getCellValue(Object v, ServerDescriptor desc)
926  {
927    if (v != null)
928    {
929      if (v instanceof String)
930      {
931        return (String) v;
932      }
933      else if (v instanceof Integer)
934      {
935        int nEntries = ((Integer)v).intValue();
936        if (nEntries >= 0)
937        {
938          return String.valueOf(nEntries);
939        }
940        else if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
941        {
942          return getNotAvailableBecauseAuthenticationIsRequiredText();
943        }
944        else
945        {
946          return getNotAvailableText();
947        }
948      }
949      else
950      {
951        throw new IllegalStateException("Unknown object type: "+v);
952      }
953    }
954    return getNotAvailableText();
955  }
956
957  /**
958   * Writes the contents of the provided base DN table model. Every base DN is
959   * written in a block containing pairs of labels and values.
960   *
961   * @param tableModel
962   *          The TableModel.
963   * @param desc
964   *          The Server Status descriptor.
965   */
966  private void writeBaseDNTableModel(BaseDNTableModel tableModel, ServerDescriptor desc)
967  {
968    boolean isRunning = desc.getStatus() == ServerDescriptor.ServerStatus.STARTED;
969
970    int labelWidth = 0;
971    int labelWidthWithoutReplicated = 0;
972    LocalizableMessage[] labels = new LocalizableMessage[tableModel.getColumnCount()];
973    for (int i=0; i<tableModel.getColumnCount(); i++)
974    {
975      LocalizableMessage header = LocalizableMessage.raw(tableModel.getColumnName(i));
976      labels[i] = new LocalizableMessageBuilder(header).append(":").toMessage();
977      labelWidth = Math.max(labelWidth, labels[i].length());
978      if (i != 4 && i != 5)
979      {
980        labelWidthWithoutReplicated =
981          Math.max(labelWidthWithoutReplicated, labels[i].length());
982      }
983    }
984
985    LocalizableMessage replicatedLabel = INFO_BASEDN_REPLICATED_LABEL.get();
986    for (int i=0; i<tableModel.getRowCount(); i++)
987    {
988      if (isScriptFriendly())
989      {
990        println(LocalizableMessage.raw("-"));
991      }
992      else if (i > 0)
993      {
994        println();
995      }
996      for (int j=0; j<tableModel.getColumnCount(); j++)
997      {
998        Object v = tableModel.getValueAt(i, j);
999        String value = getValue(desc, isRunning, v);
1000
1001        boolean doWrite = true;
1002        boolean isReplicated =
1003          replicatedLabel.toString().equals(
1004              String.valueOf(tableModel.getValueAt(i, 3)));
1005        if (j == 4 || j == 5)
1006        {
1007          // If the suffix is not replicated we do not have to display these lines
1008          doWrite = isReplicated;
1009        }
1010        if (doWrite)
1011        {
1012          writeLabelValue(labels[j], value,
1013              isReplicated?labelWidth:labelWidthWithoutReplicated);
1014        }
1015      }
1016    }
1017  }
1018
1019  private String getValue(ServerDescriptor desc, boolean isRunning, Object v)
1020  {
1021    if (v != null)
1022    {
1023      if (v == BaseDNTableModel.NOT_AVAILABLE_SERVER_DOWN)
1024      {
1025        return getNotAvailableBecauseServerIsDownText();
1026      }
1027      else if (v == BaseDNTableModel.NOT_AVAILABLE_AUTHENTICATION_REQUIRED)
1028      {
1029        return getNotAvailableBecauseAuthenticationIsRequiredText();
1030      }
1031      else if (v == BaseDNTableModel.NOT_AVAILABLE)
1032      {
1033        return getNotAvailableText(desc, isRunning);
1034      }
1035      else if (v instanceof String)
1036      {
1037        return (String) v;
1038      }
1039      else if (v instanceof LocalizableMessage)
1040      {
1041        return ((LocalizableMessage) v).toString();
1042      }
1043      else if (v instanceof Integer)
1044      {
1045        final int nEntries = ((Integer) v).intValue();
1046        if (nEntries >= 0)
1047        {
1048          return String.valueOf(nEntries);
1049        }
1050        return getNotAvailableText(desc, isRunning);
1051      }
1052      else
1053      {
1054        throw new IllegalStateException("Unknown object type: " + v);
1055      }
1056    }
1057    return "";
1058  }
1059
1060  private String getNotAvailableText(ServerDescriptor desc, boolean isRunning)
1061  {
1062    if (!isRunning)
1063    {
1064      return getNotAvailableBecauseServerIsDownText();
1065    }
1066    if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
1067    {
1068      return getNotAvailableBecauseAuthenticationIsRequiredText();
1069    }
1070    return getNotAvailableText();
1071  }
1072
1073  private void writeLabelValue(final LocalizableMessage label, final String value, final int maxLabelWidth)
1074  {
1075    final LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
1076    buf.append(label);
1077
1078    int extra = maxLabelWidth - label.length();
1079    for (int i = 0; i<extra; i++)
1080    {
1081      buf.append(" ");
1082    }
1083    buf.append(" ").append(value);
1084    println(buf.toMessage());
1085  }
1086
1087  private LocalizableMessage centerTitle(final LocalizableMessage text)
1088  {
1089    if (text.length() <= MAX_LINE_WIDTH - 8)
1090    {
1091      final LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
1092      int extra = Math.min(10,
1093          (MAX_LINE_WIDTH - 8 - text.length()) / 2);
1094      for (int i=0; i<extra; i++)
1095      {
1096        buf.append(" ");
1097      }
1098      buf.append("--- ").append(text).append(" ---");
1099      return buf.toMessage();
1100    }
1101    return text;
1102  }
1103
1104  /**
1105   * Returns the trust manager to be used by this application.
1106   *
1107   * @return the trust manager to be used by this application.
1108   */
1109  private ApplicationTrustManager getTrustManager()
1110  {
1111    if (useInteractiveTrustManager)
1112    {
1113      return interactiveTrustManager;
1114    }
1115    return argParser.getTrustManager();
1116  }
1117
1118  /** {@inheritDoc} */
1119  @Override
1120  public boolean isAdvancedMode()
1121  {
1122    return false;
1123  }
1124
1125  /** {@inheritDoc} */
1126  @Override
1127  public boolean isInteractive() {
1128    return argParser.isInteractive();
1129  }
1130
1131  /** {@inheritDoc} */
1132  @Override
1133  public boolean isMenuDrivenMode() {
1134    return true;
1135  }
1136
1137  /** {@inheritDoc} */
1138  @Override
1139  public boolean isQuiet() {
1140    return false;
1141  }
1142
1143  /** {@inheritDoc} */
1144  @Override
1145  public boolean isScriptFriendly() {
1146    return argParser.isScriptFriendly();
1147  }
1148
1149  /** {@inheritDoc} */
1150  @Override
1151  public boolean isVerbose() {
1152    return true;
1153  }
1154
1155  /** FIXME Common code with DSConfigand tools*. This method needs to be moved. */
1156  private ManagementContext getManagementContextFromConnection(
1157      final LDAPConnectionConsoleInteraction ci) throws ClientException
1158  {
1159    // Interact with the user though the console to get
1160    // LDAP connection information
1161    final String hostName = getHostNameForLdapUrl(ci.getHostName());
1162    final Integer portNumber = ci.getPortNumber();
1163    final String bindDN = ci.getBindDN();
1164    final String bindPassword = ci.getBindPassword();
1165    TrustManager trustManager = ci.getTrustManager();
1166    final KeyManager keyManager = ci.getKeyManager();
1167
1168    // This connection should always be secure. useSSL = true.
1169    Connection connection = null;
1170    final LDAPOptions options = new LDAPOptions();
1171    options.setConnectTimeout(ci.getConnectTimeout(), TimeUnit.MILLISECONDS);
1172    LDAPConnectionFactory factory = null;
1173    while (true)
1174    {
1175      try
1176      {
1177        final SSLContextBuilder sslBuilder = new SSLContextBuilder();
1178        sslBuilder.setTrustManager(trustManager == null ? TrustManagers.trustAll() : trustManager);
1179        sslBuilder.setKeyManager(keyManager);
1180        options.setUseStartTLS(ci.useStartTLS());
1181        options.setSSLContext(sslBuilder.getSSLContext());
1182
1183        factory = new LDAPConnectionFactory(hostName, portNumber, options);
1184        connection = factory.getConnection();
1185        connection.bind(bindDN, bindPassword.toCharArray());
1186        break;
1187      }
1188      catch (LdapException e)
1189      {
1190        if (ci.isTrustStoreInMemory() && e.getCause() instanceof SSLException
1191            && e.getCause().getCause() instanceof CertificateException)
1192        {
1193          String authType = null;
1194          if (trustManager instanceof ApplicationTrustManager)
1195          { // FIXME use PromptingTrustManager
1196            ApplicationTrustManager appTrustManager =
1197                (ApplicationTrustManager) trustManager;
1198            authType = appTrustManager.getLastRefusedAuthType();
1199            X509Certificate[] cert = appTrustManager.getLastRefusedChain();
1200
1201            if (ci.checkServerCertificate(cert, authType, hostName))
1202            {
1203              // If the certificate is trusted, update the trust manager.
1204              trustManager = ci.getTrustManager();
1205              // Try to connect again.
1206              continue;
1207            }
1208          }
1209        }
1210        if (e.getCause() instanceof SSLException)
1211        {
1212          LocalizableMessage message =
1213              ERR_FAILED_TO_CONNECT_NOT_TRUSTED.get(hostName, portNumber);
1214          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
1215              message);
1216        }
1217        if (e.getCause() instanceof AuthorizationException)
1218        {
1219          throw new ClientException(ReturnCode.AUTH_METHOD_NOT_SUPPORTED,
1220              ERR_SIMPLE_BIND_NOT_SUPPORTED.get());
1221        }
1222        else if (e.getCause() instanceof AuthenticationException
1223            || e.getResult().getResultCode() == ResultCode.INVALID_CREDENTIALS)
1224        {
1225          // Status Cli must not fail when un-authenticated.
1226          return null;
1227        }
1228        throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
1229            ERR_FAILED_TO_CONNECT.get(hostName, portNumber));
1230      }
1231      catch (GeneralSecurityException e)
1232      {
1233        LocalizableMessage message =
1234            ERR_FAILED_TO_CONNECT.get(hostName, portNumber);
1235        throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR, message);
1236      }
1237      finally
1238      {
1239        closeSilently(factory, connection);
1240      }
1241    }
1242
1243    return LDAPManagementContext.newManagementContext(connection, LDAPProfile.getInstance());
1244  }
1245}