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