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 2008-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel.task;
028
029import static com.forgerock.opendj.cli.Utils.*;
030import static com.forgerock.opendj.util.OperatingSystem.*;
031
032import static org.opends.messages.AdminToolMessages.*;
033
034import java.io.File;
035import java.util.ArrayList;
036import java.util.Collection;
037import java.util.List;
038import java.util.Map;
039import java.util.Objects;
040import java.util.Set;
041
042import javax.naming.NamingException;
043import javax.naming.directory.Attribute;
044import javax.naming.directory.DirContext;
045import javax.naming.directory.ModificationItem;
046import javax.naming.ldap.InitialLdapContext;
047
048import org.forgerock.i18n.LocalizableMessage;
049import org.forgerock.opendj.ldap.ByteString;
050import org.opends.admin.ads.util.ConnectionUtils;
051import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
052import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
053import org.opends.guitools.controlpanel.event.ConfigurationElementCreatedEvent;
054import org.opends.guitools.controlpanel.event.ConfigurationElementCreatedListener;
055import org.opends.guitools.controlpanel.event.PrintStreamListener;
056import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
057import org.opends.guitools.controlpanel.ui.ProgressDialog;
058import org.opends.guitools.controlpanel.util.ApplicationPrintStream;
059import org.opends.guitools.controlpanel.util.ConfigReader;
060import org.opends.guitools.controlpanel.util.ProcessReader;
061import org.opends.guitools.controlpanel.util.Utilities;
062import org.opends.quicksetup.Installation;
063import org.opends.quicksetup.UserData;
064import org.opends.server.types.DN;
065import org.opends.server.types.Schema;
066import org.opends.server.util.Base64;
067import org.opends.server.util.SetupUtils;
068
069import com.forgerock.opendj.cli.CommandBuilder;
070
071/**
072 * The class used to define a number of common methods and mechanisms for the
073 * tasks that are run in the Control Panel.
074 */
075public abstract class Task
076{
077  private static String localHostName = UserData.getDefaultHostName();
078  private String binDir;
079  /**
080   * The different task types.
081   */
082  public enum Type
083  {
084    /**
085     * New Base DN creation.
086     */
087    NEW_BASEDN,
088    /**
089     * New index creation.
090     */
091    NEW_INDEX,
092    /**
093     * Modification of indexes.
094     */
095    MODIFY_INDEX,
096    /**
097     * Deletion of indexes.
098     */
099    DELETE_INDEX,
100    /**
101     * Creation of VLV indexes.
102     */
103    NEW_VLV_INDEX,
104    /**
105     * Modification of VLV indexes.
106     */
107    MODIFY_VLV_INDEX,
108    /**
109     * Deletion of VLV indexes.
110     */
111    DELETE_VLV_INDEX,
112    /**
113     * Import of an LDIF file.
114     */
115    IMPORT_LDIF,
116    /**
117     * Export of an LDIF file.
118     */
119    EXPORT_LDIF,
120    /**
121     * Backup.
122     */
123    BACKUP,
124    /**
125     * Restore.
126     */
127    RESTORE,
128    /**
129     * Verification of indexes.
130     */
131    VERIFY_INDEXES,
132    /**
133     * Rebuild of indexes.
134     */
135    REBUILD_INDEXES,
136    /**
137     * Enabling of Windows Service.
138     */
139    ENABLE_WINDOWS_SERVICE,
140    /**
141     * Disabling of Windows Service.
142     */
143    DISABLE_WINDOWS_SERVICE,
144    /**
145     * Starting the server.
146     */
147    START_SERVER,
148    /**
149     * Stopping the server.
150     */
151    STOP_SERVER,
152    /**
153     * Updating the java settings for the different command-lines.
154     */
155    JAVA_SETTINGS_UPDATE,
156    /**
157     * Creating a new element in the schema.
158     */
159    NEW_SCHEMA_ELEMENT,
160    /**
161     * Deleting an schema element.
162     */
163    DELETE_SCHEMA_ELEMENT,
164    /**
165     * Modify an schema element.
166     */
167    MODIFY_SCHEMA_ELEMENT,
168    /**
169     * Modifying an entry.
170     */
171    MODIFY_ENTRY,
172    /**
173     * Creating an entry.
174     */
175    NEW_ENTRY,
176    /**
177     * Deleting an entry.
178     */
179    DELETE_ENTRY,
180    /**
181     * Deleting a base DN.
182     */
183    DELETE_BASEDN,
184    /**
185     * Deleting a backend.
186     */
187    DELETE_BACKEND,
188    /**
189     * Other task.
190     */
191    OTHER
192  }
193
194  /**
195   * The state on which the task can be.
196   */
197  public enum State
198  {
199    /**
200     * The task is not started.
201     */
202    NOT_STARTED,
203    /**
204     * The task is running.
205     */
206    RUNNING,
207    /**
208     * The task finished successfully.
209     */
210    FINISHED_SUCCESSFULLY,
211    /**
212     * The task finished with error.
213     */
214    FINISHED_WITH_ERROR
215  }
216
217  /**
218   * Returns the names of the backends that are affected by the task.
219   * @return the names of the backends that are affected by the task.
220   */
221  public abstract Set<String> getBackends();
222
223  /**
224   * The current state of the task.
225   */
226  protected State state = State.NOT_STARTED;
227  /**
228   * The return code of the task.
229   */
230  protected Integer returnCode;
231  /**
232   * The last exception encountered during the task execution.
233   */
234  protected Throwable lastException;
235  /**
236   * The progress logs of the task.  Note that the user of StringBuffer is not
237   * a bug, because of the way the contents of logs is updated, using
238   * StringBuffer instead of StringBuilder is required.
239   */
240  protected StringBuffer logs = new StringBuffer();
241  /**
242   * The error logs of the task.
243   */
244  protected StringBuilder errorLogs = new StringBuilder();
245  /**
246   * The standard output logs of the task.
247   */
248  protected StringBuilder outputLogs = new StringBuilder();
249  /**
250   * The print stream for the error logs.
251   */
252  protected ApplicationPrintStream errorPrintStream =
253    new ApplicationPrintStream();
254  /**
255   * The print stream for the standard output logs.
256   */
257  protected ApplicationPrintStream outPrintStream =
258    new ApplicationPrintStream();
259
260  /**
261   * The process (if any) that the task launched.  For instance if this is a
262   * start server task, the process generated executing the start-ds
263   * command-line.
264   */
265  protected Process process;
266  private ControlPanelInfo info;
267
268  private ServerDescriptor server;
269
270  private ProgressDialog progressDialog;
271
272  private ArrayList<ConfigurationElementCreatedListener> confListeners = new ArrayList<>();
273
274  private static int MAX_BINARY_LENGTH_TO_DISPLAY = 1024;
275
276  /**
277   * Constructor of the task.
278   * @param info the control panel information.
279   * @param progressDialog the progress dialog where the task progress will be
280   * displayed.
281   */
282  protected Task(ControlPanelInfo info, ProgressDialog progressDialog)
283  {
284    this.info = info;
285    this.progressDialog = progressDialog;
286    outPrintStream.addListener(new PrintStreamListener()
287    {
288      /**
289       * Add a new line to the logs.
290       * @param msg the new line.
291       */
292      @Override
293      public void newLine(String msg)
294      {
295        outputLogs.append(msg).append("\n");
296        logs.append(msg).append("\n");
297      }
298    });
299    errorPrintStream.addListener(new PrintStreamListener()
300    {
301      /**
302       * Add a new line to the error logs.
303       * @param msg the new line.
304       */
305      @Override
306      public void newLine(String msg)
307      {
308        errorLogs.append(msg).append("\n");
309        logs.append(msg).append("\n");
310      }
311    });
312    server = info.getServerDescriptor();
313  }
314
315  /**
316   * Returns the ControlPanelInfo object.
317   * @return the ControlPanelInfo object.
318   */
319  public ControlPanelInfo getInfo()
320  {
321    return info;
322  }
323
324  /**
325   * Returns the logs of the task.
326   * @return the logs of the task.
327   */
328  public String getLogs()
329  {
330    return logs.toString();
331  }
332
333  /**
334   * Returns the error logs of the task.
335   * @return the error logs of the task.
336   */
337  public String getErrorLogs()
338  {
339    return errorLogs.toString();
340  }
341
342  /**
343   * Returns the output logs of the task.
344   * @return the output logs of the task.
345   */
346  public String getOutputLogs()
347  {
348    return outputLogs.toString();
349  }
350
351  /**
352   * Returns the state of the task.
353   * @return the state of the task.
354   */
355  public State getState()
356  {
357    return state;
358  }
359
360  /**
361   * Returns last exception encountered during the task execution.
362   * Returns <CODE>null</CODE> if no exception was found.
363   * @return last exception encountered during the task execution.
364   */
365  public Throwable getLastException()
366  {
367    return lastException;
368  }
369
370  /**
371   * Returns the return code (this makes sense when the task launches a
372   * command-line, it will return the error code returned by the command-line).
373   * @return the return code.
374   */
375  public Integer getReturnCode()
376  {
377    return returnCode;
378  }
379
380  /**
381   * Returns the process that the task launched.
382   * Returns <CODE>null</CODE> if not process was launched.
383   * @return the process that the task launched.
384   */
385  public Process getProcess()
386  {
387    return process;
388  }
389
390  /**
391   * Returns the progress dialog.
392   * @return the progress dialog.
393   */
394  protected ProgressDialog getProgressDialog()
395  {
396    return progressDialog;
397  }
398
399  /**
400   * Tells whether a new server descriptor should be regenerated when the task
401   * is over.  If the task has an influence in the configuration or state of
402   * the server (for instance the creation of a base DN) this method should
403   * return <CODE>true</CODE> so that the configuration will be re-read and
404   * all the ConfigChangeListeners will receive a notification with the new
405   * configuration.
406   * @return <CODE>true</CODE> if a new server descriptor must be regenerated
407   * when the task is over and <CODE>false</CODE> otherwise.
408   */
409  public boolean regenerateDescriptor()
410  {
411    return true;
412  }
413
414  /**
415   * Method that is called when everything is finished after updating the
416   * progress dialog.  It is called from the event thread.
417   */
418  public void postOperation()
419  {
420  }
421
422  /**
423   * The description of the task.  It is used in both the incompatibility
424   * messages and in the warning message displayed when the user wants to
425   * quit and there are tasks running.
426   * @return the description of the task.
427   */
428  public abstract LocalizableMessage getTaskDescription();
429
430  /**
431   * Adds a configuration element created listener.
432   * @param listener the listener.
433   */
434  public void addConfigurationElementCreatedListener(
435      ConfigurationElementCreatedListener listener)
436  {
437    confListeners.add(listener);
438  }
439
440  /**
441   * Removes a configuration element created listener.
442   * @param listener the listener.
443   */
444  public void removeConfigurationElementCreatedListener(
445      ConfigurationElementCreatedListener listener)
446  {
447    confListeners.remove(listener);
448  }
449
450  /**
451   * Notifies the configuration element created listener that a new object has
452   * been created.
453   * @param configObject the created object.
454   */
455  protected void notifyConfigurationElementCreated(Object configObject)
456  {
457    for (ConfigurationElementCreatedListener listener : confListeners)
458    {
459      listener.elementCreated(
460          new ConfigurationElementCreatedEvent(this, configObject));
461    }
462  }
463
464  /**
465   * Returns a String representation of a value.  In general this is called
466   * to display the command-line equivalent when we do a modification in an
467   * entry.  But since some attributes must be obfuscated (like the user
468   * password) we pass through this method.
469   * @param attrName the attribute name.
470   * @param o the attribute value.
471   * @return the obfuscated String representing the attribute value to be
472   * displayed in the logs of the user.
473   */
474  protected String obfuscateAttributeStringValue(String attrName, Object o)
475  {
476    if (Utilities.mustObfuscate(attrName,
477        getInfo().getServerDescriptor().getSchema()))
478    {
479      return OBFUSCATED_VALUE;
480    }
481    else if (o instanceof byte[])
482    {
483      byte[] bytes = (byte[])o;
484      if (displayBase64(attrName))
485      {
486        if (bytes.length > MAX_BINARY_LENGTH_TO_DISPLAY)
487        {
488          return INFO_CTRL_PANEL_VALUE_IN_BASE64.get().toString();
489        }
490        else
491        {
492          return Base64.encode(bytes);
493        }
494      }
495      else
496      {
497        if (bytes.length > MAX_BINARY_LENGTH_TO_DISPLAY)
498        {
499          return INFO_CTRL_PANEL_BINARY_VALUE.get().toString();
500        }
501        else
502        {
503          // Get the String value
504          ByteString v = ByteString.wrap(bytes);
505          return v.toString();
506        }
507      }
508    }
509    else
510    {
511      return String.valueOf(o);
512    }
513  }
514
515  /**
516   * Obfuscates (if required) the attribute value in an LDIF line.
517   * @param line the line of the LDIF file that must be treated.
518   * @return the line obfuscated.
519   */
520  protected String obfuscateLDIFLine(String line)
521  {
522    int index = line.indexOf(":");
523    if (index != -1)
524    {
525      String attrName = line.substring(0, index).trim();
526      if (Utilities.mustObfuscate(attrName,
527          getInfo().getServerDescriptor().getSchema()))
528      {
529        return attrName + ": " + OBFUSCATED_VALUE;
530      }
531    }
532    return line;
533  }
534
535  /**
536   * Executes a command-line synchronously.
537   * @param commandLineName the command line full path.
538   * @param args the arguments for the command-line.
539   * @return the error code returned by the command-line.
540   */
541  protected int executeCommandLine(String commandLineName, String[] args)
542  {
543    returnCode = -1;
544    String[] cmd = new String[args.length + 1];
545    cmd[0] = commandLineName;
546    System.arraycopy(args, 0, cmd, 1, args.length);
547
548    ProcessBuilder pb = new ProcessBuilder(cmd);
549    // Use the java args in the script.
550    Map<String, String> env = pb.environment();
551    //env.put(SetupUtils.OPENDJ_JAVA_ARGS, "");
552    env.remove(SetupUtils.OPENDJ_JAVA_ARGS);
553    env.remove("CLASSPATH");
554    ProcessReader outReader = null;
555    ProcessReader errReader = null;
556    try {
557      process = pb.start();
558
559      outReader = new ProcessReader(process, outPrintStream, false);
560      errReader = new ProcessReader(process, errorPrintStream, true);
561
562      outReader.startReading();
563      errReader.startReading();
564
565      returnCode = process.waitFor();
566    } catch (Throwable t)
567    {
568      lastException = t;
569    }
570    finally
571    {
572      if (outReader != null)
573      {
574        outReader.interrupt();
575      }
576      if (errReader != null)
577      {
578        errReader.interrupt();
579      }
580    }
581    return returnCode;
582  }
583
584  /**
585   * Informs of whether the task to be launched can be launched or not. Every
586   * task must implement this method so that we avoid launching in paralel two
587   * tasks that are not compatible.  Note that in general if the current task
588   * is not running this method will return <CODE>true</CODE>.
589   *
590   * @param taskToBeLaunched the Task that we are trying to launch.
591   * @param incompatibilityReasons the list of incompatibility reasons that
592   * must be updated.
593   * @return <CODE>true</CODE> if the task that we are trying to launch can be
594   * launched in parallel with this task and <CODE>false</CODE> otherwise.
595   */
596  public abstract boolean canLaunch(Task taskToBeLaunched,
597      Collection<LocalizableMessage> incompatibilityReasons);
598
599  /**
600   * Execute the task.  This method is synchronous.
601   *
602   */
603  public abstract void runTask();
604
605  /**
606   * Returns the type of the task.
607   * @return the type of the task.
608   */
609  public abstract Type getType();
610
611
612  /**
613   * Returns the binary/script directory.
614   * @return the binary/script directory.
615   */
616  protected String getBinaryDir()
617  {
618    if (binDir == null)
619    {
620      File f = Installation.getLocal().getBinariesDirectory();
621      try
622      {
623        binDir = f.getCanonicalPath();
624      }
625      catch (Throwable t)
626      {
627        binDir = f.getAbsolutePath();
628      }
629      if (binDir.lastIndexOf(File.separatorChar) != binDir.length() - 1)
630      {
631        binDir += File.separatorChar;
632      }
633    }
634
635    return binDir;
636  }
637
638  /**
639   * Check whether the provided task and this task run on the same server.
640   * @param task the task the task to be analyzed.
641   * @return <CODE>true</CODE> if both tasks run on the same server and
642   * <CODE>false</CODE> otherwise.
643   */
644  protected boolean runningOnSameServer(Task task)
645  {
646    if (getServer().isLocal() && task.getServer().isLocal())
647    {
648      return true;
649    }
650
651    // Compare the host name and the instance path. This is safer than
652    // comparing ports: we might be running locally on a stopped instance with
653    // the same configuration as a "remote" (though located on the same machine) server.
654    String host1 = getServer().getHostname();
655    String host2 = task.getServer().getHostname();
656    boolean runningOnSameServer = host1 == null ? host2 == null : host1.equalsIgnoreCase(host2);
657    if (runningOnSameServer)
658    {
659      String f1 = getServer().getInstancePath();
660      String f2 = task.getServer().getInstancePath();
661      return Objects.equals(f1, f2);
662    }
663    return runningOnSameServer;
664  }
665
666  /**
667   * Returns the server descriptor on which the task was launched.
668   * @return the server descriptor on which the task was launched.
669   */
670  public ServerDescriptor getServer()
671  {
672    return server;
673  }
674
675  /**
676   * Returns the full path of the command-line associated with this task or
677   * <CODE>null</CODE> if there is not a command-line (or a single command-line)
678   * associated with the task.
679   * @return the full path of the command-line associated with this task.
680   */
681  protected abstract String getCommandLinePath();
682
683  /**
684   * Returns the full path of the command-line for a given script name.
685   * @param scriptBasicName the script basic name (with no extension).
686   * @return the full path of the command-line for a given script name.
687   */
688  protected String getCommandLinePath(String scriptBasicName)
689  {
690    if (isWindows())
691    {
692      return getBinaryDir() + scriptBasicName + ".bat";
693    }
694    return getBinaryDir() + scriptBasicName;
695  }
696
697  /**
698   * Returns the list of command-line arguments.
699   * @return the list of command-line arguments.
700   */
701  protected abstract List<String> getCommandLineArguments();
702
703
704
705  /**
706   * Returns the list of obfuscated command-line arguments.  This is called
707   * basically to display the equivalent command-line to the user.
708   * @param clearArgs the arguments in clear.
709   * @return the list of obfuscated command-line arguments.
710   */
711  protected List<String> getObfuscatedCommandLineArguments(List<String> clearArgs)
712  {
713    String[] toObfuscate = { "--bindPassword", "--currentPassword", "--newPassword" };
714    ArrayList<String> args = new ArrayList<>(clearArgs);
715    for (int i=1; i<args.size(); i++)
716    {
717      for (String argName : toObfuscate)
718      {
719        if (args.get(i-1).equalsIgnoreCase(argName))
720        {
721          args.set(i, OBFUSCATED_VALUE);
722          break;
723        }
724      }
725    }
726    return args;
727  }
728
729  /**
730   * Returns the command-line arguments that correspond to the configuration.
731   * This method is called to remove them when we display the equivalent
732   * command-line.  In some cases we run the methods of the command-line
733   * directly (on this JVM) instead of launching the script in another process.
734   * When we call this methods we must add these arguments, but they are not
735   * to be included as arguments of the command-line (when is launched as a
736   * script).
737   * @return the command-line arguments that correspond to the configuration.
738   */
739  protected List<String> getConfigCommandLineArguments()
740  {
741    List<String> args = new ArrayList<>();
742    args.add("--configClass");
743    args.add(org.opends.server.extensions.ConfigFileHandler.class.getName());
744    args.add("--configFile");
745    args.add(ConfigReader.configFile);
746    return args;
747  }
748
749  /**
750   * Returns the list of arguments related to the connection (host, port, bind
751   * DN, etc.).
752   * @return the list of arguments related to the connection.
753   */
754  protected List<String> getConnectionCommandLineArguments()
755  {
756    return getConnectionCommandLineArguments(true, false);
757  }
758
759  /**
760   * Returns the list of arguments related to the connection (host, port, bind
761   * DN, etc.).
762   * @param useAdminConnector use the administration connector to generate
763   * the command line.
764   * @param addConnectionTypeParameters add the connection type parameters
765   * (--useSSL or --useStartTLS parameters: for ldapadd, ldapdelete, etc.).
766   * @return the list of arguments related to the connection.
767   */
768  protected List<String> getConnectionCommandLineArguments(
769      boolean useAdminConnector, boolean addConnectionTypeParameters)
770  {
771    ArrayList<String> args = new ArrayList<>();
772    InitialLdapContext ctx;
773
774    if (useAdminConnector)
775    {
776      ctx = getInfo().getDirContext();
777    }
778    else
779    {
780      ctx = getInfo().getUserDataDirContext();
781    }
782    if (isServerRunning() && ctx != null)
783    {
784      String hostName = localHostName;
785      if (hostName == null || !getInfo().getServerDescriptor().isLocal())
786      {
787        hostName = ConnectionUtils.getHostName(ctx);
788      }
789      int port = ConnectionUtils.getPort(ctx);
790      boolean isSSL = ConnectionUtils.isSSL(ctx);
791      boolean isStartTLS = ConnectionUtils.isStartTLS(ctx);
792      String bindDN = ConnectionUtils.getBindDN(ctx);
793      String bindPwd = ConnectionUtils.getBindPassword(ctx);
794      args.add("--hostName");
795      args.add(hostName);
796      args.add("--port");
797      args.add(String.valueOf(port));
798      args.add("--bindDN");
799      args.add(bindDN);
800      args.add("--bindPassword");
801      args.add(bindPwd);
802      if (isSSL || isStartTLS)
803      {
804        args.add("--trustAll");
805      }
806      if (isSSL && addConnectionTypeParameters)
807      {
808        args.add("--useSSL");
809      }
810      else if (isStartTLS && addConnectionTypeParameters)
811      {
812        args.add("--useStartTLS");
813      }
814    }
815    return args;
816  }
817
818  /**
819   * Returns the noPropertiesFile argument.
820   * @return the noPropertiesFile argument.
821   */
822  protected String getNoPropertiesFileArgument()
823  {
824    return "--noPropertiesFile";
825  }
826
827  /**
828   * Returns the command-line to be displayed (when we display the equivalent
829   * command-line).
830   * @return the command-line to be displayed.
831   */
832  public String getCommandLineToDisplay()
833  {
834    String cmdLineName = getCommandLinePath();
835    if (cmdLineName != null)
836    {
837      List<String> args =
838        getObfuscatedCommandLineArguments(getCommandLineArguments());
839      args.removeAll(getConfigCommandLineArguments());
840      return getEquivalentCommandLine(cmdLineName, args);
841    }
842    return null;
843  }
844
845  /**
846   * Commodity method to know if the server is running or not.
847   * @return <CODE>true</CODE> if the server is running and <CODE>false</CODE>
848   * otherwise.
849   */
850  protected boolean isServerRunning()
851  {
852    return getInfo().getServerDescriptor().getStatus() ==
853      ServerDescriptor.ServerStatus.STARTED;
854  }
855
856  /**
857   *
858   * Returns the print stream for the error logs.
859   * @return the print stream for the error logs.
860   */
861  public ApplicationPrintStream getErrorPrintStream()
862  {
863    return errorPrintStream;
864  }
865
866  /**
867  *
868  * Returns the print stream for the output logs.
869  * @return the print stream for the output logs.
870  */
871  public ApplicationPrintStream getOutPrintStream()
872  {
873    return outPrintStream;
874  }
875
876  /**
877   * Prints the equivalent modify command line in the progress dialog.
878   * @param dn the dn of the modified entry.
879   * @param mods the modifications.
880   * @param useAdminCtx use the administration connector.
881   */
882  protected void printEquivalentCommandToModify(DN dn,
883      Collection<ModificationItem> mods, boolean useAdminCtx)
884  {
885    printEquivalentCommandToModify(dn.toString(), mods, useAdminCtx);
886  }
887
888  /**
889   * Prints the equivalent modify command line in the progress dialog.
890   * @param dn the dn of the modified entry.
891   * @param mods the modifications.
892   * @param useAdminCtx use the administration connector.
893   */
894  protected void printEquivalentCommandToModify(String dn,
895      Collection<ModificationItem> mods, boolean useAdminCtx)
896  {
897    ArrayList<String> args = new ArrayList<>(getObfuscatedCommandLineArguments(
898        getConnectionCommandLineArguments(useAdminCtx, true)));
899    args.add(getNoPropertiesFileArgument());
900    String equiv = getEquivalentCommandLine(getCommandLinePath("ldapmodify"), args);
901
902    StringBuilder sb = new StringBuilder();
903    sb.append(INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_MODIFY.get()).append("<br><b>");
904    sb.append(equiv);
905    sb.append("<br>");
906    sb.append("dn: ").append(dn);
907    boolean firstChangeType = true;
908    for (ModificationItem mod : mods)
909    {
910      if (firstChangeType)
911      {
912        sb.append("<br>changetype: modify<br>");
913      }
914      else
915      {
916        sb.append("-<br>");
917      }
918      firstChangeType = false;
919      Attribute attr = mod.getAttribute();
920      String attrName = attr.getID();
921      if (mod.getModificationOp() == DirContext.ADD_ATTRIBUTE)
922      {
923        sb.append("add: ").append(attrName).append("<br>");
924      }
925      else if (mod.getModificationOp() == DirContext.REPLACE_ATTRIBUTE)
926      {
927        sb.append("replace: ").append(attrName).append("<br>");
928      }
929      else
930      {
931        sb.append("delete: ").append(attrName).append("<br>");
932      }
933      for (int i=0; i<attr.size(); i++)
934      {
935        try
936        {
937          Object o = attr.get(i);
938          // We are systematically adding the values in binary mode.
939          // Use the attribute names to figure out the value to be displayed.
940          if (displayBase64(attr.getID()))
941          {
942            sb.append(attrName).append(":: ");
943          }
944          else
945          {
946            sb.append(attrName).append(": ");
947          }
948          sb.append(obfuscateAttributeStringValue(attrName, o));
949          sb.append("<br>");
950        }
951        catch (NamingException ne)
952        {
953          // Bug
954          throw new RuntimeException(
955              "Unexpected error parsing modifications: "+ne, ne);
956        }
957      }
958    }
959    sb.append("</b><br><br>");
960
961    getProgressDialog().appendProgressHtml(Utilities.applyFont(
962        sb.toString(), ColorAndFontConstants.progressFont));
963  }
964
965  /**
966   * The separator used to link the lines of the resulting command-lines.
967   */
968  private static final String LINE_SEPARATOR = CommandBuilder.HTML_LINE_SEPARATOR;
969
970  /**
971   * Returns the equivalent command line in HTML without font properties.
972   * @param cmdName the command name.
973   * @param args the arguments for the command line.
974   * @return the equivalent command-line in HTML.
975   */
976  public static String getEquivalentCommandLine(String cmdName,
977      List<String> args)
978  {
979    StringBuilder sb = new StringBuilder(cmdName);
980    for (String arg : args)
981    {
982      if (arg.charAt(0) == '-')
983      {
984        sb.append(LINE_SEPARATOR);
985      }
986      else
987      {
988        sb.append(" ");
989      }
990      sb.append(CommandBuilder.escapeValue(arg));
991    }
992    return sb.toString();
993  }
994
995  /**
996   * Prints the equivalent command line.
997   * @param cmdName the command name.
998   * @param args the arguments for the command line.
999   * @param msg the message associated with the command line.
1000   */
1001  protected void printEquivalentCommandLine(String cmdName, List<String> args,
1002      LocalizableMessage msg)
1003  {
1004    getProgressDialog().appendProgressHtml(Utilities.applyFont(msg+"<br><b>"+
1005        getEquivalentCommandLine(cmdName, args)+"</b><br><br>",
1006        ColorAndFontConstants.progressFont));
1007  }
1008
1009  /**
1010   * Tells whether the provided attribute's values must be displayed using
1011   * base 64 when displaying the equivalent command-line or not.
1012   * @param attrName the attribute name.
1013   * @return <CODE>true</CODE> if the attribute must be displayed using base 64
1014   * and <CODE>false</CODE> otherwise.
1015   */
1016  protected boolean displayBase64(String attrName)
1017  {
1018    Schema schema = null;
1019    if (getInfo() != null)
1020    {
1021      schema = getInfo().getServerDescriptor().getSchema();
1022    }
1023    return Utilities.hasBinarySyntax(attrName, schema);
1024  }
1025
1026  /**
1027   * Prints the equivalent rename command line in the progress dialog.
1028   * @param oldDN the old DN of the entry.
1029   * @param newDN the new DN of the entry.
1030   * @param useAdminCtx use the administration connector.
1031   */
1032  protected void printEquivalentRenameCommand(DN oldDN, DN newDN,
1033      boolean useAdminCtx)
1034  {
1035    ArrayList<String> args = new ArrayList<>(getObfuscatedCommandLineArguments(
1036        getConnectionCommandLineArguments(useAdminCtx, true)));
1037    args.add(getNoPropertiesFileArgument());
1038    String equiv = getEquivalentCommandLine(getCommandLinePath("ldapmodify"), args);
1039    StringBuilder sb = new StringBuilder();
1040    sb.append(INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_RENAME.get()).append("<br><b>");
1041    sb.append(equiv);
1042    sb.append("<br>");
1043    sb.append("dn: ").append(oldDN);
1044    sb.append("<br>");
1045    sb.append("changetype: moddn<br>");
1046    sb.append("newrdn: ").append(newDN.rdn()).append("<br>");
1047    sb.append("deleteoldrdn: 1");
1048    sb.append("</b><br><br>");
1049    getProgressDialog().appendProgressHtml(
1050        Utilities.applyFont(sb.toString(),
1051        ColorAndFontConstants.progressFont));
1052  }
1053
1054  /**
1055   * Returns the incompatible message between two tasks.
1056   * @param taskRunning the task that is running.
1057   * @param taskToBeLaunched the task that we are trying to launch.
1058   * @return the incompatible message between two tasks.
1059   */
1060  protected LocalizableMessage getIncompatibilityMessage(Task taskRunning,
1061      Task taskToBeLaunched)
1062  {
1063    return INFO_CTRL_PANEL_INCOMPATIBLE_TASKS.get(
1064        taskRunning.getTaskDescription(), taskToBeLaunched.getTaskDescription());
1065  }
1066}