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.File;
022    import java.io.IOException;
023    import java.io.OutputStream;
024    import java.io.RandomAccessFile;
025    import java.nio.ByteBuffer;
026    import java.util.HashMap;
027    import java.util.Map;
028    
029    /**
030     * Extends OutputStreamManager but instead of using a buffered output stream,
031     * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
032     * I/O.
033     */
034    public class FastFileManager extends OutputStreamManager {
035        private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
036    
037        private static final FastFileManagerFactory FACTORY = new FastFileManagerFactory();
038    
039        private final boolean isImmediateFlush;
040        private final String advertiseURI;
041        private final RandomAccessFile randomAccessFile;
042        private final ByteBuffer buffer;
043        private ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
044    
045        protected FastFileManager(final RandomAccessFile file, final String fileName,
046                final OutputStream os, final boolean immediateFlush, final String advertiseURI,
047                final Layout layout) {
048            super(os, fileName, layout);
049            this.isImmediateFlush = immediateFlush;
050            this.randomAccessFile = file;
051            this.advertiseURI = advertiseURI;
052            isEndOfBatch.set(Boolean.FALSE);
053    
054            // TODO make buffer size configurable?
055            buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
056        }
057    
058        /**
059         * Returns the FastFileManager.
060         *
061         * @param fileName The name of the file to manage.
062         * @param append true if the file should be appended to, false if it should be overwritten.
063         * @param isFlush true if the contents should be flushed to disk on every write
064         * @param advertiseURI the URI to use when advertising the file
065         * @param layout The layout.
066         * @return A FastFileManager for the File.
067         */
068        public static FastFileManager getFileManager(final String fileName, final boolean append,
069                                                     final boolean isFlush, final String advertiseURI,
070                                                     final Layout layout) {
071            return (FastFileManager) getManager(fileName, new FactoryData(append, isFlush, advertiseURI, layout), FACTORY);
072        }
073    
074        public Boolean isEndOfBatch() {
075            return isEndOfBatch.get();
076        }
077    
078        public void setEndOfBatch(boolean isEndOfBatch) {
079            this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
080        }
081    
082        @Override
083        protected synchronized void write(byte[] bytes, int offset, int length) {
084            super.write(bytes, offset, length); // writes to dummy output stream
085    
086            if (length > buffer.remaining()) {
087                flush();
088            }
089            buffer.put(bytes, offset, length);
090            if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
091                flush();
092            }
093        }
094    
095        @Override
096        public void flush() {
097            buffer.flip();
098            try {
099                randomAccessFile.write(buffer.array(), 0, buffer.limit());
100            } catch (IOException ex) {
101                String msg = "Error writing to RandomAccessFile " + getName();
102                throw new AppenderRuntimeException(msg, ex);
103            }
104            buffer.clear();
105        }
106    
107        @Override
108        public void close() {
109            flush();
110            try {
111                randomAccessFile.close();
112            } catch (final IOException ex) {
113                LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
114                        + ex);
115            }
116        }
117    
118        /**
119         * Returns the name of the File being managed.
120         *
121         * @return The name of the File being managed.
122         */
123        public String getFileName() {
124            return getName();
125        }
126    
127        /** {@code OutputStream} subclass that does not write anything. */
128        private static class DummyOutputStream extends OutputStream {
129            @Override
130            public void write(int b) throws IOException {
131            }
132    
133            @Override
134            public void write(byte[] b, int off, int len) throws IOException {
135            }
136        }
137    
138        /**
139         * FileManager's content format is specified by:
140         * <p/>
141         * Key: "fileURI" Value: provided "advertiseURI" param.
142         *
143         * @return Map of content format keys supporting FileManager
144         */
145        @Override
146        public Map<String, String> getContentFormat() {
147            Map<String, String> result = new HashMap<String, String>(
148                    super.getContentFormat());
149            result.put("fileURI", advertiseURI);
150            return result;
151        }
152    
153        /**
154         * Factory Data.
155         */
156        private static class FactoryData {
157            private final boolean append;
158            private final boolean immediateFlush;
159            private final String advertiseURI;
160            private final Layout layout;
161    
162            /**
163             * Constructor.
164             *
165             * @param append Append status.
166             */
167            public FactoryData(final boolean append, final boolean immediateFlush, final String advertiseURI,
168                               final Layout layout) {
169                this.append = append;
170                this.immediateFlush = immediateFlush;
171                this.advertiseURI = advertiseURI;
172                this.layout = layout;
173            }
174        }
175    
176        /**
177         * Factory to create a FastFileManager.
178         */
179        private static class FastFileManagerFactory implements ManagerFactory<FastFileManager, FactoryData> {
180    
181            /**
182             * Create a FastFileManager.
183             *
184             * @param name The name of the File.
185             * @param data The FactoryData
186             * @return The FastFileManager for the File.
187             */
188            @Override
189            public FastFileManager createManager(String name, FactoryData data) {
190                File file = new File(name);
191                final File parent = file.getParentFile();
192                if (null != parent && !parent.exists()) {
193                    parent.mkdirs();
194                }
195                if (!data.append) {
196                    file.delete();
197                }
198    
199                OutputStream os = new DummyOutputStream();
200                RandomAccessFile raf;
201                try {
202                    raf = new RandomAccessFile(name, "rw");
203                    return new FastFileManager(raf, name, os, data.immediateFlush, data.advertiseURI, data.layout);
204                } catch (Exception ex) {
205                    LOGGER.error("FastFileManager (" + name + ") " + ex);
206                }
207                return null;
208            }
209        }
210    
211    }