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 */
017package org.apache.logging.log4j.core.appender;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.OutputStream;
022import java.io.RandomAccessFile;
023import java.io.Serializable;
024import java.nio.ByteBuffer;
025import java.util.HashMap;
026import java.util.Map;
027
028import org.apache.logging.log4j.core.Layout;
029import 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 */
036public 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}