All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.logstash.logback.pattern.AbstractJsonPatternParser Maven / Gradle / Ivy

/**
 * 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 net.logstash.logback.pattern;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.juli.logging.ch.qos.logback.core.pattern.PatternLayoutBase;
import org.apache.juli.logging.ch.qos.logback.core.spi.ContextAware;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;

/**
 * Parser that takes a JSON pattern, resolves all the conversion specifiers and returns an instance
 * of NodeWriter that, when its write() method is invoked, produces JSON defined by the parsed pattern.
 *
 * @param  - type of the event (ILoggingEvent, IAccessEvent)
 *
 * @author Dmitry Andrianov
 */
public abstract class AbstractJsonPatternParser {

    public static final Pattern OPERATION_PATTERN = Pattern.compile("\\# (\\w+) (?: \\{ (.*) \\} )?", Pattern.COMMENTS);
    
    private final ContextAware contextAware;
    private final JsonFactory jsonFactory;
    
    private final Map operations = new HashMap();

    public AbstractJsonPatternParser(final ContextAware contextAware, final JsonFactory jsonFactory) {
        this.contextAware = contextAware;
        this.jsonFactory = jsonFactory;
        addOperation(new AsLongOperation());
        addOperation(new AsDoubleOperation());
        addOperation(new AsJsonOperation());
    }
    
    protected void addOperation(Operation operation) {
        this.operations.put(operation.getName(), operation);
    }

    protected abstract class Operation {
        private final String name;
        private final boolean requiresData;

        public Operation(String name, boolean requiresData) {
            this.name = name;
            this.requiresData = requiresData;
        }
        
        public String getName() {
            return name;
        }
        
        public boolean requiresData() {
            return requiresData;
        }
        
        public abstract ValueGetter createValueGetter(String data);
        
    }
    
    protected class AsLongOperation extends Operation {

        public AsLongOperation() {
            super("asLong", true);
        }
        
        @Override
        public ValueGetter createValueGetter(String data) {
            return new AsLongValueTransformer(makeLayoutValueGetter(data));
        }
    }

    protected class AsDoubleOperation extends Operation {

        public AsDoubleOperation() {
            super("asDouble", true);
        }
        
        @Override
        public ValueGetter createValueGetter(String data) {
            return new AsDoubleValueTransformer(makeLayoutValueGetter(data));
        }
    }

    protected class AsJsonOperation extends Operation {

        public AsJsonOperation() {
            super("asJson", true);
        }

        @Override
        public ValueGetter createValueGetter(String data) {
            return new AsJsonValueTransformer(makeLayoutValueGetter(data));
        }
    }

    protected static class LayoutValueGetter implements ValueGetter {

        private final PatternLayoutBase layout;

        LayoutValueGetter(final PatternLayoutBase layout) {
            this.layout = layout;
        }

        @Override
        public String getValue(final Event event) {
            return layout.doLayout(event);
        }
    }

    protected static abstract class AbstractAsObjectTransformer implements ValueGetter {

        private final ValueGetter generator;

        AbstractAsObjectTransformer(final ValueGetter generator) {
            this.generator = generator;
        }

        @Override
        public T getValue(final Event event) {
            final String value = generator.getValue(event);
            if (value == null || value.isEmpty()) {
                return null;
            }
            try {
                return transform(value);
            } catch (Exception e) {
                return null;
            }
        }

        abstract protected T transform(final String value) throws NumberFormatException, IOException;
    }

    protected static abstract class AbstractAsNumberTransformer implements ValueGetter {

        private final ValueGetter generator;

        AbstractAsNumberTransformer(final ValueGetter generator) {
            this.generator = generator;
        }

        @Override
        public T getValue(final Event event) {
            final String value = generator.getValue(event);
            if (value == null || value.isEmpty()) {
                return null;
            }
            try {
                return transform(value);
            } catch (NumberFormatException e) {
                return null;
            }
        }

        abstract protected T transform(final String value) throws NumberFormatException;
    }

    protected static class AsLongValueTransformer extends AbstractAsNumberTransformer {
        public AsLongValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected Long transform(final String value) throws NumberFormatException {
            return Long.parseLong(value);
        }
    }

    protected static class AsDoubleValueTransformer extends AbstractAsNumberTransformer {
        public AsDoubleValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected Double transform(final String value)  throws NumberFormatException {
            return Double.parseDouble(value);
        }
    }

    protected class AsJsonValueTransformer extends AbstractAsObjectTransformer {

        public AsJsonValueTransformer(final ValueGetter generator) {
            super(generator);
        }

        protected JsonNode transform(final String value) throws IOException {
            return jsonFactory.getCodec().readTree(jsonFactory.createParser(value));
        }
    }

    protected static interface FieldWriter extends NodeWriter {
    }

    protected static class ConstantValueWriter implements NodeWriter {
        private final Object value;

        public ConstantValueWriter(final Object value) {
            this.value = value;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            generator.writeObject(value);
        }
    }

    protected static class ListWriter implements NodeWriter {
        private final List> items;

