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