001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017
018package org.apache.logging.log4j.core.appender.rolling.action;
019
020import java.io.IOException;
021import java.nio.file.FileVisitResult;
022import java.nio.file.Files;
023import java.nio.file.Path;
024import java.nio.file.SimpleFileVisitor;
025import java.nio.file.attribute.BasicFileAttributes;
026import java.util.List;
027import java.util.Objects;
028
029import org.apache.logging.log4j.Logger;
030import org.apache.logging.log4j.status.StatusLogger;
031
032/**
033 * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored.
034 */
035public class DeletingVisitor extends SimpleFileVisitor<Path> {
036    private static final Logger LOGGER = StatusLogger.getLogger();
037
038    private final Path basePath;
039    private final boolean testMode;
040    private final List<? extends PathCondition> pathConditions;
041
042    /**
043     * Constructs a new DeletingVisitor.
044     * 
045     * @param basePath used to relativize paths
046     * @param pathConditions objects that need to confirm whether a file can be deleted
047     * @param testMode if true, files are not deleted but instead a message is printed to the <a
048     *            href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages">status logger</a>
049     *            at INFO level. Users can use this to do a dry run to test if their configuration works as expected.
050     */
051    public DeletingVisitor(final Path basePath, final List<? extends PathCondition> pathConditions,
052            final boolean testMode) {
053        this.testMode = testMode;
054        this.basePath = Objects.requireNonNull(basePath, "basePath");
055        this.pathConditions = Objects.requireNonNull(pathConditions, "pathConditions");
056        for (final PathCondition condition : pathConditions) {
057            condition.beforeFileTreeWalk();
058        }
059    }
060
061    @Override
062    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
063        for (final PathCondition pathFilter : pathConditions) {
064            final Path relative = basePath.relativize(file);
065            if (!pathFilter.accept(basePath, relative, attrs)) {
066                LOGGER.trace("Not deleting base={}, relative={}", basePath, relative);
067                return FileVisitResult.CONTINUE;
068            }
069        }
070        if (isTestMode()) {
071            LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file);
072        } else {
073            delete(file);
074        }
075        return FileVisitResult.CONTINUE;
076    }
077
078    /**
079     * Deletes the specified file.
080     * 
081     * @param file the file to delete
082     * @throws IOException if a problem occurred deleting the file
083     */
084    protected void delete(final Path file) throws IOException {
085        LOGGER.trace("Deleting {}", file);
086        Files.deleteIfExists(file);
087    }
088
089    /**
090     * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise.
091     * 
092     * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise
093     */
094    public boolean isTestMode() {
095        return testMode;
096    }
097}