All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.logmanager.handlers.SuffixRotator Maven / Gradle / Ivy

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 *
 * Copyright 2018 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.jboss.logmanager.handlers;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.logging.ErrorManager;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * A utility for rotating files based on a files suffix.
 *
 * @author James R. Perkins
 */
class SuffixRotator {

    /**
     * The compression type for the rotation
     */
    public enum CompressionType {
        NONE,
        GZIP,
        ZIP
    }

    /**
     * An empty rotation suffix.
     */
    static final SuffixRotator EMPTY = new SuffixRotator(AccessController.getContext(), "", "", "", CompressionType.NONE);

    private final AccessControlContext acc;
    private final String originalSuffix;
    private final String datePattern;
    private final SimpleDateFormat formatter;
    private final String compressionSuffix;
    private final CompressionType compressionType;

    private SuffixRotator(final AccessControlContext acc, final String originalSuffix, final String datePattern,
            final String compressionSuffix, final CompressionType compressionType) {
        this.acc = acc;
        this.originalSuffix = originalSuffix;
        this.datePattern = datePattern;
        this.compressionSuffix = compressionSuffix;
        this.compressionType = compressionType;
        if (datePattern.isEmpty()) {
            formatter = null;
        } else {
            formatter = new SimpleDateFormat(datePattern);
        }
    }

    /**
     * Parses a suffix into possible parts for rotation.
     *
     * @param suffix the suffix to parse
     *
     * @return the file rotator used to determine the suffix parts and rotate the file.
     */
    static SuffixRotator parse(final AccessControlContext acc, final String suffix) {
        if (suffix == null || suffix.isEmpty()) {
            return EMPTY;
        }
        // Check the if the suffix contains a compression suffix
        String compressionSuffix = "";
        String datePattern = "";
        CompressionType compressionType = CompressionType.NONE;
        final String lSuffix = suffix.toLowerCase(Locale.ROOT);
        int compressionIndex = lSuffix.indexOf(".gz");
        if (compressionIndex != -1) {
            compressionSuffix = suffix.substring(compressionIndex);
            datePattern = suffix.substring(0, compressionIndex);
            compressionType = CompressionType.GZIP;
        } else {
            compressionIndex = lSuffix.indexOf(".zip");
            if (compressionIndex != -1) {
                compressionSuffix = suffix.substring(compressionIndex);
                datePattern = suffix.substring(0, compressionIndex);
                compressionType = CompressionType.ZIP;
            }
        }
        if (compressionSuffix.isEmpty() && datePattern.isEmpty()) {
            return new SuffixRotator(acc, suffix, suffix, "", CompressionType.NONE);
        }
        return new SuffixRotator(acc, suffix, datePattern, compressionSuffix, compressionType);
    }

    /**
     * The {@linkplain SimpleDateFormat date format pattern} for the suffix or an empty
     * {@linkplain String string}.
     *
     * @return the date pattern or an empty string
     */
    String getDatePattern() {
        return datePattern;
    }

    /**
     * The compression suffix or an empty {@linkplain String string}
     *
     * @return the compression suffix or an empty string
     */
    @SuppressWarnings("unused")
    String getCompressionSuffix() {
        return compressionSuffix;
    }

    /**
     * The compression type.
     *
     * @return the compression type
     */
    @SuppressWarnings("unused")
    CompressionType getCompressionType() {
        return compressionType;
    }

