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