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