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