1 /**
2  * Licensed under the Apache License, Version 2.0 (the "License");
3  * you may not use this file except in compliance with the License.
4  * You may obtain a copy of the License at
5  *
6  *      http://www.apache.org/licenses/LICENSE-2.0
7  *
8  * Unless required by applicable law or agreed to in writing, software
9  * distributed under the License is distributed on an "AS IS" BASIS,
10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  * See the License for the specific language governing permissions and
12  * limitations under the License.
13  */

14 package net.logstash.logback.pattern;
15
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import ch.qos.logback.core.pattern.PatternLayoutBase;
27 import ch.qos.logback.core.spi.ContextAware;
28
29 import com.fasterxml.jackson.core.JsonFactory;
30 import com.fasterxml.jackson.core.JsonGenerator;
31 import com.fasterxml.jackson.core.JsonParseException;
32 import com.fasterxml.jackson.core.JsonParser;
33 import com.fasterxml.jackson.core.TreeNode;
34 import com.fasterxml.jackson.databind.JsonNode;
35 import com.fasterxml.jackson.databind.node.JsonNodeType;
36
37 /**
38  * Parser that takes a JSON pattern, resolves all the conversion specifiers and returns an instance
39  * of NodeWriter that, when its write() method is invoked, produces JSON defined by the parsed pattern.
40  *
41  * @param <Event> - type of the event (ILoggingEvent, IAccessEvent)
42  *
43  * @author <a href="mailto:dimas@dataart.com">Dmitry Andrianov</a>
44  */

