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