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