org.springframework.integration.x.rollover.file.RolloverFileOutputStream.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.x.rollover.file.RolloverFileOutputStream.java

Source

//
//========================================================================
//Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//All rights reserved. This program and the accompanying materials
//are made available under the terms of the Eclipse Public License v1.0
//and Apache License v2.0 which accompanies this distribution.
//
//  The Eclipse Public License is available at
//  http://www.eclipse.org/legal/epl-v10.html
//
//  The Apache License v2.0 is available at
//  http://www.opensource.org/licenses/apache2.0.php
//
//You may elect to redistribute this code under either of these licenses.
//========================================================================
//

package org.springframework.integration.x.rollover.file;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * RolloverFileOutputStream
 * 
 * This output stream puts content in a file that is rolled over every rolloverPeriodMs, starting from
 * rolloverStartTimeMs. The filename must include the string "yyyy_mm_dd", which is replaced with the actual date when
 * creating and rolling over the file.
 * 
 */
public class RolloverFileOutputStream extends FilterOutputStream {

    private Logger logger = LoggerFactory.getLogger(RolloverFileOutputStream.class);

    private static Timer rolloverTimer;

    final static String YYYY_MM_DD = "yyyy_mm_dd";
    final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
    final static long DEFAULT_ROLLOVER_PERIOD = 1000L * 60 * 60 * 24;

    private RollTask rollTask;
    private SimpleDateFormat fileDateFormat;

    private String filePath;
    private File primaryFile;
    private boolean appendToFile;
    private long maxRolledFileSize;
    private String archivePrefix = "archive";
    private boolean compressArchive = true;
    private int bufferSize = 8192;

    private AtomicLong writtenBytesCounter = new AtomicLong(0);

    private File fileDir;

    private FileCompressor fileCompressor;

    /**
     * @param filename
     *            The filename must include the string "yyyy_mm_dd", which is replaced with the actual date when
     *            creating and rolling over the file.
     * @param append
     *            If true, existing files will be appended to.
     * @param zone
     *            the timezone for the output
     * @param dateFormat
     *            The format for the date file substitution. The default is "yyyy_MM_dd".
     * @param rolloverStartTimeMs
     *            Defines the time in [ms] of the first file roll over process to start.
     * @param rolloverPeriodMs
     *            Defines the frequency (in ms) of the file roll over processes.
     * @throws IOException
     *             if unable to create output
     */
    public RolloverFileOutputStream(String filename, boolean append, TimeZone zone, String dateFormat,
            long rolloverStartTimeMs, long rolloverPeriodMs, long maxRolledFileSize, String archivePrefix,
            boolean compressArchive, int bufferSize, FileCompressor fileCompressor) throws IOException {

        super(null);

        this.bufferSize = bufferSize;
        this.compressArchive = compressArchive;
        this.archivePrefix = archivePrefix;
        this.maxRolledFileSize = maxRolledFileSize;

        if (dateFormat == null) {
            dateFormat = ROLLOVER_FILE_DATE_FORMAT;
        }

        fileDateFormat = new SimpleDateFormat(dateFormat);

        if (StringUtils.isEmpty(filename)) {
            throw new IllegalArgumentException("Invalid filename");
        }

        filePath = filename.trim();
        fileDir = new File(new File(filename).getAbsolutePath()).getParentFile();

        if (fileDir != null && (!fileDir.isDirectory() || !fileDir.canWrite())) {
            throw new IOException("Cannot write into directory: " + fileDir);
        }

        appendToFile = append;
        setFile(true);

        synchronized (RolloverFileOutputStream.class) {

            if (rolloverTimer == null) {
                rolloverTimer = new Timer(RolloverFileOutputStream.class.getName(), true);
            }

            rollTask = new RollTask();

            Date startTime = null;
            if (rolloverStartTimeMs <= 0) {
                Calendar now = Calendar.getInstance();
                now.setTimeZone(zone);

                GregorianCalendar midnight = new GregorianCalendar(now.get(Calendar.YEAR), now.get(Calendar.MONTH),
                        now.get(Calendar.DAY_OF_MONTH), 23, 0);
                midnight.setTimeZone(zone);
                midnight.add(Calendar.HOUR, 1);

                startTime = midnight.getTime();
            } else {
                startTime = new Date(rolloverStartTimeMs);
            }

            long period = (rolloverPeriodMs <= 0) ? 86400000 : rolloverPeriodMs;

            rolloverTimer.scheduleAtFixedRate(rollTask, startTime, period);
        }

        this.fileCompressor = fileCompressor;
    }

