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