001/*
002 * Copyright 2008-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2018 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.io.Serializable;
026import java.text.ParseException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.Date;
031import java.util.Iterator;
032import java.util.LinkedHashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.UUID;
036
037import com.unboundid.ldap.sdk.Attribute;
038import com.unboundid.ldap.sdk.Entry;
039import com.unboundid.util.Debug;
040import com.unboundid.util.NotExtensible;
041import com.unboundid.util.StaticUtils;
042import com.unboundid.util.ThreadSafety;
043import com.unboundid.util.ThreadSafetyLevel;
044import com.unboundid.util.Validator;
045
046import static com.unboundid.ldap.sdk.unboundidds.tasks.TaskMessages.*;
047
048
049
050/**
051 * This class defines a data structure for holding information about scheduled
052 * tasks as used by the Ping Identity, UnboundID, or Nokia/Alcatel-Lucent 8661
053 * Directory Server.  Subclasses be used to provide additional functionality
054 * when dealing with certain types of tasks.
055 * <BR>
056 * <BLOCKQUOTE>
057 *   <B>NOTE:</B>  This class, and other classes within the
058 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
059 *   supported for use against Ping Identity, UnboundID, and
060 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
061 *   for proprietary functionality or for external specifications that are not
062 *   considered stable or mature enough to be guaranteed to work in an
063 *   interoperable way with other types of LDAP servers.
064 * </BLOCKQUOTE>
065 * <BR>
066 * All types of tasks can include the following information:
067 * <UL>
068 *   <LI>Task ID -- Uniquely identifies the task in the server.  It may be
069 *       omitted when scheduling a new task in order to have a task ID generated
070 *       for the task.</LI>
071 *   <LI>Task Class Name -- The fully-qualified name of the {@code Task}
072 *       subclass that provides the logic for the task.  This does not need to
073 *       be provided when creating a new task from one of the task-specific
074 *       subclasses.</LI>
075 *   <LI>Task State -- The current state of the task.  See the {@link TaskState}
076 *       enum for information about the possible states that a task may
077 *       have.</LI>
078 *   <LI>Scheduled Start Time -- The earliest time that the task should be
079 *       eligible to start.  It may be omitted when scheduling a new task in
080 *       order to use the current time.</LI>
081 *   <LI>Actual Start Time -- The time that server started processing the
082 *       task.</LI>
083 *   <LI>Actual Start Time -- The time that server completed processing for the
084 *       task.</LI>
085 *   <LI>Dependency IDs -- A list of task IDs for tasks that must complete
086 *       before this task may be considered eligible to start.</LI>
087 *   <LI>Failed Dependency Action -- Specifies how the server should treat this
088 *       task if any of the tasks on which it depends failed.  See the
089 *       {@link FailedDependencyAction} enum for the failed dependency action
090 *       values that may be used.</LI>
091 *   <LI>Notify on Completion -- A list of e-mail addresses for users that
092 *       should be notified when the task completes, regardless of whether it
093 *       was successful.</LI>
094 *   <LI>Notify On Error -- A list of e-mail addresses for users that should be
095 *       notified if the task fails.</LI>
096 *   <LI>Log Messages -- A list of the messages logged by the task while it was
097 *       running.</LI>
098 * </UL>
099 * Each of these elements can be retrieving using specific methods within this
100 * class (e.g., the {@link Task#getTaskID} method can be used to retrieve the
101 * task ID), but task properties (including those specific to the particular
102 * type to task) may also be accessed using a generic API.  For example, the
103 * {@link Task#getTaskPropertyValues} method retrieves a map that correlates the
104 * {@link TaskProperty} objects for the task with the values that have been set
105 * for those properties.  See the documentation for the {@link TaskManager}
106 * class for an example that demonstrates accessing task information using the
107 * generic API.
108 * <BR><BR>
109 * Also note that it is possible to create new tasks using information obtained
110 * from the generic API, but that is done on a per-class basis.  For example, in
111 * order to create a new {@link BackupTask} instance using the generic API, you
112 * would use the {@link BackupTask#BackupTask(Map)} constructor, in which the
113 * provided map contains a mapping between the properties and their values for
114 * that task.  The {@link Task#getTaskSpecificProperties} method may be used to
115 * retrieve a list of the task-specific properties that may be provided when
116 * scheduling a task, and the {@link Task#getCommonTaskProperties} method may be
117 * used to retrieve a list of properties that can be provided when scheduling
118 * any type of task.
119 */
120@NotExtensible()
121@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
122public class Task
123       implements Serializable
124{
125  /**
126   * The name of the attribute used to hold the actual start time for scheduled
127   * tasks.
128   */
129  private static final String ATTR_ACTUAL_START_TIME =
130       "ds-task-actual-start-time";
131
132
133
134  /**
135   * The name of the attribute used to indicate whether the server should
136   * generate an administrative alert when the task fails to complete
137   * successfully.
138   */
139  private static final String ATTR_ALERT_ON_ERROR =
140       "ds-task-alert-on-error";
141
142
143
144  /**
145   * The name of the attribute used to indicate whether the server should
146   * generate an administrative alert when the task starts running.
147   */
148  private static final String ATTR_ALERT_ON_START = "ds-task-alert-on-start";
149
150
151
152  /**
153   * The name of the attribute used to indicate whether the server should
154   * generate an administrative alert when the task completes successfully.
155   */
156  private static final String ATTR_ALERT_ON_SUCCESS =
157       "ds-task-alert-on-success";
158
159
160
161  /**
162   * The name of the attribute used to hold the completion time for scheduled
163   * tasks.
164   */
165  private static final String ATTR_COMPLETION_TIME = "ds-task-completion-time";
166
167
168
169  /**
170   * The name of the attribute used to hold the task IDs for tasks on which a
171   * scheduled task is dependent.
172   */
173  private static final String ATTR_DEPENDENCY_ID = "ds-task-dependency-id";
174
175
176
177  /**
178   * The name of the attribute used to indicate what action to take if one of
179   * the dependencies for a task failed to complete successfully.
180   */
181  private static final String ATTR_FAILED_DEPENDENCY_ACTION =
182       "ds-task-failed-dependency-action";
183
184
185
186  /**
187   * The name of the attribute used to hold the log messages for scheduled
188   * tasks.
189   */
190  private static final String ATTR_LOG_MESSAGE = "ds-task-log-message";
191
192
193
194  /**
195   * The name of the attribute used to hold the e-mail addresses of the users
196   * that should be notified whenever a scheduled task completes, regardless of
197   * success or failure.
198   */
199  private static final String ATTR_NOTIFY_ON_COMPLETION =
200       "ds-task-notify-on-completion";
201
202
203
204  /**
205   * The name of the attribute used to hold the e-mail addresses of the users
206   * that should be notified if a scheduled task fails to complete successfully.
207   */
208  private static final String ATTR_NOTIFY_ON_ERROR = "ds-task-notify-on-error";
209
210
211
212  /**
213   * The name of the attribute used to hold the e-mail addresses of the users
214   * that should be notified when a scheduled task starts running.
215   */
216  private static final String ATTR_NOTIFY_ON_START = "ds-task-notify-on-start";
217
218
219
220  /**
221   * The name of the attribute used to hold the e-mail addresses of the users
222   * that should be notified when a scheduled task completes successfully.
223   */
224  private static final String ATTR_NOTIFY_ON_SUCCESS =
225       "ds-task-notify-on-success";
226
227
228
229  /**
230   * The name of the attribute used to hold the scheduled start time for
231   * scheduled tasks.
232   */
233  private static final String ATTR_SCHEDULED_START_TIME =
234       "ds-task-scheduled-start-time";
235
236
237
238  /**
239   * The name of the attribute used to hold the name of the class that provides
240   * the logic for scheduled tasks.
241   */
242  private static final String ATTR_TASK_CLASS = "ds-task-class-name";
243
244
245
246  /**
247   * The name of the attribute used to hold the task ID for scheduled tasks.
248   */
249  static final String ATTR_TASK_ID = "ds-task-id";
250
251
252
253  /**
254   * The name of the attribute used to hold the current state for scheduled
255   * tasks.
256   */
257  static final String ATTR_TASK_STATE = "ds-task-state";
258
259
260
261  /**
262   * The name of the base object class for scheduled tasks.
263   */
264  static final String OC_TASK = "ds-task";
265
266
267
268  /**
269   * The DN of the entry below which scheduled tasks reside.
270   */
271  static final String SCHEDULED_TASKS_BASE_DN =
272       "cn=Scheduled Tasks,cn=tasks";
273
274
275
276  /**
277   * The task property that will be used for the task ID.
278   */
279  private static final TaskProperty PROPERTY_TASK_ID =
280       new TaskProperty(ATTR_TASK_ID, INFO_DISPLAY_NAME_TASK_ID.get(),
281                        INFO_DESCRIPTION_TASK_ID.get(), String.class, false,
282                        false, true);
283
284
285
286  /**
287   * The task property that will be used for the scheduled start time.
288   */
289  private static final TaskProperty PROPERTY_SCHEDULED_START_TIME =
290       new TaskProperty(ATTR_SCHEDULED_START_TIME,
291                        INFO_DISPLAY_NAME_SCHEDULED_START_TIME.get(),
292                        INFO_DESCRIPTION_SCHEDULED_START_TIME.get(), Date.class,
293                        false, false, true);
294
295
296
297  /**
298   * The task property that will be used for the set of dependency IDs.
299   */
300  private static final TaskProperty PROPERTY_DEPENDENCY_ID =
301       new TaskProperty(ATTR_DEPENDENCY_ID,
302                        INFO_DISPLAY_NAME_DEPENDENCY_ID.get(),
303                        INFO_DESCRIPTION_DEPENDENCY_ID.get(), String.class,
304                        false, true, true);
305
306
307
308  /**
309   * The task property that will be used for the failed dependency action.
310   */
311  private static final TaskProperty PROPERTY_FAILED_DEPENDENCY_ACTION =
312       new TaskProperty(ATTR_FAILED_DEPENDENCY_ACTION,
313                        INFO_DISPLAY_NAME_FAILED_DEPENDENCY_ACTION.get(),
314                        INFO_DESCRIPTION_FAILED_DEPENDENCY_ACTION.get(),
315                        String.class, false, false, true,
316                        new String[]
317                        {
318                          FailedDependencyAction.CANCEL.getName(),
319                          FailedDependencyAction.DISABLE.getName(),
320                          FailedDependencyAction.PROCESS.getName()
321                        });
322
323
324
325  /**
326   * The task property that will be used for the notify on completion addresses.
327   */
328  private static final TaskProperty PROPERTY_NOTIFY_ON_COMPLETION =
329       new TaskProperty(ATTR_NOTIFY_ON_COMPLETION,
330                        INFO_DISPLAY_NAME_NOTIFY_ON_COMPLETION.get(),
331                        INFO_DESCRIPTION_NOTIFY_ON_COMPLETION.get(),
332                        String.class, false, true, true);
333
334
335
336  /**
337   * The task property that will be used for the notify on error addresses.
338   */
339  private static final TaskProperty PROPERTY_NOTIFY_ON_ERROR =
340       new TaskProperty(ATTR_NOTIFY_ON_ERROR,
341                        INFO_DISPLAY_NAME_NOTIFY_ON_ERROR.get(),
342                        INFO_DESCRIPTION_NOTIFY_ON_ERROR.get(),
343                        String.class, false, true, true);
344
345
346
347  /**
348   * The task property that will be used for the notify on success addresses.
349   */
350  private static final TaskProperty PROPERTY_NOTIFY_ON_SUCCESS =
351       new TaskProperty(ATTR_NOTIFY_ON_SUCCESS,
352                        INFO_DISPLAY_NAME_NOTIFY_ON_SUCCESS.get(),
353                        INFO_DESCRIPTION_NOTIFY_ON_SUCCESS.get(),
354                        String.class, false, true, true);
355
356
357
358  /**
359   * The task property that will be used for the notify on start addresses.
360   */
361  private static final TaskProperty PROPERTY_NOTIFY_ON_START =
362       new TaskProperty(ATTR_NOTIFY_ON_START,
363                        INFO_DISPLAY_NAME_NOTIFY_ON_START.get(),
364                        INFO_DESCRIPTION_NOTIFY_ON_START.get(),
365                        String.class, false, true, true);
366
367
368
369  /**
370   * The task property that will be used for the alert on error flag.
371   */
372  private static final TaskProperty PROPERTY_ALERT_ON_ERROR =
373       new TaskProperty(ATTR_ALERT_ON_ERROR,
374                        INFO_DISPLAY_NAME_ALERT_ON_ERROR.get(),
375                        INFO_DESCRIPTION_ALERT_ON_ERROR.get(),
376                        Boolean.class, false, false, true);
377
378
379
380  /**
381   * The task property that will be used for the alert on start flag.
382   */
383  private static final TaskProperty PROPERTY_ALERT_ON_START =
384       new TaskProperty(ATTR_ALERT_ON_START,
385                        INFO_DISPLAY_NAME_ALERT_ON_START.get(),
386                        INFO_DESCRIPTION_ALERT_ON_START.get(),
387                        Boolean.class, false, false, true);
388
389
390
391  /**
392   * The task property that will be used for the alert on success flag.
393   */
394  private static final TaskProperty PROPERTY_ALERT_ON_SUCCESS =
395       new TaskProperty(ATTR_ALERT_ON_SUCCESS,
396                        INFO_DISPLAY_NAME_ALERT_ON_SUCCESS.get(),
397                        INFO_DESCRIPTION_ALERT_ON_SUCCESS.get(),
398                        Boolean.class, false, false, true);
399
400
401
402  /**
403   * The serial version UID for this serializable class.
404   */
405  private static final long serialVersionUID = -4082350090081577623L;
406
407
408
409  // Indicates whether to generate an administrative alert when the task fails
410  // to complete successfully.
411  private final Boolean alertOnError;
412
413  // Indicates whether to generate an administrative alert when the task starts.
414  private final Boolean alertOnStart;
415
416  // Indicates whether to generate an administrative alert when the task
417  // completes successfully.
418  private final Boolean alertOnSuccess;
419
420  // The time that this task actually started.
421  private final Date actualStartTime;
422
423  // The time that this task completed.
424  private final Date completionTime;
425
426  // The time that this task was scheduled to start.
427  private final Date scheduledStartTime;
428
429  // The entry from which this task was decoded.
430  private final Entry taskEntry;
431
432  // The failed dependency action for this task.
433  private final FailedDependencyAction failedDependencyAction;
434
435  // The set of task IDs of the tasks on which this task is dependent.
436  private final List<String> dependencyIDs;
437
438  // The set of log messages for this task.
439  private final List<String> logMessages;
440
441  // The set of e-mail addresses of users that should be notified when the task
442  // processing is complete.
443  private final List<String> notifyOnCompletion;
444
445  // The set of e-mail addresses of users that should be notified if task
446  // processing completes with an error.
447  private final List<String> notifyOnError;
448
449  // The set of e-mail addresses of users that should be notified if task
450  // processing starts.
451  private final List<String> notifyOnStart;
452
453  // The set of e-mail addresses of users that should be notified if task
454  // processing completes successfully.
455  private final List<String> notifyOnSuccess;
456
457  // The fully-qualified name of the task class.
458  private final String taskClassName;
459
460  // The DN of the entry for this task.
461  private final String taskEntryDN;
462
463  // The task ID for this task.
464  private final String taskID;
465
466  // The current state for this task.
467  private final TaskState taskState;
468
469
470
471  /**
472   * Creates a new uninitialized task instance which should only be used for
473   * obtaining general information about this task, including the task name,
474   * description, and supported properties.  Attempts to use a task created with
475   * this constructor for any other reason will likely fail.
476   */
477  protected Task()
478  {
479    alertOnError           = null;
480    alertOnStart           = null;
481    alertOnSuccess         = null;
482    actualStartTime        = null;
483    completionTime         = null;
484    scheduledStartTime     = null;
485    taskEntry              = null;
486    failedDependencyAction = null;
487    dependencyIDs          = null;
488    logMessages            = null;
489    notifyOnCompletion     = null;
490    notifyOnError          = null;
491    notifyOnStart          = null;
492    notifyOnSuccess        = null;
493    taskClassName          = null;
494    taskEntryDN            = null;
495    taskID                 = null;
496    taskState              = null;
497  }
498
499
500
501  /**
502   * Creates a new unscheduled task with the specified task ID and class name.
503   *
504   * @param  taskID         The task ID to use for this task.  If it is
505   *                        {@code null} then a UUID will be generated for use
506   *                        as the task ID.
507   * @param  taskClassName  The fully-qualified name of the Java class that
508   *                        provides the logic for the task.  It must not be
509   *                        {@code null}.
510   */
511  public Task(final String taskID, final String taskClassName)
512  {
513    this(taskID, taskClassName, null, null, null, null, null);
514  }
515
516
517
518  /**
519   * Creates a new unscheduled task with the provided information.
520   *
521   * @param  taskID                  The task ID to use for this task.
522   * @param  taskClassName           The fully-qualified name of the Java class
523   *                                 that provides the logic for the task.  It
524   *                                 must not be {@code null}.
525   * @param  scheduledStartTime      The time that this task should start
526   *                                 running.
527   * @param  dependencyIDs           The list of task IDs that will be required
528   *                                 to complete before this task will be
529   *                                 eligible to start.
530   * @param  failedDependencyAction  Indicates what action should be taken if
531   *                                 any of the dependencies for this task do
532   *                                 not complete successfully.
533   * @param  notifyOnCompletion      The list of e-mail addresses of individuals
534   *                                 that should be notified when this task
535   *                                 completes.
536   * @param  notifyOnError           The list of e-mail addresses of individuals
537   *                                 that should be notified if this task does
538   *                                 not complete successfully.
539   */
540  public Task(final String taskID, final String taskClassName,
541              final Date scheduledStartTime, final List<String> dependencyIDs,
542              final FailedDependencyAction failedDependencyAction,
543              final List<String> notifyOnCompletion,
544              final List<String> notifyOnError)
545  {
546    this(taskID, taskClassName, scheduledStartTime, dependencyIDs,
547         failedDependencyAction, null, notifyOnCompletion, null,
548         notifyOnError, null, null, null);
549  }
550
551
552
553  /**
554   * Creates a new unscheduled task with the provided information.
555   *
556   * @param  taskID                  The task ID to use for this task.
557   * @param  taskClassName           The fully-qualified name of the Java class
558   *                                 that provides the logic for the task.  It
559   *                                 must not be {@code null}.
560   * @param  scheduledStartTime      The time that this task should start
561   *                                 running.
562   * @param  dependencyIDs           The list of task IDs that will be required
563   *                                 to complete before this task will be
564   *                                 eligible to start.
565   * @param  failedDependencyAction  Indicates what action should be taken if
566   *                                 any of the dependencies for this task do
567   *                                 not complete successfully.
568   * @param  notifyOnStart           The list of e-mail addresses of individuals
569   *                                 that should be notified when this task
570   *                                 starts running.
571   * @param  notifyOnCompletion      The list of e-mail addresses of individuals
572   *                                 that should be notified when this task
573   *                                 completes.
574   * @param  notifyOnSuccess         The list of e-mail addresses of individuals
575   *                                 that should be notified if this task
576   *                                 completes successfully.
577   * @param  notifyOnError           The list of e-mail addresses of individuals
578   *                                 that should be notified if this task does
579   *                                 not complete successfully.
580   * @param  alertOnStart            Indicates whether the server should send an
581   *                                 alert notification when this task starts.
582   * @param  alertOnSuccess          Indicates whether the server should send an
583   *                                 alert notification if this task completes
584   *                                 successfully.
585   * @param  alertOnError            Indicates whether the server should send an
586   *                                 alert notification if this task fails to
587   *                                 complete successfully.
588   */
589  public Task(final String taskID, final String taskClassName,
590              final Date scheduledStartTime, final List<String> dependencyIDs,
591              final FailedDependencyAction failedDependencyAction,
592              final List<String> notifyOnStart,
593              final List<String> notifyOnCompletion,
594              final List<String> notifyOnSuccess,
595              final List<String> notifyOnError, final Boolean alertOnStart,
596              final Boolean alertOnSuccess, final Boolean alertOnError)
597  {
598    Validator.ensureNotNull(taskClassName);
599
600    this.taskClassName          = taskClassName;
601    this.scheduledStartTime     = scheduledStartTime;
602    this.failedDependencyAction = failedDependencyAction;
603    this.alertOnStart            = alertOnStart;
604    this.alertOnSuccess          = alertOnSuccess;
605    this.alertOnError            = alertOnError;
606
607    if (taskID == null)
608    {
609      this.taskID = UUID.randomUUID().toString();
610    }
611    else
612    {
613      this.taskID = taskID;
614    }
615
616    if (dependencyIDs == null)
617    {
618      this.dependencyIDs = Collections.emptyList();
619    }
620    else
621    {
622      this.dependencyIDs = Collections.unmodifiableList(dependencyIDs);
623    }
624
625    if (notifyOnStart == null)
626    {
627      this.notifyOnStart = Collections.emptyList();
628    }
629    else
630    {
631      this.notifyOnStart =
632           Collections.unmodifiableList(notifyOnStart);
633    }
634
635    if (notifyOnCompletion == null)
636    {
637      this.notifyOnCompletion = Collections.emptyList();
638    }
639    else
640    {
641      this.notifyOnCompletion =
642           Collections.unmodifiableList(notifyOnCompletion);
643    }
644
645    if (notifyOnSuccess == null)
646    {
647      this.notifyOnSuccess = Collections.emptyList();
648    }
649    else
650    {
651      this.notifyOnSuccess = Collections.unmodifiableList(notifyOnSuccess);
652    }
653
654    if (notifyOnError == null)
655    {
656      this.notifyOnError = Collections.emptyList();
657    }
658    else
659    {
660      this.notifyOnError = Collections.unmodifiableList(notifyOnError);
661    }
662
663    taskEntry       = null;
664    taskEntryDN     = ATTR_TASK_ID + '=' + this.taskID + ',' +
665                      SCHEDULED_TASKS_BASE_DN;
666    actualStartTime = null;
667    completionTime  = null;
668    logMessages     = Collections.emptyList();
669    taskState       = TaskState.UNSCHEDULED;
670  }
671
672
673
674  /**
675   * Creates a new task from the provided entry.
676   *
677   * @param  entry  The entry to use to create this task.
678   *
679   * @throws  TaskException  If the provided entry cannot be parsed as a
680   *                         scheduled task.
681   */
682  public Task(final Entry entry)
683         throws TaskException
684  {
685    taskEntry   = entry;
686    taskEntryDN = entry.getDN();
687
688    // Ensure that the task entry has the appropriate object class for a
689    // scheduled task.
690    if (! entry.hasObjectClass(OC_TASK))
691    {
692      throw new TaskException(ERR_TASK_MISSING_OC.get(taskEntryDN));
693    }
694
695
696    // Get the task ID.  It must be present.
697    taskID = entry.getAttributeValue(ATTR_TASK_ID);
698    if (taskID == null)
699    {
700      throw new TaskException(ERR_TASK_NO_ID.get(taskEntryDN));
701    }
702
703
704    // Get the task class name.  It must be present.
705    taskClassName = entry.getAttributeValue(ATTR_TASK_CLASS);
706    if (taskClassName == null)
707    {
708      throw new TaskException(ERR_TASK_NO_CLASS.get(taskEntryDN));
709    }
710
711
712    // Get the task state.  If it is not present, then assume "unscheduled".
713    final String stateStr = entry.getAttributeValue(ATTR_TASK_STATE);
714    if (stateStr == null)
715    {
716      taskState = TaskState.UNSCHEDULED;
717    }
718    else
719    {
720      taskState = TaskState.forName(stateStr);
721      if (taskState == null)
722      {
723        throw new TaskException(ERR_TASK_INVALID_STATE.get(taskEntryDN,
724                                                           stateStr));
725      }
726    }
727
728
729    // Get the scheduled start time.  It may be absent.
730    String timestamp = entry.getAttributeValue(ATTR_SCHEDULED_START_TIME);
731    if (timestamp == null)
732    {
733      scheduledStartTime = null;
734    }
735    else
736    {
737      try
738      {
739        scheduledStartTime = StaticUtils.decodeGeneralizedTime(timestamp);
740      }
741      catch (final ParseException pe)
742      {
743        Debug.debugException(pe);
744        throw new TaskException(ERR_TASK_CANNOT_PARSE_SCHEDULED_START_TIME.get(
745                                     taskEntryDN, timestamp, pe.getMessage()),
746                                pe);
747      }
748    }
749
750
751    // Get the actual start time.  It may be absent.
752    timestamp = entry.getAttributeValue(ATTR_ACTUAL_START_TIME);
753    if (timestamp == null)
754    {
755      actualStartTime = null;
756    }
757    else
758    {
759      try
760      {
761        actualStartTime = StaticUtils.decodeGeneralizedTime(timestamp);
762      }
763      catch (final ParseException pe)
764      {
765        Debug.debugException(pe);
766        throw new TaskException(ERR_TASK_CANNOT_PARSE_ACTUAL_START_TIME.get(
767                                     taskEntryDN, timestamp, pe.getMessage()),
768                                pe);
769      }
770    }
771
772
773    // Get the completion start time.  It may be absent.
774    timestamp = entry.getAttributeValue(ATTR_COMPLETION_TIME);
775    if (timestamp == null)
776    {
777      completionTime = null;
778    }
779    else
780    {
781      try
782      {
783        completionTime = StaticUtils.decodeGeneralizedTime(timestamp);
784      }
785      catch (final ParseException pe)
786      {
787        Debug.debugException(pe);
788        throw new TaskException(ERR_TASK_CANNOT_PARSE_COMPLETION_TIME.get(
789                                     taskEntryDN, timestamp, pe.getMessage()),
790                                pe);
791      }
792    }
793
794
795    // Get the failed dependency action for this task.  It may be absent.
796    final String name = entry.getAttributeValue(ATTR_FAILED_DEPENDENCY_ACTION);
797    if (name == null)
798    {
799      failedDependencyAction = null;
800    }
801    else
802    {
803      failedDependencyAction = FailedDependencyAction.forName(name);
804    }
805
806
807    // Get the dependent task IDs for this task.  It may be absent.
808    dependencyIDs = parseStringList(entry, ATTR_DEPENDENCY_ID);
809
810
811    // Get the log messages for this task.  It may be absent.
812    logMessages = parseStringList(entry, ATTR_LOG_MESSAGE);
813
814
815    // Get the notify on start addresses for this task.  It may be absent.
816    notifyOnStart = parseStringList(entry, ATTR_NOTIFY_ON_START);
817
818
819    // Get the notify on completion addresses for this task.  It may be absent.
820    notifyOnCompletion = parseStringList(entry, ATTR_NOTIFY_ON_COMPLETION);
821
822
823    // Get the notify on success addresses for this task.  It may be absent.
824    notifyOnSuccess = parseStringList(entry, ATTR_NOTIFY_ON_SUCCESS);
825
826
827    // Get the notify on error addresses for this task.  It may be absent.
828    notifyOnError = parseStringList(entry, ATTR_NOTIFY_ON_ERROR);
829
830
831    // Get the alert on start flag for this task.  It may be absent.
832    alertOnStart = entry.getAttributeValueAsBoolean(ATTR_ALERT_ON_START);
833
834
835    // Get the alert on success flag for this task.  It may be absent.
836    alertOnSuccess = entry.getAttributeValueAsBoolean(ATTR_ALERT_ON_SUCCESS);
837
838
839    // Get the alert on error flag for this task.  It may be absent.
840    alertOnError = entry.getAttributeValueAsBoolean(ATTR_ALERT_ON_ERROR);
841  }
842
843
844
845  /**
846   * Creates a new task from the provided set of task properties.
847   *
848   * @param  taskClassName  The fully-qualified name of the Java class that
849   *                        provides the logic for the task.  It must not be
850   *                        {@code null}.
851   * @param  properties     The set of task properties and their corresponding
852   *                        values to use for the task.  It must not be
853   *                        {@code null}.
854   *
855   * @throws  TaskException  If the provided set of properties cannot be used to
856   *                         create a valid scheduled task.
857   */
858  public Task(final String taskClassName,
859              final Map<TaskProperty,List<Object>> properties)
860         throws TaskException
861  {
862    Validator.ensureNotNull(taskClassName, properties);
863
864    this.taskClassName = taskClassName;
865
866    String                 idStr  = UUID.randomUUID().toString();
867    Date                   sst    = null;
868    String[]               depIDs = StaticUtils.NO_STRINGS;
869    FailedDependencyAction fda    = FailedDependencyAction.CANCEL;
870    String[]               nob    = StaticUtils.NO_STRINGS;
871    String[]               noc    = StaticUtils.NO_STRINGS;
872    String[]               noe    = StaticUtils.NO_STRINGS;
873    String[]               nos    = StaticUtils.NO_STRINGS;
874    Boolean                aob    = null;
875    Boolean                aoe    = null;
876    Boolean                aos    = null;
877
878    for (final Map.Entry<TaskProperty,List<Object>> entry :
879         properties.entrySet())
880    {
881      final TaskProperty p        = entry.getKey();
882      final String       attrName = p.getAttributeName();
883      final List<Object> values   = entry.getValue();
884
885      if (attrName.equalsIgnoreCase(ATTR_TASK_ID))
886      {
887        idStr = parseString(p, values, idStr);
888      }
889      else if (attrName.equalsIgnoreCase(ATTR_SCHEDULED_START_TIME))
890      {
891        sst = parseDate(p, values, sst);
892      }
893      else if (attrName.equalsIgnoreCase(ATTR_DEPENDENCY_ID))
894      {
895        depIDs = parseStrings(p, values, depIDs);
896      }
897      else if (attrName.equalsIgnoreCase(ATTR_FAILED_DEPENDENCY_ACTION))
898      {
899        fda = FailedDependencyAction.forName(
900                   parseString(p, values, fda.getName()));
901      }
902      else if (attrName.equalsIgnoreCase(ATTR_NOTIFY_ON_START))
903      {
904        nob = parseStrings(p, values, nob);
905      }
906      else if (attrName.equalsIgnoreCase(ATTR_NOTIFY_ON_COMPLETION))
907      {
908        noc = parseStrings(p, values, noc);
909      }
910      else if (attrName.equalsIgnoreCase(ATTR_NOTIFY_ON_SUCCESS))
911      {
912        nos = parseStrings(p, values, nos);
913      }
914      else if (attrName.equalsIgnoreCase(ATTR_NOTIFY_ON_ERROR))
915      {
916        noe = parseStrings(p, values, noe);
917      }
918      else if (attrName.equalsIgnoreCase(ATTR_ALERT_ON_START))
919      {
920        aob = parseBoolean(p, values, aob);
921      }
922      else if (attrName.equalsIgnoreCase(ATTR_ALERT_ON_SUCCESS))
923      {
924        aos = parseBoolean(p, values, aos);
925      }
926      else if (attrName.equalsIgnoreCase(ATTR_ALERT_ON_ERROR))
927      {
928        aoe = parseBoolean(p, values, aoe);
929      }
930    }
931
932    taskID = idStr;
933    scheduledStartTime = sst;
934    dependencyIDs = Collections.unmodifiableList(Arrays.asList(depIDs));
935    failedDependencyAction = fda;
936    notifyOnStart = Collections.unmodifiableList(Arrays.asList(nob));
937    notifyOnCompletion = Collections.unmodifiableList(Arrays.asList(noc));
938    notifyOnSuccess = Collections.unmodifiableList(Arrays.asList(nos));
939    notifyOnError = Collections.unmodifiableList(Arrays.asList(noe));
940    alertOnStart = aob;
941    alertOnSuccess = aos;
942    alertOnError = aoe;
943    taskEntry = null;
944    taskEntryDN = ATTR_TASK_ID + '=' + taskID + ',' + SCHEDULED_TASKS_BASE_DN;
945    actualStartTime = null;
946    completionTime = null;
947    logMessages = Collections.emptyList();
948    taskState = TaskState.UNSCHEDULED;
949  }
950
951
952
953  /**
954   * Retrieves a list containing instances of the available task types.  The
955   * provided task instances will may only be used for obtaining general
956   * information about the task (e.g., name, description, and supported
957   * properties).
958   *
959   * @return  A list containing instances of the available task types.
960   */
961  public static List<Task> getAvailableTaskTypes()
962  {
963    final List<Task> taskList = Arrays.asList(
964         new AddSchemaFileTask(),
965         new AlertTask(),
966         new AuditDataSecurityTask(),
967         new BackupTask(),
968         new DelayTask(),
969         new DisconnectClientTask(),
970         new DumpDBDetailsTask(),
971         new EnterLockdownModeTask(),
972         new ExecTask(),
973         new ExportTask(),
974         new FileRetentionTask(),
975         new GroovyScriptedTask(),
976         new ImportTask(),
977         new LeaveLockdownModeTask(),
978         new RebuildTask(),
979         new ReEncodeEntriesTask(),
980         new RefreshEncryptionSettingsTask(),
981         new ReloadGlobalIndexTask(),
982         new ReloadHTTPConnectionHandlerCertificatesTask(),
983         new RestoreTask(),
984         new RotateLogTask(),
985         new SearchTask(),
986         new ShutdownTask(),
987         new SynchronizeEncryptionSettingsTask(),
988         new ThirdPartyTask());
989
990    return Collections.unmodifiableList(taskList);
991  }
992
993
994
995  /**
996   * Retrieves a human-readable name for this task.
997   *
998   * @return  A human-readable name for this task.
999   */
1000  public String getTaskName()
1001  {
1002    return INFO_TASK_NAME_GENERIC.get();
1003  }
1004
1005
1006
1007  /**
1008   * Retrieves a human-readable description for this task.
1009   *
1010   * @return  A human-readable description for this task.
1011   */
1012  public String getTaskDescription()
1013  {
1014    return INFO_TASK_DESCRIPTION_GENERIC.get();
1015  }
1016
1017
1018
1019  /**
1020   * Retrieves the entry from which this task was decoded, if available.  Note
1021   * that although the entry is not immutable, changes made to it will not be
1022   * reflected in this task.
1023   *
1024   * @return  The entry from which this task was decoded, or {@code null} if
1025   *          this task was not created from an existing entry.
1026   */
1027  protected final Entry getTaskEntry()
1028  {
1029    return taskEntry;
1030  }
1031
1032
1033
1034  /**
1035   * Retrieves the DN of the entry in which this scheduled task is defined.
1036   *
1037   * @return  The DN of the entry in which this scheduled task is defined.
1038   */
1039  public final String getTaskEntryDN()
1040  {
1041    return taskEntryDN;
1042  }
1043
1044
1045
1046  /**
1047   * Retrieves the task ID for this task.
1048   *
1049   * @return  The task ID for this task.
1050   */
1051  public final String getTaskID()
1052  {
1053    return taskID;
1054  }
1055
1056
1057
1058  /**
1059   * Retrieves the fully-qualified name of the Java class that provides the
1060   * logic for this class.
1061   *
1062   * @return  The fully-qualified name of the Java class that provides the logic
1063   *          for this task.
1064   */
1065  public final String getTaskClassName()
1066  {
1067    return taskClassName;
1068  }
1069
1070
1071
1072  /**
1073   * Retrieves the current state for this task.
1074   *
1075   * @return  The current state for this task.
1076   */
1077  public final TaskState getState()
1078  {
1079    return taskState;
1080  }
1081
1082
1083
1084  /**
1085   * Indicates whether this task is currently pending execution.
1086   *
1087   * @return  {@code true} if this task is currently pending execution, or
1088   *          {@code false} if not.
1089   */
1090  public final boolean isPending()
1091  {
1092    return taskState.isPending();
1093  }
1094
1095
1096
1097  /**
1098   * Indicates whether this task is currently running.
1099   *
1100   * @return  {@code true} if this task is currently running, or {@code false}
1101   *          if not.
1102   */
1103  public final boolean isRunning()
1104  {
1105    return taskState.isRunning();
1106  }
1107
1108
1109
1110  /**
1111   * Indicates whether this task has completed execution.
1112   *
1113   * @return  {@code true} if this task has completed execution, or
1114   *          {@code false} if not.
1115   */
1116  public final boolean isCompleted()
1117  {
1118    return taskState.isCompleted();
1119  }
1120
1121
1122
1123  /**
1124   * Retrieves the time that this task is/was scheduled to start running.
1125   *
1126   * @return  The time that this task is/was scheduled to start running, or
1127   *          {@code null} if that is not available and therefore the task
1128   *          should start running as soon as all dependencies have been met.
1129   */
1130  public final Date getScheduledStartTime()
1131  {
1132    return scheduledStartTime;
1133  }
1134
1135
1136
1137  /**
1138   * Retrieves the time that this task actually started running.
1139   *
1140   * @return  The time that this task actually started running, or {@code null}
1141   *          if that is not available (e.g., because the task has not yet
1142   *          started).
1143   */
1144  public final Date getActualStartTime()
1145  {
1146    return actualStartTime;
1147  }
1148
1149
1150
1151  /**
1152   * Retrieves the time that this task completed.
1153   *
1154   * @return  The time that this task completed, or {@code null} if it has not
1155   *          yet completed.
1156   */
1157  public final Date getCompletionTime()
1158  {
1159    return completionTime;
1160  }
1161
1162
1163
1164  /**
1165   * Retrieves a list of the task IDs for tasks that must complete before this
1166   * task will be eligible to start.
1167   *
1168   * @return  A list of the task IDs for tasks that must complete before this
1169   *          task will be eligible to start, or an empty list if this task does
1170   *          not have any dependencies.
1171   */
1172  public final List<String> getDependencyIDs()
1173  {
1174    return dependencyIDs;
1175  }
1176
1177
1178
1179  /**
1180   * Retrieves the failed dependency action for this task, which indicates the
1181   * behavior that it should exhibit if any of its dependencies encounter a
1182   * failure.
1183   *
1184   * @return  The failed dependency action for this task, or {@code null} if it
1185   *          is not available.
1186   */
1187  public final FailedDependencyAction getFailedDependencyAction()
1188  {
1189    return failedDependencyAction;
1190  }
1191
1192
1193
1194  /**
1195   * Retrieves the log messages for this task.  Note that if the task has
1196   * generated a very large number of log messages, then only a portion of the
1197   * most recent messages may be available.
1198   *
1199   * @return  The log messages for this task, or an empty list if this task does
1200   *          not have any log messages.
1201   */
1202  public final List<String> getLogMessages()
1203  {
1204    return logMessages;
1205  }
1206
1207
1208
1209  /**
1210   * Retrieves a list of the e-mail addresses of the individuals that should be
1211   * notified whenever this task starts running.
1212   *
1213   * @return  A list of the e-mail addresses of the individuals that should be
1214   *          notified whenever this task starts running, or an empty list if
1215   *          there are none.
1216   */
1217  public final List<String> getNotifyOnStartAddresses()
1218  {
1219    return notifyOnStart;
1220  }
1221
1222
1223
1224  /**
1225   * Retrieves a list of the e-mail addresses of the individuals that should be
1226   * notified whenever this task completes processing, regardless of whether it
1227   * was successful.
1228   *
1229   * @return  A list of the e-mail addresses of the individuals that should be
1230   *          notified whenever this task completes processing, or an empty list
1231   *          if there are none.
1232   */
1233  public final List<String> getNotifyOnCompletionAddresses()
1234  {
1235    return notifyOnCompletion;
1236  }
1237
1238
1239
1240  /**
1241   * Retrieves a list of the e-mail addresses of the individuals that should be
1242   * notified if this task completes successfully.
1243   *
1244   * @return  A list of the e-mail addresses of the individuals that should be
1245   *          notified if this task completes successfully, or an empty list
1246   *          if there are none.
1247   */
1248  public final List<String> getNotifyOnSuccessAddresses()
1249  {
1250    return notifyOnSuccess;
1251  }
1252
1253
1254
1255  /**
1256   * Retrieves a list of the e-mail addresses of the individuals that should be
1257   * notified if this task stops processing prematurely due to an error or
1258   * other external action (e.g., server shutdown or administrative cancel).
1259   *
1260   * @return  A list of the e-mail addresses of the individuals that should be
1261   *          notified if this task stops processing prematurely, or an empty
1262   *          list if there are none.
1263   */
1264  public final List<String> getNotifyOnErrorAddresses()
1265  {
1266    return notifyOnError;
1267  }
1268
1269
1270
1271  /**
1272   * Retrieves the flag that indicates whether the server should generate an
1273   * administrative alert when this task starts running.
1274   *
1275   * @return  {@code true} if the server should send an alert when this task
1276   *          starts running, {@code false} if the server should not send an
1277   *          alert, or {@code null} if it is not available.
1278   */
1279  public final Boolean getAlertOnStart()
1280  {
1281    return alertOnStart;
1282  }
1283
1284
1285
1286  /**
1287   * Retrieves the flag that indicates whether the server should generate an
1288   * administrative alert if this task completes successfully.
1289   *
1290   * @return  {@code true} if the server should send an alert if this task
1291   *          completes successfully, {@code false} if the server should not
1292   *          send an alert, or {@code null} if it is not available.
1293   */
1294  public final Boolean getAlertOnSuccess()
1295  {
1296    return alertOnSuccess;
1297  }
1298
1299
1300
1301  /**
1302   * Retrieves the flag that indicates whether the server should generate an
1303   * administrative alert if this task fails to complete successfully.
1304   *
1305   * @return  {@code true} if the server should send an alert if this task fails
1306   *          to complete successfully, {@code false} if the server should not
1307   *          send an alert, or {@code null} if it is not available.
1308   */
1309  public final Boolean getAlertOnError()
1310  {
1311    return alertOnError;
1312  }
1313
1314
1315
1316  /**
1317   * Creates an entry that may be added to the Directory Server to create a new
1318   * instance of this task.
1319   *
1320   * @return  An entry that may be added to the Directory Server to create a new
1321   *          instance of this task.
1322   */
1323  public final Entry createTaskEntry()
1324  {
1325    final ArrayList<Attribute> attributes = new ArrayList<>(20);
1326
1327    final ArrayList<String> ocValues = new ArrayList<>(5);
1328    ocValues.add("top");
1329    ocValues.add(OC_TASK);
1330    ocValues.addAll(getAdditionalObjectClasses());
1331    attributes.add(new Attribute("objectClass", ocValues));
1332
1333    attributes.add(new Attribute(ATTR_TASK_ID, taskID));
1334
1335    attributes.add(new Attribute(ATTR_TASK_CLASS, taskClassName));
1336
1337    if (scheduledStartTime != null)
1338    {
1339      attributes.add(new Attribute(ATTR_SCHEDULED_START_TIME,
1340           StaticUtils.encodeGeneralizedTime(scheduledStartTime)));
1341    }
1342
1343    if (! dependencyIDs.isEmpty())
1344    {
1345      attributes.add(new Attribute(ATTR_DEPENDENCY_ID, dependencyIDs));
1346    }
1347
1348    if (failedDependencyAction != null)
1349    {
1350      attributes.add(new Attribute(ATTR_FAILED_DEPENDENCY_ACTION,
1351                                   failedDependencyAction.getName()));
1352    }
1353
1354    if (! notifyOnStart.isEmpty())
1355    {
1356      attributes.add(new Attribute(ATTR_NOTIFY_ON_START,
1357                                   notifyOnStart));
1358    }
1359
1360    if (! notifyOnCompletion.isEmpty())
1361    {
1362      attributes.add(new Attribute(ATTR_NOTIFY_ON_COMPLETION,
1363                                   notifyOnCompletion));
1364    }
1365
1366    if (! notifyOnSuccess.isEmpty())
1367    {
1368      attributes.add(new Attribute(ATTR_NOTIFY_ON_SUCCESS, notifyOnSuccess));
1369    }
1370
1371    if (! notifyOnError.isEmpty())
1372    {
1373      attributes.add(new Attribute(ATTR_NOTIFY_ON_ERROR, notifyOnError));
1374    }
1375
1376    if (alertOnStart != null)
1377    {
1378      attributes.add(new Attribute(ATTR_ALERT_ON_START,
1379           String.valueOf(alertOnStart)));
1380    }
1381
1382    if (alertOnSuccess != null)
1383    {
1384      attributes.add(new Attribute(ATTR_ALERT_ON_SUCCESS,
1385           String.valueOf(alertOnSuccess)));
1386    }
1387
1388    if (alertOnError != null)
1389    {
1390      attributes.add(new Attribute(ATTR_ALERT_ON_ERROR,
1391           String.valueOf(alertOnError)));
1392    }
1393
1394    attributes.addAll(getAdditionalAttributes());
1395
1396    return new Entry(taskEntryDN, attributes);
1397  }
1398
1399
1400
1401  /**
1402   * Parses the value of the specified attribute as a {@code boolean} value, or
1403   * throws an exception if the value cannot be decoded as a boolean.
1404   *
1405   * @param  taskEntry      The entry containing the attribute to be parsed.
1406   * @param  attributeName  The name of the attribute from which the value was
1407   *                        taken.
1408   * @param  defaultValue   The default value to use if the provided value
1409   *                        string is {@code null}.
1410   *
1411   * @return  {@code true} if the value string represents a boolean value of
1412   *          {@code true}, {@code false} if the value string represents a
1413   *          boolean value of {@code false}, or the default value if the value
1414   *          string is {@code null}.
1415   *
1416   * @throws  TaskException  If the provided value string cannot be parsed as a
1417   *                         {@code boolean} value.
1418   */
1419  protected static boolean parseBooleanValue(final Entry taskEntry,
1420                                             final String attributeName,
1421                                             final boolean defaultValue)
1422            throws TaskException
1423  {
1424    final String valueString = taskEntry.getAttributeValue(attributeName);
1425    if (valueString == null)
1426    {
1427      return defaultValue;
1428    }
1429    else if (valueString.equalsIgnoreCase("true"))
1430    {
1431      return true;
1432    }
1433    else if (valueString.equalsIgnoreCase("false"))
1434    {
1435      return false;
1436    }
1437    else
1438    {
1439      throw new TaskException(ERR_TASK_CANNOT_PARSE_BOOLEAN.get(
1440                                   taskEntry.getDN(), valueString,
1441                                   attributeName));
1442    }
1443  }
1444
1445
1446
1447  /**
1448   * Parses the values of the specified attribute as a list of strings.
1449   *
1450   * @param  taskEntry      The entry containing the attribute to be parsed.
1451   * @param  attributeName  The name of the attribute from which the value was
1452   *                        taken.
1453   *
1454   * @return  A list of strings containing the values of the specified
1455   *          attribute, or an empty list if the specified attribute does not
1456   *          exist in the target entry.  The returned list will be
1457   *          unmodifiable.
1458   */
1459  protected static List<String> parseStringList(final Entry taskEntry,
1460                                                final String attributeName)
1461  {
1462    final String[] valueStrings = taskEntry.getAttributeValues(attributeName);
1463    if (valueStrings == null)
1464    {
1465      return Collections.emptyList();
1466    }
1467    else
1468    {
1469      return Collections.unmodifiableList(Arrays.asList(valueStrings));
1470    }
1471  }
1472
1473
1474
1475  /**
1476   * Parses the provided set of values for the associated task property as a
1477   * {@code Boolean}.
1478   *
1479   * @param  p             The task property with which the values are
1480   *                       associated.
1481   * @param  values        The provided values for the task property.
1482   * @param  defaultValue  The default value to use if the provided object array
1483   *                       is empty.
1484   *
1485   * @return  The parsed {@code Boolean} value.
1486   *
1487   * @throws  TaskException  If there is a problem with the provided values.
1488   */
1489  protected static Boolean parseBoolean(final TaskProperty p,
1490                                        final List<Object> values,
1491                                        final Boolean defaultValue)
1492            throws TaskException
1493  {
1494    // Check to see if any values were provided.  If not, then it may or may not
1495    // be a problem.
1496    if (values.isEmpty())
1497    {
1498      if (p.isRequired())
1499      {
1500        throw new TaskException(ERR_TASK_REQUIRED_PROPERTY_WITHOUT_VALUES.get(
1501                                     p.getDisplayName()));
1502      }
1503      else
1504      {
1505        return defaultValue;
1506      }
1507    }
1508
1509    // If there were multiple values, then that's always an error.
1510    if (values.size() > 1)
1511    {
1512      throw new TaskException(ERR_TASK_PROPERTY_NOT_MULTIVALUED.get(
1513                                   p.getDisplayName()));
1514    }
1515
1516    // Make sure that the value can be interpreted as a Boolean.
1517    final Boolean booleanValue;
1518    final Object o = values.get(0);
1519    if (o instanceof Boolean)
1520    {
1521      booleanValue = (Boolean) o;
1522    }
1523    else if (o instanceof String)
1524    {
1525      final String valueStr = (String) o;
1526      if (valueStr.equalsIgnoreCase("true"))
1527      {
1528        booleanValue = Boolean.TRUE;
1529      }
1530      else if (valueStr.equalsIgnoreCase("false"))
1531      {
1532        booleanValue = Boolean.FALSE;
1533      }
1534      else
1535      {
1536        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_BOOLEAN.get(
1537                                     p.getDisplayName()));
1538      }
1539    }
1540    else
1541    {
1542      throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_BOOLEAN.get(
1543                                   p.getDisplayName()));
1544    }
1545
1546    return booleanValue;
1547  }
1548
1549
1550
1551  /**
1552   * Parses the provided set of values for the associated task property as a
1553   * {@code Date}.
1554   *
1555   * @param  p             The task property with which the values are
1556   *                       associated.
1557   * @param  values        The provided values for the task property.
1558   * @param  defaultValue  The default value to use if the provided object array
1559   *                       is empty.
1560   *
1561   * @return  The parsed {@code Date} value.
1562   *
1563   * @throws  TaskException  If there is a problem with the provided values.
1564   */
1565  protected static Date parseDate(final TaskProperty p,
1566                                  final List<Object> values,
1567                                  final Date defaultValue)
1568            throws TaskException
1569  {
1570    // Check to see if any values were provided.  If not, then it may or may not
1571    // be a problem.
1572    if (values.isEmpty())
1573    {
1574      if (p.isRequired())
1575      {
1576        throw new TaskException(ERR_TASK_REQUIRED_PROPERTY_WITHOUT_VALUES.get(
1577                                     p.getDisplayName()));
1578      }
1579      else
1580      {
1581        return defaultValue;
1582      }
1583    }
1584
1585    // If there were multiple values, then that's always an error.
1586    if (values.size() > 1)
1587    {
1588      throw new TaskException(ERR_TASK_PROPERTY_NOT_MULTIVALUED.get(
1589                                   p.getDisplayName()));
1590    }
1591
1592    // Make sure that the value can be interpreted as a Date.
1593    final Date dateValue;
1594    final Object o = values.get(0);
1595    if (o instanceof Date)
1596    {
1597      dateValue = (Date) o;
1598    }
1599    else if (o instanceof String)
1600    {
1601      try
1602      {
1603        dateValue = StaticUtils.decodeGeneralizedTime((String) o);
1604      }
1605      catch (final ParseException pe)
1606      {
1607        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_DATE.get(
1608                                     p.getDisplayName()), pe);
1609      }
1610    }
1611    else
1612    {
1613      throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_DATE.get(
1614                                   p.getDisplayName()));
1615    }
1616
1617    // If the task property has a set of allowed values, then make sure that the
1618    // provided value is acceptable.
1619    final Object[] allowedValues = p.getAllowedValues();
1620    if (allowedValues != null)
1621    {
1622      boolean found = false;
1623      for (final Object allowedValue : allowedValues)
1624      {
1625        if (dateValue.equals(allowedValue))
1626        {
1627          found = true;
1628          break;
1629        }
1630      }
1631
1632      if (! found)
1633      {
1634        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_ALLOWED.get(
1635                                     p.getDisplayName(), dateValue.toString()));
1636      }
1637    }
1638
1639    return dateValue;
1640  }
1641
1642
1643
1644  /**
1645   * Parses the provided set of values for the associated task property as a
1646   * {@code Long}.
1647   *
1648   * @param  p             The task property with which the values are
1649   *                       associated.
1650   * @param  values        The provided values for the task property.
1651   * @param  defaultValue  The default value to use if the provided object array
1652   *                       is empty.
1653   *
1654   * @return  The parsed {@code Long} value.
1655   *
1656   * @throws  TaskException  If there is a problem with the provided values.
1657   */
1658  protected static Long parseLong(final TaskProperty p,
1659                                  final List<Object> values,
1660                                  final Long defaultValue)
1661            throws TaskException
1662  {
1663    // Check to see if any values were provided.  If not, then it may or may not
1664    // be a problem.
1665    if (values.isEmpty())
1666    {
1667      if (p.isRequired())
1668      {
1669        throw new TaskException(ERR_TASK_REQUIRED_PROPERTY_WITHOUT_VALUES.get(
1670                                     p.getDisplayName()));
1671      }
1672      else
1673      {
1674        return defaultValue;
1675      }
1676    }
1677
1678    // If there were multiple values, then that's always an error.
1679    if (values.size() > 1)
1680    {
1681      throw new TaskException(ERR_TASK_PROPERTY_NOT_MULTIVALUED.get(
1682                                   p.getDisplayName()));
1683    }
1684
1685    // Make sure that the value can be interpreted as a Long.
1686    final Long longValue;
1687    final Object o = values.get(0);
1688    if (o instanceof Long)
1689    {
1690      longValue = (Long) o;
1691    }
1692    else if (o instanceof Number)
1693    {
1694      longValue = ((Number) o).longValue();
1695    }
1696    else if (o instanceof String)
1697    {
1698      try
1699      {
1700        longValue = Long.parseLong((String) o);
1701      }
1702      catch (final Exception e)
1703      {
1704        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_LONG.get(
1705                                     p.getDisplayName()), e);
1706      }
1707    }
1708    else
1709    {
1710      throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_LONG.get(
1711                                   p.getDisplayName()));
1712    }
1713
1714    // If the task property has a set of allowed values, then make sure that the
1715    // provided value is acceptable.
1716    final Object[] allowedValues = p.getAllowedValues();
1717    if (allowedValues != null)
1718    {
1719      boolean found = false;
1720      for (final Object allowedValue : allowedValues)
1721      {
1722        if (longValue.equals(allowedValue))
1723        {
1724          found = true;
1725          break;
1726        }
1727      }
1728
1729      if (! found)
1730      {
1731        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_ALLOWED.get(
1732                                     p.getDisplayName(), longValue.toString()));
1733      }
1734    }
1735
1736    return longValue;
1737  }
1738
1739
1740
1741  /**
1742   * Parses the provided set of values for the associated task property as a
1743   * {@code String}.
1744   *
1745   * @param  p             The task property with which the values are
1746   *                       associated.
1747   * @param  values        The provided values for the task property.
1748   * @param  defaultValue  The default value to use if the provided object array
1749   *                       is empty.
1750   *
1751   * @return  The parsed {@code String} value.
1752   *
1753   * @throws  TaskException  If there is a problem with the provided values.
1754   */
1755  protected static String parseString(final TaskProperty p,
1756                                      final List<Object> values,
1757                                      final String defaultValue)
1758            throws TaskException
1759  {
1760    // Check to see if any values were provided.  If not, then it may or may not
1761    // be a problem.
1762    if (values.isEmpty())
1763    {
1764      if (p.isRequired())
1765      {
1766        throw new TaskException(ERR_TASK_REQUIRED_PROPERTY_WITHOUT_VALUES.get(
1767                                     p.getDisplayName()));
1768      }
1769      else
1770      {
1771        return defaultValue;
1772      }
1773    }
1774
1775    // If there were multiple values, then that's always an error.
1776    if (values.size() > 1)
1777    {
1778      throw new TaskException(ERR_TASK_PROPERTY_NOT_MULTIVALUED.get(
1779                                   p.getDisplayName()));
1780    }
1781
1782    // Make sure that the value is a String.
1783    final String valueStr;
1784    final Object o = values.get(0);
1785    if (o instanceof String)
1786    {
1787      valueStr = (String) o;
1788    }
1789    else if (values.get(0) instanceof CharSequence)
1790    {
1791      valueStr = o.toString();
1792    }
1793    else
1794    {
1795      throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_STRING.get(
1796                                   p.getDisplayName()));
1797    }
1798
1799    // If the task property has a set of allowed values, then make sure that the
1800    // provided value is acceptable.
1801    final Object[] allowedValues = p.getAllowedValues();
1802    if (allowedValues != null)
1803    {
1804      boolean found = false;
1805      for (final Object allowedValue : allowedValues)
1806      {
1807        final String s = (String) allowedValue;
1808        if (valueStr.equalsIgnoreCase(s))
1809        {
1810          found = true;
1811          break;
1812        }
1813      }
1814
1815      if (! found)
1816      {
1817        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_ALLOWED.get(
1818                                     p.getDisplayName(), valueStr));
1819      }
1820    }
1821
1822    return valueStr;
1823  }
1824
1825
1826
1827  /**
1828   * Parses the provided set of values for the associated task property as a
1829   * {@code String} array.
1830   *
1831   * @param  p              The task property with which the values are
1832   *                        associated.
1833   * @param  values         The provided values for the task property.
1834   * @param  defaultValues  The set of default values to use if the provided
1835   *                        object array is empty.
1836   *
1837   * @return  The parsed {@code String} values.
1838   *
1839   * @throws  TaskException  If there is a problem with the provided values.
1840   */
1841  protected static String[] parseStrings(final TaskProperty p,
1842                                         final List<Object> values,
1843                                         final String[] defaultValues)
1844            throws TaskException
1845  {
1846    // Check to see if any values were provided.  If not, then it may or may not
1847    // be a problem.
1848    if (values.isEmpty())
1849    {
1850      if (p.isRequired())
1851      {
1852        throw new TaskException(ERR_TASK_REQUIRED_PROPERTY_WITHOUT_VALUES.get(
1853                                     p.getDisplayName()));
1854      }
1855      else
1856      {
1857        return defaultValues;
1858      }
1859    }
1860
1861
1862    // Iterate through each of the values and perform appropriate validation for
1863    // them.
1864    final String[] stringValues = new String[values.size()];
1865    for (int i=0; i < values.size(); i++)
1866    {
1867      final Object o = values.get(i);
1868
1869      // Make sure that the value is a String.
1870      final String valueStr;
1871      if (o instanceof String)
1872      {
1873        valueStr = (String) o;
1874      }
1875      else if (o instanceof CharSequence)
1876      {
1877        valueStr = o.toString();
1878      }
1879      else
1880      {
1881        throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_STRING.get(
1882                                     p.getDisplayName()));
1883      }
1884
1885      // If the task property has a set of allowed values, then make sure that
1886      // the provided value is acceptable.
1887      final Object[] allowedValues = p.getAllowedValues();
1888      if (allowedValues != null)
1889      {
1890        boolean found = false;
1891        for (final Object allowedValue : allowedValues)
1892        {
1893          final String s = (String) allowedValue;
1894          if (valueStr.equalsIgnoreCase(s))
1895          {
1896            found = true;
1897            break;
1898          }
1899        }
1900
1901        if (! found)
1902        {
1903          throw new TaskException(ERR_TASK_PROPERTY_VALUE_NOT_ALLOWED.get(
1904                                       p.getDisplayName(), valueStr));
1905        }
1906      }
1907
1908      stringValues[i] = valueStr;
1909    }
1910
1911    return stringValues;
1912  }
1913
1914
1915
1916  /**
1917   * Retrieves a list of the additional object classes (other than the base
1918   * "top" and "ds-task" classes) that should be included when creating new task
1919   * entries of this type.
1920   *
1921   * @return  A list of the additional object classes that should be included in
1922   *          new task entries of this type, or an empty list if there do not
1923   *          need to be any additional classes.
1924   */
1925  protected List<String> getAdditionalObjectClasses()
1926  {
1927    return Collections.emptyList();
1928  }
1929
1930
1931
1932  /**
1933   * Retrieves a list of the additional attributes (other than attributes common
1934   * to all task types) that should be included when creating new task entries
1935   * of this type.
1936   *
1937   * @return  A list of the additional attributes that should be included in new
1938   *          task entries of this type, or an empty list if there do not need
1939   *          to be any additional attributes.
1940   */
1941  protected List<Attribute> getAdditionalAttributes()
1942  {
1943    return Collections.emptyList();
1944  }
1945
1946
1947
1948  /**
1949   * Decodes the provided entry as a scheduled task.  An attempt will be made to
1950   * decode the entry as an appropriate subclass if possible, but it will fall
1951   * back to a generic task if it is not possible to decode as a more specific
1952   * task type.
1953   *
1954   * @param  entry  The entry to be decoded.
1955   *
1956   * @return  The decoded task.
1957   *
1958   * @throws  TaskException  If the provided entry cannot be parsed as a
1959   *                         scheduled task.
1960   */
1961  public static Task decodeTask(final Entry entry)
1962         throws TaskException
1963  {
1964    final String taskClass = entry.getAttributeValue(ATTR_TASK_CLASS);
1965    if (taskClass == null)
1966    {
1967      throw new TaskException(ERR_TASK_NO_CLASS.get(entry.getDN()));
1968    }
1969
1970    try
1971    {
1972      if (taskClass.equals(AddSchemaFileTask.ADD_SCHEMA_FILE_TASK_CLASS))
1973      {
1974        return new AddSchemaFileTask(entry);
1975      }
1976      else if (taskClass.equals(AlertTask.ALERT_TASK_CLASS))
1977      {
1978        return new AlertTask(entry);
1979      }
1980      else if (taskClass.equals(AuditDataSecurityTask.
1981                    AUDIT_DATA_SECURITY_TASK_CLASS))
1982      {
1983        return new AuditDataSecurityTask(entry);
1984      }
1985      else if (taskClass.equals(BackupTask.BACKUP_TASK_CLASS))
1986      {
1987        return new BackupTask(entry);
1988      }
1989      else if (taskClass.equals(DelayTask.DELAY_TASK_CLASS))
1990      {
1991        return new DelayTask(entry);
1992      }
1993      else if (taskClass.equals(
1994                    DisconnectClientTask.DISCONNECT_CLIENT_TASK_CLASS))
1995      {
1996        return new DisconnectClientTask(entry);
1997      }
1998      else if (taskClass.equals(DumpDBDetailsTask.DUMP_DB_DETAILS_TASK_CLASS))
1999      {
2000        return new DumpDBDetailsTask(entry);
2001      }
2002      else if (taskClass.equals(
2003                    EnterLockdownModeTask.ENTER_LOCKDOWN_MODE_TASK_CLASS))
2004      {
2005        return new EnterLockdownModeTask(entry);
2006      }
2007      else if (taskClass.equals(ExecTask.EXEC_TASK_CLASS))
2008      {
2009        return new ExecTask(entry);
2010      }
2011      else if (taskClass.equals(ExportTask.EXPORT_TASK_CLASS))
2012      {
2013        return new ExportTask(entry);
2014      }
2015      else if (taskClass.equals(FileRetentionTask.FILE_RETENTION_TASK_CLASS))
2016      {
2017        return new FileRetentionTask(entry);
2018      }
2019      else if (taskClass.equals(GroovyScriptedTask.GROOVY_SCRIPTED_TASK_CLASS))
2020      {
2021        return new GroovyScriptedTask(entry);
2022      }
2023      else if (taskClass.equals(ImportTask.IMPORT_TASK_CLASS))
2024      {
2025        return new ImportTask(entry);
2026      }
2027      else if (taskClass.equals(
2028                    LeaveLockdownModeTask.LEAVE_LOCKDOWN_MODE_TASK_CLASS))
2029      {
2030        return new LeaveLockdownModeTask(entry);
2031      }
2032      else if (taskClass.equals(RebuildTask.REBUILD_TASK_CLASS))
2033      {
2034        return new RebuildTask(entry);
2035      }
2036      else if (taskClass.equals(
2037                    ReEncodeEntriesTask.RE_ENCODE_ENTRIES_TASK_CLASS))
2038      {
2039        return new ReEncodeEntriesTask(entry);
2040      }
2041      else if (taskClass.equals(RefreshEncryptionSettingsTask.
2042                    REFRESH_ENCRYPTION_SETTINGS_TASK_CLASS))
2043      {
2044        return new RefreshEncryptionSettingsTask(entry);
2045      }
2046      else if (taskClass.equals(
2047           ReloadGlobalIndexTask.RELOAD_GLOBAL_INDEX_TASK_CLASS))
2048      {
2049        return new ReloadGlobalIndexTask(entry);
2050      }
2051      else if (taskClass.equals(
2052           ReloadHTTPConnectionHandlerCertificatesTask.
2053                RELOAD_HTTP_CONNECTION_HANDLER_CERTIFICATES_TASK_CLASS))
2054      {
2055        return new ReloadHTTPConnectionHandlerCertificatesTask(entry);
2056      }
2057      else if (taskClass.equals(RestoreTask.RESTORE_TASK_CLASS))
2058      {
2059        return new RestoreTask(entry);
2060      }
2061      else if (taskClass.equals(RotateLogTask.ROTATE_LOG_TASK_CLASS))
2062      {
2063        return new RotateLogTask(entry);
2064      }
2065      else if (taskClass.equals(SearchTask.SEARCH_TASK_CLASS))
2066      {
2067        return new SearchTask(entry);
2068      }
2069      else if (taskClass.equals(ShutdownTask.SHUTDOWN_TASK_CLASS))
2070      {
2071        return new ShutdownTask(entry);
2072      }
2073      else if (taskClass.equals(SynchronizeEncryptionSettingsTask.
2074                    SYNCHRONIZE_ENCRYPTION_SETTINGS_TASK_CLASS))
2075      {
2076        return new SynchronizeEncryptionSettingsTask(entry);
2077      }
2078      else if (taskClass.equals(ThirdPartyTask.THIRD_PARTY_TASK_CLASS))
2079      {
2080        return new ThirdPartyTask(entry);
2081      }
2082    }
2083    catch (final TaskException te)
2084    {
2085      Debug.debugException(te);
2086    }
2087
2088    return new Task(entry);
2089  }
2090
2091
2092
2093  /**
2094   * Retrieves a list of task properties that may be provided when scheduling
2095   * any type of task.  This includes:
2096   * <UL>
2097   *   <LI>The task ID</LI>
2098   *   <LI>The scheduled start time</LI>
2099   *   <LI>The task IDs of any tasks on which this task is dependent</LI>
2100   *   <LI>The action to take for this task if any of its dependencies fail</LI>
2101   *   <LI>The addresses of users to notify when this task starts</LI>
2102   *   <LI>The addresses of users to notify when this task completes</LI>
2103   *   <LI>The addresses of users to notify if this task succeeds</LI>
2104   *   <LI>The addresses of users to notify if this task fails</LI>
2105   *   <LI>A flag indicating whether to generate an alert when the task
2106   *       starts</LI>
2107   *   <LI>A flag indicating whether to generate an alert when the task
2108   *       succeeds</LI>
2109   *   <LI>A flag indicating whether to generate an alert when the task
2110   *       fails</LI>
2111   * </UL>
2112   *
2113   * @return  A list of task properties that may be provided when scheduling any
2114   *          type of task.
2115   */
2116  public static List<TaskProperty> getCommonTaskProperties()
2117  {
2118    final List<TaskProperty> taskList = Arrays.asList(
2119         PROPERTY_TASK_ID,
2120         PROPERTY_SCHEDULED_START_TIME,
2121         PROPERTY_DEPENDENCY_ID,
2122         PROPERTY_FAILED_DEPENDENCY_ACTION,
2123         PROPERTY_NOTIFY_ON_START,
2124         PROPERTY_NOTIFY_ON_COMPLETION,
2125         PROPERTY_NOTIFY_ON_SUCCESS,
2126         PROPERTY_NOTIFY_ON_ERROR,
2127         PROPERTY_ALERT_ON_START,
2128         PROPERTY_ALERT_ON_SUCCESS,
2129         PROPERTY_ALERT_ON_ERROR);
2130
2131    return Collections.unmodifiableList(taskList);
2132  }
2133
2134
2135
2136  /**
2137   * Retrieves a list of task-specific properties that may be provided when
2138   * scheduling a task of this type.  This method should be overridden by
2139   * subclasses in order to provide an appropriate set of properties.
2140   *
2141   * @return  A list of task-specific properties that may be provided when
2142   *          scheduling a task of this type.
2143   */
2144  public List<TaskProperty> getTaskSpecificProperties()
2145  {
2146    return Collections.emptyList();
2147  }
2148
2149
2150
2151  /**
2152   * Retrieves the values of the task properties for this task.  The data type
2153   * of the values will vary based on the data type of the corresponding task
2154   * property and may be one of the following types:  {@code Boolean},
2155   * {@code Date}, {@code Long}, or {@code String}.  Task properties which do
2156   * not have any values will be included in the map with an empty value list.
2157   * <BR><BR>
2158   * Note that subclasses which have additional task properties should override
2159   * this method and return a map which contains both the property values from
2160   * this class (obtained from {@code super.getTaskPropertyValues()} and the
2161   * values of their own task-specific properties.
2162   *
2163   * @return  A map of the task property values for this task.
2164   */
2165  public Map<TaskProperty,List<Object>> getTaskPropertyValues()
2166  {
2167    final LinkedHashMap<TaskProperty,List<Object>> props =
2168         new LinkedHashMap<>(20);
2169
2170    props.put(PROPERTY_TASK_ID,
2171              Collections.<Object>singletonList(taskID));
2172
2173    if (scheduledStartTime == null)
2174    {
2175      props.put(PROPERTY_SCHEDULED_START_TIME, Collections.emptyList());
2176    }
2177    else
2178    {
2179      props.put(PROPERTY_SCHEDULED_START_TIME,
2180                Collections.<Object>singletonList(scheduledStartTime));
2181    }
2182
2183    props.put(PROPERTY_DEPENDENCY_ID,
2184              Collections.<Object>unmodifiableList(dependencyIDs));
2185
2186    if (failedDependencyAction == null)
2187    {
2188      props.put(PROPERTY_FAILED_DEPENDENCY_ACTION, Collections.emptyList());
2189    }
2190    else
2191    {
2192      props.put(PROPERTY_FAILED_DEPENDENCY_ACTION,
2193           Collections.<Object>singletonList(failedDependencyAction.getName()));
2194    }
2195
2196    props.put(PROPERTY_NOTIFY_ON_START,
2197              Collections.<Object>unmodifiableList(notifyOnStart));
2198
2199    props.put(PROPERTY_NOTIFY_ON_COMPLETION,
2200              Collections.<Object>unmodifiableList(notifyOnCompletion));
2201
2202    props.put(PROPERTY_NOTIFY_ON_SUCCESS,
2203              Collections.<Object>unmodifiableList(notifyOnSuccess));
2204
2205    props.put(PROPERTY_NOTIFY_ON_ERROR,
2206              Collections.<Object>unmodifiableList(notifyOnError));
2207
2208    if (alertOnStart != null)
2209    {
2210      props.put(PROPERTY_ALERT_ON_START,
2211           Collections.<Object>singletonList(alertOnStart));
2212    }
2213
2214    if (alertOnSuccess != null)
2215    {
2216      props.put(PROPERTY_ALERT_ON_SUCCESS,
2217           Collections.<Object>singletonList(alertOnSuccess));
2218    }
2219
2220    if (alertOnError!= null)
2221    {
2222      props.put(PROPERTY_ALERT_ON_ERROR,
2223           Collections.<Object>singletonList(alertOnError));
2224    }
2225
2226    return Collections.unmodifiableMap(props);
2227  }
2228
2229
2230
2231  /**
2232   * Retrieves a string representation of this task.
2233   *
2234   * @return  A string representation of this task.
2235   */
2236  @Override()
2237  public final String toString()
2238  {
2239    final StringBuilder buffer = new StringBuilder();
2240    toString(buffer);
2241    return buffer.toString();
2242  }
2243
2244
2245
2246  /**
2247   * Appends a string representation of this task to the provided buffer.
2248   *
2249   * @param  buffer  The buffer to which the string representation should be
2250   *                 provided.
2251   */
2252  public final void toString(final StringBuilder buffer)
2253  {
2254    buffer.append("Task(name='");
2255    buffer.append(getTaskName());
2256    buffer.append("', className='");
2257    buffer.append(taskClassName);
2258    buffer.append(", properties={");
2259
2260    boolean added = false;
2261    for (final Map.Entry<TaskProperty,List<Object>> e :
2262         getTaskPropertyValues().entrySet())
2263    {
2264      if (added)
2265      {
2266        buffer.append(", ");
2267      }
2268      else
2269      {
2270        added = true;
2271      }
2272
2273      buffer.append(e.getKey().getAttributeName());
2274      buffer.append("={");
2275
2276      final Iterator<Object> iterator = e.getValue().iterator();
2277      while (iterator.hasNext())
2278      {
2279        buffer.append('\'');
2280        buffer.append(String.valueOf(iterator.next()));
2281        buffer.append('\'');
2282
2283        if (iterator.hasNext())
2284        {
2285          buffer.append(',');
2286        }
2287      }
2288
2289      buffer.append('}');
2290    }
2291
2292    buffer.append("})");
2293  }
2294}