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.rolling;
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;
025
026import org.apache.logging.log4j.core.Layout;
027import org.apache.logging.log4j.core.appender.AppenderLoggingException;
028import org.apache.logging.log4j.core.appender.ManagerFactory;
029import org.apache.logging.log4j.core.util.NullOutputStream;
030
031/**
032 * Extends RollingFileManager but instead of using a buffered output stream, this class uses a {@code ByteBuffer} and a
033 * {@code RandomAccessFile} to do the I/O.
034 */
035public class RollingRandomAccessFileManager extends RollingFileManager {
036    /**
037     * The default buffer size.
038     */
039    public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
040
041    private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
042
043    private RandomAccessFile randomAccessFile;
044    private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
045
046    public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName, final String pattern,
047            final OutputStream os, final boolean append, final boolean immediateFlush, final int bufferSize,
048            final long size, final long time, final TriggeringPolicy policy, final RolloverStrategy strategy,
049            final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader) {
050        super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, writeHeader,
051                ByteBuffer.wrap(new byte[bufferSize]));
052        this.randomAccessFile = raf;
053        isEndOfBatch.set(Boolean.FALSE);
054        writeHeader();
055    }
056
057    /**
058     * Writes the layout's header to the file if it exists.
059     */
060    private void writeHeader() {
061        if (layout == null) {
062            return;
063        }
064        final byte[] header = layout.getHeader();
065        if (header == null) {
066            return;
067        }
068        try {
069            // write to the file, not to the buffer: the buffer may not be empty
070            randomAccessFile.write(header, 0, header.length);
071        } catch (final IOException e) {
072            logError("unable to write header", e);
073        }
074    }
075
076    public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
077            final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
078            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
079            final Layout<? extends Serializable> layout) {
080        return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
081                immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
082    }
083
084    public Boolean isEndOfBatch() {
085        return isEndOfBatch.get();
086    }
087
088    public void setEndOfBatch(final boolean endOfBatch) {
089        this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
090    }
091
092    // override to make visible for unit tests
093    @Override
094    protected synchronized void write(final byte[] bytes, final int offset, final int length,
095            final boolean immediateFlush) {
096        super.write(bytes, offset, length, immediateFlush);
097    }
098
099    @Override
100    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
101        try {
102            randomAccessFile.write(bytes, offset, length);
103            size += length;
104        } catch (final IOException ex) {
105            final String msg = "Error writing to RandomAccessFile " + getName();
106            throw new AppenderLoggingException(msg, ex);
107        }
108    }
109
110    @Override
111    protected void createFileAfterRollover() throws IOException {
112        this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
113        if (isAppend()) {
114            randomAccessFile.seek(randomAccessFile.length());
115        }
116        writeHeader();
117    }
118
119    @Override
120    public synchronized void flush() {
121        flushBuffer(byteBuffer);
122    }
123
124    @Override
125    public synchronized void close() {
126        flush();
127        try {
128            randomAccessFile.close();
129        } catch (final IOException e) {
130            logError("unable to close RandomAccessFile", e);
131        }
132    }
133
134    /**
135     * Returns the buffer capacity.
136     *
137     * @return the buffer size
138     */
139    @Override
140    public int getBufferSize() {
141        return byteBuffer.capacity();
142    }
143
144    /**
145     * Factory to create a RollingRandomAccessFileManager.
146     */
147    private static class RollingRandomAccessFileManagerFactory implements
148            ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
149
150        /**
151         * Create the RollingRandomAccessFileManager.
152         *
153         * @param name The name of the entity to manage.
154         * @param data The data required to create the entity.
155         * @return a RollingFileManager.
156         */
157        @Override
158        public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
159            final File file = new File(name);
160            final File parent = file.getParentFile();
161            if (null != parent && !parent.exists()) {
162                parent.mkdirs();
163            }
164
165            if (!data.append) {
166                file.delete();
167            }
168            final long size = data.append ? file.length() : 0;
169            final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
170
171            final boolean writeHeader = !data.append || !file.exists();
172            RandomAccessFile raf = null;
173            try {
174                raf = new RandomAccessFile(name, "rw");
175                if (data.append) {
176                    final long length = raf.length();
177                    LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
178                    raf.seek(length);
179                } else {
180                    LOGGER.trace("RandomAccessFile {} set length to 0", name);
181                    raf.setLength(0);
182                }
183                return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM,
184                        data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy,
185                        data.advertiseURI, data.layout, writeHeader);
186            } catch (final IOException ex) {
187                LOGGER.error("Cannot access RandomAccessFile " + ex, ex);
188                if (raf != null) {
189                    try {
190                        raf.close();
191                    } catch (final IOException e) {
192                        LOGGER.error("Cannot close RandomAccessFile {}", name, e);
193                    }
194                }
195            }
196            return null;
197        }
198    }
199
200    /**
201     * Factory data.
202     */
203    private static class FactoryData {
204        private final String pattern;
205        private final boolean append;
206        private final boolean immediateFlush;
207        private final int bufferSize;
208        private final TriggeringPolicy policy;
209        private final RolloverStrategy strategy;
210        private final String advertiseURI;
211        private final Layout<? extends Serializable> layout;
212
213        /**
214         * Create the data for the factory.
215         *
216         * @param pattern The pattern.
217         * @param append The append flag.
218         * @param immediateFlush
219         * @param bufferSize
220         * @param policy
221         * @param strategy
222         * @param advertiseURI
223         * @param layout
224         */
225        public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
226                final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
227                final String advertiseURI, final Layout<? extends Serializable> layout) {
228            this.pattern = pattern;
229            this.append = append;
230            this.immediateFlush = immediateFlush;
231            this.bufferSize = bufferSize;
232            this.policy = policy;
233            this.strategy = strategy;
234            this.advertiseURI = advertiseURI;
235            this.layout = layout;
236        }
237
238        public TriggeringPolicy getTriggeringPolicy()
239        {
240            return this.policy;
241        }
242
243        public RolloverStrategy getRolloverStrategy()
244        {
245            return this.strategy;
246        }
247    }
248
249    @Override
250    public void updateData(final Object data) {
251        final FactoryData factoryData = (FactoryData) data;
252        setRolloverStrategy(factoryData.getRolloverStrategy());
253        setTriggeringPolicy(factoryData.getTriggeringPolicy());
254    }
255}