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