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    protected final StrSubstitutor strSubstitutor;
050
051    protected AbstractRolloverStrategy(final StrSubstitutor strSubstitutor) {
052        this.strSubstitutor = strSubstitutor;
053    }
054
055
056    public StrSubstitutor getStrSubstitutor() {
057        return strSubstitutor;
058    }
059
060    protected Action merge(final Action compressAction, final List<Action> custom, final boolean stopOnError) {
061        if (custom.isEmpty()) {
062            return compressAction;
063        }
064        if (compressAction == null) {
065            return new CompositeAction(custom, stopOnError);
066        }
067        final List<Action> all = new ArrayList<>();
068        all.add(compressAction);
069        all.addAll(custom);
070        return new CompositeAction(all, stopOnError);
071    }
072
073    protected int suffixLength(final String lowFilename) {
074        for (final FileExtension extension : FileExtension.values()) {
075            if (extension.isExtensionFor(lowFilename)) {
076                return extension.length();
077            }
078        }
079        return 0;
080    }
081
082
083    protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager) {
084        return getEligibleFiles(manager, true);
085    }
086
087    protected SortedMap<Integer, Path> getEligibleFiles(final RollingFileManager manager,
088                                                        final boolean isAscending) {
089        final StringBuilder buf = new StringBuilder();
090        final String pattern = manager.getPatternProcessor().getPattern();
091        manager.getPatternProcessor().formatFileName(strSubstitutor, buf, NotANumber.NAN);
092        return getEligibleFiles(buf.toString(), pattern, isAscending);
093    }
094
095    protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String pattern) {
096        return getEligibleFiles(path, pattern, true);
097    }
098
099    protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String logfilePattern, final boolean isAscending) {
100        final TreeMap<Integer, Path> eligibleFiles = new TreeMap<>();
101        final File file = new File(path);
102        File parent = file.getParentFile();
103        if (parent == null) {
104            parent = new File(".");
105        } else {
106            parent.mkdirs();
107        }
108        if (!logfilePattern.contains("%i")) {
109            return eligibleFiles;
110        }
111        final Path dir = parent.toPath();
112        String fileName = file.getName();
113        final int suffixLength = suffixLength(fileName);
114        if (suffixLength > 0) {
115            fileName = fileName.substring(0, fileName.length() - suffixLength) + ".*";
116        }
117        final String filePattern = fileName.replace(NotANumber.VALUE, "(\\d+)");
118        final Pattern pattern = Pattern.compile(filePattern);
119
120        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
121            for (final Path entry: stream) {
122                final Matcher matcher = pattern.matcher(entry.toFile().getName());
123                if (matcher.matches()) {
124                    final Integer index = Integer.parseInt(matcher.group(1));
125                    eligibleFiles.put(index, entry);
126                }
127            }
128        } catch (final IOException ioe) {
129            throw new LoggingException("Error reading folder " + dir + " " + ioe.getMessage(), ioe);
130        }
131        return isAscending? eligibleFiles : eligibleFiles.descendingMap();
132    }
133}