TimeBasedCleanupPolicy.java

/*
 * Copyright 2017 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.morimekta.providence.logging.rolling;

import net.morimekta.providence.logging.RollingFileMessageWriter;

import javax.annotation.Nonnull;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;

import static net.morimekta.providence.logging.rolling.TimeBasedRollingPolicy.FILE_PATTERN;
import static net.morimekta.providence.logging.rolling.TimeBasedRollingPolicy.getFormatter;

/**
 * A cleanup policy that removes files older than a certain date.
 * Uses a date-based file pattern in order to determine the date
 * on the file. the actual file mtime is <b>not</b> checked.
 */
public class TimeBasedCleanupPolicy implements RollingFileMessageWriter.CleanupPolicy {
    /**
     * Create a time based cleanup policy using the system clock for parsing timestampts.
     *
     * @param units The amount of time back to keep files for.
     * @param resolution The unit of time to keep files for.
     * @param rollingFilePattern Pattern matching the files, see {@link TimeBasedRollingPolicy}.
     */
    public TimeBasedCleanupPolicy(int units, TimeUnit resolution, String rollingFilePattern) {
        this(units, resolution, rollingFilePattern, Clock.systemDefaultZone());
    }

    /**
     * Create a time based cleanup policy.
     *
     * @param units The amount of time back to keep files for.
     * @param resolution The unit of time to keep files for.
     * @param rollingFilePattern Pattern matching the files, see {@link TimeBasedRollingPolicy}.
     * @param clock The clock to use.
     */
    public TimeBasedCleanupPolicy(int units, TimeUnit resolution, String rollingFilePattern, Clock clock) {
        Matcher matcher = FILE_PATTERN.matcher(rollingFilePattern);
        if (!matcher.matches()) {
            throw new IllegalArgumentException("No timestamp input in rolling file pattern");
        }
        if (units < 1) {
            throw new IllegalArgumentException("Invalid duration: " + units);
        }

        this.filePrefix = matcher.group(1);
        this.fileSuffix = matcher.group(3);

        this.units = units;
        this.resolution = resolution;
        this.formatter = getFormatter(matcher.group(2), resolution);
        this.clock = clock;
    }

    @Nonnull
    @Override
    public List<String> getFilesToDelete(@Nonnull List<String> candidateFiles, @Nonnull String currentFileName) {
        List<String> out = new ArrayList<>(candidateFiles.size());

        ZonedDateTime removeBefore = ZonedDateTime.ofInstant(Instant.ofEpochMilli(clock.millis()), clock.getZone())
                                                  .minusSeconds(resolution.toSeconds(units));

        for (String candidate : candidateFiles) {
            if (candidate.startsWith(filePrefix) && candidate.endsWith(fileSuffix) &&
                candidate.length() > filePrefix.length() + fileSuffix.length()) {
                try {
                    String date = candidate.substring(filePrefix.length(), candidate.length() - fileSuffix.length());
                    ZonedDateTime ts = ZonedDateTime.ofInstant(Instant.ofEpochMilli(LocalDateTime.parse(date, formatter)
                                                                                                 .toInstant(ZoneOffset.UTC)
                                                                                                 .toEpochMilli()), clock.getZone());
                    if (ts.isBefore(removeBefore)) {
                        out.add(candidate);
                    }
                } catch (Exception ignore) {
                    // Just ignore any errors.
                }
            }
        }
        return out;
    }

    private final String            filePrefix;
    private final String            fileSuffix;
    private final int               units;
    private final TimeUnit          resolution;
    private final DateTimeFormatter formatter;
    private final Clock             clock;
}