com.cisco.oss.foundation.logging.structured.AbstractFoundationLoggingMarker.java Source code

Java tutorial

Introduction

Here is the source code for com.cisco.oss.foundation.logging.structured.AbstractFoundationLoggingMarker.java

Source

/*
 * Copyright 2015 Cisco Systems, Inc.
 *
 *  Licensed 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 com.cisco.oss.foundation.logging.structured;

import com.cisco.oss.foundation.logging.FoundationLoggerConstants;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.common.collect.Multiset;
import org.apache.commons.lang3.text.WordUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public abstract class AbstractFoundationLoggingMarker implements FoundationLoggingMarker {

    private boolean isInit = false;

    private static final long serialVersionUID = 6354894754547315308L;

    protected Map<String, Object> userFields = new ConcurrentHashMap<String, Object>();

    private static final Map<Class<? extends FoundationLoggingMarker>, Multiset<Field>> markerClassFields = new ConcurrentHashMap<Class<? extends FoundationLoggingMarker>, Multiset<Field>>();

    private static HashMap<Class<? extends FoundationLoggingMarker>, Class<FoundationLoggingMarkerFormatter>> markersMap = new HashMap<Class<? extends FoundationLoggingMarker>, Class<FoundationLoggingMarkerFormatter>>();

    public static HashMap<String, Element> markersXmlMap = new HashMap<String, Element>();

    private FoundationLoggingMarkerFormatter formatter;

    private static Logger LOGGER = LoggerFactory.getLogger(AbstractFoundationLoggingMarker.class);

    public static void init() {
        if (LOGGER == null) {
            LOGGER = LoggerFactory.getLogger(AbstractFoundationLoggingMarker.class);
        }
        udpateMarkerStructuredLogOverrideMap();

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    scanClassPathForFormattingAnnotations();
                } catch (Exception e) {
                    LOGGER.error("Problem parsing marker annotations. Error is: " + e, e);
                }

            }
        }).start();
    }

    private static void udpateMarkerStructuredLogOverrideMap() {

        InputStream messageFormatIS = AbstractFoundationLoggingMarker.class
                .getResourceAsStream("/messageFormat.xml");
        if (messageFormatIS == null) {
            LOGGER.debug("file messageformat.xml not found in classpath");
        } else {
            try {
                SAXBuilder builder = new SAXBuilder();
                Document document = builder.build(messageFormatIS);

                messageFormatIS.close();

                Element rootElement = document.getRootElement();
                List<Element> markers = rootElement.getChildren("marker");
                for (Element marker : markers) {
                    AbstractFoundationLoggingMarker.markersXmlMap.put(marker.getAttributeValue("id"), marker);
                }

            } catch (Exception e) {
                LOGGER.error("cannot load the structured log override file. error is: " + e, e);
                throw new IllegalArgumentException("Problem parsing messageformat.xml", e);
            }
        }

    }

    public AbstractFoundationLoggingMarker() {
        super();
        // create new instance of a formatter specific to this marker
        // implementation.
        // if it is the first time we create a marker of this class - we
        // generate a new formatter implementation
        FoundationLoggingMarkerFormatter newFormatter = getFormatter(getClass());
        newFormatter.setMarker(this);
        setFormatter(newFormatter);
    }

    private static FoundationLoggingMarkerFormatter getFormatter(
            Class<? extends FoundationLoggingMarker> markerClass) {
        try {

            //         generateAndUpdateFormatterInMap(markerClass);

            Class<? extends FoundationLoggingMarkerFormatter> formatterClass = null;
            try {
                formatterClass = (Class<? extends FoundationLoggingMarkerFormatter>) Class
                        .forName(markerClass.getName() + "Formatter");
            } catch (ClassNotFoundException e) {
                LOGGER.error("annotation processign ahs failed. " + e.toString());
            }
            FoundationLoggingMarkerFormatter newFormatter = null;
            if (formatterClass == null) {
                newFormatter = new DefaultMarkerFormatter();
            } else {
                newFormatter = formatterClass.newInstance();
            }

            return newFormatter;
        } catch (Exception e) {
            LOGGER.error("Can't find a proxied class for: " + markerClass, e);
            throw new IllegalArgumentException(e);
        }
    }

    private static void generateAndUpdateFormatterInMap(Class<? extends FoundationLoggingMarker> markerClass) {

        if (markersMap.get(markerClass) == null) {

            Element markerElement = markersXmlMap.get(markerClass.getName());

            StringBuilder builder = new StringBuilder();
            buildClassPrefix(markerClass, builder);

            boolean shouldGenereate = buildFormat(markerElement, markerClass, builder);

            if (shouldGenereate) {

                buildClassSuffix(builder);
                String newClassSource = builder.toString();

                synchronized (markerClass) {

                    LOGGER.trace("The generated class is: {}", newClassSource);
                    Class<FoundationLoggingMarkerFormatter> formatterClass = generateMarkerClass(newClassSource,
                            markerClass.getName() + "Formatter");
                    markersMap.put(markerClass, formatterClass);

                }
            } else {
                LOGGER.debug("Not generating any specific markers for {} as it doesn't contain any annotations",
                        markerClass);
            }

        }
    }

    @Override
    public String getName() {
        if (!isInit) {
            populateUserFieldMap();
        }
        return this.getClass().getSimpleName();
    }

    @Override
    public void add(Marker reference) {
    }

    @Override
    public boolean remove(Marker reference) {
        return false;
    }

    @Override
    public boolean hasChildren() {
        return false;
    }

    @Override
    public boolean hasReferences() {
        return false;
    }

    @Override
    public Iterator iterator() {
        return null;
    }

    @Override
    public boolean contains(Marker other) {
        return false;
    }

    @Override
    public boolean contains(String name) {
        return false;
    }

    @Override
    public String valueOf(String userFieldName) {

        String value = null;

        if (FoundationLoggerConstants.TRANSACTION_NAME.toString().equals(userFieldName)) {
            value = getName();
        } else if (FoundationLoggerConstants.ALL_VALUES.toString().equals(userFieldName)) {
            value = buildAllValues();
        } else {
            value = userFields.get(userFieldName) != null ? userFields.get(userFieldName).toString() : null;
        }

        return value;

    }

    private String buildAllValues() {

        boolean first = true;

        StringBuilder builder = new StringBuilder("{");

        Set<Entry<String, Object>> entrySet = userFields.entrySet();
        for (Entry<String, Object> entry : entrySet) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (!FoundationLoggingMarker.NO_OPERATION.equals(value)) {
                if (first) {
                    first = false;
                } else {
                    builder.append(",");
                }
                builder.append("\"");
                builder.append(key);
                builder.append("\"");
                builder.append(":");
                if (value instanceof String) {
                    builder.append("\"");
                    value = ((String) value).replaceAll("\"", "'");
                    builder.append(value);
                    builder.append("\"");
                } else {
                    builder.append(value);
                }
            }
        }
        builder.append("}");
        return builder.toString();
    }

    protected void populateUserFieldMap() {

        Class<? extends AbstractFoundationLoggingMarker> clazz = this.getClass();
        Multiset<Field> fieldList = markerClassFields.get(clazz);

        if (fieldList == null) {

            fieldList = ConcurrentHashMultiset.create();

            Class<?> cls = clazz;

            while (AbstractFoundationLoggingMarker.class.isAssignableFrom(cls)) {
                Field[] declaredFields = cls.getDeclaredFields();

                for (Field field : declaredFields) {

                    if (field.isAnnotationPresent(UserField.class)) {
                        field.setAccessible(true);
                        fieldList.add(field);
                    }
                }
                markerClassFields.put(clazz, fieldList);
                cls = cls.getSuperclass();
            }

        }

        for (Field field : fieldList) {

            try {

                Object value = field.get(this);

                UserField userField = field.getAnnotation(UserField.class);
                if (value == null && userField.suppressNull()) {
                    value = FoundationLoggingMarker.NO_OPERATION;
                }
                userFields.put(field.getName(), value == null ? "null" : value);
            } catch (IllegalAccessException e) {
                throw new IllegalArgumentException(e);
            }
        }

    }

    @Override
    public FoundationLoggingMarkerFormatter getFormatter() {
        return formatter;
    }

    public void setFormatter(FoundationLoggingMarkerFormatter formatter) {
        this.formatter = formatter;
    }

    private static Class<FoundationLoggingMarkerFormatter> generateMarkerClass(String sourceCode,
            String newClassName) {

        try {

            // We get an instance of JavaCompiler. Then
            // we create a file manager
            // (our custom implementation of it)
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));

            // Dynamic compiling requires specifying
            // a list of "files" to compile. In our case
            // this is a list containing one "file" which is in our case
            // our own implementation (see details below)
            List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
            jfiles.add(new DynamicJavaSourceCodeObject(newClassName, sourceCode));

            List<String> optionList = new ArrayList<String>();
            // set compiler's classpath to be same as the runtime's
            optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));

            // We specify a task to the compiler. Compiler should use our file
            // manager and our list of "files".
            // Then we run the compilation with call()
            compiler.getTask(null, fileManager, null, optionList, null, jfiles).call();

            // Creating an instance of our compiled class and
            // running its toString() method
            Class<FoundationLoggingMarkerFormatter> clazz = (Class<FoundationLoggingMarkerFormatter>) fileManager
                    .getClassLoader(null).loadClass(newClassName);

            return clazz;
        } catch (Exception e) {
            throw new UnsupportedOperationException("can't create class of: " + newClassName, e);
        }
    }

    private static <T extends FoundationLoggingMarker> void buildClassPrefix(Class<T> markerClass,
            StringBuilder builder) {
        builder.append("\npackage ").append(markerClass.getPackage().getName()).append(";\n\n");
        builder.append("import ").append(FoundationLoggingMarkerFormatter.class.getName()).append(";\n");
        builder.append("import ").append(FoundationLoggingMarker.class.getName()).append(";\n");
        //      builder.append("import ").append(FoundationLoggingEvent.class.getName()).append(";\n");

        builder.append("public class ").append(markerClass.getSimpleName()).append("Formatter")
                .append(" implements FoundationLoggingMarkerFormatter ").append(" {\n\n");
        builder.append("private FoundationLoggingMarker loggingMarker;\n\n");
        builder.append("   \n@Override\n" + "   public void setMarker(FoundationLoggingMarker marker) {\r\n"
                + "      this.loggingMarker = marker;\r\n" + "   }");
        builder.append("\n@Override\npublic String getFormat(String appenderName) {\n")
                .append(markerClass.getSimpleName()).append(" marker = (").append(markerClass.getSimpleName())
                .append(")loggingMarker;\n");
    }

    private static void buildClassSuffix(StringBuilder builder) {
        builder.append("}\n}");
    }

    private static boolean buildFormat(Element markerElement, Class<? extends FoundationLoggingMarker> clazz,
            StringBuilder builder) {

        if (markerElement == null) {
            return buildFromAnnotations(clazz, builder);
        } else {
            return buildFromXml(markerElement, builder);
        }

    }

    private static boolean buildFromXml(Element markerElement, StringBuilder builder) {

        Element defaultAppender = markerElement.getChild("defaultAppender");

        List<Element> appenders = markerElement.getChildren("appender");
        String markerId = markerElement.getAttributeValue("id");

        if (appenders != null) {
            for (Element appender : appenders) {
                String appenderId = appender.getAttributeValue("id");
                if (appenderId == null) {
                    LOGGER.error("the appender element must have an id poiting to a valid appender name");
                } else {
                    buildFromAppenderElement(markerId, appenderId, appender, builder, false, appenderId);
                }
            }
        }

        if (defaultAppender == null) {

            LOGGER.error("The marker element: '{}' must contain a 'defaultAppender' element", markerId);
            builder.append("return null;");

        } else {

            buildFromAppenderElement(markerId, "defaultAppender", defaultAppender, builder, true, "DEFAULT");
        }

        return true;
    }

    private static void buildFromAppenderElement(String markerId, String appenderId, Element appenderElement,
            StringBuilder builder, boolean isDefault, String appenderName) {

        if (!isDefault) {
            builder.append("if (\"").append(appenderName).append("\".equals(").append("appenderName)){\n");
        }

        Element criteriaElement = appenderElement.getChild("criteria");

        if (criteriaElement != null) {

            List<Element> criterionList = criteriaElement.getChildren("criterion");

            if (criterionList != null) {

                for (Element criterionElement : criterionList) {

                    String result = criterionElement.getAttributeValue("format");
                    List<Element> fieldList = criterionElement.getChildren("field");

                    if (fieldList != null) {

                        for (int i = 0; i < fieldList.size(); i++) {
                            Element fieldElement = fieldList.get(i);

                            String key = fieldElement.getAttributeValue("name");
                            String value = fieldElement.getAttributeValue("equals");

                            String getterField = "marker.get" + WordUtils.capitalize(key) + "()";

                            if (i == 0) {
                                builder.append("if (").append(getterField).append(" != null && \"").append(value)
                                        .append("\".equals(").append(getterField).append(".toString())");
                            } else {
                                builder.append(" && ").append(getterField).append(" != null && \"").append(value)
                                        .append("\".equals(").append(getterField).append(".toString())");
                            }

                        }

                        builder.append(")\n\treturn \"").append(result).append("\";\n");
                    }

                }
            }
        } else {
            LOGGER.info("The marker element '{}' does not contain a 'criteria' element for appender: '{}'",
                    markerId, appenderId);
        }

        String defaultFormat = appenderElement.getAttributeValue("defaultFormat");
        if (defaultFormat == null) {
            LOGGER.error("The marker element: '{}' must contain a 'defaultFormat' element", markerId);
        }
        builder.append("return \"" + defaultFormat + "\";");

        if (!isDefault) {
            builder.append("\n}\n");
        }
    }

    private static boolean buildFromAnnotations(Class<? extends FoundationLoggingMarker> clazz,
            StringBuilder builder) {
        boolean shouldGenerate = false;

        final DefaultFormat defaultFormat = clazz.getAnnotation(DefaultFormat.class);

        ConditionalFormats conditionalFormats = clazz.getAnnotation(ConditionalFormats.class);
        if (conditionalFormats != null) {

            shouldGenerate = true;

            if (defaultFormat == null) {
                throw new IllegalArgumentException(
                        "when using conditionals - you must also speicfy a DefaultFormat annotation");
            }
            ConditionalFormat[] formats = conditionalFormats.value();
            if (formats != null) {
                for (ConditionalFormat conditionalFormat : formats) {
                    final ConditionalFormat format = conditionalFormat;
                    buldConditionalFormat(format, builder);
                }
            }
        }

        if (defaultFormat != null) {
            shouldGenerate = true;
            buildDefaultFormat(defaultFormat, builder);
        }

        return shouldGenerate;
    }

    private static void buldConditionalFormat(ConditionalFormat format, final StringBuilder builder) {
        String result = format.format();
        FieldCriterion[] criteria = format.criteria();
        if (criteria != null) {

            for (int i = 0; i < criteria.length; i++) {

                FieldCriterion criterion = criteria[i];

                String key = criterion.name();
                String value = criterion.value();

                String getterField = "marker.get" + WordUtils.capitalize(key) + "()";

                if (i == 0) {
                    builder.append("if (").append(getterField).append(" != null && \"").append(value)
                            .append("\".equals(").append(getterField).append(".toString())");
                } else {
                    builder.append(" && ").append(getterField).append(" != null && \"").append(value)
                            .append("\".equals(").append(getterField).append(".toString())");
                }
            }

            builder.append(")\n\treturn \"").append(result).append("\";\n");
        }

    }

    private static void buildDefaultFormat(DefaultFormat defaultFormat, final StringBuilder builder) {
        builder.append("return \"" + defaultFormat.value() + "\";");
    }

    /**
     * Scan all the class path and look for all classes that have the Format
     * Annotations.
     */
    public static void scanClassPathForFormattingAnnotations() {

        ExecutorService executorService = Executors
                .newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);

        // scan classpath and filter out classes that don't begin with "com.nds"
        Reflections reflections = new Reflections("com.nds", "com.cisco");

        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(DefaultFormat.class);

        //        Reflections ciscoReflections = new Reflections("com.cisco");
        //
        //        annotated.addAll(ciscoReflections.getTypesAnnotatedWith(DefaultFormat.class));

        for (Class<?> markerClass : annotated) {

            // if the marker class is indeed implementing FoundationLoggingMarker
            // interface
            if (FoundationLoggingMarker.class.isAssignableFrom(markerClass)) {

                final Class<? extends FoundationLoggingMarker> clazz = (Class<? extends FoundationLoggingMarker>) markerClass;

                executorService.execute(new Runnable() {

                    @Override
                    public void run() {

                        if (markersMap.get(clazz) == null) {
                            try {
                                // generate formatter class for this marker
                                // class
                                generateAndUpdateFormatterInMap(clazz);
                            } catch (Exception e) {
                                LOGGER.trace(
                                        "problem generating formatter class from static scan method. error is: "
                                                + e.toString());
                            }
                        }

                    }
                });

            } else {// if marker class does not implement FoundationLoggingMarker
                    // interface, log ERROR

                // verify the LOGGER was initialized. It might not be as this
                // Method is called in a static block
                if (LOGGER == null) {
                    LOGGER = LoggerFactory.getLogger(AbstractFoundationLoggingMarker.class);
                }
                LOGGER.error("Formatter annotations should only appear on foundationLoggingMarker implementations");
            }
        }

        executorService.shutdown();
        // try {
        // executorService.awaitTermination(15, TimeUnit.SECONDS);
        // } catch (InterruptedException e) {
        // LOGGER.error("creation of formatters has been interrupted");
        // }
    }

    /**
     * Creates a dynamic source code file object
     * 
     * This is an example of how we can prepare a dynamic java source code for
     * compilation. This class reads the java code from a string and prepares a
     * JavaFileObject
     * 
     */
    private static class DynamicJavaSourceCodeObject extends SimpleJavaFileObject {
        private String sourceCode;

        /**
         * Converts the name to an URI, as that is the format expected by
         * JavaFileObject
         * 
         * 
         * @param name
         *            qualified name given to the class file
         * @param code
         *            the source code string
         */
        protected DynamicJavaSourceCodeObject(String name, String code) {
            super(URI.create("string:///" + name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
            this.sourceCode = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return sourceCode;
        }

    }

    public static class JavaClassObject extends SimpleJavaFileObject {

        /**
         * Byte code created by the compiler will be stored in this
         * ByteArrayOutputStream so that we can later get the byte array out of
         * it and put it in the memory as an instance of our class.
         */
        protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();

        /**
         * Registers the compiled class object under URI containing the class
         * full name
         * 
         * @param name
         *            Full name of the compiled class
         * @param kind
         *            Kind of the data. It will be CLASS in our case
         */
        public JavaClassObject(String name, Kind kind) {
            super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
        }

        /**
         * Will be used by our file manager to get the byte code that can be put
         * into memory to instantiate our class
         * 
         * @return compiled byte code
         */
        public byte[] getBytes() {
            return bos.toByteArray();
        }

        /**
         * Will provide the compiler with an output stream that leads to our
         * byte array. This way the compiler will write everything into the byte
         * array that we will instantiate later
         */
        @Override
        public OutputStream openOutputStream() throws IOException {
            return bos;
        }
    }

    public static class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
        /**
         * Instance of JavaClassObject that will store the compiled bytecode of
         * our class
         */
        private JavaClassObject jclassObject;

        /**
         * Will initialize the manager with the specified standard java file
         * manager
         * 
         * @param standardManager
         */
        public ClassFileManager(StandardJavaFileManager standardManager) {
            super(standardManager);
        }

        /**
         * Will be used by us to get the class loader for our compiled class. It
         * creates an anonymous class extending the SecureClassLoader which uses
         * the byte code created by the compiler and stored in the
         * JavaClassObject, and returns the Class for it
         */
        @Override
        public ClassLoader getClassLoader(Location location) {
            return new SecureClassLoader() {
                @Override
                protected Class<?> findClass(String name) throws ClassNotFoundException {
                    byte[] b = jclassObject.getBytes();
                    return super.defineClass(name, jclassObject.getBytes(), 0, b.length);
                }
            };
        }

        /**
         * Gives the compiler an instance of the JavaClassObject so that the
         * compiler can write the byte code into it.
         */
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind,
                FileObject sibling) throws IOException {
            jclassObject = new JavaClassObject(className, kind);
            return jclassObject;
        }
    }

}