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.installer;
018
019import static com.forgerock.opendj.cli.Utils.*;
020import static com.forgerock.opendj.util.OperatingSystem.*;
021
022import static org.opends.messages.QuickSetupMessages.*;
023import static org.opends.quicksetup.Installation.*;
024import static org.opends.quicksetup.util.Utils.*;
025import static org.opends.server.types.ExistingFileBehavior.*;
026
027import java.io.BufferedReader;
028import java.io.BufferedWriter;
029import java.io.Closeable;
030import java.io.File;
031import java.io.FileInputStream;
032import java.io.FileReader;
033import java.io.FileWriter;
034import java.io.IOException;
035import java.io.InputStreamReader;
036import java.util.ArrayList;
037import java.util.Arrays;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.List;
041import java.util.Map;
042import java.util.Properties;
043import java.util.Random;
044import java.util.Set;
045import java.util.TreeSet;
046
047import org.forgerock.i18n.LocalizableMessage;
048import org.forgerock.i18n.LocalizedIllegalArgumentException;
049import org.forgerock.i18n.slf4j.LocalizedLogger;
050import org.forgerock.opendj.config.ManagedObjectDefinition;
051import org.forgerock.opendj.config.ManagedObjectNotFoundException;
052import org.forgerock.opendj.config.PropertyException;
053import org.forgerock.opendj.config.server.ConfigException;
054import org.forgerock.opendj.ldap.DN;
055import org.forgerock.opendj.server.config.client.BackendCfgClient;
056import org.forgerock.opendj.server.config.client.CryptoManagerCfgClient;
057import org.forgerock.opendj.server.config.client.ReplicationDomainCfgClient;
058import org.forgerock.opendj.server.config.client.ReplicationServerCfgClient;
059import org.forgerock.opendj.server.config.client.ReplicationSynchronizationProviderCfgClient;
060import org.forgerock.opendj.server.config.client.RootCfgClient;
061import org.forgerock.opendj.server.config.meta.BackendCfgDefn;
062import org.forgerock.opendj.server.config.meta.ReplicationDomainCfgDefn;
063import org.forgerock.opendj.server.config.meta.ReplicationServerCfgDefn;
064import org.forgerock.opendj.server.config.meta.ReplicationSynchronizationProviderCfgDefn;
065import org.forgerock.opendj.server.config.server.BackendCfg;
066import org.opends.admin.ads.util.ConnectionWrapper;
067import org.opends.guitools.controlpanel.util.Utilities;
068import org.opends.messages.BackendMessages;
069import org.opends.messages.CoreMessages;
070import org.opends.messages.ReplicationMessages;
071import org.opends.quicksetup.Application;
072import org.opends.quicksetup.ApplicationException;
073import org.opends.quicksetup.JavaArguments;
074import org.opends.quicksetup.ReturnCode;
075import org.opends.quicksetup.UserData;
076import org.opends.quicksetup.util.OutputReader;
077import org.opends.quicksetup.util.Utils;
078import org.opends.server.backends.task.TaskState;
079import org.opends.server.core.DirectoryServer;
080import org.opends.server.tools.ConfigureDS;
081import org.opends.server.tools.ConfigureWindowsService;
082import org.opends.server.tools.JavaPropertiesTool;
083import org.opends.server.types.DirectoryException;
084import org.opends.server.types.LDIFExportConfig;
085import org.opends.server.types.OpenDsException;
086import org.opends.server.util.LDIFException;
087import org.opends.server.util.LDIFWriter;
088import org.opends.server.util.SetupUtils;
089import org.opends.server.util.StaticUtils;
090
091/**
092 * This is the only class that uses classes in org.opends.server (excluding the
093 * case of DynamicConstants, SetupUtils and CertificateManager
094 * which are already included in quicksetup.jar).
095 *
096 * Important note: do not include references to this class until OpenDS.jar has
097 * been loaded. These classes must be loaded during Runtime.
098 * The code is written in a way that when we execute the code that uses these
099 * classes the required jar files are already loaded. However these jar files
100 * are not necessarily loaded when we create this class.
101 */
102public class InstallerHelper {
103  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
104
105  private static final int MAX_ID_VALUE = Short.MAX_VALUE;
106  private static final long ONE_MEGABYTE = 1024L * 1024;
107
108  /**
109   * Invokes the method ConfigureDS.configMain with the provided parameters.
110   * @param args the arguments to be passed to ConfigureDS.configMain.
111   * @return the return code of the ConfigureDS.configMain method.
112   * @throws ApplicationException if something goes wrong.
113   * @see org.opends.server.tools.ConfigureDS#configMain(String[],
114   *                                java.io.OutputStream, java.io.OutputStream)
115   */
116  public int invokeConfigureServer(String[] args) throws ApplicationException {
117    return ConfigureDS.configMain(args, System.out, System.err);
118  }
119
120  /**
121   * Invokes the import-ldif command-line with the provided parameters.
122   *
123   * @param application
124   *          the application that is launching this.
125   * @param args
126   *          the arguments to be passed to import-ldif.
127   * @return the return code of the import-ldif call.
128   * @throws IOException
129   *           if the process could not be launched.
130   * @throws InterruptedException
131   *           if the process was interrupted.
132   */
133  public int invokeImportLDIF(final Application application, String[] args) throws IOException, InterruptedException
134  {
135    final File installPath = new File(application.getInstallationPath());
136    final File importLDIFPath = getImportPath(installPath);
137
138    final ArrayList<String> argList = new ArrayList<>();
139    argList.add(Utils.getScriptPath(importLDIFPath.getAbsolutePath()));
140    argList.addAll(Arrays.asList(args));
141    logger.info(LocalizableMessage.raw("import-ldif arg list: " + argList));
142
143    final ProcessBuilder processBuilder = new ProcessBuilder(argList.toArray(new String[argList.size()]));
144    final Map<String, String> env = processBuilder.environment();
145    env.remove(SetupUtils.OPENDJ_JAVA_HOME);
146    env.remove(SetupUtils.OPENDJ_JAVA_ARGS);
147    env.remove("CLASSPATH");
148    processBuilder.directory(installPath);
149
150    Process process = null;
151    try
152    {
153      process = processBuilder.start();
154      final BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
155      new OutputReader(err)
156      {
157        @Override
158        public void processLine(final String line)
159        {
160          logger.warn(LocalizableMessage.raw("import-ldif error log: " + line));
161          application.notifyListeners(LocalizableMessage.raw(line));
162          application.notifyListeners(application.getLineBreak());
163        }
164      };
165
166      final BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()));
167      new OutputReader(out)
168      {
169        @Override
170        public void processLine(final String line)
171        {
172          logger.info(LocalizableMessage.raw("import-ldif out log: " + line));
173          application.notifyListeners(LocalizableMessage.raw(line));
174          application.notifyListeners(application.getLineBreak());
175        }
176      };
177
178      return process.waitFor();
179    }
180    finally
181    {
182      if (process != null)
183      {
184        closeProcessStream(process.getErrorStream(), "error");
185        closeProcessStream(process.getOutputStream(), "output");
186      }
187    }
188  }
189
190  private File getImportPath(final File installPath)
191  {
192    if (isWindows())
193    {
194      return buildImportPath(installPath, WINDOWS_BINARIES_PATH_RELATIVE, WINDOWS_IMPORT_LDIF);
195    }
196    return buildImportPath(installPath, UNIX_BINARIES_PATH_RELATIVE, UNIX_IMPORT_LDIF);
197  }
198
199  private File buildImportPath(final File installPath, String binDir, String importLdif)
200  {
201    final File binPath = new File(installPath, binDir);
202    return new File(binPath, importLdif);
203  }
204
205  private void closeProcessStream(final Closeable stream, final String streamName)
206  {
207    try
208    {
209      stream.close();
210    }
211    catch (Throwable t)
212    {
213      logger.warn(LocalizableMessage.raw("Error closing " + streamName + " stream: " + t, t));
214    }
215  }
216
217  /**
218   * Returns the LocalizableMessage ID that corresponds to a successfully started server.
219   * @return the LocalizableMessage ID that corresponds to a successfully started server.
220   */
221  public String getStartedId()
222  {
223    return String.valueOf(CoreMessages.NOTE_DIRECTORY_SERVER_STARTED.ordinal());
224  }
225
226  /**
227   * This methods enables this server as a Windows service.
228   * @throws ApplicationException if something goes wrong.
229   */
230  public void enableWindowsService() throws ApplicationException {
231    int code = ConfigureWindowsService.enableService(System.out, System.err);
232
233    LocalizableMessage errorMessage = INFO_ERROR_ENABLING_WINDOWS_SERVICE.get();
234
235    switch (code) {
236      case
237        ConfigureWindowsService.SERVICE_ENABLE_SUCCESS:
238        break;
239      case
240        ConfigureWindowsService.SERVICE_ALREADY_ENABLED:
241        break;
242      default:
243        throw new ApplicationException(
244            ReturnCode.WINDOWS_SERVICE_ERROR,
245                errorMessage, null);
246    }
247  }
248
249  /**
250   * This method disables this server as a Windows service.
251   * @throws ApplicationException if something goes worong.
252   */
253  public void disableWindowsService() throws ApplicationException
254  {
255    int code = ConfigureWindowsService.disableService(System.out, System.err);
256    if (code == ConfigureWindowsService.SERVICE_DISABLE_ERROR) {
257      throw new ApplicationException(
258          // TODO: fix this message's format string
259          ReturnCode.WINDOWS_SERVICE_ERROR,
260              INFO_ERROR_DISABLING_WINDOWS_SERVICE.get(""), null);
261    }
262  }
263
264  /**
265   * Creates a template LDIF file with an entry that has as dn the provided
266   * baseDn.
267   * @param baseDn the dn of the entry that will be created in the LDIF file.
268   * @return the File object pointing to the created temporary file.
269   * @throws ApplicationException if something goes wrong.
270   */
271  public File createBaseEntryTempFile(String baseDn)
272          throws ApplicationException {
273    File ldifFile;
274    try
275    {
276      ldifFile = File.createTempFile("opendj-base-entry", ".ldif");
277      ldifFile.deleteOnExit();
278    } catch (IOException ioe)
279    {
280      LocalizableMessage failedMsg =
281              getThrowableMsg(INFO_ERROR_CREATING_TEMP_FILE.get(), ioe);
282      throw new ApplicationException(
283          ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
284          failedMsg, ioe);
285    }
286
287    LDIFExportConfig exportConfig = new LDIFExportConfig(ldifFile.getAbsolutePath(), OVERWRITE);
288    try (LDIFWriter writer = new LDIFWriter(exportConfig)) {
289      DN dn = DN.valueOf(baseDn);
290      writer.writeEntry(StaticUtils.createEntry(dn));
291    } catch (LocalizedIllegalArgumentException | LDIFException | IOException de) {
292      throw new ApplicationException(
293          ReturnCode.CONFIGURATION_ERROR,
294              getThrowableMsg(INFO_ERROR_IMPORTING_LDIF.get(), de), de);
295    } catch (Throwable t) {
296      throw new ApplicationException(
297          ReturnCode.BUG, getThrowableMsg(
298              INFO_BUG_MSG.get(), t), t);
299    }
300    return ldifFile;
301  }
302
303  /**
304   * Deletes a backend on the server.  It assumes the server is stopped.
305   * @param backendName the name of the backend to be deleted.
306   * @throws ApplicationException if something goes wrong.
307   */
308  public void deleteBackend(String backendName)
309  throws ApplicationException
310  {
311    try
312    {
313      // Read the configuration file.
314      DN dn = DN.valueOf("ds-cfg-backend-id" + "=" + backendName + ",cn=Backends,cn=config");
315      Utilities.deleteConfigSubtree(DirectoryServer.getConfigurationHandler(), dn);
316    }
317    catch (OpenDsException | ConfigException ode)
318    {
319      throw new ApplicationException(
320          ReturnCode.CONFIGURATION_ERROR, ode.getMessageObject(), ode);
321    }
322  }
323
324  /**
325   * Creates a database backend on the server.
326   *
327   * @param conn
328   *          the connection to the server.
329   * @param backendName
330   *          the name of the backend to be created.
331   * @param baseDNs
332   *          the list of base DNs to be defined on the server.
333   * @param backendType
334   *          the backend type.
335   * @throws ApplicationException
336   *           if something goes wrong.
337   */
338  public void createBackend(ConnectionWrapper conn, String backendName, Set<String> baseDNs,
339      ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendType)
340      throws ApplicationException
341  {
342    try
343    {
344      RootCfgClient root = conn.getRootConfiguration();
345      BackendCfgClient backend = root.createBackend(backendType, backendName, null);
346      backend.setEnabled(true);
347      backend.setBaseDN(toDNs(baseDNs));
348      backend.setBackendId(backendName);
349      backend.setWritabilityMode(BackendCfgDefn.WritabilityMode.ENABLED);
350      backend.commit();
351    }
352    catch (Throwable t)
353    {
354      throw new ApplicationException(
355          ReturnCode.CONFIGURATION_ERROR, INFO_ERROR_CONFIGURING_REMOTE_GENERIC.get(conn.getHostPort(), t), t);
356    }
357  }
358
359  private Set<DN> toDNs(Set<String> strings) throws DirectoryException
360  {
361    Set<DN> results = new HashSet<>();
362    for (String s : strings)
363    {
364      results.add(DN.valueOf(s));
365    }
366    return results;
367  }
368
369  /**
370   * Configures the replication on a given server.
371   * @param conn the connection to the server where we want to configure
372   * the replication.
373   * @param replicationServers a Map where the key value is the base dn and
374   * the value is the list of replication servers for that base dn (or domain).
375   * @param replicationPort the replicationPort of the server that is being
376   * configured (it might not exist and the user specified it in the setup).
377   * @param useSecureReplication whether to encrypt connections with the
378   * replication port or not.
379   * @param usedReplicationServerIds the list of replication server ids that
380   * are already used.
381   * @param usedServerIds the list of server ids (domain ids) that
382   * are already used.
383   * @throws ApplicationException if something goes wrong.
384   * @return a ConfiguredReplication object describing what has been configured.
385   */
386  public ConfiguredReplication configureReplication(
387      ConnectionWrapper conn, Map<String,Set<String>> replicationServers,
388      int replicationPort, boolean useSecureReplication, Set<Integer> usedReplicationServerIds,
389      Set<Integer> usedServerIds)
390  throws ApplicationException
391  {
392    boolean synchProviderCreated;
393    boolean synchProviderEnabled;
394    boolean replicationServerCreated;
395    boolean secureReplicationEnabled;
396    try
397    {
398      RootCfgClient root = conn.getRootConfiguration();
399
400      /*
401       * Configure Synchronization plugin.
402       */
403      ReplicationSynchronizationProviderCfgClient sync = null;
404      try
405      {
406        sync = (ReplicationSynchronizationProviderCfgClient)
407        root.getSynchronizationProvider("Multimaster Synchronization");
408      }
409      catch (ManagedObjectNotFoundException monfe)
410      {
411        // It does not exist.
412      }
413      if (sync == null)
414      {
415        ReplicationSynchronizationProviderCfgDefn provider =
416          ReplicationSynchronizationProviderCfgDefn.getInstance();
417        sync = root.createSynchronizationProvider(provider,
418            "Multimaster Synchronization",
419            new ArrayList<PropertyException>());
420        sync.setJavaClass(
421            org.opends.server.replication.plugin.MultimasterReplication.class.
422            getName());
423        sync.setEnabled(Boolean.TRUE);
424        synchProviderCreated = true;
425        synchProviderEnabled = false;
426      }
427      else
428      {
429        synchProviderCreated = false;
430        if (!sync.isEnabled())
431        {
432          sync.setEnabled(Boolean.TRUE);
433          synchProviderEnabled = true;
434        }
435        else
436        {
437          synchProviderEnabled = false;
438        }
439      }
440      sync.commit();
441
442      /*
443       * Configure the replication server.
444       */
445      ReplicationServerCfgClient replicationServer;
446
447      if (!sync.hasReplicationServer())
448      {
449        if (useSecureReplication)
450        {
451         CryptoManagerCfgClient crypto = root.getCryptoManager();
452         if (!crypto.isSSLEncryption())
453         {
454           crypto.setSSLEncryption(true);
455           crypto.commit();
456           secureReplicationEnabled = true;
457         }
458         else
459         {
460           // Only mark as true if we actually change the configuration
461           secureReplicationEnabled = false;
462         }
463        }
464        else
465        {
466          secureReplicationEnabled = false;
467        }
468        int id = getReplicationId(usedReplicationServerIds);
469        usedReplicationServerIds.add(id);
470        replicationServer = sync.createReplicationServer(
471            ReplicationServerCfgDefn.getInstance(),
472            new ArrayList<PropertyException>());
473        replicationServer.setReplicationServerId(id);
474        replicationServer.setReplicationPort(replicationPort);
475        replicationServerCreated = true;
476      }
477      else
478      {
479        secureReplicationEnabled = false;
480        replicationServer = sync.getReplicationServer();
481        usedReplicationServerIds.add(
482            replicationServer.getReplicationServerId());
483        replicationServerCreated = false;
484      }
485
486      Set<String> servers = replicationServer.getReplicationServer();
487      if (servers == null)
488      {
489        servers = new HashSet<>();
490      }
491      Set<String> oldServers = new HashSet<>(servers);
492      for (Set<String> rs : replicationServers.values())
493      {
494        servers.addAll(rs);
495      }
496
497      replicationServer.setReplicationServer(servers);
498      replicationServer.commit();
499
500      Set<String> newReplicationServers = intersect(servers, oldServers);
501
502      /*
503       * Create the domains
504       */
505      String[] domainNames = sync.listReplicationDomains();
506      if (domainNames == null)
507      {
508        domainNames = new String[]{};
509      }
510      Set<ConfiguredDomain> domainsConf = new HashSet<>();
511      ReplicationDomainCfgClient[] domains =
512        new ReplicationDomainCfgClient[domainNames.length];
513      for (int i=0; i<domains.length; i++)
514      {
515        domains[i] = sync.getReplicationDomain(domainNames[i]);
516      }
517      for (String dn : replicationServers.keySet())
518      {
519        ReplicationDomainCfgClient domain = null;
520        boolean isCreated;
521        String domainName = null;
522        for (int i = 0; i < domains.length && domain == null; i++)
523        {
524          if (areDnsEqual(dn,
525              domains[i].getBaseDN().toString()))
526          {
527            domain = domains[i];
528            domainName = domainNames[i];
529          }
530        }
531        if (domain == null)
532        {
533          int domainId = getReplicationId(usedServerIds);
534          usedServerIds.add(domainId);
535          domainName = getDomainName(domainNames, dn);
536          domain = sync.createReplicationDomain(
537              ReplicationDomainCfgDefn.getInstance(), domainName,
538              new ArrayList<PropertyException>());
539          domain.setServerId(domainId);
540          domain.setBaseDN(DN.valueOf(dn));
541          isCreated = true;
542        }
543        else
544        {
545          isCreated = false;
546        }
547        oldServers = domain.getReplicationServer();
548        if (oldServers == null)
549        {
550          oldServers = new TreeSet<>();
551        }
552        servers = replicationServers.get(dn);
553        domain.setReplicationServer(servers);
554        usedServerIds.add(domain.getServerId());
555
556        domain.commit();
557        Set<String> addedServers = intersect(servers, oldServers);
558        ConfiguredDomain domainConf = new ConfiguredDomain(domainName,
559            isCreated, addedServers);
560        domainsConf.add(domainConf);
561      }
562      return new ConfiguredReplication(synchProviderCreated,
563          synchProviderEnabled, replicationServerCreated,
564          secureReplicationEnabled, newReplicationServers,
565          domainsConf);
566    }
567    catch (Throwable t)
568    {
569      throw new ApplicationException(
570          ReturnCode.CONFIGURATION_ERROR,
571          INFO_ERROR_CONFIGURING_REMOTE_GENERIC.get(conn.getHostPort(), t),
572          t);
573    }
574  }
575
576  private Set<String> intersect(Set<String> set1, Set<String> set2)
577  {
578    Set<String> result = new TreeSet<>(set1);
579    result.removeAll(set2);
580    return result;
581  }
582
583  /**
584   * Configures the replication on a given server.
585   *
586   * @param conn
587   *          the connection to the server where we want to configure the
588   *          replication.
589   * @param replConf
590   *          the object describing what was configured.
591   * @throws ApplicationException
592   *           if something goes wrong.
593   */
594  public void unconfigureReplication(ConnectionWrapper conn, ConfiguredReplication replConf) throws ApplicationException
595  {
596    try
597    {
598      RootCfgClient root = conn.getRootConfiguration();
599      final String syncProvider = "Multimaster Synchronization";
600      // Unconfigure Synchronization plugin.
601      if (replConf.isSynchProviderCreated())
602      {
603        try
604        {
605          root.removeSynchronizationProvider(syncProvider);
606        }
607        catch (ManagedObjectNotFoundException monfe)
608        {
609          // It does not exist.
610        }
611      }
612      else
613      {
614        try
615        {
616          ReplicationSynchronizationProviderCfgClient sync =
617              (ReplicationSynchronizationProviderCfgClient) root.getSynchronizationProvider(syncProvider);
618          if (replConf.isSynchProviderEnabled())
619          {
620            sync.setEnabled(Boolean.FALSE);
621          }
622
623          if (replConf.isReplicationServerCreated())
624          {
625            sync.removeReplicationServer();
626          }
627          else if (sync.hasReplicationServer())
628          {
629            ReplicationServerCfgClient replicationServer = sync.getReplicationServer();
630            Set<String> replServers = replicationServer.getReplicationServer();
631            if (replServers != null)
632            {
633              replServers.removeAll(replConf.getNewReplicationServers());
634              replicationServer.setReplicationServer(replServers);
635              replicationServer.commit();
636            }
637          }
638
639          for (ConfiguredDomain domain : replConf.getDomainsConf())
640          {
641            if (domain.isCreated())
642            {
643              sync.removeReplicationDomain(domain.getDomainName());
644            }
645            else
646            {
647              try
648              {
649                ReplicationDomainCfgClient d = sync.getReplicationDomain(domain.getDomainName());
650                Set<String> replServers = d.getReplicationServer();
651                if (replServers != null)
652                {
653                  replServers.removeAll(domain.getAddedReplicationServers());
654                  d.setReplicationServer(replServers);
655                  d.commit();
656                }
657              }
658              catch (ManagedObjectNotFoundException monfe)
659              {
660                // It does not exist.
661              }
662            }
663          }
664          sync.commit();
665        }
666        catch (ManagedObjectNotFoundException monfe)
667        {
668          // It does not exist.
669        }
670      }
671
672      if (replConf.isSecureReplicationEnabled())
673      {
674        CryptoManagerCfgClient crypto = root.getCryptoManager();
675        if (crypto.isSSLEncryption())
676        {
677          crypto.setSSLEncryption(false);
678          crypto.commit();
679        }
680      }
681    }
682    catch (Throwable t)
683    {
684      throw new ApplicationException(ReturnCode.CONFIGURATION_ERROR, INFO_ERROR_CONFIGURING_REMOTE_GENERIC.get(
685          conn.getHostPort(), t), t);
686    }
687  }
688
689  /**
690   * For the given state provided by a Task tells if the task is done or not.
691   *
692   * @param sState
693   *          the String representing the task state.
694   * @return <CODE>true</CODE> if the task is done and <CODE>false</CODE>
695   *         otherwise.
696   */
697  public boolean isDone(String sState)
698  {
699    return TaskState.isDone(TaskState.fromString(sState));
700  }
701
702  /**
703   * For the given state provided by a Task tells if the task is successful or
704   * not.
705   *
706   * @param sState
707   *          the String representing the task state.
708   * @return <CODE>true</CODE> if the task is successful and <CODE>false</CODE>
709   *         otherwise.
710   */
711  public boolean isSuccessful(String sState)
712  {
713    return TaskState.isSuccessful(TaskState.fromString(sState));
714  }
715
716  /**
717   * For the given state provided by a Task tells if the task is complete with
718   * errors or not.
719   *
720   * @param sState
721   *          the String representing the task state.
722   * @return <CODE>true</CODE> if the task is complete with errors and
723   *         <CODE>false</CODE> otherwise.
724   */
725  public boolean isCompletedWithErrors(String sState)
726  {
727    return TaskState.COMPLETED_WITH_ERRORS == TaskState.fromString(sState);
728  }
729
730  /**
731   * For the given state provided by a Task tells if the task is stopped by
732   * error or not.
733   *
734   * @param sState
735   *          the String representing the task state.
736   * @return <CODE>true</CODE> if the task is stopped by error and
737   *         <CODE>false</CODE> otherwise.
738   */
739  public boolean isStoppedByError(String sState)
740  {
741    return TaskState.STOPPED_BY_ERROR == TaskState.fromString(sState);
742  }
743
744  /**
745   * Tells whether the provided log message corresponds to a peers not found
746   * error during the initialization of a replica or not.
747   *
748   * @param logMsg
749   *          the log message.
750   * @return <CODE>true</CODE> if the log message corresponds to a peers not
751   *         found error during initialization and <CODE>false</CODE> otherwise.
752   */
753  public boolean isPeersNotFoundError(String logMsg)
754  {
755    return logMsg.contains("=" + ReplicationMessages.ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN.ordinal());
756  }
757
758  /**
759   * Returns the ID to be used for a new replication server or domain.
760   * @param usedIds the list of already used ids.
761   * @return the ID to be used for a new replication server or domain.
762   */
763  public static int getReplicationId(Set<Integer> usedIds)
764  {
765    Random r = new Random();
766    int id = 0;
767    while (id == 0 || usedIds.contains(id))
768    {
769      id = r.nextInt(MAX_ID_VALUE);
770    }
771    return id;
772  }
773
774  /**
775   * Returns the name to be used for a new replication domain.
776   * @param existingDomains the existing domains names.
777   * @param baseDN the base DN of the domain.
778   * @return the name to be used for a new replication domain.
779   */
780  public static String getDomainName(String[] existingDomains, String baseDN)
781  {
782    String domainName = baseDN;
783    boolean nameExists = true;
784    int j = 0;
785    while (nameExists)
786    {
787      boolean found = false;
788      for (int i=0; i<existingDomains.length && !found; i++)
789      {
790        found = existingDomains[i].equalsIgnoreCase(domainName);
791      }
792      if (found)
793      {
794        domainName = baseDN+"-"+j;
795      }
796      else
797      {
798        nameExists = false;
799      }
800      j++;
801    }
802    return domainName;
803  }
804
805  /**
806   * Writes the set-java-home file that is used by the scripts to set the java
807   * home and the java arguments.
808   *
809   * @param uData
810   *          the data provided by the user.
811   * @param installPath
812   *          where the server is installed.
813   * @throws IOException
814   *           if an error occurred writing the file.
815   */
816  public void writeSetOpenDSJavaHome(UserData uData, String installPath) throws IOException
817  {
818    String javaHome = System.getProperty("java.home");
819    if (javaHome == null || javaHome.length() == 0)
820    {
821      javaHome = System.getenv(SetupUtils.OPENDJ_JAVA_HOME);
822    }
823
824    // Try to transform things if necessary.  The following map has as key
825    // the original JavaArgument object and as value the 'transformed' JavaArgument.
826    Map<JavaArguments, JavaArguments> hmJavaArguments = new HashMap<>();
827    for (String script : uData.getScriptNamesForJavaArguments())
828    {
829      JavaArguments origJavaArguments = uData.getJavaArguments(script);
830      if (hmJavaArguments.get(origJavaArguments) == null)
831      {
832        if (Utils.supportsOption(origJavaArguments.getStringArguments(), javaHome, installPath))
833        {
834          // The argument works, so just use it.
835          hmJavaArguments.put(origJavaArguments, origJavaArguments);
836        }
837        else
838        {
839          // We have to fix it somehow: test separately memory and other
840          // arguments to see if something works.
841          JavaArguments transformedArguments = getBestEffortArguments(origJavaArguments, javaHome, installPath);
842          hmJavaArguments.put(origJavaArguments, transformedArguments);
843        }
844      }
845      // else, support is already checked.
846    }
847
848    Properties fileProperties = getJavaPropertiesFileContents(getPropertiesFileName(installPath));
849    Map<String, JavaArguments> args = new HashMap<>();
850    Map<String, String> otherProperties = new HashMap<>();
851
852    for (String script : uData.getScriptNamesForJavaArguments())
853    {
854      JavaArguments origJavaArgument = uData.getJavaArguments(script);
855      JavaArguments transformedJavaArg = hmJavaArguments.get(origJavaArgument);
856      JavaArguments defaultJavaArg = uData.getDefaultJavaArguments(script);
857
858      // Apply the following policy: overwrite the values in the file only
859      // if the values provided by the user are not the default ones.
860      String propertiesKey = getJavaArgPropertyForScript(script);
861      if (origJavaArgument.equals(defaultJavaArg) && fileProperties.containsKey(propertiesKey))
862      {
863        otherProperties.put(propertiesKey, fileProperties.getProperty(propertiesKey));
864      }
865      else
866      {
867        args.put(script, transformedJavaArg);
868      }
869    }
870
871    putBooleanPropertyFrom("overwrite-env-java-home", fileProperties, otherProperties);
872    putBooleanPropertyFrom("overwrite-env-java-args", fileProperties, otherProperties);
873
874    if (!fileProperties.containsKey("default.java-home"))
875    {
876      otherProperties.put("default.java-home", javaHome);
877    }
878
879    writeSetOpenDSJavaHome(installPath, args, otherProperties);
880  }
881
882  private void putBooleanPropertyFrom(
883      final String propertyName, final Properties propertiesSource, final Map<String, String> destMap)
884  {
885    final String propertyValue = propertiesSource.getProperty(propertyName);
886    if (propertyValue == null || !("true".equalsIgnoreCase(propertyValue) || "false".equalsIgnoreCase(propertyValue)))
887    {
888      destMap.put(propertyName, "false");
889    }
890    else
891    {
892      destMap.put("overwrite-env-java-home", propertyValue.toLowerCase());
893    }
894  }
895
896  /**
897   * Tries to figure out a new JavaArguments object that works, based on the
898   * provided JavaArguments. It is more efficient to call this method if we are
899   * sure that the provided JavaArguments object does not work.
900   *
901   * @param origJavaArguments
902   *          the java arguments that does not work.
903   * @param javaHome
904   *          the java home to be used to test the java arguments.
905   * @param installPath
906   *          the install path.
907   * @return a working JavaArguments object.
908   */
909  private JavaArguments getBestEffortArguments(JavaArguments origJavaArguments, String javaHome, String installPath)
910  {
911    JavaArguments memArgs = new JavaArguments();
912    memArgs.setInitialMemory(origJavaArguments.getInitialMemory());
913    memArgs.setMaxMemory(origJavaArguments.getMaxMemory());
914    String m = memArgs.getStringArguments();
915    boolean supportsMemory = false;
916    if (m.length() > 0)
917    {
918      supportsMemory = Utils.supportsOption(m, javaHome, installPath);
919    }
920
921    JavaArguments additionalArgs = new JavaArguments();
922    additionalArgs.setAdditionalArguments(origJavaArguments.getAdditionalArguments());
923    String a = additionalArgs.getStringArguments();
924    boolean supportsAdditional = false;
925    if (a.length() > 0)
926    {
927      supportsAdditional = Utils.supportsOption(a, javaHome, installPath);
928    }
929
930    JavaArguments javaArgs = new JavaArguments();
931    if (supportsMemory)
932    {
933      javaArgs.setInitialMemory(origJavaArguments.getInitialMemory());
934      javaArgs.setMaxMemory(origJavaArguments.getMaxMemory());
935    }
936    else
937    {
938      // Try to figure out a smaller amount of memory.
939      long currentMaxMemory = Runtime.getRuntime().maxMemory();
940      int maxMemory = origJavaArguments.getMaxMemory();
941      if (maxMemory != -1)
942      {
943        maxMemory = maxMemory / 2;
944        while (ONE_MEGABYTE * maxMemory < currentMaxMemory
945            && !Utils.supportsOption(JavaArguments.getMaxMemoryArgument(maxMemory), javaHome, installPath))
946        {
947          maxMemory = maxMemory / 2;
948        }
949
950        if (ONE_MEGABYTE * maxMemory > currentMaxMemory)
951        {
952          // Supports this option.
953          javaArgs.setMaxMemory(maxMemory);
954        }
955      }
956    }
957    if (supportsAdditional)
958    {
959      javaArgs.setAdditionalArguments(origJavaArguments.getAdditionalArguments());
960    }
961    return javaArgs;
962  }
963
964  private List<String> getJavaPropertiesFileComments(String propertiesFile) throws IOException
965  {
966    ArrayList<String> commentLines = new ArrayList<>();
967    BufferedReader reader = new BufferedReader(new FileReader(propertiesFile));
968    String line;
969    while ((line = reader.readLine()) != null)
970    {
971      String trimmedLine = line.trim();
972      if (trimmedLine.startsWith("#") || trimmedLine.length() == 0)
973      {
974        commentLines.add(line);
975      }
976      else
977      {
978        break;
979      }
980    }
981    return commentLines;
982  }
983
984  private Properties getJavaPropertiesFileContents(String propertiesFile) throws IOException
985  {
986    Properties fileProperties = new Properties();
987    try (FileInputStream fs = new FileInputStream(propertiesFile))
988    {
989      fileProperties.load(fs);
990    }
991    catch (Throwable t)
992    { /* do nothing */
993    }
994    return fileProperties;
995  }
996
997  private String getPropertiesFileName(String installPath)
998  {
999    String configDir = Utils.getPath(
1000        Utils.getInstancePathFromInstallPath(installPath), CONFIG_PATH_RELATIVE);
1001    return Utils.getPath(configDir, DEFAULT_JAVA_PROPERTIES_FILE);
1002  }
1003
1004  /**
1005   * Writes the set-java-home file that is used by the scripts to set the java
1006   * home and the java arguments. Since the set-java-home file is created and
1007   * may be changed, it's created under the instancePath.
1008   *
1009   * @param installPath
1010   *          the install path of the server.
1011   * @param arguments
1012   *          a Map containing as key the name of the script and as value, the
1013   *          java arguments to be set for the script.
1014   * @param otherProperties
1015   *          other properties that must be set in the file.
1016   * @throws IOException
1017   *           if an error occurred writing the file.
1018   */
1019  private void writeSetOpenDSJavaHome(String installPath, Map<String, JavaArguments> arguments,
1020      Map<String, String> otherProperties) throws IOException
1021  {
1022    String propertiesFile = getPropertiesFileName(installPath);
1023    List<String> commentLines = getJavaPropertiesFileComments(propertiesFile);
1024    try (BufferedWriter writer = new BufferedWriter(new FileWriter(propertiesFile, false)))
1025    {
1026      for (String line: commentLines)
1027      {
1028        writer.write(line);
1029        writer.newLine();
1030      }
1031
1032      for (String key : otherProperties.keySet())
1033      {
1034        writer.write(key + "=" + otherProperties.get(key));
1035        writer.newLine();
1036      }
1037
1038      for (String scriptName : arguments.keySet())
1039      {
1040        String argument = arguments.get(scriptName).getStringArguments();
1041        writer.newLine();
1042        writer.write(getJavaArgPropertyForScript(scriptName) + "=" + argument);
1043      }
1044    }
1045
1046    String libDir = Utils.getPath(
1047        Utils.getInstancePathFromInstallPath(installPath), LIBRARIES_PATH_RELATIVE);
1048    // Create directory if it doesn't exist yet
1049    File fLib = new File(libDir);
1050    if (!fLib.exists())
1051    {
1052      fLib.mkdir();
1053    }
1054    final String destinationFile = Utils.getPath(libDir, isWindows() ? SET_JAVA_PROPERTIES_FILE_WINDOWS
1055                                                                     : SET_JAVA_PROPERTIES_FILE_UNIX);
1056    // Launch the script
1057    int returnValue = JavaPropertiesTool.mainCLI(
1058        "--propertiesFile", propertiesFile, "--destinationFile", destinationFile, "--quiet");
1059    if (JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL.getReturnCode() != returnValue &&
1060        JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode() != returnValue)
1061    {
1062      logger.warn(LocalizableMessage.raw("Error creating java home scripts, error code: " + returnValue));
1063      throw new IOException(ERR_ERROR_CREATING_JAVA_HOME_SCRIPTS.get(returnValue).toString());
1064    }
1065  }
1066
1067  /**
1068   * Returns the java argument property for a given script.
1069   *
1070   * @param scriptName
1071   *          the script name.
1072   * @return the java argument property for a given script.
1073   */
1074  private static String getJavaArgPropertyForScript(String scriptName)
1075  {
1076    return scriptName + ".java-args";
1077  }
1078
1079  /**
1080   * If the log message is of type "[03/Apr/2008:21:25:43 +0200] category=JEB
1081   * severity=NOTICE msgID=8847454 Processed 1 entries, imported 0, skipped 1,
1082   * rejected 0 and migrated 0 in 1 seconds (average rate 0.0/sec)" returns the
1083   * message part. Returns <CODE>null</CODE> otherwise.
1084   *
1085   * @param msg
1086   *          the message to be parsed.
1087   * @return the parsed import message.
1088   */
1089  public String getImportProgressMessage(String msg)
1090  {
1091    if (msg != null && (msg.contains("msgID=" + BackendMessages.NOTE_IMPORT_FINAL_STATUS.ordinal())
1092                        || msg.contains("msgID=" + BackendMessages.NOTE_IMPORT_PROGRESS_REPORT.ordinal())))
1093    {
1094      int index = msg.indexOf("msg=");
1095      if (index != -1)
1096      {
1097        return msg.substring(index + 4);
1098      }
1099    }
1100    return null;
1101  }
1102}