/*
 *  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.mina.util;

import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

import org.slf4j.MDC;

/**
 * Implementation of {@link java.util.logging.Formatter} that generates xml in the log4j format.
 * <p>
 * The generated xml corresponds 100% with what is generated by
 * log4j's <a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/XMLLayout.html">XMLLayout</a>
 * <p>
 * The MDC properties will only be correct when <code>format</code> is called from the same thread
 * that generated the LogRecord.
 * <p>
 * The file and line attributes in the locationInfo element will always be "?"
 * since java.util.logging.LogRecord does not provide that info.
 * <p>
 * The implementation is heavily based on org.apache.log4j.xml.XMLLayout
 * </p>
 *
 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
 */
public class Log4jXmlFormatter extends Formatter {

    private static final int DEFAULT_SIZE = 256;

    private static final int UPPER_LIMIT = 2048;

    private StringBuilder buf = new StringBuilder(DEFAULT_SIZE);

    private boolean locationInfo = false;

    private boolean properties = false;

    /**
     * The <b>LocationInfo</b> option takes a boolean value. By default,
     * it is set to false which means there will be no location
     * information output by this layout. If the the option is set to
     * true, then the file name and line number of the statement at the
     * origin of the log statement will be output.
     *
     * @param flag whether locationInfo should be output by this layout
     */
    public void setLocationInfo(boolean flag) {
        locationInfo = flag;
    }

    /**
     * @return the current value of the <b>LocationInfo</b> option.
     */
    public boolean getLocationInfo() {
        return locationInfo;
    }

    /**
     * Sets whether MDC key-value pairs should be output, default false.
     *
     * @param flag new value.
     */
    public void setProperties(final boolean flag) {
        properties = flag;
    }

    /**
     * Gets whether MDC key-value pairs should be output.
     *
     * @return true if MDC key-value pairs are output.
     */
    public boolean getProperties() {
        return properties;
    }

    @Override
    public String format(final LogRecord record) {
        // Reset working buffer. If the buffer is too large, then we need a new
        // one in order to avoid the penalty of creating a large array.
        if (buf.capacity() > UPPER_LIMIT) {
            buf = new StringBuilder(DEFAULT_SIZE);
        } else {
            buf.setLength(0);
        }
        
        buf.append("<log4j:event logger=\"");
        buf.append(Transform.escapeTags(record.getLoggerName()));
        buf.append("\" timestamp=\"");
        buf.append(record.getMillis());
        buf.append("\" level=\"");

        buf.append(Transform.escapeTags(record.getLevel().getName()));
        buf.append("\" thread=\"");
        buf.append(String.valueOf(record.getThreadID()));
        buf.append("\">\r\n");

        buf.append("<log4j:message><![CDATA[");
        // Append the rendered message. Also make sure to escape any
        // existing CDATA sections.
        Transform.appendEscapingCDATA(buf, record.getMessage());
        buf.append("]]></log4j:message>\r\n");

        if (record.getThrown() != null) {
            String[] s = Transform.getThrowableStrRep(record.getThrown());
            
            if (s != null) {
                buf.append("<log4j:throwable><![CDATA[");
                
                for (String value : s) {
                    Transform.appendEscapingCDATA(buf, value);
                    buf.append("\r\n");
                }
                
                buf.append("]]></log4j:throwable>\r\n");
            }
        }

        if (locationInfo) {
            buf.append("<log4j:locationInfo class=\"");
            buf.append(Transform.escapeTags(record.getSourceClassName()));
            buf.append("\" method=\"");
            buf.append(Transform.escapeTags(record.getSourceMethodName()));
            buf.append("\" file=\"?\" line=\"?\"/>\r\n");
        }

        if (properties) {
            Map<String,String> contextMap = MDC.getCopyOfContextMap();
            
            if (contextMap != null) {
                Set<String> keySet = contextMap.keySet();
                
                if ((keySet != null) && !keySet.isEmpty()) {
                    buf.append("<log4j:properties>\r\n");
                    Object[] keys = keySet.toArray();
                    Arrays.sort(keys);
                    
                    for (Object key1 : keys) {
                        String key = key1 == null ? "" : key1.toString();
                        Object val = contextMap.get(key);
                        
                        if (val != null) {
                            buf.append("<log4j:data name=\"");
                            buf.append(Transform.escapeTags(key));
                            buf.append("\" value=\"");
                            buf.append(Transform.escapeTags(String.valueOf(val)));
                            buf.append("\"/>\r\n");
                        }
                    }
                    
                    buf.append("</log4j:properties>\r\n");
                }
            }
        }
        
        buf.append("</log4j:event>\r\n\r\n");

        return buf.toString();
    }
}
