I wonder if Duration belongs in core.util; we could reuse it other places,
like in the Configuration monitorInterval setting.

Gary

---------- Forwarded message ----------
From: <rpo...@apache.org>
Date: Sat, Nov 14, 2015 at 11:25 AM
Subject: [1/4] logging-log4j2 git commit: LOG4J2-435 actions and filters
for delete-on-rollover support
To: comm...@logging.apache.org


Repository: logging-log4j2
Updated Branches:
  refs/heads/LOG4J2-435-delete-on-rollover [created] 07175148e


LOG4J2-435 actions and filters for delete-on-rollover support

- AbstractPathAction defines an Action that can process multiple files
starting at some base path and excluding files rejected by nested
filters
- DeleteAction extends AbstractPathAction to run Files.walkFileTree with
a DeletingVisitor
- DeletingVisitor deletes files that are accepted by all PathFilters
- PathFilter accepts/rejects a basePath + relativePath + fileAttributes
combination
- And, Or, Not are composite PathFilters
- FileLastModifiedFilter accepts paths whose lastModifiedTime is at
least the specified duration in the past
- Duration implements ISO8601
- FileNameFilter accepts paths based on either a regex or a name check
(with simple wildcarts)

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit:
http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/7b7bdfaf
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/7b7bdfaf
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/7b7bdfaf

Branch: refs/heads/LOG4J2-435-delete-on-rollover
Commit: 7b7bdfafb348caf59025c8f6efc530a78d0ff45f
Parents: d9fa7eb
Author: rpopma <rpo...@apache.org>
Authored: Sun Nov 15 04:22:27 2015 +0900
Committer: rpopma <rpo...@apache.org>
Committed: Sun Nov 15 04:22:27 2015 +0900

----------------------------------------------------------------------
 .../rolling/action/AbstractPathAction.java      | 154 +++++++++++
 .../log4j/core/appender/rolling/action/And.java |  76 ++++++
 .../appender/rolling/action/DeleteAction.java   |  83 ++++++
 .../rolling/action/DeletingVisitor.java         |  74 ++++++
 .../core/appender/rolling/action/Duration.java  | 256 +++++++++++++++++++
 .../rolling/action/FileLastModifiedFilter.java  |  77 ++++++
 .../appender/rolling/action/FileNameFilter.java | 144 +++++++++++
 .../log4j/core/appender/rolling/action/Not.java |  70 +++++
 .../log4j/core/appender/rolling/action/Or.java  |  74 ++++++
 .../appender/rolling/action/PathFilter.java     |  37 +++
 10 files changed, 1045 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java
