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