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