45 public abstract class AbstractJsonPatternParser<Event> {
46
47     public static final Pattern OPERATION_PATTERN = Pattern.compile("\\# (\\w+) (?: \\{ (.*) \\} )?", Pattern.COMMENTS);
48     
49     private final ContextAware contextAware;
50     private final JsonFactory jsonFactory;
51     
52     private final Map<String, Operation> operations = new HashMap<>();
53
54     /**
55      * When true, fields whose values are considered empty ({@link #isEmptyValue(Object)}})
56      * will be omitted from json output.
57      */

58     private boolean omitEmptyFields;
59
60     public AbstractJsonPatternParser(final ContextAware contextAware, final JsonFactory jsonFactory) {
61         this.contextAware = contextAware;
62         this.jsonFactory = jsonFactory;
63         addOperation(new AsLongOperation());
64         addOperation(new AsDoubleOperation());
65         addOperation(new AsJsonOperation());
66         addOperation(new TryJsonOperation());
67     }
68     
69     protected void addOperation(Operation operation) {
70         this.operations.put(operation.getName(), operation);
71     }
72
73     protected abstract class Operation {
74         private final String name;
75         private final boolean requiresData;
76
77         public Operation(String name, boolean requiresData) {
78             this.name = name;
79             this.requiresData = requiresData;
80         }
81         
82         public String getName() {
83             return name;
84         }
85         
86         public boolean requiresData() {
87             return requiresData;
88         }
89         
90         public abstract ValueGetter<?, Event> createValueGetter(String data);
91         
92     }
93     
94     protected class AsLongOperation extends Operation {
95
96         public AsLongOperation() {
97             super("asLong"true);
98         }
99         
100         @Override
101         public ValueGetter<?, Event> createValueGetter(String data) {
102             return new AsLongValueTransformer<>(makeLayoutValueGetter(data));
103         }
104     }
105
106     protected class AsDoubleOperation extends Operation {
107
108         public AsDoubleOperation() {
109             super("asDouble"true);
110         }
111         
112         @Override
113         public ValueGetter<?, Event> createValueGetter(String data) {
114             return new AsDoubleValueTransformer<>(makeLayoutValueGetter(data));
115         }
116     }
117
118     protected class AsJsonOperation extends Operation {
119
120         public AsJsonOperation() {
121             super("asJson"true);
122         }
123
124         @Override
125         public ValueGetter<?, Event> createValueGetter(String data) {
126             return new AsJsonValueTransformer(makeLayoutValueGetter(data));
127         }
128     }
129     
130     protected class TryJsonOperation extends Operation {
131
132         public TryJsonOperation() {
133             super("tryJson"true);
134         }
135
136         @Override
137         public ValueGetter<?, Event> createValueGetter(String data) {
138             return new TryJsonValueTransformer(makeLayoutValueGetter(data));
139         }
140     }
141
142     protected static class LayoutValueGetter<Event> implements ValueGetter<String, Event> {
143
144         private final PatternLayoutBase<Event> layout;
145
146         LayoutValueGetter(final PatternLayoutBase<Event> layout) {
147             this.layout = layout;
148         }
149
150         @Override
151         public String getValue(final Event event) {
152             return layout.doLayout(event);
153         }
154     }
155
156     protected static abstract class AbstractAsObjectTransformer<T, Event> implements ValueGetter<T, Event> {
157
158         private final ValueGetter<String, Event> generator;
159
160         AbstractAsObjectTransformer(final ValueGetter<String, Event> generator) {
161             this.generator = generator;
162         }
163
164         @Override
165         public T getValue(final Event event) {
166             final String value = generator.getValue(event);
167             if (value == null || value.isEmpty()) {
168                 return null;
169             }
170             try {
171                 return transform(value);
172             } catch (Exception e) {
173                 return null;
174             }
175         }
176
177         abstract protected T transform(final String value) throws NumberFormatException, IOException;
178     }
179
180     protected static abstract class AbstractAsNumberTransformer<T extends Number, Event> implements ValueGetter<T, Event> {
181
182         private final ValueGetter<String, Event> generator;
183
184         AbstractAsNumberTransformer(final ValueGetter<String, Event> generator) {
185             this.generator = generator;
186         }
187
188         @Override
189         public T getValue(final Event event) {
190             final String value = generator.getValue(event);
191             if (value == null || value.isEmpty()) {
192                 return null;
193             }
194             try {
195                 return transform(value);
196             } catch (NumberFormatException e) {
197                 return null;
198             }
199         }
200
201         abstract protected T transform(final String value) throws NumberFormatException;
202     }
203
204     protected static class AsLongValueTransformer<Event> extends AbstractAsNumberTransformer<Long, Event> {
205         public AsLongValueTransformer(final ValueGetter<String, Event> generator) {
206             super(generator);
207         }
208
209         protected Long transform(final String value) throws NumberFormatException {
210             return Long.parseLong(value);
211         }
212     }
213
214     protected static class AsDoubleValueTransformer<Event> extends AbstractAsNumberTransformer<Double, Event> {
215         public AsDoubleValueTransformer(final ValueGetter<String, Event> generator) {
216             super(generator);
217         }
218
219         protected Double transform(final String value)  throws NumberFormatException {
220             return Double.parseDouble(value);
221         }
222     }
223
224     protected class AsJsonValueTransformer extends AbstractAsObjectTransformer<JsonNode, Event> {
225
226         public AsJsonValueTransformer(final ValueGetter<String, Event> generator) {
227             super(generator);
228         }
229
230         protected JsonNode transform(final String value) throws IOException {
231             return jsonFactory.getCodec().readTree(jsonFactory.createParser(value));
232         }
233     }
234     
235     protected class TryJsonValueTransformer extends AbstractAsObjectTransformer<Object, Event> {
236
237         public TryJsonValueTransformer(final ValueGetter<String, Event> generator) {
238             super(generator);
239         }
240
241         protected Object transform(final String value) throws IOException {
242             try {
243                 final String trimmedValue = value.trim();
244                 final JsonParser parser = jsonFactory.createParser(trimmedValue);
245                 final TreeNode tree = jsonFactory.getCodec().readTree(parser);
246                 if (parser.getCurrentLocation().getCharOffset() < trimmedValue.length()) {
247                     /*
248                      * If the full trimmed string was not read, then the full trimmed string contains a json value plus other text.
249                      * For example, trimmedValue = '10 foobar', or 'true foobar', or '{"foo","bar"} baz'.
250                      * In these cases readTree will only read the first part, and will not read the remaining text.
251                      */

252                     return value;
253                 }
254                 return tree;
255             } catch (JsonParseException e) {
256                 return value;
257             }
258         }
259     }
260
261     protected interface FieldWriter<Event> extends NodeWriter<Event> {
262     }
263
264     protected class ConstantValueWriter implements NodeWriter<Event> {
265         private final Object value;
266
267         public ConstantValueWriter(final Object value) {
268             this.value = value;
269         }
270
271         public void write(JsonGenerator generator, Event event) throws IOException {
272             generator.writeObject(value);
273         }
274
275         @Override
276         public boolean shouldWrite(JsonGenerator generator, Event event) {
277             return !omitEmptyFields
278                     || (value != null
279                             && !(value instanceof JsonNode && ((JsonNode) value).getNodeType() == JsonNodeType.NULL));
280         }
281     }
282
283     protected class ListWriter<Event> implements NodeWriter<Event> {
284         private final List<NodeWriter<Event>> items;
285
286         public ListWriter(final List<NodeWriter<Event>> items) {
287             this.items = items;
288         }
289
290         public void write(JsonGenerator generator, Event event) throws IOException {
291             generator.writeStartArray();
292             for (NodeWriter<Event> item : items) {
293                 if (item.shouldWrite(generator, event)) {
294                     item.write(generator, event);
295                 }
296             }
297             generator.writeEndArray();
298         }
299
300         @Override
301         public boolean shouldWrite(JsonGenerator generator, Event event) {
302             if (!omitEmptyFields) {
303                 return true;
304             }
305
306             for (NodeWriter<Event> item : items) {
307                 if (item.shouldWrite(generator, event)) {
308                     return true;
309                 }
310             }
311
312             return false;
313         }
314     }
315
316     protected class ComputableValueWriter<Event> implements NodeWriter<Event> {
317
318         private final ValueGetter<?, Event> getter;
319
320         public ComputableValueWriter(final ValueGetter<?, Event> getter) {
321             this.getter = getter;
322         }
323
324         public void write(JsonGenerator generator, Event event) throws IOException {
325             Object value = getter.getValue(event);
326             generator.writeObject(value);
327         }
328
329         @Override
330         public boolean shouldWrite(JsonGenerator generator, Event event) {
331             return !omitEmptyFields || getter.getValue(event) != null;
332         }
333     }
334
335     protected class DelegatingObjectFieldWriter<Event> implements FieldWriter<Event> {
336
337         private final String name;
338         private final NodeWriter<Event> delegate;
339
340         public DelegatingObjectFieldWriter(final String name, final NodeWriter<Event> delegate) {
341             this.name = name;
342             this.delegate = delegate;
343         }
344
345         public void write(JsonGenerator generator, Event event) throws IOException {
346             if (delegate.shouldWrite(generator, event)) {
347                 generator.writeFieldName(name);
348                 delegate.write(generator, event);
349             }
350         }
351
352         @Override
353         public boolean shouldWrite(JsonGenerator generator, Event event) {
354             return delegate.shouldWrite(generator, event);
355         }
356     }
357
358     protected class ComputableObjectFieldWriter<Event> implements FieldWriter<Event> {
359
360         private final String name;
361         private final ValueGetter<?, Event> getter;
362
363         public ComputableObjectFieldWriter(final String name, final ValueGetter<?, Event> getter) {
364             this.name = name;
365             this.getter = getter;
366         }
367
368         public void write(JsonGenerator generator, Event event) throws IOException {
369             Object value = getter.getValue(event);
370             if (!omitEmptyFields || value != null) {
371                 generator.writeFieldName(name);
372                 generator.writeObject(value);
373             }
374         }
375
376         @Override
377         public boolean shouldWrite(JsonGenerator generator, Event event) {
378             return !omitEmptyFields || !isEmptyValue(getter.getValue(event));
379         }
380     }
381
382     protected class ObjectWriter<Event> implements NodeWriter<Event> {
383
384         private final ChildrenWriter<Event> childrenWriter;
385         
386         public ObjectWriter(ChildrenWriter<Event> childrenWriter) {
387             this.childrenWriter = childrenWriter;
388         }
389
390         public void write(JsonGenerator generator, Event event) throws IOException {
391             if (childrenWriter.shouldWrite(generator, event)) {
392                 generator.writeStartObject();
393                 this.childrenWriter.write(generator, event);
394                 generator.writeEndObject();
395             }
396         }
397
398         @Override
399         public boolean shouldWrite(JsonGenerator generator, Event event) {
400             return childrenWriter.shouldWrite(generator, event);
401         }
402     }
403
404     protected class ChildrenWriter<Event> implements NodeWriter<Event> {
405
406         private final List<FieldWriter<Event>> items;
407
408         public ChildrenWriter(final List<FieldWriter<Event>> items) {
409             this.items = items;
410         }
411
412         public void write(JsonGenerator generator, Event event) throws IOException {
413             for (FieldWriter<Event> item : items) {
414                 if (item.shouldWrite(generator, event)) {
415                     item.write(generator, event);
416                 }
417             }
418         }
419
420         @Override
421         public boolean shouldWrite(JsonGenerator generator, Event event) {
422             if (!omitEmptyFields) {
423                 return true;
424             }
425
426             for (FieldWriter<Event> item : items) {
427                 if (item.shouldWrite(generator, event)) {
428                     return true;
429                 }
430             }
431             return false;
432         }
433     }
434
435     protected PatternLayoutBase<Event> buildLayout(String format) {
436         PatternLayoutBase<Event> layout = createLayout();
437         layout.setContext(contextAware.getContext());
438         layout.setPattern(format);
439         layout.setPostCompileProcessor(null); // Remove EnsureLineSeparation which is there by default
440         layout.start();
441
442         return layout;
443     }
444
445     protected abstract PatternLayoutBase<Event> createLayout();
446
447     private ValueGetter<?, Event> makeComputableValueGetter(String pattern) {
448
449         Matcher matcher = OPERATION_PATTERN.matcher(pattern);
450
451         if (matcher.matches()) {
452             String operationName = matcher.group(1);
453             String operationData = matcher.groupCount() > 1
454                     ? matcher.group(2)
455                     : null;
456                     
457             Operation operation = this.operations.get(operationName);
458             if (operation != null) {
459                 if (operation.requiresData() && operationData == null) {
460                     contextAware.addError("No parameter provided to operation: " + operation.getName());
461                 } else {
462                     return operation.createValueGetter(operationData);
463                 }
464             }
465         } 
466         return makeLayoutValueGetter(pattern);
467     }
468
469     protected LayoutValueGetter<Event> makeLayoutValueGetter(final String data) {
470         return new LayoutValueGetter<Event>(buildLayout(data));
471     }
472
473     private NodeWriter<Event> parseValue(JsonNode node) {
474         if (node.isTextual()) {
475             ValueGetter<?, Event> getter = makeComputableValueGetter(node.asText());
476             return new ComputableValueWriter<Event>(getter);
477         } else if (node.isArray()) {
478             return parseArray(node);
479         } else if (node.isObject()) {
480             return parseObject(node);
481         } else {
482             // Anything else, we will be just writing as is (nulls, numbers, booleans and whatnot)
483             return new ConstantValueWriter(node);
484         }
485     }
486
487     private ListWriter<Event> parseArray(JsonNode node) {
488
489         List<NodeWriter<Event>> children = new ArrayList<>();
490         for (JsonNode item : node) {
491             children.add(parseValue(item));
492         }
493
494         return new ListWriter<>(children);
495     }
496
497     private ObjectWriter<Event> parseObject(JsonNode node) {
498
499         return new ObjectWriter<>(parseChildren(node));
500     }
501
502     private ChildrenWriter<Event> parseChildren(JsonNode node) {
503         List<FieldWriter<Event>> children = new ArrayList<>();
504         for (Iterator<Map.Entry<String, JsonNode>> nodeFields = node.fields(); nodeFields.hasNext(); ) {
505             Map.Entry<String, JsonNode> field = nodeFields.next();
506
507             String key = field.getKey();
508             JsonNode value = field.getValue();
509
510             if (value.isTextual()) {
511                 ValueGetter<?, Event> getter = makeComputableValueGetter(value.asText());
512                 children.add(new ComputableObjectFieldWriter<>(key, getter));
513             } else {
514                 children.add(new DelegatingObjectFieldWriter<>(key, parseValue(value)));
515             }
516         }
517         return new ChildrenWriter<>(children);
518     }
519
520     public NodeWriter<Event> parse(String pattern) {
521
522         if (pattern == null) {
523             contextAware.addError("No pattern specified");
524             return null;
525         }
526
527         JsonNode node;
528         try {
529             node = jsonFactory.createParser(pattern).readValueAsTree();
530         } catch (IOException e) {
531             contextAware.addError("Failed to parse pattern [" + pattern + "]", e);
532             return null;
533         }
534
535         if (node == null) {
536             contextAware.addError("Empty JSON pattern");
537             return null;
538         }
539
540         if (!node.isObject()) {
541             contextAware.addError("Invalid pattern JSON - must be an object");
542             return null;
543         }
544
545         return parseChildren(node);
546     }
547
548     /**
549      * Return true if the given value is considered to be "empty".
550      * @param value value to inspect
551      * @return true if the given value is considered to be "empty".
552      */

553     private boolean isEmptyValue(Object value) {
554         if (value == null) {
555             return true;
556         }
557         if (value instanceof String && ((String) value).isEmpty()) {
558             return true;
559         }
560         if (value instanceof Collection && ((Collection) value).isEmpty()) {
561             return true;
562         }
563         if (value instanceof Map && ((Map) value).isEmpty()) {
564             return true;
565         }
566
567         if (value instanceof JsonNode) {
568             JsonNode node = (JsonNode) value;
569             if (node.getNodeType() == JsonNodeType.NULL) {
570                 return true;
571             }
572             if (node.isTextual() && node.textValue().isEmpty()) {
573                 return true;
574             }
575             if (node.isContainerNode() && node.size() == 0) {
576                 return true;
577             }
578         }
579         return false;
580
581     }
582
583     /**
584      * When true, fields whose values are considered empty ({@link #isEmptyValue(Object)}})
585      * will be omitted from json output.
586      */

587     public boolean isOmitEmptyFields() {
588         return omitEmptyFields;
589     }
590
591     /**
592      * When true, fields whose values are considered empty ({@link #isEmptyValue(Object)}})
593      * will be omitted from json output.
594      */

595     public void setOmitEmptyFields(boolean omitEmptyFields) {
596         this.omitEmptyFields = omitEmptyFields;
597     }
598 }
599