new file mode 100644
index 0000000..d976549
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java
@@ -0,0 +1,154 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+
+/**
+ * Abstract action for processing files that are accepted by the specified
PathFilters.
+ */
+public abstract class AbstractPathAction extends AbstractAction {
+
+    private final String basePathString;
+    private final Set<FileVisitOption> options;
+    private final int maxDepth;
+    private final List<PathFilter> pathFilters;
+    private final StrSubstitutor subst;
+
+    /**
+     * Creates a new AbstractPathAction that starts scanning for files to
process from the specified base path.
+     *
+     * @param basePath base path from where to start scanning for files to
process.
+     * @param followSymbolicLinks whether to follow symbolic links.
Default is false.
+     * @param maxDepth The maxDepth parameter is the maximum number of
levels of directories to visit. A value of 0
+     *            means that only the starting file is visited, unless
denied by the security manager. A value of
+     *            MAX_VALUE may be used to indicate that all levels should
be visited.
+     * @param pathFilters an array of path filters (if more than one, they
all need to accept a path before it is
+     *            processed).
+     */
+    protected AbstractPathAction(final String basePath, final boolean
followSymbolicLinks, final int maxDepth,
+            final PathFilter[] pathFilters, final StrSubstitutor subst) {
+        this.basePathString = basePath;
+        this.options = followSymbolicLinks ?
EnumSet.of(FileVisitOption.FOLLOW_LINKS) //
+                : Collections.<FileVisitOption> emptySet();
+        this.maxDepth = maxDepth;
+        this.pathFilters = Arrays.asList(Arrays.copyOf(pathFilters,
pathFilters.length));
+        this.subst = subst;
+    }
+
+    @Override
+    public boolean execute() throws IOException {
+        return execute(createFileVisitor(getBasePath(), pathFilters));
+    }
+
+    public boolean execute(final FileVisitor<Path> visitor) throws
IOException {
+        final long start = System.nanoTime();
+        LOGGER.debug("Starting {}", this);
+
+        Files.walkFileTree(getBasePath(), options, maxDepth, visitor);
+
+        final double duration = System.nanoTime() - start;
+        LOGGER.debug("{} complete in {} seconds",
getClass().getSimpleName(), duration / TimeUnit.SECONDS.toNanos(1));
+
+        // TODO return (visitor.success || ignoreProcessingFailure)
+        return true; // do not abort rollover even if processing failed
+    }
+
+    /**
+     * Creates a new {@code FileVisitor<Path>} to pass to the {@link
Files#walkFileTree(Path, Set, int, FileVisitor)}
+     * method when the {@link #execute()} method is invoked.
+     * <p>
+     * The visitor is responsible for processing the files it encounters
that are accepted by all filters.
+     *
+     * @param visitorBaseDir base dir from where to start scanning for
files to process
+     * @param visitorFilters filters that determine if a file should be
processed
+     * @return a new {@code FileVisitor<Path>}
+     */
+    protected abstract FileVisitor<Path> createFileVisitor(final Path
visitorBaseDir,
+            final List<PathFilter> visitorFilters);
+
+    /**
+     * Returns the base path from where to start scanning for files to
delete. Lookups are resolved, so if the
+     * configuration was <code>&lt;Delete basePath="${sys:user.home}/abc"
/&gt;</code> then this method returns a path
+     * to the "abc" file or directory in the user's home directory.
+     *
+     * @return the base path (all lookups resolved)
+     */
+    public Path getBasePath() {
+        return
FileSystems.getDefault().getPath(subst.replace(getBasePathString()));
+    }
+
+    /**
+     * Returns the base path as it was specified in the configuration.
Lookups are not resolved.
+     *
+     * @return the base path as it was specified in the configuration
+     */
+    public String getBasePathString() {
+        return basePathString;
+    }
+
+    public StrSubstitutor getStrSubstitutor() {
+        return subst;
+    }
+
+    /**
+     * Returns whether to follow symbolic links or not.
+     *
+     * @return the options
+     */
+    public Set<FileVisitOption> getOptions() {
+        return Collections.unmodifiableSet(options);
+    }
+
+    /**
+     * Returns the the maximum number of directory levels to visit.
+     *
+     * @return the maxDepth
+     */
+    public int getMaxDepth() {
+        return maxDepth;
+    }
+
+    /**
+     * Returns the list of PathFilter objects.
+     *
+     * @return the pathFilters
+     */
+    public List<PathFilter> getPathFilters() {
+        return Collections.unmodifiableList(pathFilters);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[basePath=" + getBasePath() +
", options=" + options + ", maxDepth="
+                + maxDepth + ", filters=" + pathFilters + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java
new file mode 100644
index 0000000..c701c8e
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/And.java
@@ -0,0 +1,76 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Composite {@code DeleteFilter} that only accepts objects that are
accepted by <em>all</em> component filters.
+ */
+@Plugin(name = "And", category = "Core", printObject = true)
+public final class And implements PathFilter {
+
+    private final PathFilter[] components;
+
+    private And(final PathFilter... filters) {
+        this.components = Objects.requireNonNull(filters, "filters");
+    }
+
+    public PathFilter[] getDeleteFilters() {
+        return components;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see
org.apache.logging.log4j.core.appender.rolling.action.DeleteFilter#accept(java.nio.file.Path,
+     * java.nio.file.Path)
+     */
+    @Override
+    public boolean accept(final Path baseDir, final Path relativePath,
final BasicFileAttributes attrs) {
+        for (final PathFilter component : components) {
+            if (!component.accept(baseDir, relativePath, attrs)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Create a Composite DeleteFilter.
+     *
+     * @param components The component filters.
+     * @return A CompositeDeleteFilter.
+     */
+    @PluginFactory
+    public static And createAndFilter( //
+            @PluginElement("Filters") final PathFilter... components) {
+        return new And(components);
+    }
+
+    @Override
+    public String toString() {
+        return "And(filters=" + Arrays.toString(components) + ")";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
new file mode 100644
index 0000000..47fddc5
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
@@ -0,0 +1,83 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.FileVisitor;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+
+/**
+ * Rollover or scheduled action for deleting old log files that are
accepted by the specified PathFilters.
+ */
+@Plugin(name = "Delete", category = "Core", printObject = true)
+public class DeleteAction extends AbstractPathAction {
+
+    /**
+     * Creates a new DeleteAction that starts scanning for files to delete
from the specified base path.
+     *
+     * @param basePath base path from where to start scanning for files to
delete.
+     * @param followSymbolicLinks whether to follow symbolic links.
Default is false.
+     * @param maxDepth The maxDepth parameter is the maximum number of
levels of directories to visit. A value of 0
+     *            means that only the starting file is visited, unless
denied by the security manager. A value of
+     *            MAX_VALUE may be used to indicate that all levels should
be visited.
+     * @param pathFilters an array of path filters (if more than one, they
all need to accept a path before it is
+     *            deleted).
+     */
+    DeleteAction(final String basePath, final boolean followSymbolicLinks,
final int maxDepth,
+            final PathFilter[] pathFilters, final StrSubstitutor subst) {
+        super(basePath, followSymbolicLinks, maxDepth, pathFilters, subst);
+    }
+
+    @Override
+    protected FileVisitor<Path> createFileVisitor(final Path
visitorBaseDir, final List<PathFilter> visitorFilters) {
+        return new DeletingVisitor(visitorBaseDir, visitorFilters);
+    }
+
+    /**
+     * Create a DeleteAction.
+     *
+     * @param basePath base path from where to start scanning for files to
delete.
+     * @param followLinks whether to follow symbolic links. Default is
false.
+     * @param maxDepth The maxDepth parameter is the maximum number of
levels of directories to visit. A value of 0
+     *            means that only the starting file is visited, unless
denied by the security manager. A value of
+     *            MAX_VALUE may be used to indicate that all levels should
be visited.
+     * @param pathFilters an array of path filters (if more than one, they
all need to accept a path before it is
+     *            deleted).
+     * @param config The Configuration.
+     * @return A DeleteAction.
+     */
+    @PluginFactory
+    public static DeleteAction createDeleteAction(
+            // @formatter:off
+            @PluginAttribute("basePath") final String basePath, //
+            @PluginAttribute(value = "followLinks", defaultBoolean =
false) final boolean followLinks,
+            @PluginAttribute(value = "maxDepth", defaultInt = 1) final int
maxDepth,
+            @PluginElement("PathFilters") final PathFilter[] pathFilters,
+            @PluginConfiguration final Configuration config) {
+            // @formatter:on
+        return new DeleteAction(basePath, followLinks, maxDepth,
pathFilters, config.getStrSubstitutor());
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
new file mode 100644
index 0000000..ba290d9
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * FileVisitor that deletes files that are accepted by all PathFilters.
Directories are ignored.
+ */
+public class DeletingVisitor extends SimpleFileVisitor<Path> {
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    private final Path basePath;
+    private final List<? extends PathFilter> pathFilters;
+
+    /**
+     * Constructs a new DeletingVisitor.
+     *
+     * @param basePath used to relativize paths
+     * @param pathFilters objects that need to confirm whether a file can
be deleted
+     */
+    public DeletingVisitor(final Path basePath, final List<? extends
PathFilter> pathFilters) {
+        this.basePath = Objects.requireNonNull(basePath, "basePath");
+        this.pathFilters = Objects.requireNonNull(pathFilters, "filters");
+    }
+
+    @Override
+    public FileVisitResult visitFile(final Path file, final
BasicFileAttributes attrs) throws IOException {
+        for (final PathFilter pathFilter : pathFilters) {
+            if (!pathFilter.accept(basePath, basePath.relativize(file),
attrs)) {
+                LOGGER.trace("Not deleting {}", file);
+                return FileVisitResult.CONTINUE;
+            }
+        }
+        delete(file);
+        return FileVisitResult.CONTINUE;
+    }
+
+    /**
+     * Deletes the specified file.
+     *
+     * @param file the file to delete
+     * @throws IOException if a problem occurred deleting the file
+     */
+    protected void delete(final Path file) throws IOException {
+        LOGGER.trace("Deleting {}", file);
+        Files.delete(file);
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java
new file mode 100644
index 0000000..d2b1e18
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java
@@ -0,0 +1,256 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Simplified implementation of the <a href="
https://en.wikipedia.org/wiki/ISO_8601#Durations";>ISO-8601 Durations</a>
+ * standard. The supported format is {@code PnDTnHnMnS}. Days are
considered to be exactly 24 hours.
+ * <p>
+ * Similarly to the {@code java.time.Duration} class, this class does not
support year or month sections in the format.
+ * This implementation does not support fractions or negative values.
+ *
+ * @see #parse(CharSequence)
+ */
+public class Duration implements Serializable, Comparable<Duration> {
+    private static final long serialVersionUID = -3756810052716342061L;
+
+    /**
+     * Constant for a duration of zero.
+     */
+    public static final Duration ZERO = new Duration(0);
+
+    /**
+     * Hours per day.
+     */
+    private static final int HOURS_PER_DAY = 24;
+    /**
+     * Minutes per hour.
+     */
+    private static final int MINUTES_PER_HOUR = 60;
+    /**
+     * Seconds per minute.
+     */
+    private static final int SECONDS_PER_MINUTE = 60;
+    /**
+     * Seconds per hour.
+     */
+    private static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE *
MINUTES_PER_HOUR;
+    /**
+     * Seconds per day.
+     */
+    private static final int SECONDS_PER_DAY = SECONDS_PER_HOUR *
HOURS_PER_DAY;
+
+    /**
+     * The pattern for parsing.
+     */
+    private static final Pattern PATTERN =
Pattern.compile("P(?:([0-9]+)D)?"
+            + "(T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)?S)?)?",
Pattern.CASE_INSENSITIVE);
+
+    /**
+     * The number of seconds in the duration.
+     */
+    private final long seconds;
+
+    /**
+     * Constructs an instance of {@code Duration} using seconds.
+     *
+     * @param seconds the length of the duration in seconds, positive or
negative
+     */
+    private Duration(long seconds) {
+        super();
+        this.seconds = seconds;
+    }
+
+    /**
+     * Obtains a {@code Duration} from a text string such as {@code
PnDTnHnMnS}.
+     * <p>
+     * This will parse a textual representation of a duration, including
the string produced by {@code toString()}. The
+     * formats accepted are based on the ISO-8601 duration format {@code
PnDTnHnMnS} with days considered to be exactly
+     * 24 hours.
+     * <p>
+     * This implementation does not support negative numbers or fractions
(so the smallest non-zero value a Duration can
+     * have is one second).
+     * <p>
+     * The string starts with the ASCII letter "P" in upper or lower case.
There are then four sections, each consisting
+     * of a number and a suffix. The sections have suffixes in ASCII of
"D", "H", "M" and "S" for days, hours, minutes
+     * and seconds, accepted in upper or lower case. The suffixes must
occur in order. The ASCII letter "T" must occur
+     * before the first occurrence, if any, of an hour, minute or second
section. At least one of the four sections must
+     * be present, and if "T" is present there must be at least one
section after the "T". The number part of each
+     * section must consist of one or more ASCII digits. The number may
not be prefixed by the ASCII negative or
+     * positive symbol. The number of days, hours, minutes and seconds
must parse to a {@code long}.
+     * <p>
+     * Examples:
+     *
+     * <pre>
+     *    "PT20S" -- parses as "20 seconds"
+     *    "PT15M"     -- parses as "15 minutes" (where a minute is 60
seconds)
+     *    "PT10H"     -- parses as "10 hours" (where an hour is 3600
seconds)
+     *    "P2D"       -- parses as "2 days" (where a day is 24 hours or
86400 seconds)
+     *    "P2DT3H4M"  -- parses as "2 days, 3 hours and 4 minutes"
+     * </pre>
+     *
+     * @param text the text to parse, not null
+     * @return the parsed duration, not null
+     * @throws IllegalArgumentException if the text cannot be parsed to a
duration
+     */
+    public static Duration parse(CharSequence text) {
+        Objects.requireNonNull(text, "text");
+        Matcher matcher = PATTERN.matcher(text);
+        if (matcher.matches()) {
+            // check for letter T but no time sections
+            if ("T".equals(matcher.group(2)) == false) {
+                String dayMatch = matcher.group(1);
+                String hourMatch = matcher.group(3);
+                String minuteMatch = matcher.group(4);
+                String secondMatch = matcher.group(5);
+                if (dayMatch != null || hourMatch != null || minuteMatch
!= null || secondMatch != null) {
+                    long daysAsSecs = parseNumber(text, dayMatch,
SECONDS_PER_DAY, "days");
+                    long hoursAsSecs = parseNumber(text, hourMatch,
SECONDS_PER_HOUR, "hours");
+                    long minsAsSecs = parseNumber(text, minuteMatch,
SECONDS_PER_MINUTE, "minutes");
+                    long seconds = parseNumber(text, secondMatch, 1,
"seconds");
+                    try {
+                        return create(daysAsSecs, hoursAsSecs, minsAsSecs,
seconds);
+                    } catch (ArithmeticException ex) {
+                        throw new IllegalArgumentException("Text cannot be
parsed to a Duration (overflow) " + text, ex);
+                    }
+                }
+            }
+        }
+        throw new IllegalArgumentException("Text cannot be parsed to a
Duration: " + text);
+    }
+
+    private static long parseNumber(final CharSequence text, final String
parsed, final int multiplier,
+            final String errorText) {
+        // regex limits to [0-9]+
+        if (parsed == null) {
+            return 0;
+        }
+        try {
+            final long val = Long.parseLong(parsed);
+            return val * multiplier;
+        } catch (final Exception ex) {
+            throw new IllegalArgumentException("Text cannot be parsed to a
Duration: " + errorText + " (in " + text
+                    + ")", ex);
+        }
+    }
+
+    private static Duration create(final long daysAsSecs, final long
hoursAsSecs, final long minsAsSecs, final long secs) {
+        return create(daysAsSecs + hoursAsSecs + minsAsSecs + secs);
+    }
+
+    /**
+     * Obtains an instance of {@code Duration} using seconds.
+     *
+     * @param seconds the length of the duration in seconds, positive only
+     */
+    private static Duration create(final long seconds) {
+        if ((seconds) == 0) {
+            return ZERO;
+        }
+        return new Duration(seconds);
+    }
+
+    /**
+     * Converts this duration to the total length in milliseconds.
+     *
+     * @return the total length of the duration in milliseconds
+     */
+    public long toMillis() {
+        return seconds * 1000L;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof Duration)) {
+            return false;
+        }
+        Duration other = (Duration) obj;
+        return other.seconds == this.seconds;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) (seconds ^ (seconds >>> 32));
+    }
+
+    /**
+     * A string representation of this duration using ISO-8601 seconds
based representation, such as {@code PT8H6M12S}.
+     * <p>
+     * The format of the returned string will be {@code PnDTnHnMnS}, where
n is the relevant days, hours, minutes or
+     * seconds part of the duration. If a section has a zero value, it is
omitted. The hours, minutes and seconds are
+     * all positive.
+     * <p>
+     * Examples:
+     *
+     * <pre>
+     *    "20 seconds"                     -- "PT20S
+     *    "15 minutes" (15 * 60 seconds)   -- "PT15M"
+     *    "10 hours" (10 * 3600 seconds)   -- "PT10H"
+     *    "2 days" (2 * 86400 seconds)     -- "P2D"
+     * </pre>
+     *
+     * @return an ISO-8601 representation of this duration, not null
+     */
+    @Override
+    public String toString() {
+        if (this == ZERO) {
+            return "PT0S";
+        }
+        final long days = seconds / SECONDS_PER_DAY;
+        final long hours = (seconds % SECONDS_PER_DAY) / SECONDS_PER_HOUR;
+        final int minutes = (int) ((seconds % SECONDS_PER_HOUR) /
SECONDS_PER_MINUTE);
+        final int secs = (int) (seconds % SECONDS_PER_MINUTE);
+        final StringBuilder buf = new StringBuilder(24);
+        buf.append("P");
+        if (days != 0) {
+            buf.append(days).append('D');
+        }
+        if ((hours | minutes | secs) != 0) {
+            buf.append('T');
+        }
+        if (hours != 0) {
+            buf.append(hours).append('H');
+        }
+        if (minutes != 0) {
+            buf.append(minutes).append('M');
+        }
+        if (secs == 0 && buf.length() > 0) {
+            return buf.toString();
+        }
+        buf.append(secs).append('S');
+        return buf.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    @Override
+    public int compareTo(Duration other) {
+        return Long.signum(toMillis() - other.toMillis());
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java
new file mode 100644
index 0000000..6904ade
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileLastModifiedFilter.java
@@ -0,0 +1,77 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Objects;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.util.Clock;
+import org.apache.logging.log4j.core.util.ClockFactory;
+
+/**
+ * PathFilter that accepts paths that are older than the specified
duration.
+ */
+@Plugin(name = "LastModified", category = "Core", printObject = true)
+public final class FileLastModifiedFilter implements PathFilter {
+    private static final Clock CLOCK = ClockFactory.getClock();
+
+    private final Duration duration;
+
+    private FileLastModifiedFilter(final Duration duration) {
+        this.duration = Objects.requireNonNull(duration, "duration");
+    }
+
+    public Duration getDuration() {
+        return duration;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see
org.apache.logging.log4j.core.appender.rolling.action.PathFilter#accept(java.nio.file.Path,
+     * java.nio.file.Path)
+     */
+    @Override
+    public boolean accept(final Path baseDir, final Path relativePath,
final BasicFileAttributes attrs) {
+        final FileTime fileTime = attrs.lastModifiedTime();
+        final long millis = fileTime.toMillis();
+        final long ageMillis = CLOCK.currentTimeMillis() - millis;
+        return ageMillis >= duration.toMillis();
+    }
+
+    /**
+     * Create a FileLastModifiedFilter filter.
+     *
+     * @param duration The path age that is accepted by this filter. Must
be a valid Duration.
+     * @return A FileLastModifiedFilter filter.
+     */
+    @PluginFactory
+    public static FileLastModifiedFilter createAgeFilter( //
+            @PluginAttribute("duration") final Duration duration) {
+        return new FileLastModifiedFilter(duration);
+    }
+
+    @Override
+    public String toString() {
+        return "FileLastModifiedFilter(age=" + duration + ")";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java
new file mode 100644
index 0000000..cbde2a9
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileNameFilter.java
@@ -0,0 +1,144 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * PathFilter that accepts files for deletion if their relative path
matches either a path pattern or a regular
+ * expression.
+ * <p>
+ * If both a regular expression and a path pattern are specified the path
pattern is used and the regular expression is
+ * ignored.
+ * <p>
+ * The path pattern may contain '?' and '*' wildcarts.
+ */
+@Plugin(name = "File", category = "Core", printObject = true)
+public final class FileNameFilter implements PathFilter {
+
+    private final Pattern regex;
+    private final String pathPattern;
+
+    /**
+     * Constructs a FileNameFilter filter. If both a regular expression
and a path pattern are specified the path
+     * pattern is used and the regular expression is ignored.
+     *
+     * @param path the baseDir-relative path pattern of the files to
delete (may contain '*' and '?' wildcarts)
+     * @param regex the regular expression that matches the
baseDir-relative path of the file(s) to delete
+     */
+    private FileNameFilter(final String path, final String regex) {
+        if (regex == null && path == null) {
+            throw new IllegalArgumentException("Specify either a path or a
regular expression. Both cannot be null.");
+        }
+        this.regex = regex != null ? Pattern.compile(regex) : null;
+        this.pathPattern = path;
+    }
+
+    /**
+     * Returns the compiled regular expression that matches the
baseDir-relative path of the file(s) to delete, or
+     * {@code null} if no regular expression was specified.
+     *
+     * @return the compiled regular expression, or {@code null}
+     */
+    public Pattern getRegex() {
+        return regex;
+    }
+
+    /**
+     * Returns the baseDir-relative path pattern of the files to delete,
or {@code null} if not specified. This path
+     * pattern may contain '*' and '?' wildcarts.
+     *
+     * @return relative path of the file(s) to delete (may contain '*' and
'?' wildcarts)
+     */
+    public String getPathPattern() {
+        return pathPattern;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see
org.apache.logging.log4j.core.appender.rolling.action.PathFilter#accept(java.nio.file.Path,
+     * java.nio.file.Path)
+     */
+    @Override
+    public boolean accept(final Path baseDir, final Path relativePath,
final BasicFileAttributes attrs) {
+        if (pathPattern != null) {
+            return isMatch(relativePath.toString(), pathPattern);
+        } else {
+            Matcher matcher = regex.matcher(relativePath.toString());
+            return (matcher.matches());
+        }
+    }
+
+    // package protected for unit tests
+    static boolean isMatch(final String text, final String pattern) {
+        int i = 0;
+        int j = 0;
+        int starIndex = -1;
+        int iIndex = -1;
+
+        while (i < text.length()) {
+            if (j < pattern.length() && (pattern.charAt(j) == '?' ||
pattern.charAt(j) == text.charAt(i))) {
+                ++i;
+                ++j;
+            } else if (j < pattern.length() && pattern.charAt(j) == '*') {
+                starIndex = j;
+                iIndex = i;
+                j++;
+            } else if (starIndex != -1) {
+                j = starIndex + 1;
+                i = iIndex + 1;
+                iIndex++;
+            } else {
+                return false;
+            }
+        }
+
+        while (j < pattern.length() && pattern.charAt(j) == '*') {
+            ++j;
+        }
+        return j == pattern.length();
+    }
+
+    /**
+     * Creates a FileNameFilter filter. If both a regular expression and a
path pattern are specified the path pattern
+     * is used and the regular expression is ignored.
+     *
+     * @param path the baseDir-relative path pattern of the files to
delete (may contain '*' and '?' wildcarts)
+     * @param regex the regular expression that matches the
baseDir-relative path of the file(s) to delete
+     * @return A FileNameFilter filter.
+     */
+    @PluginFactory
+    public static FileNameFilter createNameFilter( //
+            @PluginAttribute("path") final String path, //
+            @PluginAttribute("regex") final String regex) {
+        return new FileNameFilter(path, regex);
+    }
+
+    @Override
+    public String toString() {
+        final String pattern = regex == null ? "null" : regex.pattern();
+        return "FileNameFilter(regex=" + pattern + ", name=" + pathPattern
+ ")";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java
new file mode 100644
index 0000000..7265dce
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Not.java
@@ -0,0 +1,70 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Wrapper {@code DeleteFilter} that accepts objects that are rejected by
the wrapped component filter.
+ */
+@Plugin(name = "Not", category = "Core", printObject = true)
+public final class Not implements PathFilter {
+
+    private final PathFilter negate;
+
+    private Not(final PathFilter negate) {
+        this.negate = Objects.requireNonNull(negate, "filter");
+    }
+
+    public PathFilter getWrappedFilter() {
+        return negate;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see
org.apache.logging.log4j.core.appender.rolling.action.DeleteFilter#accept(java.nio.file.Path,
+     * java.nio.file.Path)
+     */
+    @Override
+    public boolean accept(final Path baseDir, final Path relativePath,
final BasicFileAttributes attrs) {
+        return !negate.accept(baseDir, relativePath, attrs);
+    }
+
+    /**
+     * Create a Not filter.
+     *
+     * @param filter The filter to negate.
+     * @return A Not filter.
+     */
+    @PluginFactory
+    public static Not createNotFilter( //
+            @PluginElement("Filters") final PathFilter filter) {
+        return new Not(filter);
+    }
+
+    @Override
+    public String toString() {
+        return "Not(filters=" + negate + ")";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java
new file mode 100644
index 0000000..68de219
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Or.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Composite {@code DeleteFilter} that accepts objects that are accepted
by <em>any</em> component filters.
+ */
+@Plugin(name = "Or", category = "Core", printObject = true)
+public final class Or implements PathFilter {
+
+    private final PathFilter[] components;
+
+    private Or(final PathFilter... filters) {
+        this.components = Objects.requireNonNull(filters, "filters");
+    }
+
+    public PathFilter[] getDeleteFilters() {
+        return components;
+    }
+
+    /* (non-Javadoc)
+     * @see
org.apache.logging.log4j.core.appender.rolling.action.DeleteFilter#accept(java.nio.file.Path,
+     * java.nio.file.Path)
+     */
+    @Override
+    public boolean accept(final Path baseDir, final Path relativePath,
final BasicFileAttributes attrs) {
+        for (final PathFilter component : components) {
+            if (component.accept(baseDir, relativePath, attrs)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Create a Composite DeleteFilter.
+     *
+     * @param components The component filters.
+     * @return A CompositeDeleteFilter.
+     */
+    @PluginFactory
+    public static Or createOrFilter( //
+            @PluginElement("Filters") final PathFilter... components) {
+        return new Or(components);
+    }
+
+    @Override
+    public String toString() {
+        return "Or(filters=" + Arrays.toString(components) + ")";
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/7b7bdfaf/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java
----------------------------------------------------------------------
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java
new file mode 100644
index 0000000..f6b782a
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathFilter.java
@@ -0,0 +1,37 @@
+/*
+ * 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 org.apache.logging.log4j.core.appender.rolling.action;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/**
+ * Filter that accepts or rejects a candidate {@code Path} for deletion.
+ */
+public interface PathFilter {
+
+    /**
+     * Returns {@code true} if the specified candidate path should be
deleted, {@code false} otherwise.
+     *
+     * @param baseDir the directory from where to start scanning for
deletion candidate files
+     * @param relativePath the candidate for deletion. This path is
relative to the baseDir.
+     * @param attrs attributes of the candidate path
+     * @return whether the candidate path should be deleted
+     */
+    boolean accept(final Path baseDir, final Path relativePath, final
BasicFileAttributes attrs);
+}




-- 
E-Mail: garydgreg...@gmail.com | ggreg...@apache.org
Java Persistence with Hibernate, Second Edition
<http://www.manning.com/bauer3/>
JUnit in Action, Second Edition <http://www.manning.com/tahchiev/>
Spring Batch in Action <http://www.manning.com/templier/>
Blog: http://garygregory.wordpress.com
Home: http://garygregory.com/
Tweet! http://twitter.com/GaryGregory

Reply via email to