Java tutorial
/** * 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 ch.qos.logback.core.pattern.PatternLayoutBase; import ch.qos.logback.core.spi.ContextAware; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; 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; /** * 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 <Event> - type of the event (ILoggingEvent, IAccessEvent) * * @author <a href="mailto:dimas@dataart.com">Dmitry Andrianov</a> */ public abstract class AbstractJsonPatternParser<Event> { public static final Pattern OPERATION_PATTERN = Pattern.compile("\\# (\\w+) (?: \\{ (.*) \\} )?", Pattern.COMMENTS); private final ContextAware contextAware; private final JsonFactory jsonFactory; private final Map<String, Operation> operations = new HashMap<String, Operation>(); public AbstractJsonPatternParser(final ContextAware contextAware, final JsonFactory jsonFactory) { this.contextAware = contextAware; this.jsonFactory = jsonFactory; addOperation(new AsLongOperation()); addOperation(new AsDoubleOperation()); } 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<?, Event> createValueGetter(String data); } protected class AsLongOperation extends Operation { public AsLongOperation() { super("asLong", true); } @Override public ValueGetter<?, Event> createValueGetter(String data) { return new AsLongValueTransformer<Event>(makeLayoutValueGetter(data)); } } protected class AsDoubleOperation extends Operation { public AsDoubleOperation() { super("asDouble", true); } @Override public ValueGetter<?, Event> createValueGetter(String data) { return new AsDoubleValueTransformer<Event>(makeLayoutValueGetter(data)); } } protected static class LayoutValueGetter<Event> implements ValueGetter<String, Event> { private final PatternLayoutBase<Event> layout; LayoutValueGetter(final PatternLayoutBase<Event> layout) { this.layout = layout; } @Override public String getValue(final Event event) { return layout.doLayout(event); } } protected static abstract class AbstractAsNumberTransformer<T extends Number, Event> implements ValueGetter<T, Event> { private final ValueGetter<String, Event> generator; AbstractAsNumberTransformer(final ValueGetter<String, Event> 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<Event> extends AbstractAsNumberTransformer<Long, Event> { public AsLongValueTransformer(final ValueGetter<String, Event> generator) { super(generator); } protected Long transform(final String value) throws NumberFormatException { return Long.parseLong(value); } } protected static class AsDoubleValueTransformer<Event> extends AbstractAsNumberTransformer<Double, Event> { public AsDoubleValueTransformer(final ValueGetter<String, Event> generator) { super(generator); } protected Double transform(final String value) throws NumberFormatException { return Double.parseDouble(value); } } protected static interface FieldWriter<Event> extends NodeWriter<Event> { } protected static class ConstantValueWriter<Event> implements NodeWriter<Event> { 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<Event> implements NodeWriter<Event> { private final List<NodeWriter<Event>> items; public ListWriter(final List<NodeWriter<Event>> items) { this.items = items; } public void write(JsonGenerator generator, Event event) throws IOException { generator.writeStartArray(); for (NodeWriter<Event> item : items) { item.write(generator, event); } generator.writeEndArray(); } } protected static class ComputableValueWriter<Event> implements NodeWriter<Event> { private final ValueGetter<?, Event> getter; public ComputableValueWriter(final ValueGetter<?, Event> 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<Event> implements FieldWriter<Event> { private final String name; private final NodeWriter<Event> delegate; public DelegatingObjectFieldWriter(final String name, final NodeWriter<Event> 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<Event> implements FieldWriter<Event> { private final String name; private final ValueGetter<?, Event> getter; public ComputableObjectFieldWriter(final String name, final ValueGetter<?, Event> 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<Event> implements NodeWriter<Event> { private final ChildrenWriter<Event> childrenWriter; public ObjectWriter(ChildrenWriter<Event> 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<Event> implements NodeWriter<Event> { private final List<FieldWriter<Event>> items; public ChildrenWriter(final List<FieldWriter<Event>> items) { this.items = items; } public void write(JsonGenerator generator, Event event) throws IOException { for (FieldWriter<Event> item : items) { item.write(generator, event); } } } protected PatternLayoutBase<Event> buildLayout(String format) { PatternLayoutBase<Event> 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<Event> createLayout(); private ValueGetter<?, Event> 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<Event> makeLayoutValueGetter(final String data) { return new LayoutValueGetter<Event>(buildLayout(data)); } private NodeWriter<Event> parseValue(JsonNode node) { if (node.isTextual()) { ValueGetter<?, Event> getter = makeComputableValueGetter(node.asText()); return new ComputableValueWriter<Event>(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<Event>(node); } } private ListWriter<Event> parseArray(JsonNode node) { List<NodeWriter<Event>> children = new ArrayList<NodeWriter<Event>>(); for (JsonNode item : node) { children.add(parseValue(item)); } return new ListWriter<Event>(children); } private ObjectWriter<Event> parseObject(JsonNode node) { return new ObjectWriter<Event>(parseChildren(node)); } private ChildrenWriter<Event> parseChildren(JsonNode node) { List<FieldWriter<Event>> children = new ArrayList<FieldWriter<Event>>(); for (Iterator<Map.Entry<String, JsonNode>> nodeFields = node.fields(); nodeFields.hasNext();) { Map.Entry<String, JsonNode> field = nodeFields.next(); String key = field.getKey(); JsonNode value = field.getValue(); if (value.isTextual()) { ValueGetter<?, Event> getter = makeComputableValueGetter(value.asText()); children.add(new ComputableObjectFieldWriter<Event>(key, getter)); } else { children.add(new DelegatingObjectFieldWriter<Event>(key, parseValue(value))); } } return new ChildrenWriter<Event>(children); } public NodeWriter<Event> 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); } }