        public ListWriter(final List> items) {
            this.items = items;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            generator.writeStartArray();
            for (NodeWriter item : items) {
                item.write(generator, event);
            }
            generator.writeEndArray();
        }
    }

    protected static class ComputableValueWriter implements NodeWriter {

        private final ValueGetter getter;

        public ComputableValueWriter(final ValueGetter getter) {
            this.getter = getter;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            Object value = getter.getValue(event);
            generator.writeObject(value);
        }
    }

    protected static class DelegatingObjectFieldWriter implements FieldWriter {

        private final String name;
        private final NodeWriter delegate;

        public DelegatingObjectFieldWriter(final String name, final NodeWriter delegate) {
            this.name = name;
            this.delegate = delegate;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            generator.writeFieldName(name);
            delegate.write(generator, event);
        }
    }

    protected static class ComputableObjectFieldWriter implements FieldWriter {

        private final String name;
        private final ValueGetter getter;

        public ComputableObjectFieldWriter(final String name, final ValueGetter getter) {
            this.name = name;
            this.getter = getter;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            Object value = getter.getValue(event);
            generator.writeFieldName(name);
            generator.writeObject(value);
        }
    }

    protected static class ObjectWriter implements NodeWriter {

        private final ChildrenWriter childrenWriter;
        
        public ObjectWriter(ChildrenWriter childrenWriter) {
            this.childrenWriter = childrenWriter;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            generator.writeStartObject();
            this.childrenWriter.write(generator, event);
            generator.writeEndObject();
        }
    }

    protected static class ChildrenWriter implements NodeWriter {

        private final List> items;

        public ChildrenWriter(final List> items) {
            this.items = items;
        }

        public void write(JsonGenerator generator, Event event) throws IOException {
            for (FieldWriter item : items) {
                item.write(generator, event);
            }
        }
    }

    protected PatternLayoutBase buildLayout(String format) {
        PatternLayoutBase layout = createLayout();
        layout.setContext(contextAware.getContext());
        layout.setPattern(format);
        layout.setPostCompileProcessor(null); // Remove EnsureLineSeparation which is there by default
        layout.start();

        return layout;
    }

    protected abstract PatternLayoutBase createLayout();

    private ValueGetter makeComputableValueGetter(String pattern) {

        Matcher matcher = OPERATION_PATTERN.matcher(pattern);

        if (matcher.matches()) {
            String operationName = matcher.group(1);
            String operationData = matcher.groupCount() > 1
                    ? matcher.group(2)
                    : null;
                    
            Operation operation = this.operations.get(operationName);
            if (operation != null) {
                if (operation.requiresData() && operationData == null) {
                    contextAware.addError("No parameter provided to operation: " + operation.getName());
                } else {
                    return operation.createValueGetter(operationData);
                }
            }
        } 
        return makeLayoutValueGetter(pattern);
    }

    protected LayoutValueGetter makeLayoutValueGetter(final String data) {
        return new LayoutValueGetter(buildLayout(data));
    }

    private NodeWriter parseValue(JsonNode node) {
        if (node.isTextual()) {
            ValueGetter getter = makeComputableValueGetter(node.asText());
            return new ComputableValueWriter(getter);
        } else if (node.isArray()) {
            return parseArray(node);
        } else if (node.isObject()) {
            return parseObject(node);
        } else {
            // Anything else, we will be just writing as is (nulls, numbers, booleans and whatnot)
            return new ConstantValueWriter(node);
        }
    }

    private ListWriter parseArray(JsonNode node) {

        List> children = new ArrayList>();
        for (JsonNode item : node) {
            children.add(parseValue(item));
        }

        return new ListWriter(children);
    }

    private ObjectWriter parseObject(JsonNode node) {

        return new ObjectWriter(parseChildren(node));
    }

    private ChildrenWriter parseChildren(JsonNode node) {
        List> children = new ArrayList>();
        for (Iterator> nodeFields = node.fields(); nodeFields.hasNext(); ) {
            Map.Entry field = nodeFields.next();

            String key = field.getKey();
            JsonNode value = field.getValue();

            if (value.isTextual()) {
                ValueGetter getter = makeComputableValueGetter(value.asText());
                children.add(new ComputableObjectFieldWriter(key, getter));
            } else {
                children.add(new DelegatingObjectFieldWriter(key, parseValue(value)));
            }
        }
        return new ChildrenWriter(children);
    }

    public NodeWriter parse(String pattern) {

        if (pattern == null) {
            contextAware.addError("No pattern specified");
            return null;
        }

        JsonNode node;
        try {
            node = jsonFactory.createParser(pattern).readValueAsTree();
        } catch (IOException e) {
            contextAware.addError("Failed to parse pattern [" + pattern + "]", e);
            return null;
        }

        if (node == null) {
            contextAware.addError("Empty JSON pattern");
            return null;
        }

        if (!node.isObject()) {
            contextAware.addError("Invalid pattern JSON - must be an object");
            return null;
        }

        return parseChildren(node);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy