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