    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    }