/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.log4j.rewrite;

import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.helpers.AppenderAttachableImpl;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.OptionHandler;
import org.apache.log4j.xml.UnrecognizedElementHandler;
import org.w3c.dom.Element;

import java.util.Enumeration;
import java.util.Properties;

/**
 * This appender forwards a logging request to another appender after possibly rewriting the logging event.
 * <p>
 * This appender (with the appropriate policy) replaces the MapFilter, PropertyFilter and ReflectionFilter from log4j
 * 1.3.
 */
public class RewriteAppender extends AppenderSkeleton implements AppenderAttachable, UnrecognizedElementHandler {
    /**
     * Rewrite policy.
     */
    private RewritePolicy policy;
    /**
     * Nested appenders.
     */
    private final AppenderAttachableImpl appenders;

    public RewriteAppender() {
        appenders = new AppenderAttachableImpl();
    }

    /**
     * {@inheritDoc}
     */
    protected void append(final LoggingEvent event) {
        LoggingEvent rewritten = event;
        if (policy != null) {
            rewritten = policy.rewrite(event);
        }
        if (rewritten != null) {
            synchronized (appenders) {
                appenders.appendLoopOnAppenders(rewritten);
            }
        }
    }

    /**
     * Add appender.
     *
     * @param newAppender appender to add, may not be null.
     */
    public void addAppender(final Appender newAppender) {
        synchronized (appenders) {
            appenders.addAppender(newAppender);
        }
    }

    /**
     * Get iterator over attached appenders.
     *
     * @return iterator or null if no attached appenders.
     */
    public Enumeration getAllAppenders() {
        synchronized (appenders) {
            return appenders.getAllAppenders();
        }
    }

    /**
     * Get appender by name.
     *
     * @param name name, may not be null.
     * @return matching appender or null.
     */
    public Appender getAppender(final String name) {
        synchronized (appenders) {
            return appenders.getAppender(name);
        }
    }

    /**
     * Close this <code>AsyncAppender</code> by interrupting the dispatcher thread which will process all pending events
     * before exiting.
     */
    public void close() {
        closed = true;
        //
        // close all attached appenders.
        //
        synchronized (appenders) {
            Enumeration iter = appenders.getAllAppenders();

            if (iter != null) {
                while (iter.hasMoreElements()) {
                    Object next = iter.nextElement();

                    if (next instanceof Appender) {
                        ((Appender) next).close();
                    }
                }
            }
        }
    }

    /**
     * Determines if specified appender is attached.
     *
     * @param appender appender.
     * @return true if attached.
     */
    public boolean isAttached(final Appender appender) {
        synchronized (appenders) {
            return appenders.isAttached(appender);
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean requiresLayout() {
        return false;
    }

    /**
     * Removes and closes all attached appenders.
     */
    public void removeAllAppenders() {
        synchronized (appenders) {
            appenders.removeAllAppenders();
        }
    }

    /**
     * Removes an appender.
     *
     * @param appender appender to remove.
     */
    public void removeAppender(final Appender appender) {
        synchronized (appenders) {
            appenders.removeAppender(appender);
        }
    }

    /**
     * Remove appender by name.
     *
     * @param name name.
     */
    public void removeAppender(final String name) {
        synchronized (appenders) {
            appenders.removeAppender(name);
        }
    }

    public void setRewritePolicy(final RewritePolicy rewritePolicy) {
        policy = rewritePolicy;
    }

    /**
     * {@inheritDoc}
     */
    public boolean parseUnrecognizedElement(final Element element, final Properties props) throws Exception {
        final String nodeName = element.getNodeName();
        if ("rewritePolicy".equals(nodeName)) {
            Object rewritePolicy = org.apache.log4j.xml.DOMConfigurator.parseElement(element, props,
                    RewritePolicy.class);
            if (rewritePolicy != null) {
                if (rewritePolicy instanceof OptionHandler) {
                    ((OptionHandler) rewritePolicy).activateOptions();
                }
                this.setRewritePolicy((RewritePolicy) rewritePolicy);
            }
            return true;
        }
        return false;
    }

}
