1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17
18 package org.apache.logging.log4j.core.appender.rolling.action;
19
20 import java.io.IOException;
21 import java.nio.file.FileVisitOption;
22 import java.nio.file.FileVisitor;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.EnumSet;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.concurrent.TimeUnit;
32
33 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
34
35 /**
36 * Abstract action for processing files that are accepted by the specified PathFilters.
37 */
38 public abstract class AbstractPathAction extends AbstractAction {
39
40 private final String basePathString;
41 private final Set<FileVisitOption> options;
42 private final int maxDepth;
43 private final List<PathCondition> pathConditions;
44 private final StrSubstitutor subst;
45
46 /**
47 * Creates a new AbstractPathAction that starts scanning for files to process from the specified base path.
48 *
49 * @param basePath base path from where to start scanning for files to process.
50 * @param followSymbolicLinks whether to follow symbolic links. Default is false.
51 * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0
52 * means that only the starting file is visited, unless denied by the security manager. A value of
53 * MAX_VALUE may be used to indicate that all levels should be visited.
54 * @param pathFilters an array of path filters (if more than one, they all need to accept a path before it is
55 * processed).
56 */
57 protected AbstractPathAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth,
58 final PathCondition[] pathFilters, final StrSubstitutor subst) {
59 this.basePathString = basePath;
60 this.options = followSymbolicLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS)
61 : Collections.<FileVisitOption> emptySet();
62 this.maxDepth = maxDepth;
63 this.pathConditions = Arrays.asList(Arrays.copyOf(pathFilters, pathFilters.length));
64 this.subst = subst;
65 }
66
67 @Override
68 public boolean execute() throws IOException {
69 return execute(createFileVisitor(getBasePath(), pathConditions));
70 }
71
72 public boolean execute(final FileVisitor<Path> visitor) throws IOException {
73 final long start = System.nanoTime();
74 LOGGER.debug("Starting {}", this);
75
76 Files.walkFileTree(getBasePath(), options, maxDepth, visitor);
77
78 final double duration = System.nanoTime() - start;
79 LOGGER.debug("{} complete in {} seconds", getClass().getSimpleName(), duration / TimeUnit.SECONDS.toNanos(1));
80
81 // TODO return (visitor.success || ignoreProcessingFailure)
82 return true; // do not abort rollover even if processing failed
83 }
84
85 /**
86 * Creates a new {@code FileVisitor<Path>} to pass to the {@link Files#walkFileTree(Path, Set, int, FileVisitor)}
87 * method when the {@link #execute()} method is invoked.
88 * <p>
89 * The visitor is responsible for processing the files it encounters that are accepted by all filters.
90 *
91 * @param visitorBaseDir base dir from where to start scanning for files to process
92 * @param conditions filters that determine if a file should be processed
93 * @return a new {@code FileVisitor<Path>}
94 */
95 protected abstract FileVisitor<Path> createFileVisitor(final Path visitorBaseDir,
96 final List<PathCondition> conditions);
97
98 /**
99 * Returns the base path from where to start scanning for files to delete. Lookups are resolved, so if the
100 * configuration was <code><Delete basePath="${sys:user.home}/abc" /></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 }