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.FileOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.Serializable;
024import java.nio.ByteBuffer;
025import java.nio.file.Files;
026import java.nio.file.Path;
027import java.nio.file.attribute.BasicFileAttributes;
028import java.nio.file.attribute.FileTime;
029import java.util.Collection;
030import java.util.Date;
031import java.util.concurrent.ArrayBlockingQueue;
032import java.util.concurrent.CopyOnWriteArrayList;
033import java.util.concurrent.ExecutorService;
034import java.util.concurrent.Semaphore;
035import java.util.concurrent.ThreadPoolExecutor;
036import java.util.concurrent.TimeUnit;
037import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
038
039import org.apache.logging.log4j.core.Layout;
040import org.apache.logging.log4j.core.LifeCycle;
041import org.apache.logging.log4j.core.LifeCycle2;
042import org.apache.logging.log4j.core.LogEvent;
043import org.apache.logging.log4j.core.LoggerContext;
044import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
045import org.apache.logging.log4j.core.appender.FileManager;
046import org.apache.logging.log4j.core.appender.ManagerFactory;
047import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
048import org.apache.logging.log4j.core.appender.rolling.action.Action;
049import org.apache.logging.log4j.core.config.Configuration;
050import org.apache.logging.log4j.core.util.Constants;
051import org.apache.logging.log4j.core.util.FileUtils;
052import org.apache.logging.log4j.core.util.Log4jThreadFactory;
053
054/**
055 * The Rolling File Manager.
056 */
057public class RollingFileManager extends FileManager {
058
059    private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
060    private static final int MAX_TRIES = 3;
061    private static final int MIN_DURATION = 100;
062    private static final FileTime EPOCH = FileTime.fromMillis(0);
063
064    protected long size;
065    private long initialTime;
066    private volatile PatternProcessor patternProcessor;
067    private final Semaphore semaphore = new Semaphore(1);
068    private final Log4jThreadFactory threadFactory = Log4jThreadFactory.createThreadFactory("RollingFileManager");
069    private volatile TriggeringPolicy triggeringPolicy;
070    private volatile RolloverStrategy rolloverStrategy;
071    private volatile boolean renameEmptyFiles;
072    private volatile boolean initialized;
073    private volatile String fileName;
074    private final boolean directWrite;
075    private final CopyOnWriteArrayList<RolloverListener> rolloverListeners = new CopyOnWriteArrayList<>();
076
077    /* This executor pool will create a new Thread for every work async action to be performed. Using it allows
078       us to make sure all the Threads are completed when the Manager is stopped. */
079    private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
080            new EmptyQueue(), threadFactory);
081
082    private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
083            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy");
084
085    private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater =
086            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy");
087
088    private static final AtomicReferenceFieldUpdater<RollingFileManager, PatternProcessor> patternProcessorUpdater =
089            AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor");
090
091    @Deprecated
092    protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
093            final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
094            final RolloverStrategy rolloverStrategy, final String advertiseURI,
095            final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
096        this(fileName, pattern, os, append, size, initialTime, triggeringPolicy, rolloverStrategy, advertiseURI, layout,
097                writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
098    }
099
100    @Deprecated
101    protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
102            final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
103            final RolloverStrategy rolloverStrategy, final String advertiseURI,
104            final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
105        super(fileName != null ? fileName : pattern, os, append, false, advertiseURI, layout, writeHeader,
106                        buffer);
107        this.size = size;
108        this.initialTime = initialTime;
109        this.triggeringPolicy = triggeringPolicy;
110        this.rolloverStrategy = rolloverStrategy;
111        this.patternProcessor = new PatternProcessor(pattern);
112        this.patternProcessor.setPrevFileTime(initialTime);
113        this.fileName = fileName;
114        this.directWrite = rolloverStrategy instanceof DirectWriteRolloverStrategy;
115    }
116
117    @Deprecated
118    protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
119            final boolean append, final boolean createOnDemand, final long size, final long initialTime,
120            final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
121            final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
122        super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
123                        advertiseURI, layout, writeHeader, buffer);
124        this.size = size;
125        this.initialTime = initialTime;
126        this.triggeringPolicy = triggeringPolicy;
127        this.rolloverStrategy = rolloverStrategy;
128        this.patternProcessor = new PatternProcessor(pattern);
129        this.patternProcessor.setPrevFileTime(initialTime);
130        this.fileName = fileName;
131        this.directWrite = rolloverStrategy instanceof DirectWriteRolloverStrategy;
132    }
133
134    /**
135     * @since 2.9
136     */
137    protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
138            final boolean append, final boolean createOnDemand, final long size, final long initialTime,
139            final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
140            final String advertiseURI, final Layout<? extends Serializable> layout,
141            final String filePermissions, final String fileOwner, final String fileGroup,
142            final boolean writeHeader, final ByteBuffer buffer) {
143        super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
144                        advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, buffer);
145        this.size = size;
146        this.initialTime = initialTime;
147        this.patternProcessor = new PatternProcessor(pattern);
148        this.patternProcessor.setPrevFileTime(initialTime);
149        this.triggeringPolicy = triggeringPolicy;
150        this.rolloverStrategy = rolloverStrategy;
151        this.fileName = fileName;
152        this.directWrite = rolloverStrategy instanceof DirectFileRolloverStrategy;
153    }
154
155    public void initialize() {
156
157        if (!initialized) {
158            LOGGER.debug("Initializing triggering policy {}", triggeringPolicy);
159            initialized = true;
160            // LOG4J2-2981 - set the file size before initializing the triggering policy.
161            if (directWrite) {
162                // LOG4J2-2485: Initialize size from the most recently written file.
163                File file = new File(getFileName());
164                if (file.exists()) {
165                    size = file.length();
166                } else {
167                    ((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName();
168                }
169            }
170            triggeringPolicy.initialize(this);
171            if (triggeringPolicy instanceof LifeCycle) {
172                ((LifeCycle) triggeringPolicy).start();
173            }
174            if (directWrite) {
175                // LOG4J2-2485: Initialize size from the most recently written file.
176                File file = new File(getFileName());
177                if (file.exists()) {
178                    size = file.length();
179                } else {
180                    ((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName();
181                }
182            }
183        }
184    }
185
186    /**
187     * Returns a RollingFileManager.
188     * @param fileName The file name.
189     * @param pattern The pattern for rolling file.
190     * @param append true if the file should be appended to.
191     * @param bufferedIO true if data should be buffered.
192     * @param policy The TriggeringPolicy.
193     * @param strategy The RolloverStrategy.
194     * @param advertiseURI the URI to use when advertising the file
195     * @param layout The Layout.
196     * @param bufferSize buffer size to use if bufferedIO is true
197     * @param immediateFlush flush on every write or not
198     * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
199     * @param filePermissions File permissions
200     * @param fileOwner File owner
201     * @param fileGroup File group
202     * @param configuration The configuration.
203     * @return A RollingFileManager.
204     */
205    public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
206            final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
207            final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
208            final boolean immediateFlush, final boolean createOnDemand,
209            final String filePermissions, final String fileOwner, final String fileGroup,
210            final Configuration configuration) {
211
212        if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
213            LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
214            return null;
215        }
216        final String name = fileName == null ? pattern : fileName;
217        return narrow(RollingFileManager.class, getManager(name, new FactoryData(fileName, pattern, append,
218            bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand,
219            filePermissions, fileOwner, fileGroup, configuration), factory));
220    }
221
222    /**
223     * Add a RolloverListener.
224     * @param listener The RolloverListener.
225     */
226    public void addRolloverListener(RolloverListener listener) {
227        rolloverListeners.add(listener);
228    }
229
230    /**
231     * Remove a RolloverListener.
232     * @param listener The RolloverListener.
233     */
234    public void removeRolloverListener(RolloverListener listener) {
235        rolloverListeners.remove(listener);
236    }
237
238    /**
239     * Returns the name of the File being managed.
240     * @return The name of the File being managed.
241     */
242    @Override
243    public String getFileName() {
244        if (directWrite) {
245            fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this);
246        }
247        return fileName;
248    }
249    
250    @Override
251    protected void createParentDir(File file) {
252        if (directWrite) {
253            file.getParentFile().mkdirs();
254        }
255    }
256
257    public boolean isDirectWrite() {
258        return directWrite;
259    }
260
261    public FileExtension getFileExtension() {
262        return patternProcessor.getFileExtension();
263    }
264
265    // override to make visible for unit tests
266    @Override
267    protected synchronized void write(final byte[] bytes, final int offset, final int length,
268            final boolean immediateFlush) {
269        super.write(bytes, offset, length, immediateFlush);
270    }
271
272    @Override
273    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
274        size += length;
275        super.writeToDestination(bytes, offset, length);
276    }
277
278    public boolean isRenameEmptyFiles() {
279        return renameEmptyFiles;
280    }
281
282    public void setRenameEmptyFiles(final boolean renameEmptyFiles) {
283        this.renameEmptyFiles = renameEmptyFiles;
284    }
285
286    /**
287     * Returns the current size of the file.
288     * @return The size of the file in bytes.
289     */
290    public long getFileSize() {
291        return size + byteBuffer.position();
292    }
293
294    /**
295     * Returns the time the file was created.
296     * @return The time the file was created.
297     */
298    public long getFileTime() {
299        return initialTime;
300    }
301
302    /**
303     * Determines if a rollover should occur.
304     * @param event The LogEvent.
305     */
306    public synchronized void checkRollover(final LogEvent event) {
307        if (triggeringPolicy.isTriggeringEvent(event)) {
308            rollover();
309        }
310    }
311
312    @Override
313    public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
314        LOGGER.debug("Shutting down RollingFileManager {}", getName());
315        boolean stopped = true;
316        if (triggeringPolicy instanceof LifeCycle2) {
317            stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
318        } else if (triggeringPolicy instanceof LifeCycle) {
319            ((LifeCycle) triggeringPolicy).stop();
320            stopped &= true;
321        }
322        final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
323        asyncExecutor.shutdown();
324        try {
325            // Allow at least the minimum interval to pass so async actions can complete.
326            final long millis = timeUnit.toMillis(timeout);
327            final long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
328
329            for (int count = 1; count <= MAX_TRIES && !asyncExecutor.isTerminated(); ++count) {
330                asyncExecutor.awaitTermination(waitInterval * count, TimeUnit.MILLISECONDS);
331            }
332            if (asyncExecutor.isTerminated()) {
333                LOGGER.debug("All asynchronous threads have terminated");
334            } else {
335                asyncExecutor.shutdownNow();
336                try {
337                    asyncExecutor.awaitTermination(timeout, timeUnit);
338                    if (asyncExecutor.isTerminated()) {
339                        LOGGER.debug("All asynchronous threads have terminated");
340                    } else {
341                        LOGGER.debug("RollingFileManager shutting down but some asynchronous services may not have completed");
342                    }
343                } catch (final InterruptedException inner) {
344                    LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
345                }
346            }
347        } catch (final InterruptedException ie) {
348            asyncExecutor.shutdownNow();
349            try {
350                asyncExecutor.awaitTermination(timeout, timeUnit);
351                if (asyncExecutor.isTerminated()) {
352                    LOGGER.debug("All asynchronous threads have terminated");
353                }
354            } catch (final InterruptedException inner) {
355                LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
356            }
357            // Preserve interrupt status
358            Thread.currentThread().interrupt();
359        }
360        LOGGER.debug("RollingFileManager shutdown completed with status {}", status);
361        return status;
362    }
363
364    public synchronized void rollover(Date prevFileTime, Date prevRollTime) {
365                getPatternProcessor().setPrevFileTime(prevFileTime.getTime());
366                getPatternProcessor().setCurrentFileTime(prevRollTime.getTime());
367                rollover();
368        }
369
370    public synchronized void rollover() {
371        if (!hasOutputStream() && !isCreateOnDemand() && !isDirectWrite()) {
372            return;
373        }
374        String currentFileName = fileName;
375        if (rolloverListeners.size() > 0) {
376            for (RolloverListener listener : rolloverListeners) {
377                try {
378                    listener.rolloverTriggered(currentFileName);
379                } catch (Exception ex) {
380                    LOGGER.warn("Rollover Listener {} failed with {}: {}", listener.getClass().getSimpleName(),
381                            ex.getClass().getName(), ex.getMessage());
382                }
383            }
384        }
385        if (rollover(rolloverStrategy)) {
386            try {
387                size = 0;
388                initialTime = System.currentTimeMillis();
389                createFileAfterRollover();
390            } catch (final IOException e) {
391                logError("Failed to create file after rollover", e);
392            }
393        }
394        if (rolloverListeners.size() > 0) {
395            for (RolloverListener listener : rolloverListeners) {
396                try {
397                    listener.rolloverComplete(currentFileName);
398                } catch (Exception ex) {
399                    LOGGER.warn("Rollover Listener {} failed with {}: {}", listener.getClass().getSimpleName(),
400                            ex.getClass().getName(), ex.getMessage());
401                }
402            }
403        }
404    }
405
406    protected void createFileAfterRollover() throws IOException  {
407        setOutputStream(createOutputStream());
408    }
409
410    /**
411     * Returns the pattern processor.
412     * @return The PatternProcessor.
413     */
414    public PatternProcessor getPatternProcessor() {
415        return patternProcessor;
416    }
417
418    public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
419        triggeringPolicy.initialize(this);
420        final TriggeringPolicy policy = this.triggeringPolicy;
421        int count = 0;
422        boolean policyUpdated = false;
423        do {
424            ++count;
425        } while (!(policyUpdated = triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy))
426                && count < MAX_TRIES);
427        if (policyUpdated) {
428            if (triggeringPolicy instanceof LifeCycle) {
429                ((LifeCycle) triggeringPolicy).start();
430            }
431            if (policy instanceof LifeCycle) {
432                ((LifeCycle) policy).stop();
433            }
434        } else if (triggeringPolicy instanceof LifeCycle) {
435            ((LifeCycle) triggeringPolicy).stop();
436        }
437    }
438
439    public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) {
440        rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
441    }
442
443    public void setPatternProcessor(final PatternProcessor patternProcessor) {
444        patternProcessorUpdater.compareAndSet(this, this.patternProcessor, patternProcessor);
445    }
446
447    /**
448     * Returns the triggering policy.
449     * @param <T> TriggeringPolicy type
450     * @return The TriggeringPolicy
451     */
452    @SuppressWarnings("unchecked")
453    public <T extends TriggeringPolicy> T getTriggeringPolicy() {
454        // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here.
455        return (T) this.triggeringPolicy;
456    }
457
458    /**
459     * Package-private access for tests only.
460     *
461     * @return The semaphore that controls access to the rollover operation.
462     */
463    Semaphore getSemaphore() {
464        return semaphore;
465    }
466
467    /**
468     * Returns the rollover strategy.
469     * @return The RolloverStrategy
470     */
471    public RolloverStrategy getRolloverStrategy() {
472        return this.rolloverStrategy;
473    }
474
475    private boolean rollover(final RolloverStrategy strategy) {
476
477        boolean releaseRequired = false;
478        try {
479            // Block until the asynchronous operation is completed.
480            semaphore.acquire();
481            releaseRequired = true;
482        } catch (final InterruptedException e) {
483            logError("Thread interrupted while attempting to check rollover", e);
484            return false;
485        }
486
487        boolean success = true;
488
489        try {
490            final RolloverDescription descriptor = strategy.rollover(this);
491            if (descriptor != null) {
492                writeFooter();
493                closeOutputStream();
494                if (descriptor.getSynchronous() != null) {
495                    LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
496                    try {
497                        success = descriptor.getSynchronous().execute();
498                    } catch (final Exception ex) {
499                        success = false;
500                        logError("Caught error in synchronous task", ex);
501                    }
502                }
503
504                if (success && descriptor.getAsynchronous() != null) {
505                    LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
506                    asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
507                    releaseRequired = false;
508                }
509                return true;
510            }
511            return false;
512        } finally {
513            if (releaseRequired) {
514                semaphore.release();
515            }
516        }
517
518    }
519
520    /**
521     * Performs actions asynchronously.
522     */
523    private static class AsyncAction extends AbstractAction {
524
525        private final Action action;
526        private final RollingFileManager manager;
527
528        /**
529         * Constructor.
530         * @param act The action to perform.
531         * @param manager The manager.
532         */
533        public AsyncAction(final Action act, final RollingFileManager manager) {
534            this.action = act;
535            this.manager = manager;
536        }
537
538        /**
539         * Executes an action.
540         *
541         * @return true if action was successful.  A return value of false will cause
542         *         the rollover to be aborted if possible.
543         * @throws java.io.IOException if IO error, a thrown exception will cause the rollover
544         *                             to be aborted if possible.
545         */
546        @Override
547        public boolean execute() throws IOException {
548            try {
549                return action.execute();
550            } finally {
551                manager.semaphore.release();
552            }
553        }
554
555        /**
556         * Cancels the action if not already initialized or waits till completion.
557         */
558        @Override
559        public void close() {
560            action.close();
561        }
562
563        /**
564         * Determines if action has been completed.
565         *
566         * @return true if action is complete.
567         */
568        @Override
569        public boolean isComplete() {
570            return action.isComplete();
571        }
572
573        @Override
574        public String toString() {
575            final StringBuilder builder = new StringBuilder();
576            builder.append(super.toString());
577            builder.append("[action=");
578            builder.append(action);
579            builder.append(", manager=");
580            builder.append(manager);
581            builder.append(", isComplete()=");
582            builder.append(isComplete());
583            builder.append(", isInterrupted()=");
584            builder.append(isInterrupted());
585            builder.append("]");
586            return builder.toString();
587        }
588    }
589
590    /**
591     * Factory data.
592     */
593    private static class FactoryData extends ConfigurationFactoryData {
594        private final String fileName;
595        private final String pattern;
596        private final boolean append;
597        private final boolean bufferedIO;
598        private final int bufferSize;
599        private final boolean immediateFlush;
600        private final boolean createOnDemand;
601        private final TriggeringPolicy policy;
602        private final RolloverStrategy strategy;
603        private final String advertiseURI;
604        private final Layout<? extends Serializable> layout;
605        private final String filePermissions;
606        private final String fileOwner;
607        private final String fileGroup;
608
609        /**
610         * Creates the data for the factory.
611         * @param pattern The pattern.
612         * @param append The append flag.
613         * @param bufferedIO The bufferedIO flag.
614         * @param advertiseURI
615         * @param layout The Layout.
616         * @param bufferSize the buffer size
617         * @param immediateFlush flush on every write or not
618         * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
619         * @param filePermissions File permissions
620         * @param fileOwner File owner
621         * @param fileGroup File group
622         * @param configuration The configuration
623         */
624        public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO,
625                final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
626                final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush,
627                final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup,
628                final Configuration configuration) {
629            super(configuration);
630            this.fileName = fileName;
631            this.pattern = pattern;
632            this.append = append;
633            this.bufferedIO = bufferedIO;
634            this.bufferSize = bufferSize;
635            this.policy = policy;
636            this.strategy = strategy;
637            this.advertiseURI = advertiseURI;
638            this.layout = layout;
639            this.immediateFlush = immediateFlush;
640            this.createOnDemand = createOnDemand;
641            this.filePermissions = filePermissions;
642            this.fileOwner = fileOwner;
643            this.fileGroup = fileGroup;
644        }
645
646        public TriggeringPolicy getTriggeringPolicy() {
647            return this.policy;
648        }
649
650        public RolloverStrategy getRolloverStrategy() {
651            return this.strategy;
652        }
653
654        public String getPattern() {
655            return pattern;
656        }
657
658        @Override
659        public String toString() {
660            final StringBuilder builder = new StringBuilder();
661            builder.append(super.toString());
662            builder.append("[pattern=");
663            builder.append(pattern);
664            builder.append(", append=");
665            builder.append(append);
666            builder.append(", bufferedIO=");
667            builder.append(bufferedIO);
668            builder.append(", bufferSize=");
669            builder.append(bufferSize);
670            builder.append(", policy=");
671            builder.append(policy);
672            builder.append(", strategy=");
673            builder.append(strategy);
674            builder.append(", advertiseURI=");
675            builder.append(advertiseURI);
676            builder.append(", layout=");
677            builder.append(layout);
678            builder.append(", filePermissions=");
679            builder.append(filePermissions);
680            builder.append(", fileOwner=");
681            builder.append(fileOwner);
682            builder.append("]");
683            return builder.toString();
684        }
685    }
686
687    /**
688     * Updates the RollingFileManager's data during a reconfiguration. This method should be considered private.
689     * It is not thread safe and calling it outside of a reconfiguration may lead to errors. This method may be
690     * made protected in a future release.
691     * @param data The data to update.
692     */
693    @Override
694    public void updateData(final Object data) {
695        final FactoryData factoryData = (FactoryData) data;
696        setRolloverStrategy(factoryData.getRolloverStrategy());
697        setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
698        setTriggeringPolicy(factoryData.getTriggeringPolicy());
699    }
700
701    /**
702     * Factory to create a RollingFileManager.
703     */
704    private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
705
706        /**
707         * Creates a RollingFileManager.
708         * @param name The name of the entity to manage.
709         * @param data The data required to create the entity.
710         * @return a RollingFileManager.
711         */
712        @Override
713        public RollingFileManager createManager(final String name, final FactoryData data) {
714            long size = 0;
715            File file = null;
716            if (data.fileName != null) {
717                file = new File(data.fileName);
718
719                try {
720                    FileUtils.makeParentDirs(file);
721                    final boolean created = data.createOnDemand ? false : file.createNewFile();
722                    LOGGER.trace("New file '{}' created = {}", name, created);
723                } catch (final IOException ioe) {
724                    LOGGER.error("Unable to create file " + name, ioe);
725                    return null;
726                }
727                size = data.append ? file.length() : 0;
728            }
729
730            try {
731                final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
732                final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
733                final OutputStream os = data.createOnDemand  || data.fileName == null ? null :
734                        new FileOutputStream(data.fileName, data.append);
735                // LOG4J2-531 create file first so time has valid value.
736                final long initialTime = file == null || !file.exists() ? 0 : initialFileTime(file);
737                final boolean writeHeader = file != null && file.exists() && file.length() == 0;
738
739                final RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
740                    data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI,
741                    data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
742                if (os != null && rm.isAttributeViewEnabled()) {
743                    rm.defineAttributeView(file.toPath());
744                }
745
746                return rm;
747            } catch (final IOException ex) {
748                LOGGER.error("RollingFileManager (" + name + ") " + ex, ex);
749            }
750            return null;
751        }
752    }
753
754    private static long initialFileTime(final File file) {
755        final Path path = file.toPath();
756        if (Files.exists(path)) {
757            try {
758                final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
759                final FileTime fileTime = attrs.creationTime();
760                if (fileTime.compareTo(EPOCH) > 0) {
761                    LOGGER.debug("Returning file creation time for {}", file.getAbsolutePath());
762                    return fileTime.toMillis();
763                }
764                LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath());
765            } catch (final Exception ex) {
766                LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage());
767            }
768        }
769        return file.lastModified();
770    }
771
772    private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
773
774        /**
775         *
776         */
777        private static final long serialVersionUID = 1L;
778
779        EmptyQueue() {
780            super(1);
781        }
782
783        @Override
784        public int remainingCapacity() {
785            return 0;
786        }
787
788        @Override
789        public boolean add(final Runnable runnable) {
790            throw new IllegalStateException("Queue is full");
791        }
792
793        @Override
794        public void put(final Runnable runnable) throws InterruptedException {
795            /* No point in going into a permanent wait */
796            throw new InterruptedException("Unable to insert into queue");
797        }
798
799        @Override
800        public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
801            Thread.sleep(timeUnit.toMillis(timeout));
802            return false;
803        }
804
805        @Override
806        public boolean addAll(final Collection<? extends Runnable> collection) {
807            if (collection.size() > 0) {
808                throw new IllegalArgumentException("Too many items in collection");
809            }
810            return false;
811        }
812
813    }
814
815}