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.FileVisitOption;
022import java.nio.file.FileVisitor;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.nio.file.Paths;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.EnumSet;
029import java.util.List;
030import java.util.Set;
031import java.util.concurrent.TimeUnit;
032
033import org.apache.logging.log4j.core.lookup.StrSubstitutor;
034
035/**
036 * Abstract action for processing files that are accepted by the specified PathFilters.
037 */
038public abstract class AbstractPathAction extends AbstractAction {
039
040    private final String basePathString;
041    private final Set<FileVisitOption> options;
042    private final int maxDepth;
043    private final List<PathCondition> pathConditions;
044    private final StrSubstitutor subst;
045
046    /**
047     * Creates a new AbstractPathAction that starts scanning for files to process from the specified base path.
048     *
049     * @param basePath base path from where to start scanning for files to process.
050     * @param followSymbolicLinks whether to follow symbolic links. Default is false.
051     * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0
052     *            means that only the starting file is visited, unless denied by the security manager. A value of
053     *            MAX_VALUE may be used to indicate that all levels should be visited.
054     * @param pathFilters an array of path filters (if more than one, they all need to accept a path before it is
055     *            processed).
056     */
057    protected AbstractPathAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth,
058            final PathCondition[] pathFilters, final StrSubstitutor subst) {
059        this.basePathString = basePath;
060        this.options = followSymbolicLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS)
061                : Collections.<FileVisitOption> emptySet();
062        this.maxDepth = maxDepth;
063        this.pathConditions = Arrays.asList(Arrays.copyOf(pathFilters, pathFilters.length));
064        this.subst = subst;
065    }
066
067    @Override
068    public boolean execute() throws IOException {
069        return execute(createFileVisitor(getBasePath(), pathConditions));
070    }
071
072    public boolean execute(final FileVisitor<Path> visitor) throws IOException {
073        final long start = System.nanoTime();
074        LOGGER.debug("Starting {}", this);
075
076        Files.walkFileTree(getBasePath(), options, maxDepth, visitor);
077
078        final double duration = System.nanoTime() - start;
079        LOGGER.debug("{} complete in {} seconds", getClass().getSimpleName(), duration / TimeUnit.SECONDS.toNanos(1));
080
081        // TODO return (visitor.success || ignoreProcessingFailure)
082        return true; // do not abort rollover even if processing failed
083    }
084
085    /**
086     * Creates a new {@code FileVisitor<Path>} to pass to the {@link Files#walkFileTree(Path, Set, int, FileVisitor)}
087     * method when the {@link #execute()} method is invoked.
088     * <p>
089     * The visitor is responsible for processing the files it encounters that are accepted by all filters.
090     *
091     * @param visitorBaseDir base dir from where to start scanning for files to process
092     * @param conditions filters that determine if a file should be processed
093     * @return a new {@code FileVisitor<Path>}
094     */
095    protected abstract FileVisitor<Path> createFileVisitor(final Path visitorBaseDir,
096            final List<PathCondition> conditions);
097
098    /**
099     * Returns the base path from where to start scanning for files to delete. Lookups are resolved, so if the
100     * configuration was <code>&lt;Delete basePath="${sys:user.home}/abc" /&gt;</code> then this method returns a path
101     * to the "abc" file or directory in the user's home directory.
102     *
103     * @return the base path (all lookups resolved)
104     */
105    public Path getBasePath() {
106        return Paths.get(subst.replace(getBasePathString()));
107    }
108
109    /**
110     * Returns the base path as it was specified in the configuration. Lookups are not resolved.
111     *
112     * @return the base path as it was specified in the configuration
113     */
114    public String getBasePathString() {
115        return basePathString;
116    }
117
118    public StrSubstitutor getStrSubstitutor() {
119        return subst;
120    }
121
122    /**
123     * Returns whether to follow symbolic links or not.
124     *
125     * @return the options
126     */
127    public Set<FileVisitOption> getOptions() {
128        return Collections.unmodifiableSet(options);
129    }
130
131    /**
132     * Returns whether to follow symbolic links or not.
133     *
134     * @return whether to follow symbolic links or not
135     */
136    public boolean isFollowSymbolicLinks() {
137        return options.contains(FileVisitOption.FOLLOW_LINKS);
138    }
139
140    /**
141     * Returns the the maximum number of directory levels to visit.
142     *
143     * @return the maxDepth
144     */
145    public int getMaxDepth() {
146        return maxDepth;
147    }
148
149    /**
150     * Returns the list of PathCondition objects.
151     *
152     * @return the pathFilters
153     */
154    public List<PathCondition> getPathConditions() {
155        return Collections.unmodifiableList(pathConditions);
156    }
157
158    @Override
159    public String toString() {
160        return getClass().getSimpleName() + "[basePath=" + getBasePath() + ", options=" + options + ", maxDepth="
161                + maxDepth + ", conditions=" + pathConditions + "]";
162    }
163}