001/*
002 * Copyright 2018-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2018-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.unboundidds.tasks;
022
023
024
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.Date;
028import java.util.LinkedHashMap;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import java.util.concurrent.TimeUnit;
033
034import com.unboundid.ldap.sdk.Attribute;
035import com.unboundid.ldap.sdk.Entry;
036import com.unboundid.util.Debug;
037import com.unboundid.util.NotMutable;
038import com.unboundid.util.StaticUtils;
039import com.unboundid.util.ThreadSafety;
040import com.unboundid.util.ThreadSafetyLevel;
041import com.unboundid.util.Validator;
042import com.unboundid.util.args.DurationArgument;
043
044import static com.unboundid.ldap.sdk.unboundidds.tasks.TaskMessages.*;
045
046
047
048/**
049 * This class defines a Directory Server task that can be used to identify files
050 * in a specified directory that match a given pattern, and delete any of those
051 * files that are outside of a provided set of retention criteria.
052 * <BR>
053 * <BLOCKQUOTE>
054 *   <B>NOTE:</B>  This class, and other classes within the
055 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
056 *   supported for use against Ping Identity, UnboundID, and
057 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
058 *   for proprietary functionality or for external specifications that are not
059 *   considered stable or mature enough to be guaranteed to work in an
060 *   interoperable way with other types of LDAP servers.
061 * </BLOCKQUOTE>
062 * <BR>
063 * The files to examine are identified by a combination of three items:
064 * <UL>
065 *   <LI>A target directory.  This is simply the path to the directory that
066 *       contains the files to examine.</LI>
067 *   <LI>A filename pattern.  This is a string that will be used to identify
068 *       the files of interest in the target directory.  The pattern may contain
069 *       zero or more (non-consecutive) asterisks to use as wildcards that match
070 *       zero or more characters, and it may contain at most one occurrence of
071 *       the token "${timestamp}" (without the quotation marks) that is a
072 *       placeholder for a timestamp that indicates when the file was written or
073 *       the age of the data in that file.  For example, the filename pattern
074 *       "*-${timestamp}.log" will match any file in the target directory that
075 *       ends with a dash, a timestamp, and an extension of ".log".</LI>
076 *   <LI>A timestamp format.  This specifies the format that will be used for
077 *       the value that matches the "${timestamp}" token in the filename
078 *       pattern.  See the {@link FileRetentionTaskTimestampFormat} enum for the
079 *       set of defined timestamp formats.</LI>
080 * </UL>
081 * <BR>
082 * The types of retention criteria include:
083 * <UL>
084 *   <LI>A retain count, which specifies the minimum number of files to retain.
085 *       For example, if there is a retain count of five, and the target
086 *       directory contains ten files that match the filename pattern, the task
087 *       will always keep at least the five most recent files, while the five
088 *       oldest files will be candidates for removal.</LI>
089 *   <LI>A retain age, which specifies the minimum age of the files to retain.
090 *       If the filename pattern includes a timestamp, then the age of the file
091 *       will be determined using that timestamp.  If the filename pattern does
092 *       not contain a timestamp, then the age of the file will be determined
093 *       from the file's create time attribute (if available) or last modified
094 *       time.  The task will always keep all files whose age is less than or
095 *       equal to the retain age, while files older than the retain age will be
096 *       candidates for removal.</LI>
097 *   <LI>An aggregate retain size, which specifies combined minimum amount of
098 *       disk space that should be consumed by the files that should be
099 *       retained.  For example, if the task is configured with an aggregate
100 *       retain size of 500 megabytes and the files to examine are all 75
101 *       megabytes each, then the task will keep at least the seven most recent
102 *       files (because 500/75 = 6.7, and the task will always round up to the
103 *       next whole number), and any older files in the same directory that
104 *       match the pattern will be candidates for removal.
105 * </UL>
106 * <BR>
107 * The task must be configured with at least one of the three types of retention
108 * criteria, but it may combine any two or all three of them.  If a task is
109 * configured with multiple types of retention criteria, then a file will only
110 * be a candidate for removal if it is outside of all of the retention criteria.
111 * For example, if the task is configured with a retain count of 5 and a retain
112 * age of 1 week, then the task may retain more than five files if there are
113 * more than five files that are less than a week old, and it may retain files
114 * that are more than a week old if there are fewer than five files within that
115 * age.
116 */
117@NotMutable()
118@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
119public final class FileRetentionTask
120       extends Task
121{
122  /**
123   * The fully-qualified name of the Java class that is used for the file
124   * retention task.
125   */
126  static final String FILE_RETENTION_TASK_CLASS =
127       "com.unboundid.directory.server.tasks.FileRetentionTask";
128
129
130
131  /**
132   * The name of the attribute that is used to specify the path to the directory
133   * containing the files to delete.
134   */
135  private static final String ATTR_TARGET_DIRECTORY =
136       "ds-task-file-retention-target-directory";
137
138
139
140  /**
141   * The name of the attribute that is used to specify the filename pattern that
142   * is used to identify the files to examine.
143   */
144  private static final String ATTR_FILENAME_PATTERN =
145       "ds-task-file-retention-filename-pattern";
146
147
148
149  /**
150   * The name of the attribute that is used to specify the format to use for
151   * timestamp values in the filename pattern.
152   */
153  private static final String ATTR_TIMESTAMP_FORMAT =
154       "ds-task-file-retention-timestamp-format";
155
156
157
158  /**
159   * The name of the attribute that is used to specify the minimum number of
160   * files to retain.
161   */
162  private static final String ATTR_RETAIN_FILE_COUNT =
163       "ds-task-file-retention-retain-file-count";
164
165
166
167  /**
168   * The name of the attribute that is used to specify the minimum age of
169   * files to retain.
170   */
171  private static final String ATTR_RETAIN_FILE_AGE =
172       "ds-task-file-retention-retain-file-age";
173
174
175
176  /**
177   * The name of the attribute that is used to specify the minimum aggregate
178   * size, in bytes, of files to retain.
179   */
180  private static final String ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES =
181       "ds-task-file-retention-retain-aggregate-file-size-bytes";
182
183
184
185  /**
186   * The name of the object class used in file retention task entries.
187   */
188  private static final String OC_FILE_RETENTION_TASK = "ds-task-file-retention";
189
190
191
192  /**
193   * The task property that will be used for the target directory.
194   */
195  private static final TaskProperty PROPERTY_TARGET_DIRECTORY =
196     new TaskProperty(ATTR_TARGET_DIRECTORY,
197          INFO_FILE_RETENTION_DISPLAY_NAME_TARGET_DIRECTORY.get(),
198          INFO_FILE_RETENTION_DESCRIPTION_TARGET_DIRECTORY.get(), String.class,
199          true, false, false);
200
201
202
203  /**
204   * The task property that will be used for the filename pattern.
205   */
206  private static final TaskProperty PROPERTY_FILENAME_PATTERN =
207     new TaskProperty(ATTR_FILENAME_PATTERN,
208          INFO_FILE_RETENTION_DISPLAY_NAME_FILENAME_PATTERN.get(),
209          INFO_FILE_RETENTION_DESCRIPTION_FILENAME_PATTERN.get(), String.class,
210          true, false, false);
211
212
213
214  /**
215   * The task property that will be used for the timestamp format.
216   */
217  private static final TaskProperty PROPERTY_TIMESTAMP_FORMAT =
218     new TaskProperty(ATTR_TIMESTAMP_FORMAT,
219          INFO_FILE_RETENTION_DISPLAY_NAME_TIMESTAMP_FORMAT.get(),
220          INFO_FILE_RETENTION_DESCRIPTION_TIMESTAMP_FORMAT.get(), String.class,
221          true, false, false,
222          new String[]
223          {
224            FileRetentionTaskTimestampFormat.
225                 GENERALIZED_TIME_UTC_WITH_MILLISECONDS.name(),
226            FileRetentionTaskTimestampFormat.
227                 GENERALIZED_TIME_UTC_WITH_SECONDS.name(),
228            FileRetentionTaskTimestampFormat.
229                 GENERALIZED_TIME_UTC_WITH_MINUTES.name(),
230            FileRetentionTaskTimestampFormat.
231                 LOCAL_TIME_WITH_MILLISECONDS.name(),
232            FileRetentionTaskTimestampFormat.LOCAL_TIME_WITH_SECONDS.name(),
233            FileRetentionTaskTimestampFormat.LOCAL_TIME_WITH_MINUTES.name(),
234            FileRetentionTaskTimestampFormat.LOCAL_DATE.name()
235          });
236
237
238
239  /**
240   * The task property that will be used for the file retention count.
241   */
242  private static final TaskProperty PROPERTY_RETAIN_FILE_COUNT =
243     new TaskProperty(ATTR_RETAIN_FILE_COUNT,
244          INFO_FILE_RETENTION_DISPLAY_NAME_RETAIN_COUNT.get(),
245          INFO_FILE_RETENTION_DESCRIPTION_RETAIN_COUNT.get(), Long.class,
246          false, false, false);
247
248
249
250  /**
251   * The task property that will be used for the file retention age.
252   */
253  private static final TaskProperty PROPERTY_RETAIN_FILE_AGE_MILLIS =
254     new TaskProperty(ATTR_RETAIN_FILE_AGE,
255          INFO_FILE_RETENTION_DISPLAY_NAME_RETAIN_AGE.get(),
256          INFO_FILE_RETENTION_DESCRIPTION_RETAIN_AGE.get(), Long.class,
257          false, false, false);
258
259
260
261  /**
262   * The task property that will be used for the file retention size.
263   */
264  private static final TaskProperty PROPERTY_RETAIN_AGGREGATE_FILE_SIZE_BYTES =
265     new TaskProperty(ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES,
266          INFO_FILE_RETENTION_DISPLAY_NAME_RETAIN_SIZE.get(),
267          INFO_FILE_RETENTION_DESCRIPTION_RETAIN_SIZE.get(), Long.class, false,
268          false, false);
269
270
271
272  /**
273   * The serial version UID for this serializable class.
274   */
275  private static final long serialVersionUID = 7401251158315611295L;
276
277
278
279  // The format for the timestamp that may be used in the filename pattern.
280  private final FileRetentionTaskTimestampFormat timestampFormat;
281
282  // The file retention count.
283  private final Integer retainFileCount;
284
285  // The file retention aggregate size in bytes.
286  private final Long retainAggregateFileSizeBytes;
287
288  // The file retention age in milliseconds.
289  private final Long retainFileAgeMillis;
290
291  // The pattern that identifies the files to examine.
292  private final String filenamePattern;
293
294  // The path to the directory containing the files to examine.
295  private final String targetDirectory;
296
297
298
299  /**
300   * Creates a new, uninitialized file retention task instance that should only
301   * be used for obtaining general information about this task, including the
302   * task name, description, and supported properties.  Attempts to use a task
303   * created with this constructor for any other reason will likely fail.
304   */
305  public FileRetentionTask()
306  {
307    targetDirectory = null;
308    filenamePattern = null;
309    timestampFormat = null;
310    retainFileCount = null;
311    retainFileAgeMillis = null;
312    retainAggregateFileSizeBytes = null;
313  }
314
315
316
317  /**
318   * Creates a new file retention task with the provided information.
319   *
320   * @param  targetDirectory
321   *              The path to the directory containing the files to examine.
322   *              This must be provided, and the target directory must exist on
323   *              the server filesystem.
324   * @param  filenamePattern
325   *              A pattern that identifies the files to examine.  The pattern
326   *              may include zero or more (non-consecutive) asterisks that act
327   *              as wildcards and match zero or more characters.  The pattern
328   *              may also contain at most one occurrence of the "${timestamp}"
329   *              token, which indicates that the filename includes a timestamp
330   *              with the format specified in the {@code timestampFormat}
331   *              argument.  This must not be {@code null} or empty.
332   * @param  timestampFormat
333   *              The expected format for the timestamp that may appear in the
334   *              filename pattern.  This must not be {@code null}, even if the
335   *              filename pattern does not contain a "${timestamp}" token.
336   * @param  retainFileCount
337   *              The minimum number of the most recent files that should be
338   *              retained.  This may be {@code null} if only age-based or
339   *              size-based retention criteria should be used.  At least one of
340   *              the {@code retainFileCount}, {@code retainFileAgeMillis}, and
341   *              {@code retainAggregateFileSizeBytes} values must be
342   *              non-{@code null}.  If this value is non-{@code null}, then it
343   *              must be greater than or equal to zero.
344   * @param  retainFileAgeMillis
345   *              The minimum age, in milliseconds, for files that should be
346   *              retained.  This may be {@code null} if only count-based or
347   *              size-based retention criteria should be used.  At least one of
348   *              the {@code retainFileCount}, {@code retainFileAgeMillis}, and
349   *              {@code retainAggregateFileSizeBytes} values must be
350   *              non-{@code null}.  If this value is non-{@code null}, then
351   *              it must be greater than zero.
352   * @param  retainAggregateFileSizeBytes
353   *              The minimum amount of disk space, in bytes, that should be
354   *              consumed by the files to be retained.  This may be
355   *              {@code null} if only count-based or age-based retention
356   *              criteria should be used.  At least one of the
357   *              {@code retainFileCount}, {@code retainFileAgeMillis}, and
358   *              {@code retainAggregateFileSizeBytes} values must be
359   *              non-{@code null}.  If this value is non-{@code null}, then it
360   *              must be greater than zero.
361   */
362  public FileRetentionTask(final String targetDirectory,
363              final String filenamePattern,
364              final FileRetentionTaskTimestampFormat timestampFormat,
365              final Integer retainFileCount, final Long retainFileAgeMillis,
366              final Long retainAggregateFileSizeBytes)
367  {
368    this(null, targetDirectory, filenamePattern, timestampFormat,
369         retainFileCount, retainFileAgeMillis, retainAggregateFileSizeBytes,
370         null, null, null, null, null, null, null, null, null, null);
371  }
372
373
374
375  /**
376   * Creates a new file retention task with the provided information.
377   *
378   * @param  taskID
379   *              The task ID to use for this task.  If it is {@code null} then
380   *              a UUID will be generated for use as the task ID.
381   * @param  targetDirectory
382   *              The path to the directory containing the files to examine.
383   *              This must be provided, and the target directory must exist on
384   *              the server filesystem.
385   * @param  filenamePattern
386   *              A pattern that identifies the files to examine.  The pattern
387   *              may include zero or more (non-consecutive) asterisks that act
388   *              as wildcards and match zero or more characters.  The pattern
389   *              may also contain at most one occurrence of the "${timestamp}"
390   *              token, which indicates that the filename includes a timestamp
391   *              with the format specified in the {@code timestampFormat}
392   *              argument.  This must not be {@code null} or empty.
393   * @param  timestampFormat
394   *              The expected format for the timestamp that may appear in the
395   *              filename pattern.  This must not be {@code null}, even if the
396   *              filename pattern does not contain a "${timestamp}" token.
397   * @param  retainFileCount
398   *              The minimum number of the most recent files that should be
399   *              retained.  This may be {@code null} if only age-based or
400   *              size-based retention criteria should be used.  At least one of
401   *              the {@code retainFileCount}, {@code retainFileAgeMillis}, and
402   *              {@code retainAggregateFileSizeBytes} values must be
403   *              non-{@code null}.  If this value is non-{@code null}, then it
404   *              must be greater than or equal to zero.
405   * @param  retainFileAgeMillis
406   *              The minimum age, in milliseconds, for files that should be
407   *              retained.  This may be {@code null} if only count-based or
408   *              size-based retention criteria should be used.  At least one of
409   *              the {@code retainFileCount}, {@code retainFileAgeMillis}, and
410   *              {@code retainAggregateFileSizeBytes} values must be
411   *              non-{@code null}.  If this value is non-{@code null}, then
412   *              it must be greater than zero.
413   * @param  retainAggregateFileSizeBytes
414   *              The minimum amount of disk space, in bytes, that should be
415   *              consumed by the files to be retained.  This may be
416   *              {@code null} if only count-based or age-based retention
417   *              criteria should be used.  At least one of the
418   *              {@code retainFileCount}, {@code retainFileAgeMillis}, and
419   *              {@code retainAggregateFileSizeBytes} values must be
420   *              non-{@code null}.  If this value is non-{@code null}, then it
421   *              must be greater than zero.
422   * @param  scheduledStartTime
423   *              The time that this task should start running.
424   * @param  dependencyIDs
425   *              The list of task IDs that will be required to complete before
426   *              this task will be eligible to start.
427   * @param  failedDependencyAction
428   *              Indicates what action should be taken if any of the
429   *              dependencies for this task do not complete successfully.
430   * @param  notifyOnStart
431   *              The list of e-mail addresses of individuals that should be
432   *              notified when this task starts.
433   * @param  notifyOnCompletion
434   *              The list of e-mail addresses of individuals that should be
435   *              notified when this task completes.
436   * @param  notifyOnSuccess
437   *              The list of e-mail addresses of individuals that should be
438   *              notified if this task completes successfully.
439   * @param  notifyOnError
440   *              The list of e-mail addresses of individuals that should be
441   *              notified if this task does not complete successfully.
442   * @param  alertOnStart
443   *              Indicates whether the server should send an alert notification
444   *              when this task starts.
445   * @param  alertOnSuccess
446   *              Indicates whether the server should send an alert notification
447   *              if this task completes successfully.
448   * @param  alertOnError
449   *              Indicates whether the server should send an alert notification
450   *              if this task fails to complete successfully.
451   */
452  public FileRetentionTask(final String taskID, final String targetDirectory,
453              final String filenamePattern,
454              final FileRetentionTaskTimestampFormat timestampFormat,
455              final Integer retainFileCount, final Long retainFileAgeMillis,
456              final Long retainAggregateFileSizeBytes,
457              final Date scheduledStartTime, final List<String> dependencyIDs,
458              final FailedDependencyAction failedDependencyAction,
459              final List<String> notifyOnStart,
460              final List<String> notifyOnCompletion,
461              final List<String> notifyOnSuccess,
462              final List<String> notifyOnError, final Boolean alertOnStart,
463              final Boolean alertOnSuccess, final Boolean alertOnError)
464  {
465    super(taskID, FILE_RETENTION_TASK_CLASS, scheduledStartTime, dependencyIDs,
466         failedDependencyAction, notifyOnStart, notifyOnCompletion,
467         notifyOnSuccess, notifyOnError, alertOnStart, alertOnSuccess,
468         alertOnError);
469
470    Validator.ensureNotNullOrEmpty(targetDirectory,
471         "FileRetentionTask.targetDirectory must not be null or empty");
472    Validator.ensureNotNullOrEmpty(filenamePattern,
473         "FileRetentionTask.filenamePattern must not be null or empty");
474    Validator.ensureNotNullWithMessage(timestampFormat,
475         "FileRetentionTask.timestampFormat must not be null");
476
477    Validator.ensureTrue(
478         ((retainFileCount != null) || (retainFileAgeMillis != null) ||
479              (retainAggregateFileSizeBytes != null)),
480         "At least one of retainFileCount, retainFileAgeMillis, and " +
481              "retainAggregateFileSizeBytes must be non-null");
482
483    Validator.ensureTrue(
484         ((retainFileCount == null) || (retainFileCount >= 0)),
485         "FileRetentionTask.retainFileCount must not be negative");
486    Validator.ensureTrue(
487         ((retainFileAgeMillis == null) || (retainFileAgeMillis > 0L)),
488         "FileRetentionTask.retainFileAgeMillis must not be negative or zero");
489    Validator.ensureTrue(
490         ((retainAggregateFileSizeBytes == null) ||
491              (retainAggregateFileSizeBytes > 0L)),
492         "FileRetentionTask.retainAggregateFileSizeBytes must not be " +
493              "negative or zero");
494
495    this.targetDirectory = targetDirectory;
496    this.filenamePattern = filenamePattern;
497    this.timestampFormat = timestampFormat;
498    this.retainFileCount = retainFileCount;
499    this.retainFileAgeMillis = retainFileAgeMillis;
500    this.retainAggregateFileSizeBytes = retainAggregateFileSizeBytes;
501  }
502
503
504
505  /**
506   * Creates a new file retention task from the provided entry.
507   *
508   * @param  entry  The entry to use to create this file retention task.
509   *
510   * @throws  TaskException  If the provided entry cannot be parsed as a file
511   *                         retention task entry.
512   */
513  public FileRetentionTask(final Entry entry)
514         throws TaskException
515  {
516    super(entry);
517
518    // Get the path to the target directory.  It must not be null or empty.
519    targetDirectory = entry.getAttributeValue(ATTR_TARGET_DIRECTORY);
520    if ((targetDirectory == null) || targetDirectory.isEmpty())
521    {
522      throw new TaskException(
523           ERR_FILE_RETENTION_ENTRY_MISSING_REQUIRED_ATTR.get(entry.getDN(),
524                ATTR_TARGET_DIRECTORY));
525    }
526
527
528    // Get the path to the filename pattern.  It must not be null or empty.
529    filenamePattern = entry.getAttributeValue(ATTR_FILENAME_PATTERN);
530    if ((filenamePattern == null) || filenamePattern.isEmpty())
531    {
532      throw new TaskException(
533           ERR_FILE_RETENTION_ENTRY_MISSING_REQUIRED_ATTR.get(entry.getDN(),
534                ATTR_FILENAME_PATTERN));
535    }
536
537
538    // Get the timestamp format.  It must not be null, and must be a valid
539    // format.
540    final String timestampFormatName =
541         entry.getAttributeValue(ATTR_TIMESTAMP_FORMAT);
542    if (timestampFormatName == null)
543    {
544      throw new TaskException(
545           ERR_FILE_RETENTION_ENTRY_MISSING_REQUIRED_ATTR.get(entry.getDN(),
546                ATTR_TIMESTAMP_FORMAT));
547    }
548
549    timestampFormat =
550         FileRetentionTaskTimestampFormat.forName(timestampFormatName);
551    if (timestampFormat == null)
552    {
553      final StringBuilder validFormats = new StringBuilder();
554      for (final FileRetentionTaskTimestampFormat f :
555           FileRetentionTaskTimestampFormat.values())
556      {
557        if (validFormats.length() > 0)
558        {
559          validFormats.append(", ");
560        }
561
562        validFormats.append(f.name());
563      }
564
565      throw new TaskException(
566           ERR_FILE_RETENTION_ENTRY_INVALID_TIMESTAMP_FORMAT.get(
567                entry.getDN(), timestampFormatName, validFormats.toString()));
568    }
569
570
571    // Get the retain file count.  If it is non-null, then it must also be
572    // non-negative.
573    final String retainFileCountString =
574         entry.getAttributeValue(ATTR_RETAIN_FILE_COUNT);
575    if (retainFileCountString == null)
576    {
577      retainFileCount = null;
578    }
579    else
580    {
581      try
582      {
583        retainFileCount = Integer.parseInt(retainFileCountString);
584      }
585      catch (final Exception e)
586      {
587        Debug.debugException(e);
588        throw new TaskException(
589             ERR_FILE_RETENTION_ENTRY_INVALID_RETAIN_COUNT.get(
590                  entry.getDN(), retainFileCountString),
591             e);
592      }
593
594      if (retainFileCount < 0)
595      {
596        throw new TaskException(
597             ERR_FILE_RETENTION_ENTRY_INVALID_RETAIN_COUNT.get(
598                  entry.getDN(), retainFileCountString));
599      }
600    }
601
602
603    // Get the retain file age in milliseconds.
604    final String retainFileAgeString =
605         entry.getAttributeValue(ATTR_RETAIN_FILE_AGE);
606    if (retainFileAgeString == null)
607    {
608      retainFileAgeMillis = null;
609    }
610    else
611    {
612      try
613      {
614        retainFileAgeMillis = DurationArgument.parseDuration(
615             retainFileAgeString, TimeUnit.MILLISECONDS);
616      }
617      catch (final Exception e)
618      {
619        Debug.debugException(e);
620        throw new TaskException(
621             ERR_FILE_RETENTION_ENTRY_INVALID_RETAIN_AGE.get(
622                  entry.getDN(), retainFileAgeString,
623                  StaticUtils.getExceptionMessage(e)),
624             e);
625      }
626    }
627
628
629    // Get the retain aggregate file size in bytes.  If it is non-null, then it
630    // must also be positive.
631    final String retainAggregateFileSizeBytesString =
632         entry.getAttributeValue(ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES);
633    if (retainAggregateFileSizeBytesString == null)
634    {
635      retainAggregateFileSizeBytes = null;
636    }
637    else
638    {
639      try
640      {
641        retainAggregateFileSizeBytes =
642             Long.parseLong(retainAggregateFileSizeBytesString);
643      }
644      catch (final Exception e)
645      {
646        Debug.debugException(e);
647        throw new TaskException(
648             ERR_FILE_RETENTION_ENTRY_INVALID_RETAIN_SIZE.get(
649                  entry.getDN(), retainAggregateFileSizeBytesString),
650             e);
651      }
652
653      if (retainAggregateFileSizeBytes <= 0)
654      {
655        throw new TaskException(
656             ERR_FILE_RETENTION_ENTRY_INVALID_RETAIN_SIZE.get(
657                  entry.getDN(), retainAggregateFileSizeBytesString));
658      }
659    }
660
661    if ((retainFileCount == null) && (retainFileAgeMillis == null) &&
662       (retainAggregateFileSizeBytes == null))
663    {
664      throw new TaskException(
665           ERR_FILE_RETENTION_ENTRY_MISSING_RETENTION_CRITERIA.get(
666                entry.getDN(), ATTR_RETAIN_FILE_COUNT, ATTR_RETAIN_FILE_AGE,
667                ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES));
668    }
669  }
670
671
672
673  /**
674   * Creates a new file retention task from the provided set of task properties.
675   *
676   * @param  properties  The set of task properties and their corresponding
677   *                     values to use for the task.  It must not be
678   *                     {@code null}.
679   *
680   * @throws  TaskException  If the provided set of properties cannot be used to
681   *                         create a valid file retention task.
682   */
683  public FileRetentionTask(final Map<TaskProperty,List<Object>> properties)
684         throws TaskException
685  {
686    super(FILE_RETENTION_TASK_CLASS, properties);
687
688    String directory = null;
689    String pattern = null;
690    FileRetentionTaskTimestampFormat format = null;
691    Long count = null;
692    Long age = null;
693    Long size = null;
694    for (final Map.Entry<TaskProperty,List<Object>> entry :
695         properties.entrySet())
696    {
697      final TaskProperty p = entry.getKey();
698      final String attrName = StaticUtils.toLowerCase(p.getAttributeName());
699      final List<Object> values = entry.getValue();
700      switch (attrName)
701      {
702        case ATTR_TARGET_DIRECTORY:
703          directory = parseString(p, values, null);
704          break;
705        case ATTR_FILENAME_PATTERN:
706          pattern = parseString(p, values, null);
707          break;
708        case ATTR_TIMESTAMP_FORMAT:
709          final String formatName = parseString(p, values, null);
710          format = FileRetentionTaskTimestampFormat.forName(formatName);
711          break;
712        case ATTR_RETAIN_FILE_COUNT:
713          count = parseLong(p, values, null);
714          break;
715        case ATTR_RETAIN_FILE_AGE:
716          age = parseLong(p, values, null);
717          break;
718        case ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES:
719          size = parseLong(p, values, null);
720          break;
721      }
722    }
723
724    targetDirectory = directory;
725    filenamePattern = pattern;
726    timestampFormat = format;
727    retainFileAgeMillis = age;
728    retainAggregateFileSizeBytes = size;
729
730    if (count == null)
731    {
732      retainFileCount = null;
733    }
734    else
735    {
736      retainFileCount = count.intValue();
737    }
738
739    if ((targetDirectory == null) || targetDirectory.isEmpty())
740    {
741      throw new TaskException(ERR_FILE_RETENTION_MISSING_REQUIRED_PROPERTY.get(
742           ATTR_TARGET_DIRECTORY));
743    }
744
745    if ((filenamePattern == null) || filenamePattern.isEmpty())
746    {
747      throw new TaskException(ERR_FILE_RETENTION_MISSING_REQUIRED_PROPERTY.get(
748           ATTR_FILENAME_PATTERN));
749    }
750
751    if (timestampFormat == null)
752    {
753      throw new TaskException(ERR_FILE_RETENTION_MISSING_REQUIRED_PROPERTY.get(
754           ATTR_TIMESTAMP_FORMAT));
755    }
756
757    if ((retainFileCount == null) && (retainFileAgeMillis == null) &&
758         (retainAggregateFileSizeBytes == null))
759    {
760      throw new TaskException(ERR_FILE_RETENTION_MISSING_RETENTION_PROPERTY.get(
761           ATTR_RETAIN_FILE_COUNT, ATTR_RETAIN_FILE_AGE,
762           ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES));
763    }
764  }
765
766
767
768  /**
769   * {@inheritDoc}
770   */
771  @Override()
772  public String getTaskName()
773  {
774    return INFO_TASK_NAME_FILE_RETENTION.get();
775  }
776
777
778
779  /**
780   * {@inheritDoc}
781   */
782  @Override()
783  public String getTaskDescription()
784  {
785    return INFO_TASK_DESCRIPTION_FILE_RETENTION.get();
786  }
787
788
789
790  /**
791   * Retrieves the path to the directory (on the server filesystem) containing
792   * the files to examine.
793   *
794   * @return  The path to the directory (on the server filesystem) containing
795   *          the files to examine.
796   */
797  public String getTargetDirectory()
798  {
799    return targetDirectory;
800  }
801
802
803
804  /**
805   * Retrieves the filename pattern that the task should use to identify which
806   * files to examine.
807   *
808   * @return  The filename pattern that the task should use to identify which
809   *          files to examine.
810   */
811  public String getFilenamePattern()
812  {
813    return filenamePattern;
814  }
815
816
817
818  /**
819   * Retrieves the format to use to interpret the timestamp element in the
820   * filename pattern.
821   *
822   * @return  The format to use to interpret the timestamp element in the
823   *          filename pattern.
824   */
825  public FileRetentionTaskTimestampFormat getTimestampFormat()
826  {
827    return timestampFormat;
828  }
829
830
831
832  /**
833   * Retrieves the minimum number of files to retain, if defined.
834   *
835   * @return  The minimum number of files to retain, or {@code null} if there
836   *          is no count-based retention criteria.
837   */
838  public Integer getRetainFileCount()
839  {
840    return retainFileCount;
841  }
842
843
844
845  /**
846   * Retrieves the minimum age (in milliseconds) of files to retain, if defined.
847   *
848   * @return  The minimum age (in milliseconds) of files to retain, or
849   *          {@code null} if there is no age-based retention criteria.
850   */
851  public Long getRetainFileAgeMillis()
852  {
853    return retainFileAgeMillis;
854  }
855
856
857
858  /**
859   * Retrieves the minimum aggregate size (in bytes) of files to retain, if
860   * defined.
861   *
862   * @return  The minimum aggregate size (in bytes) of files to retain, or
863   *          {@code null} if there is no size-based retention criteria.
864   */
865  public Long getRetainAggregateFileSizeBytes()
866  {
867    return retainAggregateFileSizeBytes;
868  }
869
870
871
872  /**
873   * {@inheritDoc}
874   */
875  @Override()
876  protected List<String> getAdditionalObjectClasses()
877  {
878    return Collections.singletonList(OC_FILE_RETENTION_TASK);
879  }
880
881
882
883  /**
884   * {@inheritDoc}
885   */
886  @Override()
887  protected List<Attribute> getAdditionalAttributes()
888  {
889    final LinkedList<Attribute> attrList = new LinkedList<>();
890    attrList.add(new Attribute(ATTR_TARGET_DIRECTORY, targetDirectory));
891    attrList.add(new Attribute(ATTR_FILENAME_PATTERN, filenamePattern));
892    attrList.add(new Attribute(ATTR_TIMESTAMP_FORMAT, timestampFormat.name()));
893
894    if (retainFileCount != null)
895    {
896      attrList.add(new Attribute(ATTR_RETAIN_FILE_COUNT,
897           String.valueOf(retainFileCount)));
898    }
899
900    if (retainFileAgeMillis != null)
901    {
902      final long retainFileAgeNanos = retainFileAgeMillis * 1_000_000L;
903      final String retainFileAgeString =
904           DurationArgument.nanosToDuration(retainFileAgeNanos);
905      attrList.add(new Attribute(ATTR_RETAIN_FILE_AGE, retainFileAgeString));
906    }
907
908    if (retainAggregateFileSizeBytes != null)
909    {
910      attrList.add(new Attribute(ATTR_RETAIN_AGGREGATE_FILE_SIZE_BYTES,
911           String.valueOf(retainAggregateFileSizeBytes)));
912    }
913
914    return attrList;
915  }
916
917
918
919  /**
920   * {@inheritDoc}
921   */
922  @Override()
923  public List<TaskProperty> getTaskSpecificProperties()
924  {
925    return Collections.unmodifiableList(Arrays.asList(
926         PROPERTY_TARGET_DIRECTORY,
927         PROPERTY_FILENAME_PATTERN,
928         PROPERTY_TIMESTAMP_FORMAT,
929         PROPERTY_RETAIN_FILE_COUNT,
930         PROPERTY_RETAIN_FILE_AGE_MILLIS,
931         PROPERTY_RETAIN_AGGREGATE_FILE_SIZE_BYTES));
932  }
933
934
935
936  /**
937   * {@inheritDoc}
938   */
939  @Override()
940  public Map<TaskProperty,List<Object>> getTaskPropertyValues()
941  {
942    final LinkedHashMap<TaskProperty, List<Object>> props =
943         new LinkedHashMap<>(StaticUtils.computeMapCapacity(6));
944
945    props.put(PROPERTY_TARGET_DIRECTORY,
946         Collections.<Object>singletonList(targetDirectory));
947    props.put(PROPERTY_FILENAME_PATTERN,
948         Collections.<Object>singletonList(filenamePattern));
949    props.put(PROPERTY_TIMESTAMP_FORMAT,
950         Collections.<Object>singletonList(timestampFormat.name()));
951
952    if (retainFileCount != null)
953    {
954      props.put(PROPERTY_RETAIN_FILE_COUNT,
955           Collections.<Object>singletonList(retainFileCount.longValue()));
956    }
957
958    if (retainFileAgeMillis != null)
959    {
960      props.put(PROPERTY_RETAIN_FILE_AGE_MILLIS,
961           Collections.<Object>singletonList(retainFileAgeMillis));
962    }
963
964    if (retainAggregateFileSizeBytes != null)
965    {
966      props.put(PROPERTY_RETAIN_AGGREGATE_FILE_SIZE_BYTES,
967           Collections.<Object>singletonList(retainAggregateFileSizeBytes));
968    }
969
970    return Collections.unmodifiableMap(props);
971  }
972}