    public String getFilename() {
        return filePath;
    }

    public String getDatedFilename() {
        if (primaryFile == null) {
            return null;
        }
        return primaryFile.toString();
    }

    private synchronized void setFile(boolean force) throws IOException {

        Date now = new Date();

        File newFile = new File(fileDir, getNewFileName(now));

        if (newFile.exists() && !newFile.canWrite()) {
            throw new IOException("Cannot write in file: " + newFile);
        }

        if (!appendToFile && newFile.exists()) {
            // Expand the file name to prevents collision with existing files
            throw new IOException("File already exists but append is disabled: " + newFile);
        }

        // Do we need to change the output stream?
        if (force || !newFile.equals(primaryFile)) {

            File oldPrimaryFile = primaryFile;
            OutputStream oldOut = out;

            primaryFile = newFile;

            if (bufferSize > 0) {
                out = new BufferedOutputStream(new FileOutputStream(newFile, appendToFile), bufferSize);
            } else {
                out = new FileOutputStream(newFile, appendToFile);
            }

            if (oldOut != null) {
                oldOut.close();
                renameAndCompress(oldPrimaryFile);
            }
        }
    }

    private String getNewFileName(Date date) {
        // Is this a rollover file?
        String fileName = new File(filePath).getName();

        String newFileName = fileName;
        int i = fileName.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
        if (i >= 0) {
            newFileName = fileName.substring(0, i) + fileDateFormat.format(date)
                    + fileName.substring(i + YYYY_MM_DD.length());
        }
        return newFileName;
    }

    @Override
    public void write(byte[] buf) throws IOException {
        out.write(buf);
        // checkFileSizeForRollover(buf.length);
        writtenBytesCounter.addAndGet(buf.length);
    }

    @Override
    public void write(byte[] buf, int off, int len) throws IOException {
        out.write(buf, off, len);
        // checkFileSizeForRollover(len);
        writtenBytesCounter.addAndGet(len);
    }

    @Override
    public void close() throws IOException {
        synchronized (RolloverFileOutputStream.class) {
            try {
                super.close();
                renameAndCompress(primaryFile);
            } finally {
                out = null;
                primaryFile = null;
            }

            rollTask.cancel();
        }
    }

    private class RollTask extends TimerTask {
        @Override
        public void run() {
            try {
                RolloverFileOutputStream.this.setFile(false);
            } catch (IOException e) {
                logger.error("Roll task failed:", e);
            }
        }
    }

    private void renameAndCompress(File file) {
        if (file != null) {
            File archiveFile = file;

            // if a archivePrefix is configured we are going to
            // rename the file first
            if (!StringUtils.isEmpty(archivePrefix) && file != null) {
                archiveFile = new File(file.getParentFile(), archivePrefix + "." + file.getName());
                file.renameTo(archiveFile);
            }

            // compress file
            if (compressArchive) {
                fileCompressor.compressFile(archiveFile.getAbsolutePath());
            }
        }
    }

    public void rollover() {
        if (maxRolledFileSize > 0) {
            long fileSize = writtenBytesCounter.get();
            if (fileSize >= maxRolledFileSize) {
                // Start file roll over
                synchronized (RolloverFileOutputStream.class) {
                    try {
                        setFile(false);
                        writtenBytesCounter.set(0);
                    } catch (IOException e) {
                        logger.error("roll over failed:", e);
                    }
                }
            }
        }
    }

}