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.NoSuchFileException;
024import java.nio.file.Path;
025import java.nio.file.SimpleFileVisitor;
026import java.nio.file.attribute.BasicFileAttributes;
027import java.util.List;
028import java.util.Objects;
029
030import org.apache.logging.log4j.Logger;
031import org.apache.logging.log4j.status.StatusLogger;
032
033/**
034 * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored.
035 */
036public class DeletingVisitor extends SimpleFileVisitor<Path> {
037    private static final Logger LOGGER = StatusLogger.getLogger();
038
039    private final Path basePath;
040    private final boolean testMode;
041    private final List<? extends PathCondition> pathConditions;
042
043    /**
044     * Constructs a new DeletingVisitor.
045     *
046     * @param basePath used to relativize paths
047     * @param pathConditions objects that need to confirm whether a file can be deleted
048     * @param testMode if true, files are not deleted but instead a message is printed to the <a
049     *            href="http://logging.apache.org/log4j/2.x/manual/configuration.html#StatusMessages">status logger</a>
050     *            at INFO level. Users can use this to do a dry run to test if their configuration works as expected.
051     */
052    public DeletingVisitor(final Path basePath, final List<? extends PathCondition> pathConditions,
053            final boolean testMode) {
054        this.testMode = testMode;
055        this.basePath = Objects.requireNonNull(basePath, "basePath");
056        this.pathConditions = Objects.requireNonNull(pathConditions, "pathConditions");
057        for (final PathCondition condition : pathConditions) {
058            condition.beforeFileTreeWalk();
059        }
060    }
061
062    @Override
063    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
064        for (final PathCondition pathFilter : pathConditions) {
065            final Path relative = basePath.relativize(file);
066            if (!pathFilter.accept(basePath, relative, attrs)) {
067                LOGGER.trace("Not deleting base={}, relative={}", basePath, relative);
068                return FileVisitResult.CONTINUE;
069            }
070        }
071        if (isTestMode()) {
072            LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file);
073        } else {
074            delete(file);
075        }
076        return FileVisitResult.CONTINUE;
077    }
078
079    @Override
080    public FileVisitResult visitFileFailed(Path file, IOException ioException) throws IOException {
081        // LOG4J2-2677: Appenders may rollover and purge in parallel. SimpleVisitor rethrows exceptions from
082        // failed attempts to load file attributes.
083        if (ioException instanceof NoSuchFileException) {
084            LOGGER.info("File {} could not be accessed, it has likely already been deleted", file, ioException);
085            return FileVisitResult.CONTINUE;
086        } else {
087            return super.visitFileFailed(file, ioException);
088        }
089    }
090
091    /**
092     * Deletes the specified file.
093     *
094     * @param file the file to delete
095     * @throws IOException if a problem occurred deleting the file
096     */
097    protected void delete(final Path file) throws IOException {
098        LOGGER.trace("Deleting {}", file);
099        Files.deleteIfExists(file);
100    }
101
102    /**
103     * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise.
104     *
105     * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise
106     */
107    public boolean isTestMode() {
108        return testMode;
109    }
110}