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