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 */
017package org.apache.logging.log4j.core.appender.rolling;
018
019import java.io.File;
020import java.io.IOException;
021import java.nio.file.DirectoryStream;
022import java.nio.file.Files;
023import java.nio.file.Path;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.SortedMap;
027import java.util.TreeMap;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031import org.apache.logging.log4j.Logger;
032import org.apache.logging.log4j.LoggingException;
033import org.apache.logging.log4j.core.appender.rolling.action.Action;
034import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
035import org.apache.logging.log4j.core.lookup.StrSubstitutor;
036import org.apache.logging.log4j.core.pattern.NotANumber;
037import org.apache.logging.log4j.status.StatusLogger;
038
039/**
040 *
041 */
042public abstract class AbstractRolloverStrategy implements RolloverStrategy {
043
044    /**
045     * Allow subclasses access to the status logger without creating another instance.
046     */
047    protected static final Logger LOGGER = StatusLogger.getLogger();
048
049    public static final Pattern PATTERN_COUNTER= Pattern.compile(".*%((?<ZEROPAD>0)?(?<PADDING>\\d+))?i.*");
050
051    protected final StrSubstitutor strSubstitutor;
052
053    protected AbstractRolloverStrategy(final StrSubstitutor strSubstitutor) {
054        this.strSubstitutor = strSubstitutor;
055    }
056
057
058    public StrSubstitutor getStrSubstitutor() {
059        return strSubstitutor;
060    }
061
062    protected Action merge(final Action compressAction, final List<Action> custom, final boolean stopOnError) {
063        if (custom.isEmpty()) {
064            return compressAction;
065        }
066        if (compressAction == null) {
067            return new CompositeAction(custom, stopOnError);
068        }
069        final List<Action> all = new ArrayList<>();
070        all.add(compressAction);
071        all.addAll(custom);
072        return new CompositeAction(all, stopOnError);
073    }
074
075    protected int suffixLength(final String lowFilename) {
076        for (final FileExtension extension : FileExtension.values()) {
077            if (extension.isExtensionFor(lowFilename)) {
078                return extension.length();
079            }
080        }
081        return 0;
082    }
083
084
085    protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager) {
086        return getEligibleFiles(manager, true);
087    }
088
089    protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager,
090                                                        final boolean isAscending) {
091        final StringBuilder buf = new StringBuilder();
092        final String pattern = manager.getPatternProcessor().getPattern();
093        manager.getPatternProcessor().formatFileName(strSubstitutor, buf, NotANumber.NAN);
094        return getEligibleFiles(buf.toString(), pattern, isAscending);
095    }
096
097    protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String pattern) {
098        return getEligibleFiles(path, pattern, true);
099    }
100
101    protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String logfilePattern, final boolean isAscending) {
102        final TreeMap<Integer, Path> eligibleFiles = new TreeMap<>();
103        final File file = new File(path);
104        File parent = file.getParentFile();
105        if (parent == null) {
106            parent = new File(".");
107        } else {
108            parent.mkdirs();
109        }
110        if (!PATTERN_COUNTER.matcher(logfilePattern).matches()) {
111            return eligibleFiles;
112        }
113        final Path dir = parent.toPath();
114        String fileName = file.getName();
115        final int suffixLength = suffixLength(fileName);
116        if (suffixLength > 0) {
117            fileName = fileName.substring(0, fileName.length() - suffixLength) + ".*";
118        }
119        final String filePattern = fileName.replace(NotANumber.VALUE, "(\\d+)");
120        final Pattern pattern = Pattern.compile(filePattern);
121
122        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
123            for (final Path entry: stream) {
124                final Matcher matcher = pattern.matcher(entry.toFile().getName());
125                if (matcher.matches()) {
126                    final Integer index = Integer.parseInt(matcher.group(1));
127                    eligibleFiles.put(index, entry);
128                }
129            }
130        } catch (final IOException ioe) {
131            throw new LoggingException("Error reading folder " + dir + " " + ioe.getMessage(), ioe);
132        }
133        return isAscending? eligibleFiles : eligibleFiles.descendingMap();
134    }
135}