001/*
002 * Copyright 2018-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2018-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.unboundidds.tasks;
022
023
024
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.Date;
028import java.util.LinkedHashMap;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032
033import com.unboundid.ldap.sdk.Attribute;
034import com.unboundid.ldap.sdk.Entry;
035import com.unboundid.util.NotMutable;
036import com.unboundid.util.StaticUtils;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.ldap.sdk.unboundidds.tasks.TaskMessages.*;
041
042
043
044/**
045 * This class defines a Directory Server task that can be used to cause the
046 * server to execute a specified command with a given set of arguments.
047 * <BR>
048 * <BLOCKQUOTE>
049 *   <B>NOTE:</B>  This class, and other classes within the
050 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
051 *   supported for use against Ping Identity, UnboundID, and
052 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
053 *   for proprietary functionality or for external specifications that are not
054 *   considered stable or mature enough to be guaranteed to work in an
055 *   interoperable way with other types of LDAP servers.
056 * </BLOCKQUOTE>
057 * <BR>
058 * The server imposes limitation on the commands that can be executed and on the
059 * circumstances in which they can be invoked.  See the
060 * exec-command-whitelist.txt file in the server's config directory for a
061 * summary of these restrictions, and for additional information about exec
062 * tasks.
063 * <BR><BR>
064 * The properties that are available for use with this type of task include:
065 * <UL>
066 *   <LI>The absolute path to the command to execute.  This must be
067 *       provided.</LI>
068 *   <LI>An optional string with arguments to provide to the command.</LI>
069 *   <LI>An optional path to a file to which the command's output should be
070 *       written.</LI>
071 *   <LI>An optional boolean flag that indicates whether to log the command's
072 *       output to the server error log.</LI>
073 *   <LI>An optional string that specifies the task state that should be used
074 *       if the command completes with a nonzero exit code.</LI>
075 *   <LI>An optional string that specifies the path to the working directory to
076 *       use when executing the command.</LI>
077 * </UL>
078 */
079@NotMutable()
080@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
081public final class ExecTask
082       extends Task
083{
084  /**
085   * The fully-qualified name of the Java class that is used for the exec task.
086   */
087  static final String EXEC_TASK_CLASS =
088       "com.unboundid.directory.server.tasks.ExecTask";
089
090
091
092  /**
093   * The name of the attribute used to specify the absolute path for the command
094   * to be executed.
095   */
096  private static final String ATTR_COMMAND_PATH = "ds-task-exec-command-path";
097
098
099
100  /**
101   * The name of the attribute used to specify the argument string to provide
102   * when running the command.
103   */
104  private static final String ATTR_COMMAND_ARGUMENTS =
105       "ds-task-exec-command-arguments";
106
107
108
109  /**
110   * The name of the attribute used to specify the path to a file in which the
111   * command's output should be recorded.
112   */
113  private static final String ATTR_COMMAND_OUTPUT_FILE =
114       "ds-task-exec-command-output-file";
115
116
117
118  /**
119   * The name of the attribute used to indicate whether to record the command's
120   * output in the server error log.
121   */
122  private static final String ATTR_LOG_COMMAND_OUTPUT =
123       "ds-task-exec-log-command-output";
124
125
126
127  /**
128   * The name of the attribute used to specify the task state for commands that
129   * complete with a nonzero exit code.
130   */
131  private static final String ATTR_TASK_STATE_FOR_NONZERO_EXIT_CODE =
132       "ds-task-exec-task-completion-state-for-nonzero-exit-code";
133
134
135
136  /**
137   * The name of the attribute used to specify the path to the working directory
138   * to use when executing the command.
139   */
140  private static final String ATTR_WORKING_DIRECTORY =
141       "ds-task-exec-working-directory";
142
143
144
145  /**
146   * The name of the object class used in EXEC task entries.
147   */
148  private static final String OC_EXEC_TASK = "ds-task-exec";
149
150
151
152  /**
153   * The task property that will be used for the command path.
154   */
155  private static final TaskProperty PROPERTY_COMMAND_PATH =
156     new TaskProperty(ATTR_COMMAND_PATH,
157          INFO_EXEC_DISPLAY_NAME_COMMAND_PATH.get(),
158          INFO_EXEC_DESCRIPTION_COMMAND_PATH.get(), String.class, true, false,
159          false);
160
161
162
163  /**
164   * The task property that will be used for the command arguments.
165   */
166  private static final TaskProperty PROPERTY_COMMAND_ARGUMENTS =
167     new TaskProperty(ATTR_COMMAND_ARGUMENTS,
168          INFO_EXEC_DISPLAY_NAME_COMMAND_ARGUMENTS.get(),
169          INFO_EXEC_DESCRIPTION_COMMAND_ARGUMENTS.get(), String.class, false,
170          false, false);
171
172
173
174  /**
175   * The task property that will be used for the command output file.
176   */
177  private static final TaskProperty PROPERTY_COMMAND_OUTPUT_FILE =
178     new TaskProperty(ATTR_COMMAND_OUTPUT_FILE,
179          INFO_EXEC_DISPLAY_NAME_COMMAND_OUTPUT_FILE.get(),
180          INFO_EXEC_DESCRIPTION_COMMAND_OUTPUT_FILE.get(), String.class, false,
181          false, false);
182
183
184
185  /**
186   * The task property that will be used for the log command output flag.
187   */
188  private static final TaskProperty PROPERTY_LOG_COMMAND_OUTPUT =
189     new TaskProperty(ATTR_LOG_COMMAND_OUTPUT,
190          INFO_EXEC_DISPLAY_NAME_LOG_COMMAND_OUTPUT.get(),
191          INFO_EXEC_DESCRIPTION_LOG_COMMAND_OUTPUT.get(), Boolean.class, false,
192          false, false);
193
194
195
196  /**
197   * The task property that will be used for the task state for commands that
198   * complete with a nonzero exit code.
199   */
200  private static final TaskProperty PROPERTY_TASK_STATE_FOR_NONZERO_EXIT_CODE =
201     new TaskProperty(ATTR_TASK_STATE_FOR_NONZERO_EXIT_CODE,
202          INFO_EXEC_DISPLAY_NAME_TASK_STATE_FOR_NONZERO_EXIT_CODE.get(),
203          INFO_EXEC_DESCRIPTION_TASK_STATE_FOR_NONZERO_EXIT_CODE.get(),
204          String.class, false, false, false,
205          new String[]
206          {
207            "STOPPED_BY_ERROR",
208            "STOPPED-BY-ERROR",
209            "COMPLETED_WITH_ERRORS",
210            "COMPLETED-WITH-ERRORS",
211            "COMPLETED_SUCCESSFULLY",
212            "COMPLETED-SUCCESSFULLY"
213          });
214
215
216
217  /**
218   * The task property that will be used for path to use as the the path to the
219   * working directory to use when executing the command.
220   */
221  private static final TaskProperty PROPERTY_WORKING_DIRECTORY =
222     new TaskProperty(ATTR_WORKING_DIRECTORY,
223          INFO_EXEC_DISPLAY_NAME_WORKING_DIRECTORY.get(),
224          INFO_EXEC_DESCRIPTION_WORKING_DIRECTORY.get(),
225          String.class, false, false, false);
226
227
228
229  /**
230   * The serial version UID for this serializable class.
231   */
232  private static final long serialVersionUID = -1647609631634328008L;
233
234
235
236  // Indicates whether command output is to be logged.
237  private final Boolean logCommandOutput;
238
239  // The arguments to provide when executing the command.
240  private final String commandArguments;
241
242  // The path to the file to which command output should be written.
243  private final String commandOutputFile;
244
245  // The path to the command to be executed.
246  private final String commandPath;
247
248  // The name of the task state that should be used if the command completes
249  // with a nonzero exit code.
250  private final String taskStateForNonZeroExitCode;
251
252  // The path to the working directory to use when executing the command.
253  private final String workingDirectory;
254
255
256
257  /**
258   * Creates a new, uninitialized exec task instance that should only be used
259   * for obtaining general information about this task, including the task name,
260   * description, and supported properties.  Attempts to use a task created with
261   * this constructor for any other reason will likely fail.
262   */
263  public ExecTask()
264  {
265    commandPath = null;
266    commandArguments = null;
267    commandOutputFile = null;
268    logCommandOutput = null;
269    taskStateForNonZeroExitCode = null;
270    workingDirectory = null;
271  }
272
273
274
275  /**
276   * Creates a new exec task with the provided information.
277   *
278   * @param  commandPath
279   *              The absolute path (on the server filesystem) to the command
280   *              that should be executed.  This must not be {@code null}.
281   * @param  commandArguments
282   *              The complete set of arguments that should be used when
283   *              running the command.  This may be {@code null} if no arguments
284   *              should be provided.
285   * @param  commandOutputFile
286   *              The path to an output file that should be used to record all
287   *              output that the command writes to standard output or standard
288   *              error.  This may be {@code null} if the command output should
289   *              not be recorded in a file.
290   * @param  logCommandOutput
291   *              Indicates whether to record the command output in the server
292   *              error log.  If this is {@code true}, then all non-blank lines
293   *              that the command writes to standard output or standard error
294   *              will be recorded in the server error log.  if this is
295   *              {@code false}, then the output will not be recorded in the
296   *              server error log.  If this is {@code null}, then the server
297   *              will determine whether to log command output.  Note that a
298   *              value of {@code true} should only be used if you are certain
299   *              that the tool will only generate text-based output, and you
300   *              should use {@code false} if you know that the command may
301   *              generate non-text output.
302   * @param  taskStateForNonZeroExitCode
303   *              The task state that should be used if the command completes
304   *              with a nonzero exit code.  This may be {@code null} to
305   *              indicate that the server should determine the appropriate task
306   *              state.  If it is non-{@code null}, then the value must be one
307   *              of {@link TaskState#STOPPED_BY_ERROR},
308   *              {@link TaskState#COMPLETED_WITH_ERRORS}, or
309   *              {@link TaskState#COMPLETED_SUCCESSFULLY}.
310   *
311   * @throws  TaskException  If there is a problem with any of the provided
312   *                         arguments.
313   */
314  public ExecTask(final String commandPath, final String commandArguments,
315                  final String commandOutputFile,
316                  final Boolean logCommandOutput,
317                  final TaskState taskStateForNonZeroExitCode)
318         throws TaskException
319  {
320    this(null, commandPath, commandArguments, commandOutputFile,
321         logCommandOutput, taskStateForNonZeroExitCode, null, null, null, null,
322         null);
323  }
324
325
326
327  /**
328   * Creates a new exec task with the provided information.
329   *
330   * @param  commandPath
331   *              The absolute path (on the server filesystem) to the command
332   *              that should be executed.  This must not be {@code null}.
333   * @param  commandArguments
334   *              The complete set of arguments that should be used when
335   *              running the command.  This may be {@code null} if no arguments
336   *              should be provided.
337   * @param  commandOutputFile
338   *              The path to an output file that should be used to record all
339   *              output that the command writes to standard output or standard
340   *              error.  This may be {@code null} if the command output should
341   *              not be recorded in a file.
342   * @param  logCommandOutput
343   *              Indicates whether to record the command output in the server
344   *              error log.  If this is {@code true}, then all non-blank lines
345   *              that the command writes to standard output or standard error
346   *              will be recorded in the server error log.  if this is
347   *              {@code false}, then the output will not be recorded in the
348   *              server error log.  If this is {@code null}, then the server
349   *              will determine whether to log command output.  Note that a
350   *              value of {@code true} should only be used if you are certain
351   *              that the tool will only generate text-based output, and you
352   *              should use {@code false} if you know that the command may
353   *              generate non-text output.
354   * @param  taskStateForNonZeroExitCode
355   *              The task state that should be used if the command completes
356   *              with a nonzero exit code.  This may be {@code null} to
357   *              indicate that the server should determine the appropriate task
358   *              state.  If it is non-{@code null}, then the value must be one
359   *              of {@link TaskState#STOPPED_BY_ERROR},
360   *              {@link TaskState#COMPLETED_WITH_ERRORS}, or
361   *              {@link TaskState#COMPLETED_SUCCESSFULLY}.
362   * @param  workingDirectory
363   *              The path to the working directory to use when executing the
364   *              command.
365   *
366   * @throws  TaskException  If there is a problem with any of the provided
367   *                         arguments.
368   */
369  public ExecTask(final String commandPath, final String commandArguments,
370                  final String commandOutputFile,
371                  final Boolean logCommandOutput,
372                  final TaskState taskStateForNonZeroExitCode,
373                  final String workingDirectory)
374         throws TaskException
375  {
376    this(null, commandPath, commandArguments, commandOutputFile,
377         logCommandOutput, taskStateForNonZeroExitCode, workingDirectory, null,
378         null, null, null, null, null, null, null, null, null);
379  }
380
381
382
383  /**
384   * Creates a new exec task with the provided information.
385   *
386   * @param  taskID
387   *              The task ID to use for this task.  If it is {@code null} then
388   *              a UUID will be generated for use as the task ID.
389   * @param  commandPath
390   *              The absolute path (on the server filesystem) to the command
391   *              that should be executed.  This must not be {@code null}.
392   * @param  commandArguments
393   *              The complete set of arguments that should be used when
394   *              running the command.  This may be {@code null} if no arguments
395   *              should be provided.
396   * @param  commandOutputFile
397   *              The path to an output file that should be used to record all
398   *              output that the command writes to standard output or standard
399   *              error.  This may be {@code null} if the command output should
400   *              not be recorded in a file.
401   * @param  logCommandOutput
402   *              Indicates whether to record the command output in the server
403   *              error log.  If this is {@code true}, then all non-blank lines
404   *              that the command writes to standard output or standard error
405   *              will be recorded in the server error log.  if this is
406   *              {@code false}, then the output will not be recorded in the
407   *              server error log.  If this is {@code null}, then the server
408   *              will determine whether to log command output.  Note that a
409   *              value of {@code true} should only be used if you are certain
410   *              that the tool will only generate text-based output, and you
411   *              should use {@code false} if you know that the command may
412   *              generate non-text output.
413   * @param  taskStateForNonZeroExitCode
414   *              The task state that should be used if the command completes
415   *              with a nonzero exit code.  This may be {@code null} to
416   *              indicate that the server should determine the appropriate task
417   *              state.  If it is non-{@code null}, then the value must be one
418   *              of {@link TaskState#STOPPED_BY_ERROR},
419   *              {@link TaskState#COMPLETED_WITH_ERRORS}, or
420   *              {@link TaskState#COMPLETED_SUCCESSFULLY}.
421   * @param  scheduledStartTime
422   *              The time that this task should start running.
423   * @param  dependencyIDs
424   *              The list of task IDs that will be required to complete before
425   *              this task will be eligible to start.
426   * @param  failedDependencyAction
427   *              Indicates what action should be taken if any of the
428   *              dependencies for this task do not complete successfully.
429   * @param  notifyOnCompletion
430   *              The list of e-mail addresses of individuals that should be
431   *              notified when this task completes.
432   * @param  notifyOnError
433   *              The list of e-mail addresses of individuals that should be
434   *              notified if this task does not complete successfully.
435   *
436   * @throws  TaskException  If there is a problem with any of the provided
437   *                         arguments.
438   */
439  public ExecTask(final String taskID, final String commandPath,
440                  final String commandArguments, final String commandOutputFile,
441                  final Boolean logCommandOutput,
442                  final TaskState taskStateForNonZeroExitCode,
443                  final Date scheduledStartTime,
444                  final List<String> dependencyIDs,
445                  final FailedDependencyAction failedDependencyAction,
446                  final List<String> notifyOnCompletion,
447                  final List<String> notifyOnError)
448         throws TaskException
449  {
450    this(taskID, commandPath, commandArguments, commandOutputFile,
451         logCommandOutput, taskStateForNonZeroExitCode, scheduledStartTime,
452         dependencyIDs, failedDependencyAction, null, notifyOnCompletion,
453         null, notifyOnError, null, null, null);
454  }
455
456
457
458  /**
459   * Creates a new exec task with the provided information.
460   *
461   * @param  taskID
462   *              The task ID to use for this task.  If it is {@code null} then
463   *              a UUID will be generated for use as the task ID.
464   * @param  commandPath
465   *              The absolute path (on the server filesystem) to the command
466   *              that should be executed.  This must not be {@code null}.
467   * @param  commandArguments
468   *              The complete set of arguments that should be used when
469   *              running the command.  This may be {@code null} if no arguments
470   *              should be provided.
471   * @param  commandOutputFile
472   *              The path to an output file that should be used to record all
473   *              output that the command writes to standard output or standard
474   *              error.  This may be {@code null} if the command output should
475   *              not be recorded in a file.
476   * @param  logCommandOutput
477   *              Indicates whether to record the command output in the server
478   *              error log.  If this is {@code true}, then all non-blank lines
479   *              that the command writes to standard output or standard error
480   *              will be recorded in the server error log.  if this is
481   *              {@code false}, then the output will not be recorded in the
482   *              server error log.  If this is {@code null}, then the server
483   *              will determine whether to log command output.  Note that a
484   *              value of {@code true} should only be used if you are certain
485   *              that the tool will only generate text-based output, and you
486   *              should use {@code false} if you know that the command may
487   *              generate non-text output.
488   * @param  taskStateForNonZeroExitCode
489   *              The task state that should be used if the command completes
490   *              with a nonzero exit code.  This may be {@code null} to
491   *              indicate that the server should determine the appropriate task
492   *              state.  If it is non-{@code null}, then the value must be one
493   *              of {@link TaskState#STOPPED_BY_ERROR},
494   *              {@link TaskState#COMPLETED_WITH_ERRORS}, or
495   *              {@link TaskState#COMPLETED_SUCCESSFULLY}.
496   * @param  scheduledStartTime
497   *              The time that this task should start running.
498   * @param  dependencyIDs
499   *              The list of task IDs that will be required to complete before
500   *              this task will be eligible to start.
501   * @param  failedDependencyAction
502   *              Indicates what action should be taken if any of the
503   *              dependencies for this task do not complete successfully.
504   * @param  notifyOnStart
505   *              The list of e-mail addresses of individuals that should be
506   *              notified when this task starts.
507   * @param  notifyOnCompletion
508   *              The list of e-mail addresses of individuals that should be
509   *              notified when this task completes.
510   * @param  notifyOnSuccess
511   *              The list of e-mail addresses of individuals that should be
512   *              notified if this task completes successfully.
513   * @param  notifyOnError
514   *              The list of e-mail addresses of individuals that should be
515   *              notified if this task does not complete successfully.
516   * @param  alertOnStart
517   *              Indicates whether the server should send an alert notification
518   *              when this task starts.
519   * @param  alertOnSuccess
520   *              Indicates whether the server should send an alert notification
521   *              if this task completes successfully.
522   * @param  alertOnError
523   *              Indicates whether the server should send an alert notification
524   *              if this task fails to complete successfully.
525   *
526   * @throws  TaskException  If there is a problem with any of the provided
527   *                         arguments.
528   */
529  public ExecTask(final String taskID, final String commandPath,
530                  final String commandArguments, final String commandOutputFile,
531                  final Boolean logCommandOutput,
532                  final TaskState taskStateForNonZeroExitCode,
533                  final Date scheduledStartTime,
534                  final List<String> dependencyIDs,
535                  final FailedDependencyAction failedDependencyAction,
536                  final List<String> notifyOnStart,
537                  final List<String> notifyOnCompletion,
538                  final List<String> notifyOnSuccess,
539                  final List<String> notifyOnError, final Boolean alertOnStart,
540                  final Boolean alertOnSuccess, final Boolean alertOnError)
541         throws TaskException
542  {
543    this(taskID, commandPath, commandArguments, commandOutputFile,
544         logCommandOutput, taskStateForNonZeroExitCode, null,
545         scheduledStartTime, dependencyIDs, failedDependencyAction,
546         notifyOnStart, notifyOnCompletion, notifyOnSuccess, notifyOnError,
547         alertOnStart, alertOnSuccess, alertOnError);
548  }
549
550
551
552  /**
553   * Creates a new exec task with the provided information.
554   *
555   * @param  taskID
556   *              The task ID to use for this task.  If it is {@code null} then
557   *              a UUID will be generated for use as the task ID.
558   * @param  commandPath
559   *              The absolute path (on the server filesystem) to the command
560   *              that should be executed.  This must not be {@code null}.
561   * @param  commandArguments
562   *              The complete set of arguments that should be used when
563   *              running the command.  This may be {@code null} if no arguments
564   *              should be provided.
565   * @param  commandOutputFile
566   *              The path to an output file that should be used to record all
567   *              output that the command writes to standard output or standard
568   *              error.  This may be {@code null} if the command output should
569   *              not be recorded in a file.
570   * @param  logCommandOutput
571   *              Indicates whether to record the command output in the server
572   *              error log.  If this is {@code true}, then all non-blank lines
573   *              that the command writes to standard output or standard error
574   *              will be recorded in the server error log.  if this is
575   *              {@code false}, then the output will not be recorded in the
576   *              server error log.  If this is {@code null}, then the server
577   *              will determine whether to log command output.  Note that a
578   *              value of {@code true} should only be used if you are certain
579   *              that the tool will only generate text-based output, and you
580   *              should use {@code false} if you know that the command may
581   *              generate non-text output.
582   * @param  taskStateForNonZeroExitCode
583   *              The task state that should be used if the command completes
584   *              with a nonzero exit code.  This may be {@code null} to
585   *              indicate that the server should determine the appropriate task
586   *              state.  If it is non-{@code null}, then the value must be one
587   *              of {@link TaskState#STOPPED_BY_ERROR},
588   *              {@link TaskState#COMPLETED_WITH_ERRORS}, or
589   *              {@link TaskState#COMPLETED_SUCCESSFULLY}.
590   * @param  workingDirectory
591   *              The path to the working directory to use when executing the
592   *              command.
593   * @param  scheduledStartTime
594   *              The time that this task should start running.
595   * @param  dependencyIDs
596   *              The list of task IDs that will be required to complete before
597   *              this task will be eligible to start.
598   * @param  failedDependencyAction
599   *              Indicates what action should be taken if any of the
600   *              dependencies for this task do not complete successfully.
601   * @param  notifyOnStart
602   *              The list of e-mail addresses of individuals that should be
603   *              notified when this task starts.
604   * @param  notifyOnCompletion
605   *              The list of e-mail addresses of individuals that should be
606   *              notified when this task completes.
607   * @param  notifyOnSuccess
608   *              The list of e-mail addresses of individuals that should be
609   *              notified if this task completes successfully.
610   * @param  notifyOnError
611   *              The list of e-mail addresses of individuals that should be
612   *              notified if this task does not complete successfully.
613   * @param  alertOnStart
614   *              Indicates whether the server should send an alert notification
615   *              when this task starts.
616   * @param  alertOnSuccess
617   *              Indicates whether the server should send an alert notification
618   *              if this task completes successfully.
619   * @param  alertOnError
620   *              Indicates whether the server should send an alert notification
621   *              if this task fails to complete successfully.
622   *
623   * @throws  TaskException  If there is a problem with any of the provided
624   *                         arguments.
625   */
626  public ExecTask(final String taskID, final String commandPath,
627                  final String commandArguments, final String commandOutputFile,
628                  final Boolean logCommandOutput,
629                  final TaskState taskStateForNonZeroExitCode,
630                  final String workingDirectory, final Date scheduledStartTime,
631                  final List<String> dependencyIDs,
632                  final FailedDependencyAction failedDependencyAction,
633                  final List<String> notifyOnStart,
634                  final List<String> notifyOnCompletion,
635                  final List<String> notifyOnSuccess,
636                  final List<String> notifyOnError, final Boolean alertOnStart,
637                  final Boolean alertOnSuccess, final Boolean alertOnError)
638         throws TaskException
639  {
640    super(taskID, EXEC_TASK_CLASS, scheduledStartTime, dependencyIDs,
641         failedDependencyAction, notifyOnStart, notifyOnCompletion,
642         notifyOnSuccess, notifyOnError, alertOnStart, alertOnSuccess,
643         alertOnError);
644
645    this.commandPath = commandPath;
646    this.commandArguments = commandArguments;
647    this.commandOutputFile = commandOutputFile;
648    this.logCommandOutput = logCommandOutput;
649    this.workingDirectory = workingDirectory;
650
651    if ((commandPath == null) || commandPath.isEmpty())
652    {
653      throw new TaskException(ERR_EXEC_MISSING_PATH.get());
654    }
655
656    if (taskStateForNonZeroExitCode == null)
657    {
658      this.taskStateForNonZeroExitCode = null;
659    }
660    else
661    {
662      switch (taskStateForNonZeroExitCode)
663      {
664        case STOPPED_BY_ERROR:
665        case COMPLETED_WITH_ERRORS:
666        case COMPLETED_SUCCESSFULLY:
667          this.taskStateForNonZeroExitCode = taskStateForNonZeroExitCode.name();
668          break;
669        default:
670          throw new TaskException(
671               ERR_EXEC_INVALID_STATE_FOR_NONZERO_EXIT_CODE.get(
672                    TaskState.STOPPED_BY_ERROR.name(),
673                    TaskState.COMPLETED_WITH_ERRORS.name(),
674                    TaskState.COMPLETED_SUCCESSFULLY.name()));
675      }
676    }
677  }
678
679
680
681  /**
682   * Creates a new exec task from the provided entry.
683   *
684   * @param  entry  The entry to use to create this exec task.
685   *
686   * @throws  TaskException  If the provided entry cannot be parsed as an exec
687   *                         task entry.
688   */
689  public ExecTask(final Entry entry)
690         throws TaskException
691  {
692    super(entry);
693
694
695    // Get the command to execute.  It must be provided.
696    commandPath = entry.getAttributeValue(ATTR_COMMAND_PATH);
697    if (commandPath == null)
698    {
699      throw new TaskException(ERR_EXEC_ENTRY_MISSING_COMMAND_PATH.get(
700           entry.getDN(), ATTR_COMMAND_PATH));
701    }
702
703    commandArguments = entry.getAttributeValue(ATTR_COMMAND_ARGUMENTS);
704    commandOutputFile = entry.getAttributeValue(ATTR_COMMAND_OUTPUT_FILE);
705    logCommandOutput =
706         entry.getAttributeValueAsBoolean(ATTR_LOG_COMMAND_OUTPUT);
707    taskStateForNonZeroExitCode =
708         entry.getAttributeValue(ATTR_TASK_STATE_FOR_NONZERO_EXIT_CODE);
709    workingDirectory = entry.getAttributeValue(ATTR_WORKING_DIRECTORY);
710  }
711
712
713
714  /**
715   * Creates a new exec task from the provided set of task properties.
716   *
717   * @param  properties  The set of task properties and their corresponding
718   *                     values to use for the task.  It must not be
719   *                     {@code null}.
720   *
721   * @throws  TaskException  If the provided set of properties cannot be used to
722   *                         create a valid exec task.
723   */
724  public ExecTask(final Map<TaskProperty,List<Object>> properties)
725         throws TaskException
726  {
727    super(EXEC_TASK_CLASS, properties);
728
729    String path = null;
730    String arguments = null;
731    String outputFile = null;
732    Boolean logOutput = null;
733    String nonZeroExitState = null;
734    String workingDir = null;
735    for (final Map.Entry<TaskProperty,List<Object>> entry :
736         properties.entrySet())
737    {
738      final TaskProperty p = entry.getKey();
739      final String attrName = StaticUtils.toLowerCase(p.getAttributeName());
740      final List<Object> values = entry.getValue();
741
742      if (attrName.equals(ATTR_COMMAND_PATH))
743      {
744        path = parseString(p, values, path);
745      }
746      else if (attrName.equals(ATTR_COMMAND_ARGUMENTS))
747      {
748        arguments = parseString(p, values, arguments);
749      }
750      else if (attrName.equals(ATTR_COMMAND_OUTPUT_FILE))
751      {
752        outputFile = parseString(p, values, outputFile);
753      }
754      else if (attrName.equals(ATTR_LOG_COMMAND_OUTPUT))
755      {
756        logOutput = parseBoolean(p, values, logOutput);
757      }
758      else if (attrName.equals(ATTR_TASK_STATE_FOR_NONZERO_EXIT_CODE))
759      {
760        nonZeroExitState = parseString(p, values, nonZeroExitState);
761      }
762      else if (attrName.equals(ATTR_WORKING_DIRECTORY))
763      {
764        workingDir = parseString(p, values, workingDir);
765      }
766    }
767
768    commandPath = path;
769    commandArguments = arguments;
770    commandOutputFile = outputFile;
771    logCommandOutput = logOutput;
772    taskStateForNonZeroExitCode = nonZeroExitState;
773    workingDirectory = workingDir;
774
775    if (commandPath == null)
776    {
777      throw new TaskException(ERR_EXEC_PROPERTIES_MISSING_COMMAND_PATH.get());
778    }
779  }
780
781
782
783  /**
784   * {@inheritDoc}
785   */
786  @Override()
787  public String getTaskName()
788  {
789    return INFO_TASK_NAME_EXEC.get();
790  }
791
792
793
794  /**
795   * {@inheritDoc}
796   */
797  @Override()
798  public String getTaskDescription()
799  {
800    return INFO_TASK_DESCRIPTION_EXEC.get();
801  }
802
803
804
805  /**
806   * Retrieves the path to the command to be executed.
807   *
808   * @return  The path to the command to be executed.
809   */
810  public String getCommandPath()
811  {
812    return commandPath;
813  }
814
815
816
817  /**
818   * Retrieves a string with the values of the arguments that should be provided
819   * when running the command.
820   *
821   * @return  A string with the values of the arguments that should be provided
822   *          when running the command, or {@code null} if the command should be
823   *          run without any arguments.
824   */
825  public String getCommandArguments()
826  {
827    return commandArguments;
828  }
829
830
831
832  /**
833   * Retrieves the path to a file to which the command's output should be
834   * written.
835   *
836   * @return  The path to a file to which the command's output should be
837   *          written, or {@code null} if the output should not be written to a
838   *          file.
839   */
840  public String getCommandOutputFile()
841  {
842    return commandOutputFile;
843  }
844
845
846
847  /**
848   * Indicates whether the command's output should be recorded in the server's
849   * error log.
850   *
851   * @return  {@code true} if the command's output should be recorded in the
852   *          server's error log, {@code false} if the output should not be
853   *          logged, or {@code null} if the task should not specify the
854   *          behavior.
855   */
856  public Boolean logCommandOutput()
857  {
858    return logCommandOutput;
859  }
860
861
862
863  /**
864   * Retrieves a string representation of the task state that should be returned
865   * if the command completes with a nonzero exit code.
866   *
867   * @return  A string representation of the task state that should be returned
868   *          if the command completes with a nonzero exit state, or
869   *          {@code null} if the task should not specify the return state.
870   */
871  public String getTaskStateForNonZeroExitCode()
872  {
873    return taskStateForNonZeroExitCode;
874  }
875
876
877
878  /**
879   * Retrieves the path to the working directory to use when executing the
880   * command.
881   *
882   * @return  The path to the working directory to use when executing the
883   *          command, or {@code null} if the task should not specify the
884   *          working directory and the server root directory should be used by
885   *          default.
886   */
887  public String getWorkingDirectory()
888  {
889    return workingDirectory;
890  }
891
892
893
894  /**
895   * {@inheritDoc}
896   */
897  @Override()
898  protected List<String> getAdditionalObjectClasses()
899  {
900    return Collections.singletonList(OC_EXEC_TASK);
901  }
902
903
904
905  /**
906   * {@inheritDoc}
907   */
908  @Override()
909  protected List<Attribute> getAdditionalAttributes()
910  {
911    final LinkedList<Attribute> attrList = new LinkedList<>();
912    attrList.add(new Attribute(ATTR_COMMAND_PATH, commandPath));
913
914    if (commandArguments != null)
915    {
916      attrList.add(new Attribute(ATTR_COMMAND_ARGUMENTS, commandArguments));
917    }
918
919    if (commandOutputFile != null)
920    {
921      attrList.add(new Attribute(ATTR_COMMAND_OUTPUT_FILE, commandOutputFile));
922    }
923
924    if (logCommandOutput != null)
925    {
926      attrList.add(new Attribute(ATTR_LOG_COMMAND_OUTPUT,
927           String.valueOf(logCommandOutput)));
928    }
929
930    if (taskStateForNonZeroExitCode != null)
931    {
932      attrList.add(new Attribute(ATTR_TASK_STATE_FOR_NONZERO_EXIT_CODE,
933           taskStateForNonZeroExitCode));
934    }
935
936    if (workingDirectory != null)
937    {
938      attrList.add(new Attribute(ATTR_WORKING_DIRECTORY, workingDirectory));
939    }
940
941    return attrList;
942  }
943
944
945
946  /**
947   * {@inheritDoc}
948   */
949  @Override()
950  public List<TaskProperty> getTaskSpecificProperties()
951  {
952    return Collections.unmodifiableList(Arrays.asList(
953         PROPERTY_COMMAND_PATH, PROPERTY_COMMAND_ARGUMENTS,
954         PROPERTY_COMMAND_OUTPUT_FILE, PROPERTY_LOG_COMMAND_OUTPUT,
955         PROPERTY_TASK_STATE_FOR_NONZERO_EXIT_CODE,
956         PROPERTY_WORKING_DIRECTORY));
957  }
958
959
960
961  /**
962   * {@inheritDoc}
963   */
964  @Override()
965  public Map<TaskProperty,List<Object>> getTaskPropertyValues()
966  {
967    final LinkedHashMap<TaskProperty, List<Object>> props =
968         new LinkedHashMap<>(StaticUtils.computeMapCapacity(
969              StaticUtils.computeMapCapacity(6)));
970
971    props.put(PROPERTY_COMMAND_PATH,
972         Collections.<Object>singletonList(commandPath));
973
974    if (commandArguments != null)
975    {
976      props.put(PROPERTY_COMMAND_ARGUMENTS,
977           Collections.<Object>singletonList(commandArguments));
978    }
979
980    if (commandOutputFile != null)
981    {
982      props.put(PROPERTY_COMMAND_OUTPUT_FILE,
983           Collections.<Object>singletonList(commandOutputFile));
984    }
985
986    if (logCommandOutput != null)
987    {
988      props.put(PROPERTY_LOG_COMMAND_OUTPUT,
989           Collections.<Object>singletonList(logCommandOutput));
990    }
991
992    if (taskStateForNonZeroExitCode != null)
993    {
994      props.put(PROPERTY_TASK_STATE_FOR_NONZERO_EXIT_CODE,
995           Collections.<Object>singletonList(taskStateForNonZeroExitCode));
996    }
997
998    if (workingDirectory != null)
999    {
1000      props.put(PROPERTY_WORKING_DIRECTORY,
1001           Collections.<Object>singletonList(workingDirectory));
1002    }
1003
1004    return Collections.unmodifiableMap(props);
1005  }
1006}