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