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 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.quicksetup.util;
028
029import static org.forgerock.util.Utils.*;
030import static org.opends.admin.ads.util.ConnectionUtils.*;
031import static org.opends.messages.QuickSetupMessages.*;
032import static org.opends.quicksetup.Installation.*;
033import static org.opends.server.util.DynamicConstants.*;
034
035import static com.forgerock.opendj.cli.Utils.*;
036import static com.forgerock.opendj.util.OperatingSystem.*;
037
038import java.io.BufferedOutputStream;
039import java.io.BufferedReader;
040import java.io.ByteArrayOutputStream;
041import java.io.File;
042import java.io.FileOutputStream;
043import java.io.FileReader;
044import java.io.FileWriter;
045import java.io.IOException;
046import java.io.InputStream;
047import java.io.InputStreamReader;
048import java.io.PrintStream;
049import java.io.PrintWriter;
050import java.io.RandomAccessFile;
051import java.net.InetAddress;
052import java.text.SimpleDateFormat;
053import java.util.ArrayList;
054import java.util.Collection;
055import java.util.HashMap;
056import java.util.Hashtable;
057import java.util.LinkedHashSet;
058import java.util.List;
059import java.util.Locale;
060import java.util.Map;
061import java.util.Set;
062import java.util.TimeZone;
063
064import javax.naming.AuthenticationException;
065import javax.naming.CommunicationException;
066import javax.naming.NamingEnumeration;
067import javax.naming.NamingException;
068import javax.naming.NamingSecurityException;
069import javax.naming.NoPermissionException;
070import javax.naming.directory.SearchControls;
071import javax.naming.directory.SearchResult;
072import javax.naming.ldap.InitialLdapContext;
073import javax.naming.ldap.LdapName;
074import javax.net.ssl.HostnameVerifier;
075import javax.net.ssl.TrustManager;
076
077import org.forgerock.i18n.LocalizableMessage;
078import org.forgerock.i18n.LocalizableMessageBuilder;
079import org.forgerock.i18n.slf4j.LocalizedLogger;
080import org.forgerock.opendj.config.ManagedObjectDefinition;
081import org.forgerock.opendj.server.config.client.BackendCfgClient;
082import org.forgerock.opendj.server.config.server.BackendCfg;
083import org.opends.admin.ads.ADSContext;
084import org.opends.admin.ads.ReplicaDescriptor;
085import org.opends.admin.ads.ServerDescriptor;
086import org.opends.admin.ads.SuffixDescriptor;
087import org.opends.admin.ads.TopologyCacheException;
088import org.opends.admin.ads.util.ConnectionUtils;
089import org.opends.quicksetup.Constants;
090import org.opends.quicksetup.Installation;
091import org.opends.quicksetup.SecurityOptions;
092import org.opends.quicksetup.UserData;
093import org.opends.quicksetup.installer.AuthenticationData;
094import org.opends.quicksetup.installer.DataReplicationOptions;
095import org.opends.quicksetup.installer.NewSuffixOptions;
096import org.opends.quicksetup.installer.SuffixesToReplicateOptions;
097import org.opends.quicksetup.ui.UIFactory;
098import org.opends.server.tools.BackendTypeHelper;
099import org.opends.server.util.SetupUtils;
100import org.opends.server.util.StaticUtils;
101
102import com.forgerock.opendj.cli.ArgumentConstants;
103import com.forgerock.opendj.cli.ClientException;
104
105/**
106 * This class provides some static convenience methods of different nature.
107 */
108public class Utils
109{
110  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
111
112  private Utils() {}
113
114  private static final int BUFFER_SIZE = 1024;
115  private static final int MAX_LINE_WIDTH = 80;
116
117  /** Chars that require special treatment when passing them to command-line. */
118  private static final char[] CHARS_TO_ESCAPE =
119    { ' ', '\t', '\n', '|', ';', '<', '>', '(', ')', '$', '`', '\\', '"', '\'' };
120
121  /** The class name that contains the control panel customizations for products. */
122  private static final String CUSTOMIZATION_CLASS_NAME = "org.opends.server.util.ReleaseDefinition";
123
124  /**
125   * Returns <CODE>true</CODE> if the provided port is free and we can use it,
126   * <CODE>false</CODE> otherwise.
127   *
128   * @param port
129   *          the port we are analyzing.
130   * @return <CODE>true</CODE> if the provided port is free and we can use it,
131   *         <CODE>false</CODE> otherwise.
132   */
133  public static boolean canUseAsPort(int port)
134  {
135    return SetupUtils.canUseAsPort(port);
136  }
137
138  /**
139   * Returns <CODE>true</CODE> if the provided port is a privileged port,
140   * <CODE>false</CODE> otherwise.
141   *
142   * @param port
143   *          the port we are analyzing.
144   * @return <CODE>true</CODE> if the provided port is a privileged port,
145   *         <CODE>false</CODE> otherwise.
146   */
147  public static boolean isPrivilegedPort(int port)
148  {
149    return SetupUtils.isPrivilegedPort(port);
150  }
151
152  /**
153   * Tells whether the provided java installation supports a given option or
154   * not.
155   *
156   * @param javaHome
157   *          the java installation path.
158   * @param option
159   *          the java option that we want to check.
160   * @param installPath
161   *          the install path of the server.
162   * @return <CODE>true</CODE> if the provided java installation supports a
163   *         given option and <CODE>false</CODE> otherwise.
164   */
165  public static boolean supportsOption(String option, String javaHome, String installPath)
166  {
167    boolean supported = false;
168    logger.info(LocalizableMessage.raw("Checking if options " + option + " are supported with java home: " + javaHome));
169    try
170    {
171      List<String> args = new ArrayList<>();
172      String script;
173      String libPath = Utils.getPath(installPath, Installation.LIBRARIES_PATH_RELATIVE);
174      if (isWindows())
175      {
176        script = Utils.getScriptPath(Utils.getPath(libPath, Installation.SCRIPT_UTIL_FILE_WINDOWS));
177      }
178      else
179      {
180        script = Utils.getScriptPath(Utils.getPath(libPath, Installation.SCRIPT_UTIL_FILE_UNIX));
181      }
182      args.add(script);
183      ProcessBuilder pb = new ProcessBuilder(args);
184      Map<String, String> env = pb.environment();
185      env.put(SetupUtils.OPENDJ_JAVA_HOME, javaHome);
186      env.put("OPENDJ_JAVA_ARGS", option);
187      env.put("SCRIPT_UTIL_CMD", "set-full-environment-and-test-java");
188      env.remove("OPENDJ_JAVA_BIN");
189      // In windows by default the scripts ask the user to click on enter when
190      // they fail.  Set this environment variable to avoid it.
191      if (isWindows())
192      {
193        env.put("DO_NOT_PAUSE", "true");
194      }
195      final Process process = pb.start();
196      logger.info(LocalizableMessage.raw("launching " + args + " with env: " + env));
197      InputStream is = process.getInputStream();
198      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
199      String line;
200      boolean errorDetected = false;
201      while (null != (line = reader.readLine()))
202      {
203        logger.info(LocalizableMessage.raw("The output: " + line));
204        if (line.contains("ERROR:  The detected Java version"))
205        {
206          if (isWindows())
207          {
208            // If we are running windows, the process get blocked waiting for
209            // user input.  Just wait for a certain time to print the output
210            // in the logger and then kill the process.
211            Thread t = new Thread(new Runnable()
212            {
213              @Override
214              public void run()
215              {
216                try
217                {
218                  Thread.sleep(3000);
219                  // To see if the process is over, call the exitValue method.
220                  // If it is not over, a IllegalThreadStateException.
221                  process.exitValue();
222                }
223                catch (Throwable t)
224                {
225                  process.destroy();
226                }
227              }
228            });
229            t.start();
230          }
231          errorDetected = true;
232        }
233      }
234      process.waitFor();
235      int returnCode = process.exitValue();
236      logger.info(LocalizableMessage.raw("returnCode: " + returnCode));
237      supported = returnCode == 0 && !errorDetected;
238      logger.info(LocalizableMessage.raw("supported: " + supported));
239    }
240    catch (Throwable t)
241    {
242      logger.warn(LocalizableMessage.raw("Error testing option " + option + " on " + javaHome, t));
243    }
244    return supported;
245  }
246
247  /**
248   * Creates a new file attempting to create the parent directories if
249   * necessary.
250   *
251   * @param f
252   *          File to create
253   * @return boolean indicating whether the file was created; false otherwise
254   * @throws IOException
255   *           if something goes wrong
256   */
257  public static boolean createFile(File f) throws IOException
258  {
259    boolean success = false;
260    if (f != null)
261    {
262      File parent = f.getParentFile();
263      if (!parent.exists())
264      {
265        parent.mkdirs();
266      }
267      success = f.createNewFile();
268    }
269    return success;
270  }
271
272  /**
273   * Returns the absolute path for the given parentPath and relativePath.
274   *
275   * @param parentPath
276   *          the parent path.
277   * @param relativePath
278   *          the relative path.
279   * @return the absolute path for the given parentPath and relativePath.
280   */
281  public static String getPath(String parentPath, String relativePath)
282  {
283    return getPath(new File(new File(parentPath), relativePath));
284  }
285
286  /**
287   * Returns the String that can be used to launch an script using Runtime.exec.
288   * This method is required because in Windows the script that contain a "=" in
289   * their path must be quoted.
290   *
291   * @param script
292   *          the script name
293   * @return the absolute path for the given parentPath and relativePath.
294   */
295  public static String getScriptPath(String script)
296  {
297    return SetupUtils.getScriptPath(script);
298  }
299
300  /**
301   * Returns the absolute path for the given file. It tries to get the canonical
302   * file path. If it fails it returns the string representation.
303   *
304   * @param f
305   *          File to get the path
306   * @return the absolute path for the given file.
307   */
308  public static String getPath(File f)
309  {
310    String path = null;
311    if (f != null)
312    {
313      try
314      {
315        /*
316         * Do a best effort to avoid having a relative representation (for
317         * instance to avoid having ../../../).
318         */
319        f = f.getCanonicalFile();
320      }
321      catch (IOException ioe)
322      {
323        /*
324         * This is a best effort to get the best possible representation of the
325         * file: reporting the error is not necessary.
326         */
327      }
328      path = f.toString();
329    }
330    return path;
331  }
332
333  /**
334   * Returns <CODE>true</CODE> if the first provided path is under the second
335   * path in the file system.
336   *
337   * @param descendant
338   *          the descendant candidate path.
339   * @param path
340   *          the path.
341   * @return <CODE>true</CODE> if the first provided path is under the second
342   *         path in the file system; <code>false</code> otherwise or if either
343   *         of the files are null
344   */
345  public static boolean isDescendant(File descendant, File path)
346  {
347    boolean isDescendant = false;
348    if (descendant != null && path != null)
349    {
350      File parent = descendant.getParentFile();
351      while (parent != null && !isDescendant)
352      {
353        isDescendant = path.equals(parent);
354        if (!isDescendant)
355        {
356          parent = parent.getParentFile();
357        }
358      }
359    }
360    return isDescendant;
361  }
362
363  /**
364   * Returns <CODE>true</CODE> if the parent directory for the provided path
365   * exists and <CODE>false</CODE> otherwise.
366   *
367   * @param path
368   *          the path that we are analyzing.
369   * @return <CODE>true</CODE> if the parent directory for the provided path
370   *         exists and <CODE>false</CODE> otherwise.
371   */
372  public static boolean parentDirectoryExists(String path)
373  {
374    boolean parentExists = false;
375    File f = new File(path);
376    File parentFile = f.getParentFile();
377    if (parentFile != null)
378    {
379      parentExists = parentFile.isDirectory();
380    }
381    return parentExists;
382  }
383
384  /**
385   * Returns <CODE>true</CODE> if the the provided path is a file and exists and
386   * <CODE>false</CODE> otherwise.
387   *
388   * @param path
389   *          the path that we are analyzing.
390   * @return <CODE>true</CODE> if the the provided path is a file and exists and
391   *         <CODE>false</CODE> otherwise.
392   */
393  public static boolean fileExists(String path)
394  {
395    File f = new File(path);
396    return f.isFile();
397  }
398
399  /**
400   * Returns <CODE>true</CODE> if the the provided path is a directory, exists
401   * and is not empty <CODE>false</CODE> otherwise.
402   *
403   * @param path
404   *          the path that we are analyzing.
405   * @return <CODE>true</CODE> if the the provided path is a directory, exists
406   *         and is not empty <CODE>false</CODE> otherwise.
407   */
408  public static boolean directoryExistsAndIsNotEmpty(String path)
409  {
410    final File f = new File(path);
411    if (f.isDirectory())
412    {
413      final String[] ch = f.list();
414      return ch != null && ch.length > 0;
415    }
416    return false;
417  }
418
419  /**
420   * Returns <CODE>true</CODE> if the the provided string is a configuration DN
421   * and <CODE>false</CODE> otherwise.
422   *
423   * @param dn
424   *          the String we are analyzing.
425   * @return <CODE>true</CODE> if the the provided string is a configuration DN
426   *         and <CODE>false</CODE> otherwise.
427   */
428  public static boolean isConfigurationDn(String dn)
429  {
430    boolean isConfigurationDn = false;
431    String[] configDns = { "cn=config", Constants.SCHEMA_DN };
432    for (int i = 0; i < configDns.length && !isConfigurationDn; i++)
433    {
434      isConfigurationDn = areDnsEqual(dn, configDns[i]);
435    }
436    return isConfigurationDn;
437  }
438
439  /**
440   * Returns <CODE>true</CODE> if the the provided strings represent the same DN
441   * and <CODE>false</CODE> otherwise.
442   *
443   * @param dn1
444   *          the first dn to compare.
445   * @param dn2
446   *          the second dn to compare.
447   * @return <CODE>true</CODE> if the the provided strings represent the same DN
448   *         and <CODE>false</CODE> otherwise.
449   */
450  public static boolean areDnsEqual(String dn1, String dn2)
451  {
452    boolean areDnsEqual = false;
453    try
454    {
455      LdapName name1 = new LdapName(dn1);
456      LdapName name2 = new LdapName(dn2);
457      areDnsEqual = name1.equals(name2);
458    }
459    catch (Exception ex)
460    {
461      // do nothing
462    }
463
464    return areDnsEqual;
465  }
466
467  /**
468   * Creates the parent directory if it does not already exist.
469   *
470   * @param f
471   *          File for which parentage will be insured
472   * @return boolean indicating whether or not the input <code>f</code> has a
473   *         parent after this method is invoked.
474   */
475  public static boolean insureParentsExist(File f)
476  {
477    final File parent = f.getParentFile();
478    final boolean b = parent.exists();
479    if (!b)
480    {
481      return parent.mkdirs();
482    }
483    return b;
484  }
485
486  /**
487   * Creates the a directory in the provided path.
488   *
489   * @param path
490   *          the path.
491   * @return <CODE>true</CODE> if the path was created or already existed (and
492   *         was a directory) and <CODE>false</CODE> otherwise.
493   * @throws IOException
494   *           if something goes wrong.
495   */
496  public static boolean createDirectory(String path) throws IOException
497  {
498    return createDirectory(new File(path));
499  }
500
501  /**
502   * Creates the a directory in the provided path.
503   *
504   * @param f
505   *          the path.
506   * @return <CODE>true</CODE> if the path was created or already existed (and
507   *         was a directory) and <CODE>false</CODE> otherwise.
508   * @throws IOException
509   *           if something goes wrong.
510   */
511  public static boolean createDirectory(File f) throws IOException
512  {
513    boolean directoryCreated;
514    if (!f.exists())
515    {
516      directoryCreated = f.mkdirs();
517    }
518    else
519    {
520      directoryCreated = f.isDirectory();
521    }
522    return directoryCreated;
523  }
524
525  /**
526   * Creates a file on the specified path with the contents of the provided
527   * stream.
528   *
529   * @param path
530   *          the path where the file will be created.
531   * @param is
532   *          the InputStream with the contents of the file.
533   * @throws IOException
534   *           if something goes wrong.
535   */
536  public static void createFile(File path, InputStream is) throws IOException
537  {
538    FileOutputStream out;
539    BufferedOutputStream dest;
540    byte[] data = new byte[BUFFER_SIZE];
541    int count;
542
543    out = new FileOutputStream(path);
544
545    dest = new BufferedOutputStream(out);
546
547    while ((count = is.read(data, 0, BUFFER_SIZE)) != -1)
548    {
549      dest.write(data, 0, count);
550    }
551    dest.flush();
552    dest.close();
553  }
554
555  /**
556   * Creates a file on the specified path with the contents of the provided
557   * String. The file is protected, so that 'others' have no access to it.
558   *
559   * @param path
560   *          the path where the file will be created.
561   * @param content
562   *          the String with the contents of the file.
563   * @throws IOException
564   *           if something goes wrong.
565   * @throws InterruptedException
566   *           if there is a problem changing the permissions of the file.
567   */
568  public static void createProtectedFile(String path, String content) throws IOException, InterruptedException
569  {
570    FileWriter file = new FileWriter(path);
571    PrintWriter out = new PrintWriter(file);
572
573    out.println(content);
574
575    out.flush();
576    out.close();
577
578    if (!isWindows())
579    {
580      setPermissionsUnix(path, "600");
581    }
582  }
583
584  /**
585   * This is a helper method that gets a LocalizableMessage representation of
586   * the elements in the Collection of Messages. The LocalizableMessage will
587   * display the different elements separated by the separator String.
588   *
589   * @param col
590   *          the collection containing the messages.
591   * @param separator
592   *          the separator String to be used.
593   * @return the message representation for the collection; null if
594   *         <code>col</code> is null
595   */
596  public static LocalizableMessage getMessageFromCollection(Collection<LocalizableMessage> col, String separator)
597  {
598    if (col != null)
599    {
600      final LocalizableMessageBuilder mb = new LocalizableMessageBuilder();
601      for (LocalizableMessage m : col)
602      {
603        mb.append(separator).append(m);
604      }
605      return mb.toMessage();
606    }
607    return null;
608  }
609
610  /**
611   * Returns the default server location that will be proposed to the user in
612   * the installation.
613   *
614   * @return the default server location that will be proposed to the user in
615   *         the installation.
616   */
617  public static String getDefaultServerLocation()
618  {
619    String userDir = System.getProperty("user.home");
620    String firstLocation = userDir + File.separator + SHORT_NAME.toLowerCase(Locale.ENGLISH);
621    String serverLocation = firstLocation;
622    int i = 1;
623    while (fileExists(serverLocation) || directoryExistsAndIsNotEmpty(serverLocation))
624    {
625      serverLocation = firstLocation + "-" + i;
626      i++;
627    }
628    return serverLocation;
629  }
630
631  /**
632   * Returns <CODE>true</CODE> if there is more disk space in the provided path
633   * than what is specified with the bytes parameter.
634   *
635   * @param directoryPath
636   *          the path.
637   * @param bytes
638   *          the disk space.
639   * @return <CODE>true</CODE> if there is more disk space in the provided path
640   *         than what is specified with the bytes parameter.
641   */
642  public static synchronized boolean hasEnoughSpace(String directoryPath, long bytes)
643  {
644    // TODO This does not work with quotas etc. but at least it seems that
645    // we do not write all data on disk if it fails.
646    boolean hasEnoughSpace = false;
647    File file = null;
648    RandomAccessFile raf = null;
649    File directory = new File(directoryPath);
650    boolean deleteDirectory = false;
651    if (!directory.exists())
652    {
653      deleteDirectory = directory.mkdir();
654    }
655
656    try
657    {
658      file = File.createTempFile("temp" + System.nanoTime(), ".tmp", directory);
659      raf = new RandomAccessFile(file, "rw");
660      raf.setLength(bytes);
661      hasEnoughSpace = true;
662    }
663    catch (IOException ex)
664    { /* do nothing */
665    }
666    finally
667    {
668      StaticUtils.close(raf);
669      if (file != null)
670      {
671        file.delete();
672      }
673    }
674
675    if (deleteDirectory)
676    {
677      directory.delete();
678    }
679
680    return hasEnoughSpace;
681  }
682
683  /**
684   * Gets a localized representation of the provide TopologyCacheException.
685   *
686   * @param te
687   *          the exception.
688   * @return a localized representation of the provide TopologyCacheException.
689   */
690  public static LocalizableMessage getMessage(TopologyCacheException te)
691  {
692    LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
693
694    String ldapUrl = te.getLdapUrl();
695    if (ldapUrl != null)
696    {
697      String hostName = ldapUrl.substring(ldapUrl.indexOf("://") + 3);
698      buf.append(INFO_SERVER_ERROR.get(hostName));
699      buf.append(" ");
700    }
701    if (te.getType() == TopologyCacheException.Type.TIMEOUT)
702    {
703      buf.append(INFO_ERROR_CONNECTING_TIMEOUT.get());
704    }
705    else if (te.getCause() instanceof NamingException)
706    {
707      buf.append(getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), te.getCause()));
708    }
709    else
710    {
711      logger.warn(LocalizableMessage.raw("Unexpected error: " + te, te));
712      // This is unexpected.
713      if (te.getCause() != null)
714      {
715        buf.append(getThrowableMsg(INFO_BUG_MSG.get(), te.getCause()));
716      }
717      else
718      {
719        buf.append(getThrowableMsg(INFO_BUG_MSG.get(), te));
720      }
721    }
722    return buf.toMessage();
723  }
724
725  /**
726   * Sets the permissions of the provided paths with the provided permission
727   * String.
728   *
729   * @param paths
730   *          the paths to set permissions on.
731   * @param permissions
732   *          the UNIX-mode file system permission representation (for example
733   *          "644" or "755")
734   * @return the return code of the chmod command.
735   * @throws IOException
736   *           if something goes wrong.
737   * @throws InterruptedException
738   *           if the Runtime.exec method is interrupted.
739   */
740  public static int setPermissionsUnix(ArrayList<String> paths, String permissions) throws IOException,
741      InterruptedException
742  {
743    String[] args = new String[paths.size() + 2];
744    args[0] = "chmod";
745    args[1] = permissions;
746    for (int i = 2; i < args.length; i++)
747    {
748      args[i] = paths.get(i - 2);
749    }
750    Process p = Runtime.getRuntime().exec(args);
751    return p.waitFor();
752  }
753
754  /**
755   * Sets the permissions of the provided paths with the provided permission
756   * String.
757   *
758   * @param path
759   *          to set permissions on.
760   * @param permissions
761   *          the UNIX-mode file system permission representation (for example
762   *          "644" or "755")
763   * @return the return code of the chmod command.
764   * @throws IOException
765   *           if something goes wrong.
766   * @throws InterruptedException
767   *           if the Runtime.exec method is interrupted.
768   */
769  public static int setPermissionsUnix(String path, String permissions) throws IOException, InterruptedException
770  {
771    String[] args = new String[3];
772    args[0] = "chmod";
773    args[1] = permissions;
774    args[2] = path;
775    Process p = Runtime.getRuntime().exec(args);
776    return p.waitFor();
777  }
778
779  /**
780   * Returns <CODE>true</CODE> if this is executed from command line and
781   * <CODE>false</CODE> otherwise.
782   *
783   * @return <CODE>true</CODE> if this is executed from command line and
784   *         <CODE>false</CODE> otherwise.
785   */
786  public static boolean isCli()
787  {
788    return "true".equals(System.getProperty(Constants.CLI_JAVA_PROPERTY));
789  }
790
791  /**
792   * Creates an LDAP+StartTLS connection and returns the corresponding
793   * LdapContext. This method first creates an LdapContext with anonymous bind.
794   * Then it requests a StartTlsRequest extended operation. The StartTlsResponse
795   * is setup with the specified hostname verifier. Negotiation is done using a
796   * TrustSocketFactory so that the specified TrustManager gets called during
797   * the SSL handshake. If trust manager is null, certificates are not checked
798   * during SSL handshake.
799   *
800   * @param ldapsURL
801   *          the target *LDAPS* URL.
802   * @param dn
803   *          passed as Context.SECURITY_PRINCIPAL if not null.
804   * @param pwd
805   *          passed as Context.SECURITY_CREDENTIALS if not null.
806   * @param timeout
807   *          passed as com.sun.jndi.ldap.connect.timeout if > 0.
808   * @param env
809   *          null or additional environment properties.
810   * @param trustManager
811   *          null or the trust manager to be invoked during SSL. negociation.
812   * @param verifier
813   *          null or the hostname verifier to be setup in the StartTlsResponse.
814   * @return the established connection with the given parameters.
815   * @throws NamingException
816   *           the exception thrown when instantiating InitialLdapContext.
817   * @see javax.naming.Context
818   * @see javax.naming.ldap.InitialLdapContext
819   * @see javax.naming.ldap.StartTlsRequest
820   * @see javax.naming.ldap.StartTlsResponse
821   * @see org.opends.admin.ads.util.TrustedSocketFactory
822   */
823
824  public static InitialLdapContext createStartTLSContext(String ldapsURL, String dn, String pwd, int timeout,
825      Hashtable<String, String> env, TrustManager trustManager, HostnameVerifier verifier) throws NamingException
826  {
827    return ConnectionUtils.createStartTLSContext(ldapsURL, dn, pwd, timeout, env, trustManager, null, verifier);
828  }
829
830  /**
831   * Returns a message object for the given NamingException. The code assume
832   * that we are trying to connect to the local server.
833   *
834   * @param ne
835   *          the NamingException.
836   * @return a message object for the given NamingException.
837   */
838  public static LocalizableMessage getMessageForException(NamingException ne)
839  {
840    final String detailedException = ne.toString(true);
841    if (isCertificateException(ne))
842    {
843      return INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE.get(detailedException);
844    }
845    else if (ne instanceof AuthenticationException)
846    {
847      return ERR_CANNOT_CONNECT_TO_LOCAL_AUTHENTICATION.get(detailedException);
848    }
849    else if (ne instanceof NoPermissionException)
850    {
851      return ERR_CANNOT_CONNECT_TO_LOCAL_PERMISSIONS.get(detailedException);
852    }
853    else if (ne instanceof NamingSecurityException)
854    {
855      return ERR_CANNOT_CONNECT_TO_LOCAL_PERMISSIONS.get(detailedException);
856    }
857    else if (ne instanceof CommunicationException)
858    {
859      return ERR_CANNOT_CONNECT_TO_LOCAL_COMMUNICATION.get(detailedException);
860    }
861    else
862    {
863      return ERR_CANNOT_CONNECT_TO_LOCAL_GENERIC.get(detailedException);
864    }
865  }
866
867  /**
868   * Returns the path of the installation of the directory server. Note that
869   * this method assumes that this code is being run locally.
870   *
871   * @return the path of the installation of the directory server.
872   */
873  public static String getInstallPathFromClasspath()
874  {
875    String installPath = System.getProperty("org.opends.quicksetup.Root");
876    if (installPath != null)
877    {
878      return installPath;
879    }
880
881    /* Get the install path from the Class Path */
882    String sep = System.getProperty("path.separator");
883    String[] classPaths = System.getProperty("java.class.path").split(sep);
884    String path = getInstallPath(classPaths);
885    if (path != null)
886    {
887      File f = new File(path).getAbsoluteFile();
888      File librariesDir = f.getParentFile();
889
890      /*
891       * Do a best effort to avoid having a relative representation (for
892       * instance to avoid having ../../../).
893       */
894      try
895      {
896        installPath = librariesDir.getParentFile().getCanonicalPath();
897      }
898      catch (IOException ioe)
899      {
900        // Best effort
901        installPath = librariesDir.getParent();
902      }
903    }
904    return installPath;
905  }
906
907  private static String getInstallPath(final String[] classPaths)
908  {
909    for (String classPath : classPaths)
910    {
911      final String normPath = classPath.replace(File.separatorChar, '/');
912      if (normPath.endsWith(OPENDJ_BOOTSTRAP_CLIENT_JAR_RELATIVE_PATH)
913          || normPath.endsWith(OPENDJ_BOOTSTRAP_JAR_RELATIVE_PATH))
914      {
915        return classPath;
916      }
917    }
918    return null;
919  }
920
921  /**
922   * Returns the path of the installation of the directory server. Note that
923   * this method assumes that this code is being run locally.
924   *
925   * @param installPath
926   *          The installation path
927   * @return the path of the installation of the directory server.
928   */
929  public static String getInstancePathFromInstallPath(String installPath)
930  {
931    String instancePathFileName = Installation.INSTANCE_LOCATION_PATH;
932    File _svcScriptPathName = new File(
933        installPath + File.separator + Installation.LIBRARIES_PATH_RELATIVE + File.separator + "_svc-opendj.sh");
934
935    // look for /etc/opt/opendj/instance.loc
936    File f = new File(instancePathFileName);
937    if (!_svcScriptPathName.exists() || !f.exists())
938    {
939      // look for <installPath>/instance.loc
940      instancePathFileName = installPath + File.separator + Installation.INSTANCE_LOCATION_PATH_RELATIVE;
941      f = new File(instancePathFileName);
942      if (!f.exists())
943      {
944        return installPath;
945      }
946    }
947
948    // Read the first line and close the file.
949    try (BufferedReader reader = new BufferedReader(new FileReader(instancePathFileName)))
950    {
951      String line = reader.readLine();
952      File instanceLoc = new File(line.trim());
953      if (instanceLoc.isAbsolute())
954      {
955        return getCanonicalPath(instanceLoc);
956      }
957      else
958      {
959        return getCanonicalPath(new File(installPath + File.separator + instanceLoc.getPath()));
960      }
961    }
962    catch (Exception e)
963    {
964      return installPath;
965    }
966  }
967
968  /**
969   * Returns the max size in character of a line to be displayed in the command
970   * line.
971   *
972   * @return the max size in character of a line to be displayed in the command
973   *         line.
974   */
975  public static int getCommandLineMaxLineWidth()
976  {
977    return MAX_LINE_WIDTH;
978  }
979
980  /**
981   * Puts Swing menus in the Mac OS menu bar, if using the Aqua look and feel,
982   * and sets the application name that is displayed in the application menu and
983   * in the dock.
984   *
985   * @param appName
986   *          application name to display in the menu bar and the dock.
987   */
988  public static void setMacOSXMenuBar(LocalizableMessage appName)
989  {
990    System.setProperty("apple.laf.useScreenMenuBar", "true");
991    System.setProperty("com.apple.mrj.application.apple.menu.about.name", String.valueOf(appName));
992  }
993
994  /**
995   * Returns the number of entries contained in the zip file. This is used to
996   * update properly the progress bar ratio.
997   *
998   * @return the number of entries contained in the zip file.
999   */
1000  public static int getNumberZipEntries()
1001  {
1002    // TODO  we should get this dynamically during build
1003    return 165;
1004  }
1005
1006  /**
1007   * Creates a string consisting of the string representation of the elements in
1008   * the <code>list</code> separated by <code>separator</code>.
1009   *
1010   * @param list
1011   *          the list to print
1012   * @param separator
1013   *          to use in separating elements
1014   * @param prefix
1015   *          prepended to each individual element in the list before adding to
1016   *          the returned string.
1017   * @param suffix
1018   *          appended to each individual element in the list before adding to
1019   *          the returned string.
1020   * @return String representing the list
1021   */
1022  public static String listToString(List<?> list, String separator, String prefix, String suffix)
1023  {
1024    StringBuilder sb = new StringBuilder();
1025    for (int i = 0; i < list.size(); i++)
1026    {
1027      if (prefix != null)
1028      {
1029        sb.append(prefix);
1030      }
1031      sb.append(list.get(i));
1032      if (suffix != null)
1033      {
1034        sb.append(suffix);
1035      }
1036      if (i < list.size() - 1)
1037      {
1038        sb.append(separator);
1039      }
1040    }
1041    return sb.toString();
1042  }
1043
1044  /**
1045   * Returns the file system permissions for a file.
1046   *
1047   * @param file
1048   *          the file for which we want the file permissions.
1049   * @return the file system permissions for the file.
1050   */
1051  public static String getFileSystemPermissions(File file)
1052  {
1053    String name = file.getName();
1054    if (file.getParent().endsWith(File.separator + Installation.WINDOWS_BINARIES_PATH_RELATIVE)
1055        || file.getParent().endsWith(File.separator + Installation.UNIX_BINARIES_PATH_RELATIVE))
1056    {
1057      return name.endsWith(".bat") ? "644" : "755";
1058    }
1059    else if (name.endsWith(".sh")
1060          || name.endsWith(Installation.UNIX_SETUP_FILE_NAME)
1061          || name.endsWith(Installation.UNIX_UNINSTALL_FILE_NAME)
1062          || name.endsWith(Installation.UNIX_UPGRADE_FILE_NAME)
1063          || name.endsWith(Installation.MAC_JAVA_APP_STUB_NAME))
1064    {
1065      return "755";
1066    }
1067    else
1068    {
1069      return "644";
1070    }
1071  }
1072
1073  /**
1074   * Inserts HTML break tags into <code>d</code> breaking it up so that ideally
1075   * no line is longer than <code>maxll</code> assuming no single word is longer
1076   * then <code>maxll</code>. If the string already contains HTML tags that
1077   * cause a line break (e.g break and closing list item tags) they are
1078   * respected by this method when calculating where to place new breaks to
1079   * control the maximum line length.
1080   *
1081   * @param cs
1082   *          String to break
1083   * @param maxll
1084   *          int maximum line length
1085   * @return String representing <code>d</code> with HTML break tags inserted
1086   */
1087  public static String breakHtmlString(CharSequence cs, int maxll)
1088  {
1089    if (cs != null)
1090    {
1091      String d = cs.toString();
1092      int len = d.length();
1093      if (len <= 0)
1094      {
1095        return d;
1096      }
1097      if (len > maxll)
1098      {
1099
1100        // First see if there are any tags that would cause a
1101        // natural break in the line.  If so start line break
1102        // point evaluation from that point.
1103        for (String tag : Constants.BREAKING_TAGS)
1104        {
1105          int p = d.lastIndexOf(tag, maxll);
1106          if (p > 0 && p < len)
1107          {
1108            return d.substring(0, p + tag.length()) + breakHtmlString(d.substring(p + tag.length()), maxll);
1109          }
1110        }
1111
1112        // Now look for spaces in which to insert a break.
1113        // First see if there are any spaces counting backward
1114        // from the max line length.  If there aren't any, then
1115        // use the first space encountered after the max line
1116        // length.
1117        int p = d.lastIndexOf(' ', maxll);
1118        if (p <= 0)
1119        {
1120          p = d.indexOf(' ', maxll);
1121        }
1122        if (p > 0 && p < len)
1123        {
1124          return d.substring(0, p) + Constants.HTML_LINE_BREAK + breakHtmlString(d.substring(p + 1), maxll);
1125        }
1126        else
1127        {
1128          return d;
1129        }
1130      }
1131      else
1132      {
1133        return d;
1134      }
1135    }
1136    else
1137    {
1138      return null;
1139    }
1140  }
1141
1142  /**
1143   * Converts existing HTML break tags to native line separators.
1144   *
1145   * @param s
1146   *          string to convert
1147   * @return converted string
1148   */
1149  public static String convertHtmlBreakToLineSeparator(String s)
1150  {
1151    return s.replaceAll("<br>", Constants.LINE_SEPARATOR);
1152  }
1153
1154  /**
1155   * Strips any potential HTML markup from a given string.
1156   *
1157   * @param s
1158   *          string to strip
1159   * @return resulting string
1160   */
1161  public static String stripHtml(String s)
1162  {
1163    if (s != null)
1164    {
1165
1166      // This is not a comprehensive solution but addresses the few tags
1167      // that we have in Resources.properties at the moment.
1168      // Note that the following might strip out more than is intended for non-tags
1169      // like '<your name here>' or for funky tags like '<tag attr="1 > 0">'.
1170      // See test class for cases that might cause problems.
1171      return s.replaceAll("<.*?>", "");
1172    }
1173    return null;
1174  }
1175
1176  /**
1177   * Tests a text string to see if it contains HTML.
1178   *
1179   * @param text
1180   *          String to test
1181   * @return true if the string contains HTML
1182   */
1183  public static boolean containsHtml(String text)
1184  {
1185    return text != null && text.indexOf('<') != -1 && text.indexOf('>') != -1;
1186  }
1187
1188  private static EmptyPrintStream emptyStream = new EmptyPrintStream();
1189
1190  /**
1191   * Returns a printstream that does not write anything to standard output.
1192   *
1193   * @return a printstream that does not write anything to standard output.
1194   */
1195  public static EmptyPrintStream getEmptyPrintStream()
1196  {
1197    if (emptyStream == null)
1198    {
1199      emptyStream = new EmptyPrintStream();
1200    }
1201    return emptyStream;
1202  }
1203
1204  /**
1205   * Returns the current time of a server in milliseconds.
1206   *
1207   * @param ctx
1208   *          the connection to the server.
1209   * @return the current time of a server in milliseconds.
1210   */
1211  public static long getServerClock(InitialLdapContext ctx)
1212  {
1213    long time = -1;
1214    SearchControls ctls = new SearchControls();
1215    ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
1216    ctls.setReturningAttributes(new String[] { "currentTime" });
1217    String filter = "(objectclass=*)";
1218
1219    try
1220    {
1221      LdapName jndiName = new LdapName("cn=monitor");
1222      NamingEnumeration<?> listeners = ctx.search(jndiName, filter, ctls);
1223
1224      try
1225      {
1226        while (listeners.hasMore())
1227        {
1228          SearchResult sr = (SearchResult) listeners.next();
1229
1230          String v = getFirstValue(sr, "currentTime");
1231
1232          TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
1233
1234          SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
1235          formatter.setTimeZone(utcTimeZone);
1236
1237          time = formatter.parse(v).getTime();
1238        }
1239      }
1240      finally
1241      {
1242        listeners.close();
1243      }
1244    }
1245    catch (Throwable t)
1246    {
1247      logger.warn(LocalizableMessage.raw("Error retrieving server current time: " + t, t));
1248    }
1249    return time;
1250  }
1251
1252  /**
1253   * Checks that the java version we are running is compatible with OpenDS.
1254   *
1255   * @throws IncompatibleVersionException
1256   *           if the java version we are running is not compatible with OpenDS.
1257   */
1258  public static void checkJavaVersion() throws IncompatibleVersionException
1259  {
1260    try
1261    {
1262      com.forgerock.opendj.cli.Utils.checkJavaVersion();
1263    }
1264    catch (ClientException e)
1265    {
1266      throw new IncompatibleVersionException(e.getMessageObject(), e);
1267    }
1268  }
1269
1270  /**
1271   * Basic method to know if the host is local or not. This is only used to know
1272   * if we can perform a port check or not.
1273   *
1274   * @param host
1275   *          the host to analyze.
1276   * @return <CODE>true</CODE> if it is the local host and <CODE>false</CODE>
1277   *         otherwise.
1278   */
1279  public static boolean isLocalHost(String host)
1280  {
1281    if ("localhost".equalsIgnoreCase(host))
1282    {
1283      return true;
1284    }
1285
1286    try
1287    {
1288      InetAddress localAddress = InetAddress.getLocalHost();
1289      InetAddress[] addresses = InetAddress.getAllByName(host);
1290      for (InetAddress address : addresses)
1291      {
1292        if (localAddress.equals(address))
1293        {
1294          return true;
1295        }
1296      }
1297    }
1298    catch (Throwable t)
1299    {
1300      logger.warn(LocalizableMessage.raw("Failing checking host names: " + t, t));
1301    }
1302    return false;
1303  }
1304
1305  /**
1306   * Returns the HTML representation of a plain text string which is obtained
1307   * by converting some special characters (like '<') into its equivalent
1308   * escaped HTML representation.
1309   *
1310   * @param rawString the String from which we want to obtain the HTML
1311   * representation.
1312   * @return the HTML representation of the plain text string.
1313   */
1314  private static String escapeHtml(String rawString)
1315  {
1316    StringBuilder buffer = new StringBuilder();
1317    for (int i = 0; i < rawString.length(); i++)
1318    {
1319      char c = rawString.charAt(i);
1320      switch (c)
1321      {
1322      case '<':
1323        buffer.append("&lt;");
1324        break;
1325
1326      case '>':
1327        buffer.append("&gt;");
1328        break;
1329
1330      case '&':
1331        buffer.append("&amp;");
1332        break;
1333
1334      case '"':
1335        buffer.append("&quot;");
1336        break;
1337
1338      default:
1339        buffer.append(c);
1340        break;
1341      }
1342    }
1343
1344    return buffer.toString();
1345  }
1346
1347  /**
1348   * Returns the HTML representation for a given text. without adding any kind
1349   * of font or style elements.  Just escapes the problematic characters
1350   * (like '<') and transform the break lines into '\n' characters.
1351   *
1352   * @param text the source text from which we want to get the HTML
1353   * representation
1354   * @return the HTML representation for the given text.
1355   */
1356  public static String getHtml(String text)
1357  {
1358    StringBuilder buffer = new StringBuilder();
1359    if (text != null)
1360    {
1361      text = text.replaceAll("\r\n", "\n");
1362      String[] lines = text.split("[\n\r\u0085\u2028\u2029]");
1363      for (int i = 0; i < lines.length; i++)
1364      {
1365        if (i != 0)
1366        {
1367          buffer.append(Constants.HTML_LINE_BREAK);
1368        }
1369        buffer.append(escapeHtml(lines[i]));
1370      }
1371    }
1372    return buffer.toString();
1373  }
1374
1375  /**
1376   * Tries to find a customized object in the customization class. If the
1377   * customization class does not exist or it does not contain the field as the
1378   * specified type of the object, returns the default value.
1379   *
1380   * @param <T>
1381   *          the type of the customized object.
1382   * @param fieldName
1383   *          the name of the field representing an object in the customization
1384   *          class.
1385   * @param defaultValue
1386   *          the default value.
1387   * @param valueClass
1388   *          the class of the parametrized value.
1389   * @return the customized object.
1390   */
1391  public static <T> T getCustomizedObject(String fieldName, T defaultValue, Class<T> valueClass)
1392  {
1393    try
1394    {
1395      Class<?> c = Class.forName(Utils.CUSTOMIZATION_CLASS_NAME);
1396      Object obj = c.newInstance();
1397
1398      return valueClass.cast(c.getField(fieldName).get(obj));
1399    }
1400    catch (Exception ex)
1401    {
1402      //do nothing.
1403    }
1404    return defaultValue;
1405  }
1406
1407  /**
1408   * Adds word break tags to the provided html string.
1409   *
1410   * @param htmlString
1411   *          the string.
1412   * @param from
1413   *          the first index to start the spacing from.
1414   * @param spacing
1415   *          the minimal spacing between word breaks.
1416   * @return a string containing word breaks.
1417   */
1418  public static String addWordBreaks(String htmlString, int from, int spacing)
1419  {
1420    StringBuilder sb = new StringBuilder();
1421    boolean insideTag = false;
1422    int totalAddedChars = 0;
1423    int addedChars = 0;
1424    for (int i = 0; i < htmlString.length(); i++)
1425    {
1426      char c = htmlString.charAt(i);
1427      sb.append(c);
1428      if (c == '<')
1429      {
1430        insideTag = true;
1431      }
1432      else if (c == '>' && insideTag)
1433      {
1434        insideTag = false;
1435      }
1436      if (!insideTag && c != '>')
1437      {
1438        addedChars++;
1439        totalAddedChars++;
1440      }
1441      if (addedChars > spacing && totalAddedChars > from && !insideTag)
1442      {
1443        sb.append("<wbr>");
1444        addedChars = 0;
1445      }
1446    }
1447    return sb.toString();
1448  }
1449
1450  /**
1451   * Returns the localized string describing the DataOptions chosen by the user.
1452   *
1453   * @param userInstallData
1454   *          the DataOptions of the user.
1455   * @return the localized string describing the DataOptions chosen by the user.
1456   */
1457  public static String getDataDisplayString(final UserData userInstallData)
1458  {
1459    LocalizableMessage msg;
1460
1461    final DataReplicationOptions repl = userInstallData.getReplicationOptions();
1462    final SuffixesToReplicateOptions suf = userInstallData.getSuffixesToReplicateOptions();
1463
1464    boolean createSuffix = repl.getType() == DataReplicationOptions.Type.FIRST_IN_TOPOLOGY
1465                        || repl.getType() == DataReplicationOptions.Type.STANDALONE
1466                        || suf.getType() == SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY;
1467
1468    if (createSuffix)
1469    {
1470      LocalizableMessage arg2;
1471      NewSuffixOptions options = userInstallData.getNewSuffixOptions();
1472
1473      switch (options.getType())
1474      {
1475      case CREATE_BASE_ENTRY:
1476        arg2 = INFO_REVIEW_CREATE_BASE_ENTRY_LABEL.get(options.getBaseDns().getFirst());
1477        break;
1478
1479      case LEAVE_DATABASE_EMPTY:
1480        arg2 = INFO_REVIEW_LEAVE_DATABASE_EMPTY_LABEL.get();
1481        break;
1482
1483      case IMPORT_FROM_LDIF_FILE:
1484        arg2 = INFO_REVIEW_IMPORT_LDIF.get(options.getLDIFPaths().getFirst());
1485        break;
1486
1487      case IMPORT_AUTOMATICALLY_GENERATED_DATA:
1488        arg2 = INFO_REVIEW_IMPORT_AUTOMATICALLY_GENERATED.get(options.getNumberEntries());
1489        break;
1490
1491      default:
1492        throw new IllegalArgumentException("Unknown type: " + options.getType());
1493      }
1494
1495      if (options.getBaseDns().isEmpty())
1496      {
1497        msg = INFO_REVIEW_CREATE_NO_SUFFIX.get();
1498      }
1499      else
1500      {
1501        final String backendType = userInstallData.getBackendType().getUserFriendlyName().toString();
1502        if (options.getBaseDns().size() > 1)
1503        {
1504          msg = INFO_REVIEW_CREATE_SUFFIX.get(
1505              backendType, joinAsString(Constants.LINE_SEPARATOR, options.getBaseDns()), arg2);
1506        }
1507        else
1508        {
1509          msg = INFO_REVIEW_CREATE_SUFFIX.get(backendType, options.getBaseDns().getFirst(), arg2);
1510        }
1511      }
1512    }
1513    else
1514    {
1515      final StringBuilder buf = new StringBuilder();
1516      for (final SuffixDescriptor suffix : suf.getSuffixes())
1517      {
1518        if (buf.length() > 0)
1519        {
1520          buf.append(Constants.LINE_SEPARATOR);
1521        }
1522        buf.append(suffix.getDN());
1523      }
1524      msg = INFO_REVIEW_REPLICATE_SUFFIX.get(buf);
1525    }
1526
1527    return msg.toString();
1528  }
1529
1530  /**
1531   * Returns a localized String representation of the provided SecurityOptions
1532   * object.
1533   *
1534   * @param ops
1535   *          the SecurityOptions object from which we want to obtain the String
1536   *          representation.
1537   * @param html
1538   *          whether the resulting String must be in HTML or not.
1539   * @return a localized String representation of the provided SecurityOptions
1540   *         object.
1541   */
1542  public static String getSecurityOptionsString(SecurityOptions ops, boolean html)
1543  {
1544    StringBuilder buf = new StringBuilder();
1545
1546    if (ops.getCertificateType() == SecurityOptions.CertificateType.NO_CERTIFICATE)
1547    {
1548      buf.append(INFO_NO_SECURITY.get());
1549    }
1550    else
1551    {
1552      if (ops.getEnableStartTLS())
1553      {
1554        buf.append(INFO_ENABLE_STARTTLS.get());
1555      }
1556      if (ops.getEnableSSL())
1557      {
1558        if (buf.length() > 0)
1559        {
1560          if (html)
1561          {
1562            buf.append(Constants.HTML_LINE_BREAK);
1563          }
1564          else
1565          {
1566            buf.append("\n");
1567          }
1568        }
1569        buf.append(INFO_ENABLE_SSL.get(ops.getSslPort()));
1570      }
1571      if (html)
1572      {
1573        buf.append(Constants.HTML_LINE_BREAK);
1574      }
1575      else
1576      {
1577        buf.append("\n");
1578      }
1579      LocalizableMessage certMsg;
1580      switch (ops.getCertificateType())
1581      {
1582      case SELF_SIGNED_CERTIFICATE:
1583        certMsg = INFO_SELF_SIGNED_CERTIFICATE.get();
1584        break;
1585
1586      case JKS:
1587        certMsg = INFO_JKS_CERTIFICATE.get();
1588        break;
1589
1590      case JCEKS:
1591        certMsg = INFO_JCEKS_CERTIFICATE.get();
1592        break;
1593
1594      case PKCS11:
1595        certMsg = INFO_PKCS11_CERTIFICATE.get();
1596        break;
1597
1598      case PKCS12:
1599        certMsg = INFO_PKCS12_CERTIFICATE.get();
1600        break;
1601
1602      default:
1603        throw new IllegalStateException("Unknown certificate options type: " + ops.getCertificateType());
1604      }
1605      buf.append(certMsg);
1606    }
1607
1608    if (html)
1609    {
1610      return "<html>" + UIFactory.applyFontToHtml(buf.toString(), UIFactory.SECONDARY_FIELD_VALID_FONT);
1611    }
1612    else
1613    {
1614      return buf.toString();
1615    }
1616  }
1617
1618  /**
1619   * Returns a String representation of the provided command-line.
1620   *
1621   * @param cmd
1622   *          the command-line arguments.
1623   * @param formatter
1624   *          the formatted to be used to create the String representation.
1625   * @return a String representation of the provided command-line.
1626   */
1627  public static String getFormattedEquivalentCommandLine(List<String> cmd, ProgressMessageFormatter formatter)
1628  {
1629    StringBuilder builder = new StringBuilder();
1630    builder.append(formatter.getFormattedProgress(LocalizableMessage.raw(cmd.get(0))));
1631    int initialIndex = 1;
1632    StringBuilder sbSeparator = new StringBuilder();
1633    sbSeparator.append(formatter.getSpace());
1634    if (!isWindows())
1635    {
1636      sbSeparator.append("\\");
1637      sbSeparator.append(formatter.getLineBreak());
1638      for (int i = 0; i < 10; i++)
1639      {
1640        sbSeparator.append(formatter.getSpace());
1641      }
1642    }
1643
1644    String lineSeparator = sbSeparator.toString();
1645    for (int i = initialIndex; i < cmd.size(); i++)
1646    {
1647      String s = cmd.get(i);
1648      if (s.startsWith("-"))
1649      {
1650        builder.append(lineSeparator);
1651        builder.append(formatter.getFormattedProgress(LocalizableMessage.raw(s)));
1652      }
1653      else
1654      {
1655        builder.append(formatter.getSpace());
1656        builder.append(formatter.getFormattedProgress(LocalizableMessage.raw(escapeCommandLineValue(s))));
1657      }
1658    }
1659    return builder.toString();
1660  }
1661
1662  /**
1663   * This method simply takes a value and tries to transform it (with escape or
1664   * '"') characters so that it can be used in a command line.
1665   *
1666   * @param value
1667   *          the String to be treated.
1668   * @return the transformed value.
1669   */
1670  public static String escapeCommandLineValue(String value)
1671  {
1672    StringBuilder b = new StringBuilder();
1673    if (isUnix())
1674    {
1675      for (int i = 0; i < value.length(); i++)
1676      {
1677        char c = value.charAt(i);
1678        boolean charToEscapeFound = false;
1679        for (int j = 0; j < CHARS_TO_ESCAPE.length && !charToEscapeFound; j++)
1680        {
1681          charToEscapeFound = c == CHARS_TO_ESCAPE[j];
1682        }
1683        if (charToEscapeFound)
1684        {
1685          b.append('\\');
1686        }
1687        b.append(c);
1688      }
1689    }
1690    else
1691    {
1692      b.append('"').append(value).append('"');
1693    }
1694
1695    return b.toString();
1696  }
1697
1698  /**
1699   * Returns the equivalent setup CLI command-line. Note that this command-line
1700   * does not cover all the replication part of the GUI install.
1701   *
1702   * @param userData
1703   *          the user data.
1704   * @return the equivalent setup command-line.
1705   */
1706  public static List<String> getSetupEquivalentCommandLine(final UserData userData)
1707  {
1708    List<String> cmdLine = new ArrayList<>();
1709    cmdLine.add(getInstallDir(userData) + getSetupFilename());
1710    cmdLine.add("--cli");
1711
1712    final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendType =
1713        userData.getBackendType();
1714    if (backendType != null)
1715    {
1716      cmdLine.add("--" + ArgumentConstants.OPTION_LONG_BACKEND_TYPE);
1717      cmdLine.add(BackendTypeHelper.filterSchemaBackendName(backendType.getName()));
1718    }
1719
1720    for (final String baseDN : getBaseDNs(userData))
1721    {
1722      cmdLine.add("--baseDN");
1723      cmdLine.add(baseDN);
1724    }
1725
1726    switch (userData.getNewSuffixOptions().getType())
1727    {
1728    case CREATE_BASE_ENTRY:
1729      cmdLine.add("--addBaseEntry");
1730      break;
1731
1732    case IMPORT_AUTOMATICALLY_GENERATED_DATA:
1733      cmdLine.add("--sampleData");
1734      cmdLine.add(Integer.toString(userData.getNewSuffixOptions().getNumberEntries()));
1735      break;
1736
1737    case IMPORT_FROM_LDIF_FILE:
1738      for (final String ldifFile : userData.getNewSuffixOptions().getLDIFPaths())
1739      {
1740        cmdLine.add("--ldifFile");
1741        cmdLine.add(ldifFile);
1742      }
1743
1744      final String rejectFile = userData.getNewSuffixOptions().getRejectedFile();
1745      if (rejectFile != null)
1746      {
1747        cmdLine.add("--rejectFile");
1748        cmdLine.add(rejectFile);
1749      }
1750
1751      final String skipFile = userData.getNewSuffixOptions().getSkippedFile();
1752      if (skipFile != null)
1753      {
1754        cmdLine.add("--skipFile");
1755        cmdLine.add(skipFile);
1756      }
1757      break;
1758
1759    default:
1760      break;
1761    }
1762
1763    cmdLine.add("--ldapPort");
1764    cmdLine.add(Integer.toString(userData.getServerPort()));
1765
1766    cmdLine.add("--adminConnectorPort");
1767    cmdLine.add(Integer.toString(userData.getAdminConnectorPort()));
1768
1769    if (userData.getServerJMXPort() != -1)
1770    {
1771      cmdLine.add("--jmxPort");
1772      cmdLine.add(Integer.toString(userData.getServerJMXPort()));
1773    }
1774
1775    cmdLine.add("--rootUserDN");
1776    cmdLine.add(userData.getDirectoryManagerDn());
1777
1778    cmdLine.add("--rootUserPassword");
1779    cmdLine.add(OBFUSCATED_VALUE);
1780
1781    if (isWindows() && userData.getEnableWindowsService())
1782    {
1783      cmdLine.add("--enableWindowsService");
1784    }
1785
1786    if (userData.getReplicationOptions().getType() == DataReplicationOptions.Type.STANDALONE
1787        && !userData.getStartServer())
1788    {
1789      cmdLine.add("--doNotStart");
1790    }
1791
1792    if (userData.getSecurityOptions().getEnableStartTLS())
1793    {
1794      cmdLine.add("--enableStartTLS");
1795    }
1796
1797    if (userData.getSecurityOptions().getEnableSSL())
1798    {
1799      cmdLine.add("--ldapsPort");
1800      cmdLine.add(Integer.toString(userData.getSecurityOptions().getSslPort()));
1801    }
1802
1803    cmdLine.addAll(getSecurityOptionSetupEquivalentCmdLine(userData));
1804    cmdLine.add("--no-prompt");
1805    cmdLine.add("--noPropertiesFile");
1806
1807    return cmdLine;
1808  }
1809
1810  private static String getSetupFilename()
1811  {
1812    return isWindows() ? Installation.WINDOWS_SETUP_FILE_NAME : Installation.UNIX_SETUP_FILE_NAME;
1813  }
1814
1815  private static List<String> getSecurityOptionSetupEquivalentCmdLine(final UserData userData)
1816  {
1817    final List<String> cmdLine = new ArrayList<>();
1818
1819    switch (userData.getSecurityOptions().getCertificateType())
1820    {
1821    case SELF_SIGNED_CERTIFICATE:
1822      cmdLine.add("--generateSelfSignedCertificate");
1823      cmdLine.add("--hostName");
1824      cmdLine.add(userData.getHostName());
1825      break;
1826
1827    case JKS:
1828      cmdLine.add("--useJavaKeystore");
1829      cmdLine.add(userData.getSecurityOptions().getKeystorePath());
1830      addKeyStoreAndCert(userData.getSecurityOptions(), cmdLine);
1831      break;
1832
1833    case JCEKS:
1834      cmdLine.add("--useJCEKS");
1835      cmdLine.add(userData.getSecurityOptions().getKeystorePath());
1836
1837      addKeyStoreAndCert(userData.getSecurityOptions(), cmdLine);
1838      break;
1839
1840    case PKCS12:
1841      cmdLine.add("--usePkcs12keyStore");
1842      cmdLine.add(userData.getSecurityOptions().getKeystorePath());
1843
1844      addKeyStoreAndCert(userData.getSecurityOptions(), cmdLine);
1845      break;
1846
1847    case PKCS11:
1848      cmdLine.add("--usePkcs11Keystore");
1849
1850      addKeyStoreAndCert(userData.getSecurityOptions(), cmdLine);
1851      break;
1852
1853    default:
1854      break;
1855    }
1856
1857    return cmdLine;
1858  }
1859
1860  private static void addKeyStoreAndCert(final SecurityOptions securityOptions, final List<String> cmdLine)
1861  {
1862    if (securityOptions.getKeystorePassword() != null)
1863    {
1864      cmdLine.add("--keyStorePassword");
1865      cmdLine.add(OBFUSCATED_VALUE);
1866    }
1867
1868    for(String alias : securityOptions.getAliasesToUse())
1869    {
1870      cmdLine.add("--certNickname");
1871      cmdLine.add(alias);
1872    }
1873  }
1874
1875  /**
1876   * Returns the list of equivalent command-lines that must be executed to
1877   * enable or initialize replication as the setup does.
1878   *
1879   * @param subcommand
1880   *          either {@code "enable"} or {@code "initialize"}
1881   * @param userData
1882   *          the user data.
1883   * @return the list of equivalent command-lines that must be executed to
1884   *         enable or initialize replication as the setup does.
1885   */
1886  public static List<List<String>> getDsReplicationEquivalentCommandLines(String subcommand, UserData userData)
1887  {
1888    final List<List<String>> cmdLines = new ArrayList<>();
1889    final Map<ServerDescriptor, Set<String>> hmServerBaseDNs = getServerDescriptorBaseDNMap(userData);
1890    for (ServerDescriptor server : hmServerBaseDNs.keySet())
1891    {
1892      cmdLines.add(getDsReplicationEquivalentCommandLine(subcommand, userData, hmServerBaseDNs.get(server), server));
1893    }
1894    return cmdLines;
1895  }
1896
1897  private static void addEnableCommandOptions(UserData userData, ServerDescriptor server, List<String> cmdLine)
1898  {
1899    DataReplicationOptions replOptions = userData.getReplicationOptions();
1900    cmdLine.add("--host1");
1901    cmdLine.add(server.getHostName());
1902    cmdLine.add("--port1");
1903    cmdLine.add(String.valueOf(server.getEnabledAdministrationPorts().get(0)));
1904
1905    AuthenticationData authData = userData.getReplicationOptions().getAuthenticationData();
1906    if (!Utils.areDnsEqual(authData.getDn(), ADSContext.getAdministratorDN(userData.getGlobalAdministratorUID())))
1907    {
1908      cmdLine.add("--bindDN1");
1909      cmdLine.add(authData.getDn());
1910      cmdLine.add("--bindPassword1");
1911      cmdLine.add(OBFUSCATED_VALUE);
1912    }
1913    for (ServerDescriptor s : userData.getRemoteWithNoReplicationPort().keySet())
1914    {
1915      if (s.getAdminConnectorURL().equals(server.getAdminConnectorURL()))
1916      {
1917        AuthenticationData remoteRepl = userData.getRemoteWithNoReplicationPort().get(server);
1918        int remoteReplicationPort = remoteRepl.getPort();
1919
1920        cmdLine.add("--replicationPort1");
1921        cmdLine.add(String.valueOf(remoteReplicationPort));
1922        if (remoteRepl.useSecureConnection())
1923        {
1924          cmdLine.add("--secureReplication1");
1925        }
1926      }
1927    }
1928    cmdLine.add("--host2");
1929    cmdLine.add(userData.getHostName());
1930    cmdLine.add("--port2");
1931    cmdLine.add(String.valueOf(userData.getAdminConnectorPort()));
1932    cmdLine.add("--bindDN2");
1933    cmdLine.add(userData.getDirectoryManagerDn());
1934    cmdLine.add("--bindPassword2");
1935    cmdLine.add(OBFUSCATED_VALUE);
1936    if (replOptions.getReplicationPort() != -1)
1937    {
1938      cmdLine.add("--replicationPort2");
1939      cmdLine.add(String.valueOf(replOptions.getReplicationPort()));
1940      if (replOptions.useSecureReplication())
1941      {
1942        cmdLine.add("--secureReplication2");
1943      }
1944    }
1945  }
1946
1947  /**
1948   * Returns the full path of the command-line for a given script name.
1949   *
1950   * @param userData
1951   *          the user data.
1952   * @param scriptBasicName
1953   *          the script basic name (with no extension).
1954   * @return the full path of the command-line for a given script name.
1955   */
1956  private static String getCommandLinePath(UserData userData, String scriptBasicName)
1957  {
1958    String cmdLineName;
1959    if (isWindows())
1960    {
1961      cmdLineName =
1962          getInstallDir(userData) + Installation.WINDOWS_BINARIES_PATH_RELATIVE + File.separatorChar + scriptBasicName
1963              + ".bat";
1964    }
1965    else
1966    {
1967      cmdLineName =
1968          getInstallDir(userData) + Installation.UNIX_BINARIES_PATH_RELATIVE + File.separatorChar + scriptBasicName;
1969    }
1970    return cmdLineName;
1971  }
1972
1973  private static String installDir;
1974
1975  /**
1976   * Returns the installation directory.
1977   *
1978   * @return the installation directory.
1979   */
1980  private static String getInstallDir(UserData userData)
1981  {
1982    if (installDir == null)
1983    {
1984      File f = org.opends.quicksetup.Installation.getLocal().getRootDirectory();
1985      installDir = getCanonicalPath(f);
1986      if (installDir.lastIndexOf(File.separatorChar) != installDir.length() - 1)
1987      {
1988        installDir += File.separatorChar;
1989      }
1990    }
1991
1992    return installDir;
1993  }
1994
1995  private static String getCanonicalPath(File f)
1996  {
1997    try
1998    {
1999      return f.getCanonicalPath();
2000    }
2001    catch (IOException t)
2002    {
2003      return f.getAbsolutePath();
2004    }
2005  }
2006
2007  private static List<String> getDsReplicationEquivalentCommandLine(String subcommand, UserData userData,
2008      Set<String> baseDNs, ServerDescriptor server)
2009  {
2010    List<String> cmdLine = new ArrayList<>();
2011    String cmdName = getCommandLinePath(userData, "dsreplication");
2012    cmdLine.add(cmdName);
2013    cmdLine.add(subcommand);
2014
2015    if ("enable".equals(subcommand))
2016    {
2017      addEnableCommandOptions(userData, server, cmdLine);
2018    }
2019    else if ("initialize".equals(subcommand))
2020    {
2021      addInitializeCommandOptions(userData, server, cmdLine);
2022    }
2023    else
2024    {
2025      throw new IllegalArgumentException("Code is not implemented for subcommand " + subcommand);
2026    }
2027
2028    addCommonOptions(userData, baseDNs, cmdLine);
2029    return cmdLine;
2030  }
2031
2032  private static void addInitializeCommandOptions(UserData userData, ServerDescriptor server, List<String> cmdLine)
2033  {
2034    cmdLine.add("--hostSource");
2035    cmdLine.add(server.getHostName());
2036    cmdLine.add("--portSource");
2037    cmdLine.add(String.valueOf(server.getEnabledAdministrationPorts().get(0)));
2038
2039    cmdLine.add("--hostDestination");
2040    cmdLine.add(userData.getHostName());
2041    cmdLine.add("--portDestination");
2042    cmdLine.add(String.valueOf(userData.getAdminConnectorPort()));
2043  }
2044
2045  private static void addCommonOptions(UserData userData, Set<String> baseDNs, List<String> cmdLine)
2046  {
2047    for (String baseDN : baseDNs)
2048    {
2049      cmdLine.add("--baseDN");
2050      cmdLine.add(baseDN);
2051    }
2052
2053    cmdLine.add("--adminUID");
2054    cmdLine.add(userData.getGlobalAdministratorUID());
2055    cmdLine.add("--adminPassword");
2056    cmdLine.add(OBFUSCATED_VALUE);
2057
2058    cmdLine.add("--trustAll");
2059    cmdLine.add("--no-prompt");
2060    cmdLine.add("--noPropertiesFile");
2061  }
2062
2063  private static List<String> getBaseDNs(UserData userData)
2064  {
2065    List<String> baseDNs = new ArrayList<>();
2066
2067    DataReplicationOptions repl = userData.getReplicationOptions();
2068    SuffixesToReplicateOptions suf = userData.getSuffixesToReplicateOptions();
2069
2070    boolean createSuffix =
2071        repl.getType() == DataReplicationOptions.Type.FIRST_IN_TOPOLOGY
2072            || repl.getType() == DataReplicationOptions.Type.STANDALONE
2073            || suf.getType() == SuffixesToReplicateOptions.Type.NEW_SUFFIX_IN_TOPOLOGY;
2074
2075    if (createSuffix)
2076    {
2077      NewSuffixOptions options = userData.getNewSuffixOptions();
2078      baseDNs.addAll(options.getBaseDns());
2079    }
2080    else
2081    {
2082      Set<SuffixDescriptor> suffixes = suf.getSuffixes();
2083      for (SuffixDescriptor suffix : suffixes)
2084      {
2085        baseDNs.add(suffix.getDN());
2086      }
2087    }
2088    return baseDNs;
2089  }
2090
2091  private static Map<ServerDescriptor, Set<String>> getServerDescriptorBaseDNMap(UserData userData)
2092  {
2093    Map<ServerDescriptor, Set<String>> hm = new HashMap<>();
2094
2095    Set<SuffixDescriptor> suffixes = userData.getSuffixesToReplicateOptions().getSuffixes();
2096    AuthenticationData authData = userData.getReplicationOptions().getAuthenticationData();
2097    String ldapURL =
2098        ConnectionUtils.getLDAPUrl(authData.getHostName(), authData.getPort(), authData.useSecureConnection());
2099    for (SuffixDescriptor suffix : suffixes)
2100    {
2101      boolean found = false;
2102      for (ReplicaDescriptor replica : suffix.getReplicas())
2103      {
2104        if (ldapURL.equalsIgnoreCase(replica.getServer().getAdminConnectorURL()))
2105        {
2106          // This is the server we're configuring
2107          found = true;
2108          Set<String> baseDNs = hm.get(replica.getServer());
2109          if (baseDNs == null)
2110          {
2111            baseDNs = new LinkedHashSet<>();
2112            hm.put(replica.getServer(), baseDNs);
2113          }
2114          baseDNs.add(suffix.getDN());
2115          break;
2116        }
2117      }
2118      if (!found)
2119      {
2120        for (ReplicaDescriptor replica : suffix.getReplicas())
2121        {
2122          if (hm.keySet().contains(replica.getServer()))
2123          {
2124            hm.get(replica.getServer()).add(suffix.getDN());
2125            found = true;
2126            break;
2127          }
2128        }
2129      }
2130      if (!found)
2131      {
2132        // We haven't found the server yet, just take the first one
2133        ReplicaDescriptor replica = suffix.getReplicas().iterator().next();
2134        if (replica != null)
2135        {
2136          Set<String> baseDNs = new LinkedHashSet<>();
2137          hm.put(replica.getServer(), baseDNs);
2138          baseDNs.add(suffix.getDN());
2139        }
2140      }
2141    }
2142    return hm;
2143  }
2144
2145  /**
2146   * Returns the equivalent dsconfig command-line required to configure the
2147   * first replicated server in the topology.
2148   *
2149   * @param userData
2150   *          the user data.
2151   * @return the equivalent dsconfig command-line required to configure the
2152   *         first replicated server in the topology.
2153   */
2154  public static List<List<String>> getDsConfigReplicationEnableEquivalentCommandLines(UserData userData)
2155  {
2156    final List<List<String>> cmdLines = new ArrayList<>();
2157    final String cmdName = getCommandLinePath(userData, "dsconfig");
2158
2159    List<String> connectionArgs = new ArrayList<>();
2160    connectionArgs.add("--hostName");
2161    connectionArgs.add(userData.getHostName());
2162    connectionArgs.add("--port");
2163    connectionArgs.add(String.valueOf(userData.getAdminConnectorPort()));
2164    connectionArgs.add("--bindDN");
2165    connectionArgs.add(userData.getDirectoryManagerDn());
2166    connectionArgs.add("--bindPassword");
2167    connectionArgs.add(OBFUSCATED_VALUE);
2168    connectionArgs.add("--trustAll");
2169    connectionArgs.add("--no-prompt");
2170    connectionArgs.add("--noPropertiesFile");
2171
2172    List<String> cmdReplicationServer = new ArrayList<>();
2173    cmdReplicationServer.add(cmdName);
2174    cmdReplicationServer.add("create-replication-server");
2175    cmdReplicationServer.add("--provider-name");
2176    cmdReplicationServer.add("Multimaster Synchronization");
2177    cmdReplicationServer.add("--set");
2178    cmdReplicationServer.add("replication-port:" + userData.getReplicationOptions().getReplicationPort());
2179    cmdReplicationServer.add("--set");
2180    cmdReplicationServer.add("replication-server-id:1");
2181    cmdReplicationServer.add("--type");
2182    cmdReplicationServer.add("generic");
2183    cmdReplicationServer.addAll(connectionArgs);
2184
2185    cmdLines.add(cmdReplicationServer);
2186    return cmdLines;
2187  }
2188}
2189
2190/**
2191 * This class is used to avoid displaying the error message related to display
2192 * problems that we might have when trying to display the SplashWindow.
2193 */
2194class EmptyPrintStream extends PrintStream
2195{
2196  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
2197
2198  /** Default constructor. */
2199  public EmptyPrintStream()
2200  {
2201    super(new ByteArrayOutputStream(), true);
2202  }
2203
2204  @Override
2205  public void println(String msg)
2206  {
2207    logger.info(LocalizableMessage.raw("EmptyStream msg: " + msg));
2208  }
2209}