    /**
     * Rotates the file to a new file appending the suffix to the target.
     * 

* The compression suffix will automatically be appended to target file if compression is being used. If compression * is not being used the file is just moved replacing the target file if it already exists. *

* * @param errorManager the error manager used to report errors to, if {@code null} an {@link IOException} will * be thrown * @param source the file to be rotated * @param suffix the suffix to append to the rotated file. */ void rotate(final ErrorManager errorManager, final Path source, final String suffix) { final Path target = Paths.get(source + suffix + compressionSuffix); if (compressionType == CompressionType.GZIP) { try { archiveGzip(source, target); // Delete the file after it's archived to behave like a file move or rename deleteFile(source); } catch (Exception e) { errorManager.error(String.format("Failed to compress %s to %s. Compressed file may be left on the " + "filesystem corrupted.", source, target), e, ErrorManager.WRITE_FAILURE); } } else if (compressionType == CompressionType.ZIP) { try { archiveZip(source, target); // Delete the file after it's archived to behave like a file move or rename deleteFile(source); } catch (Exception e) { errorManager.error(String.format("Failed to compress %s to %s. Compressed file may be left on the " + "filesystem corrupted.", source, target), e, ErrorManager.WRITE_FAILURE); } } else { move(errorManager, source, target); } } /** * Rotates the file to a new file appending the suffix to the target. If a date suffix was specified the suffix * will be added before the index or compression suffix. The current date will be used for the suffix. *

* If the {@code maxBackupIndex} is greater than 0 previously rotated files will be moved to an numerically * incremented target. The compression suffix, if required, will be appended to this indexed file name. *

* * @param errorManager the error manager used to report errors to, if {@code null} an {@link IOException} will * be thrown * @param source the file to be rotated * @param maxBackupIndex the number of backups to keep */ void rotate(final ErrorManager errorManager, final Path source, final int maxBackupIndex) { if (formatter == null) { rotate(errorManager, source, "", maxBackupIndex); } else { final String suffix; synchronized (formatter) { suffix = formatter.format(new Date()); } rotate(errorManager, source, suffix, maxBackupIndex); } } /** * Rotates the file to a new file appending the suffix to the target. *

* If the {@code maxBackupIndex} is greater than 0 previously rotated files will be moved to an numerically * incremented target. The compression suffix, if required, will be appended to this indexed file name. *

* * @param errorManager the error manager used to report errors to, if {@code null} an {@link IOException} will * be thrown * @param source the file to be rotated * @param suffix the optional suffix to append to the file before the index and optional compression suffix * @param maxBackupIndex the number of backups to keep */ void rotate(final ErrorManager errorManager, final Path source, final String suffix, final int maxBackupIndex) { if (maxBackupIndex > 0) { final String rotationSuffix = (suffix == null ? "" : suffix); final String fileWithSuffix = source.toAbsolutePath() + rotationSuffix; final Path lastFile = Paths.get(fileWithSuffix + "." + maxBackupIndex + compressionSuffix); try { deleteFile(lastFile); } catch (Exception e) { errorManager.error(String.format("Failed to delete file %s", lastFile), e, ErrorManager.GENERIC_FAILURE); } for (int i = maxBackupIndex - 1; i >= 1; i--) { final Path src = Paths.get(fileWithSuffix + "." + i + compressionSuffix); if (fileExists(src)) { final Path target = Paths.get(fileWithSuffix + "." + (i + 1) + compressionSuffix); move(errorManager, src, target); } } rotate(errorManager, source, rotationSuffix + ".1"); } else if (suffix != null && !suffix.isEmpty()) { rotate(errorManager, source, suffix); } } @Override public String toString() { return originalSuffix; } private void move(final ErrorManager errorManager, final Path src, final Path target) { if (System.getSecurityManager() == null) { try { Files.move(src, target, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { // Report the error, but allow the rotation to continue errorManager.error(String.format("Failed to move file %s to %s.", src, target), e, ErrorManager.GENERIC_FAILURE); } } else { AccessController.doPrivileged(new MoveFileAction(errorManager, src, target), acc); } } private void archiveGzip(final Path source, final Path target) throws IOException { final byte[] buff = new byte[512]; try (final GZIPOutputStream out = new GZIPOutputStream(newOutputStream(target), true)) { try (final InputStream in = newInputStream(source)) { int len; while ((len = in.read(buff)) != -1) { out.write(buff, 0, len); } } out.finish(); } } private void archiveZip(final Path source, final Path target) throws IOException { final byte[] buff = new byte[512]; try (final ZipOutputStream out = new ZipOutputStream(newOutputStream(target), StandardCharsets.UTF_8)) { final ZipEntry entry = new ZipEntry(source.getFileName().toString()); out.putNextEntry(entry); try (final InputStream in = newInputStream(source)) { int len; while ((len = in.read(buff)) != -1) { out.write(buff, 0, len); } } out.closeEntry(); } } @SuppressWarnings("UnusedReturnValue") private boolean deleteFile(final Path path) throws IOException { if (System.getSecurityManager() == null) { return Files.deleteIfExists(path); } return AccessController.doPrivileged(new DeleteFileAction(path), acc); } private boolean fileExists(final Path file) { if (System.getSecurityManager() == null) { return Files.exists(file); } return AccessController.doPrivileged(new FileExistsAction(file), acc); } private InputStream newInputStream(final Path file) throws IOException { if (System.getSecurityManager() == null) { return Files.newInputStream(file); } return AccessController.doPrivileged(new InputStreamAction(file), acc); } private OutputStream newOutputStream(final Path file) throws IOException { if (System.getSecurityManager() == null) { return Files.newOutputStream(file); } return AccessController.doPrivileged(new OutputStreamAction(file), acc); } private static class DeleteFileAction implements PrivilegedAction { private final Path file; private DeleteFileAction(final Path file) { this.file = file; } @Override public Boolean run() { try { return Files.deleteIfExists(file); } catch (IOException e) { throw new UncheckedIOException(e); } } } private static class MoveFileAction implements PrivilegedAction { private final ErrorManager errorManager; private final Path source; private final Path target; private MoveFileAction(final ErrorManager errorManager, final Path source, final Path target) { this.errorManager = errorManager; this.source = source; this.target = target; } @Override public Path run() { try { return Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { // Report the error, but allow the rotation to continue errorManager.error(String.format("Failed to move file %s to %s.", source, target), e, ErrorManager.GENERIC_FAILURE); } return null; } } private static class FileExistsAction implements PrivilegedAction { private final Path file; private FileExistsAction(final Path file) { this.file = file; } @Override public Boolean run() { return Files.exists(file); } } private static class InputStreamAction implements PrivilegedAction { private final Path file; private InputStreamAction(final Path file) { this.file = file; } @Override public InputStream run() { try { return Files.newInputStream(file); } catch (IOException e) { throw new UncheckedIOException(e); } } } private static class OutputStreamAction implements PrivilegedAction { private final Path file; private OutputStreamAction(final Path file) { this.file = file; } @Override public OutputStream run() { try { return Files.newOutputStream(file); } catch (IOException e) { throw new UncheckedIOException(e); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy