/*
 * Decompiled with CFR 0.152.
 */
package org.apache.logging.log4j.core.appender;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.async.RingBufferLogEvent;
import org.apache.logging.log4j.core.config.AppenderControl;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAliases;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;

@Plugin(name="Async", category="Core", elementType="appender", printObject=true)
public final class AsyncAppender
extends AbstractAppender {
    private static final int DEFAULT_QUEUE_SIZE = 128;
    private static final String SHUTDOWN = "Shutdown";
    private final BlockingQueue<Serializable> queue;
    private final int queueSize;
    private final boolean blocking;
    private final Configuration config;
    private final AppenderRef[] appenderRefs;
    private final String errorRef;
    private final boolean includeLocation;
    private AppenderControl errorAppender;
    private AsyncThread thread;
    private static final AtomicLong threadSequence = new AtomicLong(1L);
    private static ThreadLocal<Boolean> isAppenderThread = new ThreadLocal();

    private AsyncAppender(String name, Filter filter, AppenderRef[] appenderRefs, String errorRef, int queueSize, boolean blocking, boolean ignoreExceptions, Configuration config, boolean includeLocation) {
        super(name, filter, null, ignoreExceptions);
        this.queue = new ArrayBlockingQueue<Serializable>(queueSize);
        this.queueSize = queueSize;
        this.blocking = blocking;
        this.config = config;
        this.appenderRefs = appenderRefs;
        this.errorRef = errorRef;
        this.includeLocation = includeLocation;
    }

    @Override
    public void start() {
        Map<String, Appender> map = this.config.getAppenders();
        ArrayList<AppenderControl> appenders = new ArrayList<AppenderControl>();
        for (AppenderRef appenderRef : this.appenderRefs) {
            if (map.containsKey(appenderRef.getRef())) {
                appenders.add(new AppenderControl(map.get(appenderRef.getRef()), appenderRef.getLevel(), appenderRef.getFilter()));
                continue;
            }
            LOGGER.error("No appender named {} was configured", appenderRef);
        }
        if (this.errorRef != null) {
            if (map.containsKey(this.errorRef)) {
                this.errorAppender = new AppenderControl(map.get(this.errorRef), null, null);
            } else {
                LOGGER.error("Unable to set up error Appender. No appender named {} was configured", this.errorRef);
            }
        }
        if (appenders.size() > 0) {
            this.thread = new AsyncThread(appenders, this.queue);
            this.thread.setName("AsyncAppender-" + this.getName());
        } else if (this.errorRef == null) {
            throw new ConfigurationException("No appenders are available for AsyncAppender " + this.getName());
        }
        this.thread.start();
        super.start();
    }

    @Override
    public void stop() {
        super.stop();
        LOGGER.trace("AsyncAppender stopping. Queue still has {} events.", this.queue.size());
        this.thread.shutdown();
        try {
            this.thread.join();
        }
        catch (InterruptedException ex) {
            LOGGER.warn("Interrupted while stopping AsyncAppender {}", this.getName());
        }
        LOGGER.trace("AsyncAppender stopped. Queue has {} events.", this.queue.size());
    }

    @Override
    public void append(LogEvent logEvent) {
        if (!this.isStarted()) {
            throw new IllegalStateException("AsyncAppender " + this.getName() + " is not active");
        }
        if (!(logEvent instanceof Log4jLogEvent)) {
            if (!(logEvent instanceof RingBufferLogEvent)) {
                return;
            }
            logEvent = ((RingBufferLogEvent)logEvent).createMemento();
        }
        Log4jLogEvent coreEvent = (Log4jLogEvent)logEvent;
        boolean appendSuccessful = false;
        if (this.blocking) {
            if (isAppenderThread.get() == Boolean.TRUE && this.queue.remainingCapacity() == 0) {
                coreEvent.setEndOfBatch(false);
                appendSuccessful = this.thread.callAppenders(coreEvent);
            } else {
                try {
                    this.queue.put(Log4jLogEvent.serialize(coreEvent, this.includeLocation));
                    appendSuccessful = true;
                }
                catch (InterruptedException e) {
                    LOGGER.warn("Interrupted while waiting for a free slot in the AsyncAppender LogEvent-queue {}", this.getName());
                }
            }
        } else {
            appendSuccessful = this.queue.offer(Log4jLogEvent.serialize(coreEvent, this.includeLocation));
            if (!appendSuccessful) {
                this.error("Appender " + this.getName() + " is unable to write primary appenders. queue is full");
            }
        }
        if (!appendSuccessful && this.errorAppender != null) {
            this.errorAppender.callAppender(coreEvent);
        }
    }

    @PluginFactory
    public static AsyncAppender createAppender(@PluginElement(value="AppenderRef") AppenderRef[] appenderRefs, @PluginAttribute(value="errorRef") @PluginAliases(value={"error-ref"}) String errorRef, @PluginAttribute(value="blocking", defaultBoolean=true) boolean blocking, @PluginAttribute(value="bufferSize", defaultInt=128) int size, @PluginAttribute(value="name") String name, @PluginAttribute(value="includeLocation", defaultBoolean=false) boolean includeLocation, @PluginElement(value="Filter") Filter filter, @PluginConfiguration Configuration config, @PluginAttribute(value="ignoreExceptions", defaultBoolean=true) boolean ignoreExceptions) {
        if (name == null) {
            LOGGER.error("No name provided for AsyncAppender");
            return null;
        }
        if (appenderRefs == null) {
            LOGGER.error("No appender references provided to AsyncAppender {}", name);
        }
        return new AsyncAppender(name, filter, appenderRefs, errorRef, size, blocking, ignoreExceptions, config, includeLocation);
    }

    public String[] getAppenderRefStrings() {
        String[] result = new String[this.appenderRefs.length];
        for (int i = 0; i < result.length; ++i) {
            result[i] = this.appenderRefs[i].getRef();
        }
        return result;
    }

    public boolean isIncludeLocation() {
        return this.includeLocation;
    }

    public boolean isBlocking() {
        return this.blocking;
    }

    public String getErrorRef() {
        return this.errorRef;
    }

    public int getQueueCapacity() {
        return this.queueSize;
    }

    public int getQueueRemainingCapacity() {
        return this.queue.remainingCapacity();
    }

    private class AsyncThread
    extends Thread {
        private volatile boolean shutdown = false;
        private final List<AppenderControl> appenders;
        private final BlockingQueue<Serializable> queue;

        public AsyncThread(List<AppenderControl> appenders, BlockingQueue<Serializable> queue) {
            this.appenders = appenders;
            this.queue = queue;
            this.setDaemon(true);
            this.setName("AsyncAppenderThread" + threadSequence.getAndIncrement());
        }

        @Override
        public void run() {
            isAppenderThread.set(Boolean.TRUE);
            while (!this.shutdown) {
                Serializable s;
                try {
                    s = this.queue.take();
                    if (s != null && s instanceof String && AsyncAppender.SHUTDOWN.equals(s.toString())) {
                        this.shutdown = true;
                    }
                }
                catch (InterruptedException ex) {}
                continue;
                Log4jLogEvent event = Log4jLogEvent.deserialize(s);
                event.setEndOfBatch(this.queue.isEmpty());
                boolean success = this.callAppenders(event);
                if (success || AsyncAppender.this.errorAppender == null) continue;
                try {
                    AsyncAppender.this.errorAppender.callAppender(event);
                }
                catch (Exception ex) {}
            }
            LOGGER.trace("AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.", this.queue.size());
            int count = 0;
            int ignored = 0;
            while (!this.queue.isEmpty()) {
                try {
                    Serializable s = this.queue.take();
                    if (Log4jLogEvent.canDeserialize(s)) {
                        Log4jLogEvent event = Log4jLogEvent.deserialize(s);
                        event.setEndOfBatch(this.queue.isEmpty());
                        this.callAppenders(event);
                        ++count;
                        continue;
                    }
                    ++ignored;
                    LOGGER.trace("Ignoring event of class {}", s.getClass().getName());
                }
                catch (InterruptedException interruptedException) {}
            }
            LOGGER.trace("AsyncAppender.AsyncThread stopped. Queue has {} events remaining. Processed {} and ignored {} events since shutdown started.", this.queue.size(), count, ignored);
        }

        boolean callAppenders(Log4jLogEvent event) {
            boolean success = false;
            for (AppenderControl control : this.appenders) {
                try {
                    control.callAppender(event);
                    success = true;
                }
                catch (Exception ex) {}
            }
            return success;
        }

        public void shutdown() {
            this.shutdown = true;
            if (this.queue.isEmpty()) {
                this.queue.offer((Serializable)((Object)AsyncAppender.SHUTDOWN));
            }
        }
    }
}
