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     */
017    package org.apache.logging.log4j.core.appender;
018    
019    import java.io.BufferedOutputStream;
020    import java.io.File;
021    import java.io.FileNotFoundException;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.OutputStream;
025    import java.nio.channels.FileChannel;
026    import java.nio.channels.FileLock;
027    
028    
029    /**
030     * Manages actual File I/O for File Appenders.
031     */
032    public class FileManager extends OutputStreamManager {
033    
034        private static final FileManagerFactory FACTORY = new FileManagerFactory();
035    
036        private final boolean isAppend;
037        private final boolean isLocking;
038    
039        protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking) {
040            super(os, fileName);
041            this.isAppend = append;
042            this.isLocking = locking;
043        }
044    
045        /**
046         * Returns the FileManager.
047         * @param fileName The name of the file to manage.
048         * @param append true if the file should be appended to, false if it should be overwritten.
049         * @param locking true if the file should be locked while writing, false otherwise.
050         * @param bufferedIO true if the contents should be buffered as they are written.
051         * @return A FileManager for the File.
052         */
053        public static FileManager getFileManager(final String fileName, final boolean append, boolean locking,
054                                                 final boolean bufferedIO) {
055    
056            if (locking && bufferedIO) {
057                locking = false;
058            }
059            return (FileManager) getManager(fileName, new FactoryData(append, locking, bufferedIO), FACTORY);
060        }
061    
062        @Override
063        protected synchronized void write(final byte[] bytes, final int offset, final int length)  {
064    
065            if (isLocking) {
066                final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
067                try {
068                    /* Lock the whole file. This could be optimized to only lock from the current file
069                       position. Note that locking may be advisory on some systems and mandatory on others,
070                       so locking just from the current position would allow reading on systems where
071                       locking is mandatory.  Also, Java 6 will throw an exception if the region of the
072                       file is already locked by another FileChannel in the same JVM. Hopefully, that will
073                       be avoided since every file should have a single file manager - unless two different
074                       files strings are configured that somehow map to the same file.*/
075                    final FileLock lock = channel.lock(0, Long.MAX_VALUE, false);
076                    try {
077                        super.write(bytes, offset, length);
078                    } finally {
079                        lock.release();
080                    }
081                } catch (final IOException ex) {
082                    throw new AppenderRuntimeException("Unable to obtain lock on " + getName(), ex);
083                }
084    
085            } else {
086                super.write(bytes, offset, length);
087            }
088        }
089    
090        /**
091         * Returns the name of the File being managed.
092         * @return The name of the File being managed.
093         */
094        public String getFileName() {
095            return getName();
096        }
097    
098        /**
099         * Returns the append status.
100         * @return true if the file will be appended to, false if it is overwritten.
101         */
102        public boolean isAppend() {
103            return isAppend;
104        }
105    
106        /**
107         * Returns the lock status.
108         * @return true if the file will be locked when writing, false otherwise.
109         */
110        public boolean isLocking() {
111            return isLocking;
112        }
113    
114        /**
115         * Factory Data.
116         */
117        private static class FactoryData {
118            private final boolean append;
119            private final boolean locking;
120            private final boolean bufferedIO;
121    
122            /**
123             * Constructor.
124             * @param append Append status.
125             * @param locking Locking status.
126             * @param bufferedIO Buffering flag.
127             */
128            public FactoryData(final boolean append, final boolean locking, final boolean bufferedIO) {
129                this.append = append;
130                this.locking = locking;
131                this.bufferedIO = bufferedIO;
132            }
133        }
134    
135        /**
136         * Factory to create a FileManager.
137         */
138        private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
139    
140            /**
141             * Create a FileManager.
142             * @param name The name of the File.
143             * @param data The FactoryData
144             * @return The FileManager for the File.
145             */
146            public FileManager createManager(final String name, final FactoryData data) {
147                final File file = new File(name);
148                final File parent = file.getParentFile();
149                if (null != parent && !parent.exists()) {
150                    parent.mkdirs();
151                }
152    
153                OutputStream os;
154                try {
155                    os = new FileOutputStream(name, data.append);
156                    if (data.bufferedIO) {
157                        os = new BufferedOutputStream(os);
158                    }
159                    return new FileManager(name, os, data.append, data.locking);
160                } catch (final FileNotFoundException ex) {
161                    LOGGER.error("FileManager (" + name + ") " + ex);
162                }
163                return null;
164            }
165        }
166    
167    }