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