You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ma...@apache.org on 2013/10/18 06:10:52 UTC

svn commit: r1533320 - in /db/derby/code/trunk: java/engine/org/apache/derby/iapi/reference/ java/engine/org/apache/derby/impl/services/stream/ java/engine/org/apache/derby/impl/store/raw/data/ java/testing/org/apache/derbyTesting/functionTests/tests/e...

Author: mamta
Date: Fri Oct 18 04:10:51 2013
New Revision: 1533320

URL: http://svn.apache.org/r1533320
Log:
DERBY-6350 (Provide a rolling file implementation of derby.log)

Committing patch submitted by Brett Bergquist


Added:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStream.java   (with props)
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStreamProvider.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/SingleStream.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/engine/ErrorStreamTest.java
    db/derby/code/trunk/tools/jar/extraDBMSclasses.properties

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java?rev=1533320&r1=1533319&r2=1533320&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java Fri Oct 18 04:10:51 2013
@@ -95,6 +95,16 @@ public interface Property { 
      */
 	
 	String LOG_BOOT_TRACE = "derby.stream.error.logBootTrace";
+    
+        /**
+		derby.stream.error.style=<b>The error stream error style.</b>
+        <b>rollingFile<b> is the only file currently supported.
+		Takes precendence over derby.stream.error.file.
+		Takes precendence over derby.stream.error.method.
+		Takes precendence over derby.stream.error.field
+	*/	
+	String ERRORLOG_STYLE_PROPERTY = "derby.stream.error.style";
+
         /**
 		derby.stream.error.file=<b>absolute or relative error log filename</b>
 		Takes precendence over derby.stream.error.method.
@@ -118,6 +128,66 @@ public interface Property { 
 	
 	String ERRORLOG_FIELD_PROPERTY = "derby.stream.error.field";
 
+        /**
+		derby.stream.error.rollingfile.pattern=<b>the pattern</b>
+        A pattern consists of a string that includes the following special
+        components that will be replaced at runtime:
+        <UL>
+        <LI>    "/"    the local pathname separator 
+        <LI>     "%t"   the system temporary directory
+        <LI>     "%h"   the value of the "user.home" system property
+        <LI>     "%d"   the value of the "derby.system.home" system property
+        <LI>     "%g"   the generation number to distinguish rotated logs
+        <LI>     "%u"   a unique number to resolve conflicts
+        <LI>     "%%"   translates to a single percent sign "%"
+        </UL>
+        If no "%g" field has been specified and the file count is greater
+        than one, then the generation number will be added to the end of
+        the generated filename, after a dot.
+        <P> 
+        Thus for example a pattern of "%t/java%g.log" with a count of 2
+        would typically cause files to be written on Solaris to 
+        /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they
+        would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log
+        <P> 
+        Generation numbers follow the sequence 0, 1, 2, etc.
+        <P>
+        Normally the "%u" unique field is set to 0.  However, if the <tt>FileHandler</tt>
+        tries to open the filename and finds the file is currently in use by
+        another process it will increment the unique number field and try
+        again.  This will be repeated until <tt>FileHandler</tt> finds a file name that
+        is  not currently in use. If there is a conflict and no "%u" field has
+        been specified, it will be added at the end of the filename after a dot.
+        (This will be after any automatically added generation number.)
+        <P>
+        Thus if three processes were all trying to output to fred%u.%g.txt then 
+        they  might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as
+        the first file in their rotating sequences.
+        <P>
+        Note that the use of unique ids to avoid conflicts is only guaranteed
+        to work reliably when using a local disk file system.
+        <P>
+        The default pattern is "%d/derby-%g.log"
+	*/	
+	String ERRORLOG_ROLLINGFILE_PATTERN_PROPERTY = "derby.stream.error.rollingFile.pattern";
+
+        /**
+		derby.stream.error.rollingfile.limit=<b>the rolling file size limit</b>
+        <P>
+        The default limit is 1024000
+	*/
+	
+	String ERRORLOG_ROLLINGFILE_LIMIT_PROPERTY = "derby.stream.error.rollingFile.limit";
+
+        /**
+		derby.stream.error.rollingfile.count=<b>the rolling file count</b>
+        <P>
+        The default count is 10
+	*/
+	
+	String ERRORLOG_ROLLINGFILE_COUNT_PROPERTY = "derby.stream.error.rollingFile.count";
+
+    
 	/** 
 	derby.infolog.append={true,false}
 	<BR>

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStream.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStream.java?rev=1533320&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStream.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStream.java Fri Oct 18 04:10:51 2013
@@ -0,0 +1,586 @@
+/*
+
+   Derby - Class org.apache.derby.impl.services.stream.RollingFileStream
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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.apache.derby.impl.services.stream;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class provides rolling file OutputStream.  The file pattern, file size,
+ * and number of files can be customized.
+ * <p>
+ * This class borrows extensively from the java.util.logger.FileHandler class for its
+ * file handling ability and instead of handling logger messages it extends 
+ * java.io.OutputStream.
+ * <p>
+ * A pattern consists of a string that includes the following special
+ * components that will be replaced at runtime:
+ * <ul>
+ * <li>    "/"    the local pathname separator 
+ * <li>     "%t"   the system temporary directory
+ * <li>     "%h"   the value of the "user.home" system property
+ * <li>     "%d"   the value of the "derby.system.home" system property
+ * <li>     "%g"   the generation number to distinguish rotated logs
+ * <li>     "%u"   a unique number to resolve conflicts
+ * <li>     "%%"   translates to a single percent sign "%"
+ * </ul>
+ * If no "%g" field has been specified and the file count is greater
+ * than one, then the generation number will be added to the end of
+ * the generated filename, after a dot.
+ * <p> 
+ * Thus for example a pattern of "%t/java%g.log" with a count of 2
+ * would typically cause files to be written on Solaris to 
+ * /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they
+ * would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log
+ * <p> 
+ * Generation numbers follow the sequence 0, 1, 2, etc.
+ * <p>
+ * Normally the "%u" unique field is set to 0.  However, if the <tt>FileHandler</tt>
+ * tries to open the filename and finds the file is currently in use by
+ * another process it will increment the unique number field and try
+ * again.  This will be repeated until <tt>FileHandler</tt> finds a file name that
+ * is  not currently in use. If there is a conflict and no "%u" field has
+ * been specified, it will be added at the end of the filename after a dot.
+ * (This will be after any automatically added generation number.)
+ * <p>
+ * Thus if three processes were all trying to output to fred%u.%g.txt then 
+ * they  might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as
+ * the first file in their rotating sequences.
+ * <p>
+ * Note that the use of unique ids to avoid conflicts is only guaranteed
+ * to work reliably when using a local disk file system.
+ *
+ */
+public class RollingFileStream extends OutputStream {
+
+    /**
+     * The underlying stream being written to that keeps track of how much
+     * has been written
+     */
+    private MeteredStream meter;
+
+    /**
+     * The append flag which indicates at creation time to append to an existing
+     * file or to always create a new one
+     */
+    private boolean append;
+
+    /*
+     * The file limit.  The value of 0 indicates no limit.
+     */
+    private int limit;
+
+    /**
+     * The rolling file count.  This many files will be created before the
+     * oldest is removed and the files rolled.
+     */
+    private int count;
+
+    /**
+     * The filename pattern.
+     */
+    private String pattern;
+
+    /**
+     * The lockfile name
+     */
+    private String lockFileName;
+
+    /**
+     * The output stream that is used as a lock
+     */
+    private FileOutputStream lockStream;
+
+    /**
+     * The array of File instance representing the rolling files
+     */
+    private File files[];
+
+    private static final int MAX_LOCKS = 100;
+
+    private static java.util.HashMap<String,String> locks = new java.util.HashMap<String,String>();
+
+    /**
+     * Construct a default <tt>RollingFileStream</tt>. This will be configured entirely with default values: 
+     * <ul>
+     * <li>pattern - %d/derby-%g.log (DERBY_HOME/derby-0.log)</li>
+     * <li>limit - 0 (unlimited)</li>
+     * <li>count - 1 (one file)</li>
+     * <li>append - false (overwrite and not append)</li>
+     * </ul>
+     *
+     * @exception IOException if there are IO problems opening the files.
+     * @exception SecurityException if a security manager exists and if the caller does not have
+     * <tt>LoggingPermission("control"))</tt>.
+     * @exception NullPointerException if pattern property is an empty String.
+     */
+    public RollingFileStream() throws IOException, SecurityException {
+        this("%d/derby-%g.log", 0, 1, false);
+    }
+
+    /**
+     * Initialize a <tt>RollingFileStream</tt> to write to a set of files with optional append. When (approximately) the
+     * given limit has been written to one file, another file will be opened. The output will cycle through a set of
+     * count files.
+     *
+     * @param pattern the pattern for naming the output file
+     * @param limit the maximum number of bytes to write to any one file
+     * @param count the number of files to use
+     * @param append specifies append mode
+     * @exception IOException if there are IO problems opening the files.
+     * @exception SecurityException if a security manager exists and if the caller does not have
+     * <tt>LoggingPermission("control")</tt>.
+     * @exception IllegalArgumentException if limit < 0, or count < 1.
+     * @exception IllegalArgumentException if pattern is an empty string
+     *
+     */
+    public RollingFileStream(String pattern, int limit, int count, boolean append)
+            throws IOException, SecurityException {
+        if (limit < 0 || count < 1 || pattern.length() < 1) {
+            throw new IllegalArgumentException();
+        }
+        this.pattern = pattern;
+        this.limit = limit;
+        this.count = count;
+        this.append = append;
+        openFiles();
+    }
+
+    /**
+     * Implements the write method of the OutputStream.  This writes the value
+     * to the metered stream.
+     * @param b The value to write
+     * @throws IOException 
+     */
+    public void write(int b) throws IOException {
+        this.meter.write(b);
+        checkMeter();
+    }
+
+    /**
+     * Opens the output files files based on the configured pattern, limit, count,
+     * and append mode.
+     * @throws IOException 
+     */
+    private void openFiles() throws IOException {
+        if (count < 1) {
+            throw new IllegalArgumentException("file count = " + count);
+        }
+        if (limit < 0) {
+            limit = 0;
+        }
+
+        // Create a lock file.  This grants us exclusive access
+        // to our set of output files, as long as we are alive.
+        int unique = -1;
+        for (;;) {
+            unique++;
+            if (unique > MAX_LOCKS) {
+                throw new IOException("Couldn't get lock for " + pattern);
+            }
+            // Generate a lock file name from the "unique" int.
+            lockFileName = generate(pattern, 0, unique).toString() + ".lck";
+            // Now try to lock that filename.
+            // Because some systems (e.g. Solaris) can only do file locks
+            // between processes (and not within a process), we first check
+            // if we ourself already have the file locked.
+            synchronized (locks) {
+                if (locks.get(lockFileName) != null) {
+                    // We already own this lock, for a different RollingFileStream
+                    // object.  Try again.
+                    continue;
+                }
+                FileChannel fc;
+                try {
+                    lockStream = openFile(lockFileName, false);
+                    fc = lockStream.getChannel();
+                } catch (IOException ix) {
+                    // We got an IOException while trying to open the file.
+                    // Try the next file.
+                    continue;
+                }
+                try {
+                    FileLock fl = fc.tryLock();
+                    if (fl == null) {
+                        // We failed to get the lock.  Try next file.
+                        continue;
+                    }
+                    // We got the lock OK.
+                } catch (IOException ix) {
+                    // We got an IOException while trying to get the lock.
+                    // This normally indicates that locking is not supported
+                    // on the target directory.  We have to proceed without
+                    // getting a lock.   Drop through.
+                }
+                // We got the lock.  Remember it.
+                locks.put(lockFileName, lockFileName);
+                break;
+            }
+        }
+
+        files = new File[count];
+        for (int i = 0; i < count; i++) {
+            files[i] = generate(pattern, i, unique);
+        }
+
+        // Create the initial log file.
+        if (append) {
+            open(files[0], true);
+        } else {
+            rotate();
+        }
+    }
+
+    /**
+     * Generates and returns File from a pattern
+     * @param pattern The filename pattern
+     * @param generation The generation number used if there is a conflict
+     * @param unique The unique number to append to the filename
+     * @return The File
+     * @throws IOException 
+     */
+    private File generate(String pattern, int generation, int unique) throws IOException {
+        File file = null;
+        String word = "";
+        int ix = 0;
+        boolean sawg = false;
+        boolean sawu = false;
+        while (ix < pattern.length()) {
+            char ch = pattern.charAt(ix);
+            ix++;
+            char ch2 = 0;
+            if (ix < pattern.length()) {
+                ch2 = Character.toLowerCase(pattern.charAt(ix));
+            }
+            if (ch == '/') {
+                if (file == null) {
+                    file = new File(word);
+                } else {
+                    file = new File(file, word);
+                }
+                word = "";
+                continue;
+            } else if (ch == '%') {
+                if (ch2 == 't') {
+                    String tmpDir = getSystemProperty("java.io.tmpdir");
+                    if (tmpDir == null) {
+                        tmpDir = getSystemProperty("user.home");
+                    }
+                    file = new File(tmpDir);
+                    ix++;
+                    word = "";
+                    continue;
+                } else if (ch2 == 'h') {
+                    file = new File(getSystemProperty("user.home"));
+                    ix++;
+                    word = "";
+                    continue;
+                } else if (ch2 == 'd') {
+                    String derbyHome = getSystemProperty("derby.system.home");
+                    if (derbyHome == null) {
+                        derbyHome = getSystemProperty("user.dir");
+                    }
+                    file = new File(derbyHome);
+                    ix++;
+                    word = "";
+                    continue;
+                } else if (ch2 == 'g') {
+                    word = word + generation;
+                    sawg = true;
+                    ix++;
+                    continue;
+                } else if (ch2 == 'u') {
+                    word = word + unique;
+                    sawu = true;
+                    ix++;
+                    continue;
+                } else if (ch2 == '%') {
+                    word = word + "%";
+                    ix++;
+                    continue;
+                }
+            }
+            word = word + ch;
+        }
+        if (count > 1 && !sawg) {
+            word = word + "." + generation;
+        }
+        if (unique > 0 && !sawu) {
+            word = word + "." + unique;
+        }
+        if (word.length() > 0) {
+            if (file == null) {
+                file = new File(word);
+            } else {
+                file = new File(file, word);
+            }
+        }
+        return file;
+    }
+
+    /**
+     * Rotates the log files.  The metered OutputStream is closed,the log files
+     * are rotated and then a new metered OutputStream is created.
+     * @throws IOException 
+     */
+    private synchronized void rotate() throws IOException {
+
+        if (null != meter) {
+            meter.close();
+        }
+        for (int i = count - 2; i >= 0; i--) {
+            File f1 = files[i];
+            File f2 = files[i + 1];
+            if (fileExists(f1)) {
+                if (fileExists(f2)) {
+                    fileDelete(f2);
+                }
+                fileRename(f1, f2);
+            }
+        }
+        try {
+            open(files[0], false);
+        } catch (IOException ix) {
+        }
+    }
+
+    /**
+     * Close all the files.
+     *
+     * @exception SecurityException if a security manager exists and if the caller does not have
+     * <tt>LoggingPermission("control")</tt>.
+     */
+    public synchronized void close() throws SecurityException {
+        // Close the underlying file
+        if (null != meter) {
+            try {
+                meter.close();
+            } catch (IOException ex) {
+                // Problems closing the stream.  Punt.S
+            }
+        }
+        // Unlock any lock file.
+        if (lockFileName == null) {
+            return;
+        }
+        try {
+            // Closing the lock file's FileOutputStream will close
+            // the underlying channel and free any locks.
+            lockStream.close();
+        } catch (Exception ex) {
+            // Problems closing the stream.  Punt.S
+        }
+        synchronized (locks) {
+            locks.remove(lockFileName);
+        }
+        fileDelete(new File(lockFileName));
+        lockFileName = null;
+        lockStream = null;
+    }
+
+    /**
+     * Gets a system property in a privileged block
+     * @param property The propety to get
+     * @return The property value
+     */
+    private String getSystemProperty(final String property) {
+        // Try to get the derby.system.home property.  This requires privileges if run with a security manager
+        String value = AccessController.doPrivileged(new PrivilegedAction<String>() {
+
+            public String run() {
+                return System.getProperty(property);
+            }
+
+        });
+
+        return value;
+    }
+
+    /**
+     * Opens a file in the privileged block
+     * @param filename The name of the file to open
+     * @param append if <code>true</code> open the file in append mode
+     * @return The FileOutputStream for the file
+     * @throws IOException 
+     */
+    private FileOutputStream openFile(final String filename, final boolean append) throws IOException {
+        FileOutputStream fis = null;
+        try {
+            fis = AccessController.doPrivileged(new PrivilegedExceptionAction<FileOutputStream>() {
+
+                public FileOutputStream run() throws FileNotFoundException {
+                    FileOutputStream res = new FileOutputStream(filename, append);
+                    return res;
+                }
+
+            });
+            return fis;
+        } catch (PrivilegedActionException x) {
+            // An exception occurs, rethrow it
+            throw (IOException) x.getException();
+        }
+    }
+    
+    /**
+     * Check to see if a file exists in a privilege block
+     * @param file The file to check
+     * @return <code>true</code> if the file exists or <code>false</code> otherwise
+     */
+    private boolean fileExists(final File file) {
+        // Try to get the derby.system.home property.  This requires privileges if run with a security manager
+        Boolean value = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+
+            public Boolean run() {
+                return new Boolean(file.exists());
+            }
+
+        });
+
+        return value.booleanValue();        
+    }
+
+    /**
+     * Delete a file in a privilege block
+     * @param file The file to delete
+     */
+    private void fileDelete(final File file) {
+        // Try to get the derby.system.home property.  This requires privileges if run with a security manager
+        AccessController.doPrivileged(new PrivilegedAction<Object>() {
+
+            public Object run() {
+                file.delete();
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Rename a file in a privilege block
+     * @param file1 The file to rename
+     * @param file2 The file to rename it to
+     * @return <code>true</code> if the file was renamed or </code>false</code> otherwise
+     */
+    private boolean fileRename(final File file1, final File file2) {
+        // Try to get the derby.system.home property.  This requires privileges if run with a security manager
+        Boolean value = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+
+            public Boolean run() {
+                return new Boolean(file1.renameTo(file2));
+            }
+        });
+
+        return value.booleanValue();        
+    }
+    
+    /**
+     * Get the length of a file in a privilege block
+     * @param file The file to get the length of
+     * @return The length of the file
+     */
+    private long fileLength(final File file) {
+        // Try to get the derby.system.home property.  This requires privileges if run with a security manager
+        Long value = AccessController.doPrivileged(new PrivilegedAction<Long>() {
+
+            public Long run() {
+                return new Long(file.length());
+            }
+        });
+
+        return value.longValue();                
+    }
+
+    /**
+     * Opens a new file that and delegates it to a MeteredStream
+     * @param fname The name of the file
+     * @param append If <code>true</code> append to the existing file
+     * @throws IOException 
+     */
+    private void open(File fname, boolean append) throws IOException {
+        int len = 0;
+        if (append) {
+            len = (int) fileLength(fname);
+        }
+        FileOutputStream fout = openFile(fname.toString(), append);
+        meter = new MeteredStream(fout, len);
+    }
+
+    /**
+     * Invoked by the metered OutputStream 
+     * @throws IOException 
+     */
+    private void checkMeter() throws IOException {
+        if (limit > 0 && meter.written >= limit) {
+            rotate();
+        }
+    }
+
+    // A metered stream is a subclass of OutputStream that
+    //   (a) forwards all its output to a target stream
+    //   (b) keeps track of how many bytes have been written
+    private class MeteredStream extends OutputStream {
+
+        /**
+         *  The real OutputStream to delegate to
+         */
+        OutputStream out;
+
+        /**
+         * The number of bytes written so far to the OutputStream
+         */
+        int written;
+
+        /**
+         * Creates a new instance of MeteredStream
+         * @param out The OutputStream to delegate to
+         * @param written The number of bytes currently written to the OuptutStream
+         */
+        MeteredStream(OutputStream out, int written) {
+            this.out = out;
+            this.written = written;
+        }
+
+        public void write(int b) throws IOException {
+            out.write(b);
+            written++;
+        }
+        
+        public int getWritten() {
+            return written;
+        }
+
+        @Override
+        public void close() throws IOException {
+            out.close();
+        }        
+    }
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStreamProvider.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStreamProvider.java?rev=1533320&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStreamProvider.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStreamProvider.java Fri Oct 18 04:10:51 2013
@@ -0,0 +1,63 @@
+/*
+
+ Derby - Class org.apache.derby.iapi.services.stream.RollingFileStreamProvider
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.apache.derby.impl.services.stream;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import org.apache.derby.iapi.reference.Property;
+import org.apache.derby.iapi.services.property.PropertyUtil;
+
+/**
+ * Creates and configures a RollingFileStream
+ *
+ * @author brett
+ */
+public class RollingFileStreamProvider {
+
+    /**
+     * Creates and returns the OutputStream for a RollingFileStream.
+     * The <tt>derbylog.properties</tt> file contains the configuration. If the
+     * file is not found, then hard coded default values are used to configure
+     * the RollingFileStream. <p>The following properties can be specified <dl>
+     * <dt>pattern</dt> <dd>The pattern to use, the default is
+     * <tt>%d/derby-%g.log</tt></dd> <dt>limit</dt> <dd>The file size limit, the
+     * default is <tt>1024000</tt></dd> <dt>count</dt> <dd>The file count, the
+     * default is <tt>10</tt></dd> <dt>append</dt> <dd>If true the last logfile
+     * is appended to, the default is <tt>true</tt></dd>
+     *
+     * @return The configured OutputStream
+     * @throws IOException
+     * @throws SecurityException  
+     */
+    public static OutputStream getOutputStream() throws IOException, SecurityException {
+        OutputStream res = null;
+
+        String pattern = PropertyUtil.getSystemProperty(Property.ERRORLOG_ROLLINGFILE_PATTERN_PROPERTY, "%d/derby-%g.log");
+        int limit = Integer.parseInt(PropertyUtil.getSystemProperty(Property.ERRORLOG_ROLLINGFILE_LIMIT_PROPERTY, "1024000"));
+        int count = Integer.parseInt(PropertyUtil.getSystemProperty(Property.ERRORLOG_ROLLINGFILE_COUNT_PROPERTY, "10"));
+        boolean append = Boolean.parseBoolean(PropertyUtil.getSystemProperty(Property.LOG_FILE_APPEND, "true"));
+
+        RollingFileStream rfh = new RollingFileStream(pattern, limit, count, append);
+        res = rfh;
+
+        return res;
+    }
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/RollingFileStreamProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/SingleStream.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/SingleStream.java?rev=1533320&r1=1533319&r2=1533320&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/SingleStream.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/stream/SingleStream.java Fri Oct 18 04:10:51 2013
@@ -151,6 +151,12 @@ implements InfoStreams, ModuleControl, j
 		// to set it. choices are file, method, field, stream
 
 		String target = PropertyUtil.
+		   getSystemProperty(Property.ERRORLOG_STYLE_PROPERTY);
+		if (target != null) {
+			return makeStyleHPW(target, header);
+		}
+
+		target = PropertyUtil.
                    getSystemProperty(Property.ERRORLOG_FILE_PROPERTY);
 		if (target!=null)
 			return makeFileHPW(target, header);
@@ -158,7 +164,7 @@ implements InfoStreams, ModuleControl, j
 		target = PropertyUtil.
                    getSystemProperty(Property.ERRORLOG_METHOD_PROPERTY);
 		if (target!=null) 
-			return makeMethodHPW(target, header);
+			return makeMethodHPW(target, header, false);
 
 		target = PropertyUtil.
                    getSystemProperty(Property.ERRORLOG_FIELD_PROPERTY);
@@ -208,7 +214,8 @@ implements InfoStreams, ModuleControl, j
 	}
 
 	private HeaderPrintWriter makeMethodHPW(String methodInvocation,
-											PrintWriterGetHeader header) {
+											PrintWriterGetHeader header,
+                                            boolean canClose) {
 
 		int lastDot = methodInvocation.lastIndexOf('.');
 		String className = methodInvocation.substring(0, lastDot);
@@ -229,7 +236,7 @@ implements InfoStreams, ModuleControl, j
 
 				try {
 					return makeValueHPW(theMethod, theMethod.invoke((Object) null, 
-						new Object[0]), header, methodInvocation);
+						new Object[0]), header, methodInvocation, canClose);
 				} catch (IllegalAccessException iae) {
 					t = iae;
 				} catch (IllegalArgumentException iarge) {
@@ -251,6 +258,24 @@ implements InfoStreams, ModuleControl, j
 
 	}
 
+	private HeaderPrintWriter makeStyleHPW(String style,
+											PrintWriterGetHeader header) {
+		HeaderPrintWriter res = null;
+		if ("rollingFile".equals(style)) {
+		String className = "org.apache.derby.impl.services.stream.RollingFileStreamProvider.getOutputStream";
+			res = makeMethodHPW(className, header, true);
+		} else {            
+			try {
+				IllegalArgumentException ex = new IllegalArgumentException("unknown derby.stream.error.style: " + style);
+                throw ex;
+			} catch (IllegalArgumentException t) {
+				res = useDefaultStream(header, t);
+			} catch (Exception t) {
+				res = useDefaultStream(header, t);
+			}
+		}
+		return res;
+	}
 
 	private HeaderPrintWriter makeFieldHPW(String fieldAccess,
 											PrintWriterGetHeader header) {
@@ -275,7 +300,7 @@ implements InfoStreams, ModuleControl, j
 
 				try {
 					return makeValueHPW(theField, theField.get((Object) null), 
-						header, fieldAccess);
+						header, fieldAccess, false);
 				} catch (IllegalAccessException iae) {
 					t = iae;
 				} catch (IllegalArgumentException iarge) {
@@ -306,12 +331,12 @@ implements InfoStreams, ModuleControl, j
 	}
 
 	private HeaderPrintWriter makeValueHPW(Member whereFrom, Object value,
-		PrintWriterGetHeader header, String name) {
+		PrintWriterGetHeader header, String name, boolean canClose) {
 
 		if (value instanceof OutputStream)
-			 return new BasicHeaderPrintWriter((OutputStream) value, header, false, name);
+			 return new BasicHeaderPrintWriter((OutputStream) value, header, canClose, name);
 		else if (value instanceof Writer)
-			 return new BasicHeaderPrintWriter((Writer) value, header, false, name);
+			 return new BasicHeaderPrintWriter((Writer) value, header, canClose, name);
 		
 		HeaderPrintWriter hpw = useDefaultStream(header);
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java?rev=1533320&r1=1533319&r2=1533320&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java Fri Oct 18 04:10:51 2013
@@ -378,6 +378,11 @@ public class BaseDataFileFactory
 		
 		//Log properties related to redirection of derby.log 
 		String target = 
+			PropertyUtil.getSystemProperty(Property.ERRORLOG_STYLE_PROPERTY);
+		if (target != null)
+			logMsg(Property.ERRORLOG_STYLE_PROPERTY+"=" + target);
+        
+		target = 
 			PropertyUtil.getSystemProperty(Property.ERRORLOG_FILE_PROPERTY);
 		if (target != null)
 			logMsg(Property.ERRORLOG_FILE_PROPERTY+"=" + target);

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/engine/ErrorStreamTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/engine/ErrorStreamTest.java?rev=1533320&r1=1533319&r2=1533320&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/engine/ErrorStreamTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/engine/ErrorStreamTest.java Fri Oct 18 04:10:51 2013
@@ -47,11 +47,18 @@ import org.apache.derbyTesting.junit.Tes
  * logStream.java, the other test* methods are from errorStream.java.
  */
 public class ErrorStreamTest extends BaseJDBCTestCase {
-
     private static final String FILE_PROP   = "derby.stream.error.file";
     private static final String METHOD_PROP = "derby.stream.error.method";
     private static final String FIELD_PROP  = "derby.stream.error.field";
+    private static final String STYLE_PROP = "derby.stream.error.style";
 
+    private static final String ROLLING_FILE_STYLE = "rollingFile";
+    private static final String ROLLING_FILE_COUNT_PROP = "derby.stream.error.rollingFile.count";
+    private static final String ROLLING_FILE_LIMIT_PROP = "derby.stream.error.rollingFile.limit";
+    private static final String ROLLING_FILE_PATTERN_PROP = "derby.stream.error.rollingFile.pattern";
+    private static final String DERBY_0_LOG = "derby-0.log";
+    private static final String DERBYLANGUAGELOG_QUERY_PLAN = "derby.language.logQueryPlan";
+    
     /**
      * runNo keeps track of which run we are in to generate unique (within a
      * JUnit run) names for files that are used in the test. Has to be static.
@@ -87,7 +94,7 @@ public class ErrorStreamTest extends Bas
      */
     private OutputStream errStream;
     private File errStreamFile;
-
+    
     public ErrorStreamTest(String name) {
         super(name);
     }
@@ -340,6 +347,237 @@ public class ErrorStreamTest extends Bas
     }
 
     /**
+     * Test the derby.stream.error.style=rollingFile property.
+     */
+    public void testStyleRollingFile() throws IOException, SQLException  {
+        setSystemProperty(STYLE_PROP, ROLLING_FILE_STYLE);
+        
+        File derby0log = new File(getSystemProperty("derby.system.home"), DERBY_0_LOG);
+        
+        File derby0lck = new File(getSystemProperty("derby.system.home"),
+              "derby-0.log.lck");
+        
+        bootDerby();
+        
+        assertIsExisting(derby0log);
+        assertNotDirectory(derby0log);
+        assertNotEmpty(derby0log);
+
+        assertIsExisting(derby0lck);
+        assertNotDirectory(derby0lck);
+        assertIsEmpty(derby0lck);
+
+        println("Shutdown database");
+        getTestConfiguration().shutdownDatabase();
+
+        assertIsExisting(derby0log);
+        assertNotDirectory(derby0log);
+        assertNotEmpty(derby0log);
+
+        assertIsExisting(derby0lck);
+        assertNotDirectory(derby0lck);
+        assertIsEmpty(derby0lck);
+
+        println("Shutdown engine");
+        getTestConfiguration().shutdownEngine();
+
+        assertNotExisting(derby0lck);
+        
+        boolean deleted = deleteFile(derby0log);
+        assertTrue("File " + derby0log + " could not be deleted", deleted);
+    }
+
+    /**
+     * Test the derby.stream.error.style property with wrong style.
+     */
+    public void testWrongStyle() throws IOException, SQLException {
+        setSystemProperty(STYLE_PROP, "unknownStyle");
+        
+        File derby0log = new File(getSystemProperty("derby.system.home"), DERBY_0_LOG);
+        
+        bootDerby();
+        getTestConfiguration().shutdownEngine();
+
+        closeStreams();
+
+        assertNotExisting(derby0log);
+        assertNotExisting(fileStreamFile);
+        assertIsEmpty(methodStreamFile);
+        assertIsEmpty(fieldStreamFile);
+        assertNotEmpty(errStreamFile);
+    }
+
+    /**
+     * Test the derby.stream.error.style=rollingFile property with default config
+     */
+    public void testDefaultRollingDefaultConfig() throws IOException, SQLException {
+        setSystemProperty(STYLE_PROP, ROLLING_FILE_STYLE);
+        
+        // This is set so that we can get enough output into the log files
+        setSystemProperty(DERBYLANGUAGELOG_QUERY_PLAN, "true");
+                
+        bootDerby();
+        
+        // This will generate enough output to roll through all 10 log files
+        for (int i = 0; i < 3699; i++) {
+            checkAllConsistency(getConnection());
+        }
+        // Make sure we remove the system property that is logging the query plan
+        removeSystemProperty(DERBYLANGUAGELOG_QUERY_PLAN);
+        getTestConfiguration().shutdownEngine();
+        
+        closeStreams();
+        
+        // There should be derb-0.log .. derby-9.log files present
+        for (int i = 0; i < 10; i++) {
+            File derbyLog = new File(getSystemProperty("derby.system.home"),
+                "derby-" + i + ".log");
+            assertIsExisting(derbyLog);
+            assertNotDirectory(derbyLog);
+            assertNotEmpty(derbyLog);
+            
+            // Check the last log file to make sure that it has the default
+            //  limit 
+            if (i == 9) {
+                assertFileSize(derbyLog, 1024000);
+            }
+            
+            boolean deleted = deleteFile(derbyLog);
+            assertTrue("File " + derbyLog + " could not be deleted", deleted);
+        }
+        
+        assertNotExisting(fileStreamFile);
+        assertIsEmpty(methodStreamFile);
+        assertIsEmpty(fieldStreamFile);
+        assertIsEmpty(errStreamFile);
+    }   
+
+    /**
+     * Test the derby.stream.error.style=rollingFile property with user configuration.
+     */
+    public void testDefaultRollingUserConfig() throws IOException, SQLException {
+        setSystemProperty(STYLE_PROP, ROLLING_FILE_STYLE);
+        setSystemProperty(ROLLING_FILE_PATTERN_PROP, "%d/db-%g.log");
+        setSystemProperty(ROLLING_FILE_COUNT_PROP, "3");
+        setSystemProperty(ROLLING_FILE_LIMIT_PROP, "10000");
+        
+        // This is set so that we can get enough output into the log files
+        setSystemProperty(DERBYLANGUAGELOG_QUERY_PLAN, "true");
+                
+        bootDerby();
+        
+        // This will generate enough output to roll through all 10 log files
+        for (int i = 0; i < 10; i++) {
+            checkAllConsistency(getConnection());
+        }
+        // Make sure we remove the system property that is logging the query plan
+        removeSystemProperty(DERBYLANGUAGELOG_QUERY_PLAN);
+        removeSystemProperty(ROLLING_FILE_PATTERN_PROP);
+        removeSystemProperty(ROLLING_FILE_COUNT_PROP);
+        removeSystemProperty(ROLLING_FILE_LIMIT_PROP);
+
+        getTestConfiguration().shutdownEngine();
+        
+        closeStreams();
+        
+        // There should be derb-0.log .. derby-3.log files present
+        for (int i = 0; i < 3; i++) {
+            File derbyLog = new File(getSystemProperty("derby.system.home"),
+                "db-" + i + ".log");
+            assertIsExisting(derbyLog);
+            assertNotDirectory(derbyLog);
+            assertNotEmpty(derbyLog);
+            
+            // Check the last log file to make sure that it has the correct
+            //  limit 
+            if (i == 2) {
+                assertFileSize(derbyLog, 10000);
+            }
+            
+            boolean deleted = deleteFile(derbyLog);
+            assertTrue("File " + derbyLog + " could not be deleted", deleted);
+        }
+        
+        assertNotExisting(fileStreamFile);
+        assertIsEmpty(methodStreamFile);
+        assertIsEmpty(fieldStreamFile);
+        assertIsEmpty(errStreamFile);
+    }   
+
+    /**
+     * Test that the derby.stream.error.style property overrides the
+     * derby.stream.error.file property.
+     */
+    public void testRollingFileStyleOverFile() throws IOException, SQLException {
+        setSystemProperty(STYLE_PROP, ROLLING_FILE_STYLE);
+        
+        File derby0log = new File(getSystemProperty("derby.system.home"), DERBY_0_LOG);
+        
+        setSystemProperty(FILE_PROP, getCanonicalPath(fileStreamFile));
+
+        bootDerby();
+        getTestConfiguration().shutdownEngine();
+
+        closeStreams();
+
+        assertNotEmpty(derby0log);
+        assertNotExisting(fileStreamFile);
+        assertIsEmpty(methodStreamFile);
+        assertIsEmpty(fieldStreamFile);
+        assertIsEmpty(errStreamFile);
+    }
+
+    /**
+     * Test that the derby.stream.error.style property overrides the
+     * derby.stream.error.method property.
+     */
+    public void testRollingFileStyleOverMethod() throws IOException, SQLException {
+        setSystemProperty(STYLE_PROP, ROLLING_FILE_STYLE);
+        
+        File derby0log = new File(getSystemProperty("derby.system.home"), DERBY_0_LOG);
+        
+        setSystemProperty(METHOD_PROP,
+              "org.apache.derbyTesting.functionTests.tests.engine."+
+              "ErrorStreamTest.getStream");
+
+        bootDerby();
+        getTestConfiguration().shutdownEngine();
+
+        closeStreams();
+
+        assertNotEmpty(derby0log);
+        assertNotExisting(fileStreamFile);
+        assertIsEmpty(methodStreamFile);
+        assertIsEmpty(fieldStreamFile);
+        assertIsEmpty(errStreamFile);
+    }
+
+    /**
+     * Test that the derby.stream.error.style property overrides the
+     * derby.stream.error.field property.
+     */
+    public void testRollingFileStyleOverField() throws IOException, SQLException {
+        setSystemProperty(STYLE_PROP, ROLLING_FILE_STYLE);
+        
+        File derby0log = new File(getSystemProperty("derby.system.home"), DERBY_0_LOG);
+        
+        setSystemProperty(FIELD_PROP,
+              "org.apache.derbyTesting.functionTests.tests.engine."+
+              "ErrorStreamTest.fieldStream");
+
+        bootDerby();
+        getTestConfiguration().shutdownEngine();
+
+        closeStreams();
+
+        assertNotEmpty(derby0log);
+        assertNotExisting(fileStreamFile);
+        assertIsEmpty(methodStreamFile);
+        assertIsEmpty(fieldStreamFile);
+        assertIsEmpty(errStreamFile);
+    }
+
+    /**
      * Method getStream used by Derby when derby.stream.error.method
      * is set.  Maps to file <database>-method-<runNo>.log
      * This method has to be static.
@@ -432,6 +670,21 @@ public class ErrorStreamTest extends Bas
         }
     }
 
+    private static void assertFileSize(final File f, final int size) throws IOException {
+        try {
+            AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Void>() {
+                public Void run() throws IOException {
+                    assertEquals("assertFileEize failed for file " + f.getName() + ": ", size, f.length());
+                    return null;
+                }
+            });
+        } catch (PrivilegedActionException e) {
+            // e.getException() should be an instance of IOException.
+            throw (IOException) e.getException();
+        }
+    }
+
     private static void assertIsExisting(final File f) throws IOException {
         String path = getCanonicalPath(f);
         assertTrue(path + " doesn't exist",
@@ -495,6 +748,7 @@ public class ErrorStreamTest extends Bas
         removeSystemProperty(FILE_PROP);
         removeSystemProperty(METHOD_PROP);
         removeSystemProperty(FIELD_PROP);
+        removeSystemProperty(STYLE_PROP);        
     }
 
     private void deleteStreamFiles() {

Modified: db/derby/code/trunk/tools/jar/extraDBMSclasses.properties
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/tools/jar/extraDBMSclasses.properties?rev=1533320&r1=1533319&r2=1533320&view=diff
==============================================================================
--- db/derby/code/trunk/tools/jar/extraDBMSclasses.properties (original)
+++ db/derby/code/trunk/tools/jar/extraDBMSclasses.properties Fri Oct 18 04:10:51 2013
@@ -107,6 +107,9 @@ derby.module.store.cpf=org.apache.derby.
 derby.module.shared.threaddump=org.apache.derby.shared.common.sanity.ThreadDump
 derby.module.engine.threaddump=org.apache.derby.iapi.error.ThreadDump
 
+derby.module.logging.provider=org.apache.derby.impl.services.stream.RollingFileStreamProvider
+derby.module.logging.stream=org.apache.derby.impl.services.stream.RollingFileStream
+
 # optional tools
 derby.module.opttrace=org.apache.derby.impl.sql.compile.OptimizerTracer
 derby.module.opttraceviewer=org.apache.derby.impl.sql.compile.OptTraceViewer