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}