TimeStampFileNamingPolicy.java

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2015-2017 ForgeRock AS.
 */
package org.forgerock.audit.retention;

import static org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRotation.DEFAULT_ROTATION_FILE_SUFFIX;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.forgerock.audit.util.LastModifiedTimeFileComparator;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Creates a time stamp based file naming policy. Rotated files are renamed with a given prefix and a timestamp suffix.
 */
public class TimeStampFileNamingPolicy implements FileNamingPolicy {

    private static final int INITIAL_STRING_BUFFER_SIZE = 64;

    private static final Logger logger = LoggerFactory.getLogger(TimeStampFileNamingPolicy.class);

    private final File initialFile;
    private DateTimeFormatter suffixDateFormat;
    private final String prefix;
    private final TimestampFilenameFilter timestampFilenameFilter;
    private final LastModifiedTimeFileComparator lastModifiedTimeFileComparator = new LastModifiedTimeFileComparator();
    private final AtomicInteger collisionCounter = new AtomicInteger();

    /**
     * Constructs a TimeStampFileNaming policy with a given initial file, a timestamp format, and a prefix string.
     * @param initialFile The initial file that will be archived.
     * @param timeStampFormat The timestamp format to append to the archived files. Should be a format that is
     *                        understood by {@link DateTimeFormat}.
     * @param prefix The prefix to prefix to the archived files.
     */
    public TimeStampFileNamingPolicy(final File initialFile, final String timeStampFormat, final String prefix) {
        this.initialFile = initialFile;
        this.prefix = prefix;

        if (timeStampFormat != null && timeStampFormat.trim().length() > 0) {
            try {
                suffixDateFormat = DateTimeFormat.forPattern(timeStampFormat);
            } catch (IllegalArgumentException iae) {
                logger.info("Date format invalid: {}", timeStampFormat, iae);
            }
        }
        if (suffixDateFormat == null) {
            // fallback to a default date format, so the filenames will differ
            suffixDateFormat = DateTimeFormat.forPattern(DEFAULT_ROTATION_FILE_SUFFIX);
        }
        this.timestampFilenameFilter = new TimestampFilenameFilter(initialFile, prefix, suffixDateFormat);
    }

    /**
     * Gets the initial file.
     * @return The initial file.
     */
    @Override
    public File getInitialName() {
        return initialFile;
    }

    /**
     * Gets the next name for this {@link FileNamingPolicy}. The next name will be formatted with prefix first,
     * then the initial filename and finally the timestamp will be appended.
     * @return The next archived file according to this {@link FileNamingPolicy}.
     */
    @Override
    public File getNextName() {
        final StringBuilder newFileName = new StringBuilder(INITIAL_STRING_BUFFER_SIZE);
        final Path path = initialFile.toPath();

        if (prefix != null) {
            newFileName.append(prefix);
        }

        newFileName.append(path.getFileName());

        if (suffixDateFormat != null) {
            newFileName.append(LocalDateTime.now().toString(suffixDateFormat));
        }

        Path newFilePath = path.resolveSibling(newFileName.toString());
        if (Files.exists(newFilePath)) {
            // prevent filename collision with unique suffix
            newFileName.append('.').append(collisionCounter.incrementAndGet());
            newFilePath = path.resolveSibling(newFileName.toString());
        }
        return newFilePath.toFile();
    }

    /**
     * List the files in the initial file directory that match the prefix, name and suffix format.
     * {@inheritDoc}
     */
    @Override
    public List<File> listFiles() {
        final File[] files = initialFile.getParentFile().listFiles(timestampFilenameFilter);
        if (files == null) {
            return Collections.emptyList();
        }
        final List<File> fileList = new LinkedList<>(Arrays.asList(files));
        // make sure the files are sorted from oldest to newest.
        Collections.sort(fileList, Collections.reverseOrder(lastModifiedTimeFileComparator));
        return fileList;
    }
}