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