--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.eclipse.jetty.util;
+
+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;
+
+/**
+ * RolloverFileOutputStream
+ *
+ * This output stream puts content in a file that is rolled over every 24 hours.
+ * The filename must include the string "yyyy_mm_dd", which is replaced with the
+ * actual date when creating and rolling over the file.
+ *
+ * Old files are retained for a number of days before being deleted.
+ *
+ *
+ */
+public class RolloverFileOutputStream extends FilterOutputStream
+{
+ private static Timer __rollover;
+
+ final static String YYYY_MM_DD="yyyy_mm_dd";
+ final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
+ final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
+ final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
+
+ private RollTask _rollTask;
+ private SimpleDateFormat _fileBackupFormat;
+ private SimpleDateFormat _fileDateFormat;
+
+ private String _filename;
+ private File _file;
+ private boolean _append;
+ private int _retainDays;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @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.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename)
+ throws IOException
+ {
+ this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @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.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename, boolean append)
+ throws IOException
+ {
+ this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @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 retainDays The number of days to retain files before deleting them. 0 to retain forever.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename,
+ boolean append,
+ int retainDays)
+ throws IOException
+ {
+ this(filename,append,retainDays,TimeZone.getDefault());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @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 retainDays The number of days to retain files before deleting them. 0 to retain forever.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename,
+ boolean append,
+ int retainDays,
+ TimeZone zone)
+ throws IOException
+ {
+
+ this(filename,append,retainDays,zone,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @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 retainDays The number of days to retain files before deleting them. 0 to retain forever.
+ * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd".
+ * @param backupFormat The format for the file extension of backup files. The default is "HHmmssSSS".
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename,
+ boolean append,
+ int retainDays,
+ TimeZone zone,
+ String dateFormat,
+ String backupFormat)
+ throws IOException
+ {
+ super(null);
+
+ if (dateFormat==null)
+ dateFormat=ROLLOVER_FILE_DATE_FORMAT;
+ _fileDateFormat = new SimpleDateFormat(dateFormat);
+
+ if (backupFormat==null)
+ backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
+ _fileBackupFormat = new SimpleDateFormat(backupFormat);
+
+ _fileBackupFormat.setTimeZone(zone);
+ _fileDateFormat.setTimeZone(zone);
+
+ if (filename!=null)
+ {
+ filename=filename.trim();
+ if (filename.length()==0)
+ filename=null;
+ }
+ if (filename==null)
+ throw new IllegalArgumentException("Invalid filename");
+
+ _filename=filename;
+ _append=append;
+ _retainDays=retainDays;
+ setFile();
+
+ synchronized(RolloverFileOutputStream.class)
+ {
+ if (__rollover==null)
+ __rollover=new Timer(RolloverFileOutputStream.class.getName(),true);
+
+ _rollTask=new RollTask();
+
+ 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);
+ __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getFilename()
+ {
+ return _filename;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getDatedFilename()
+ {
+ if (_file==null)
+ return null;
+ return _file.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getRetainDays()
+ {
+ return _retainDays;
+ }
+
+ /* ------------------------------------------------------------ */
+ private synchronized void setFile()
+ throws IOException
+ {
+ // Check directory
+ File file = new File(_filename);
+ _filename=file.getCanonicalPath();
+ file=new File(_filename);
+ File dir= new File(file.getParent());
+ if (!dir.isDirectory() || !dir.canWrite())
+ throw new IOException("Cannot write log directory "+dir);
+
+ Date now=new Date();
+
+ // Is this a rollover file?
+ String filename=file.getName();
+ int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+ if (i>=0)
+ {
+ file=new File(dir,
+ filename.substring(0,i)+
+ _fileDateFormat.format(now)+
+ filename.substring(i+YYYY_MM_DD.length()));
+ }
+
+ if (file.exists()&&!file.canWrite())
+ throw new IOException("Cannot write log file "+file);
+
+ // Do we need to change the output stream?
+ if (out==null || !file.equals(_file))
+ {
+ // Yep
+ _file=file;
+ if (!_append && file.exists())
+ file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
+ OutputStream oldOut=out;
+ out=new FileOutputStream(file.toString(),_append);
+ if (oldOut!=null)
+ oldOut.close();
+ //if(log.isDebugEnabled())log.debug("Opened "+_file);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void removeOldFiles()
+ {
+ if (_retainDays>0)
+ {
+ long now = System.currentTimeMillis();
+
+ File file= new File(_filename);
+ File dir = new File(file.getParent());
+ String fn=file.getName();
+ int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+ if (s<0)
+ return;
+ String prefix=fn.substring(0,s);
+ String suffix=fn.substring(s+YYYY_MM_DD.length());
+
+ String[] logList=dir.list();
+ for (int i=0;i<logList.length;i++)
+ {
+ fn = logList[i];
+ if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
+ {
+ File f = new File(dir,fn);
+ long date = f.lastModified();
+ if ( ((now-date)/(1000*60*60*24))>_retainDays)
+ f.delete();
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (byte[] buf)
+ throws IOException
+ {
+ out.write (buf);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (byte[] buf, int off, int len)
+ throws IOException
+ {
+ out.write (buf, off, len);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ synchronized(RolloverFileOutputStream.class)
+ {
+ try{super.close();}
+ finally
+ {
+ out=null;
+ _file=null;
+ }
+
+ _rollTask.cancel();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class RollTask extends TimerTask
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ RolloverFileOutputStream.this.setFile();
+ RolloverFileOutputStream.this.removeOldFiles();
+
+ }
+ catch(IOException e)
+ {
+ // Cannot log this exception to a LOG, as RolloverFOS can be used by logging
+ e.printStackTrace();
+ }
+ }
+ }
+}