1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.logging.log4j.message;
18
19 import java.util.Collections;
20 import java.util.Map;
21 import java.util.TreeMap;
22
23 import org.apache.logging.log4j.util.BiConsumer;
24 import org.apache.logging.log4j.util.Chars;
25 import org.apache.logging.log4j.util.EnglishEnums;
26 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
27 import org.apache.logging.log4j.util.IndexedStringMap;
28 import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable;
29 import org.apache.logging.log4j.util.PerformanceSensitive;
30 import org.apache.logging.log4j.util.ReadOnlyStringMap;
31 import org.apache.logging.log4j.util.SortedArrayStringMap;
32 import org.apache.logging.log4j.util.StringBuilders;
33 import org.apache.logging.log4j.util.Strings;
34 import org.apache.logging.log4j.util.TriConsumer;
35
36 /**
37 * Represents a Message that consists of a Map.
38 * <p>
39 * Thread-safety note: the contents of this message can be modified after construction.
40 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
41 * logged, because it is undefined whether the logged message string will contain the old values or the modified
42 * values.
43 * </p>
44 * <p>
45 * This class was pulled up from {@link StringMapMessage} to allow for Objects as values.
46 * </p>
47 * @param <M> Allow subclasses to use fluent APIs and override methods that return instances of subclasses.
48 * @param <V> The value type
49 */
50 @PerformanceSensitive("allocation")
51 @AsynchronouslyFormattable
52 public class MapMessage<M extends MapMessage<M, V>, V> implements MultiFormatStringBuilderFormattable {
53
54 private static final long serialVersionUID = -5031471831131487120L;
55
56 /**
57 * When set as the format specifier causes the Map to be formatted as XML.
58 */
59 public enum MapFormat {
60
61 /** The map should be formatted as XML. */
62 XML,
63
64 /** The map should be formatted as JSON. */
65 JSON,
66
67 /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */
68 JAVA;
69
70 /**
71 * Maps a format name to an {@link MapFormat} while ignoring case.
72 *
73 * @param format a MapFormat name
74 * @return a MapFormat
75 */
76 public static MapFormat lookupIgnoreCase(final String format) {
77 return XML.name().equalsIgnoreCase(format) ? XML //
78 : JSON.name().equalsIgnoreCase(format) ? JSON //
79 : JAVA.name().equalsIgnoreCase(format) ? JAVA //
80 : null;
81 }
82
83 /**
84 * All {@code MapFormat} names.
85 *
86 * @return All {@code MapFormat} names.
87 */
88 public static String[] names() {
89 return new String[] {XML.name(), JSON.name(), JAVA.name()};
90 }
91 }
92
93 private final IndexedStringMap data;
94
95 /**
96 * Constructs a new instance.
97 */
98 public MapMessage() {
99 this.data = new SortedArrayStringMap();
100 }
101
102 /**
103 * Constructs a new instance.
104 *
105 * @param initialCapacity the initial capacity.
106 */
107 public MapMessage(final int initialCapacity) {
108 this.data = new SortedArrayStringMap(initialCapacity);
109 }
110
111 /**
112 * Constructs a new instance based on an existing {@link Map}.
113 * @param map The Map.
114 */
115 public MapMessage(final Map<String, V> map) {
116 this.data = new SortedArrayStringMap(map);
117 }
118
119 @Override
120 public String[] getFormats() {
121 return MapFormat.names();
122 }
123
124 /**
125 * Returns the data elements as if they were parameters on the logging event.
126 * @return the data elements.
127 */
128 @Override
129 public Object[] getParameters() {
130 final Object[] result = new Object[data.size()];
131 for (int i = 0; i < data.size(); i++) {
132 result[i] = data.getValueAt(i);
133 }
134 return result;
135 }
136
137 /**
138 * Returns the message.
139 * @return the message.
140 */
141 @Override
142 public String getFormat() {
143 return Strings.EMPTY;
144 }
145
146 /**
147 * Returns the message data as an unmodifiable Map.
148 * @return the message data as an unmodifiable map.
149 */
150 @SuppressWarnings("unchecked")
151 public Map<String, V> getData() {
152 final TreeMap<String, V> result = new TreeMap<>(); // returned map must be sorted
153 for (int i = 0; i < data.size(); i++) {
154 // The Eclipse compiler does not need the typecast to V, but the Oracle compiler sure does.
155 result.put(data.getKeyAt(i), (V) data.getValueAt(i));
156 }
157 return Collections.unmodifiableMap(result);
158 }
159
160 /**
161 * Returns a read-only view of the message data.
162 * @return the read-only message data.
163 */
164 public IndexedReadOnlyStringMap getIndexedReadOnlyStringMap() {
165 return data;
166 }
167
168 /**
169 * Clear the data.
170 */
171 public void clear() {
172 data.clear();
173 }
174
175 /**
176 * Returns {@code true} if this data structure contains the specified key, {@code false} otherwise.
177 *
178 * @param key the key whose presence to check. May be {@code null}.
179 * @return {@code true} if this data structure contains the specified key, {@code false} otherwise
180 * @since 2.9
181 */
182 public boolean containsKey(final String key) {
183 return data.containsKey(key);
184 }
185
186 /**
187 * Adds an item to the data Map.
188 * @param key The name of the data item.
189 * @param value The value of the data item.
190 */
191 public void put(final String key, final String value) {
192 if (value == null) {
193 throw new IllegalArgumentException("No value provided for key " + key);
194 }
195 validate(key, value);
196 data.putValue(key, value);
197 }
198
199 /**
200 * Adds all the elements from the specified Map.
201 * @param map The Map to add.
202 */
203 public void putAll(final Map<String, String> map) {
204 for (final Map.Entry<String, String> entry : map.entrySet()) {
205 data.putValue(entry.getKey(), entry.getValue());
206 }
207 }
208
209 /**
210 * Retrieves the value of the element with the specified key or null if the key is not present.
211 * @param key The name of the element.
212 * @return The value of the element or null if the key is not present.
213 */
214 public String get(final String key) {
215 Object result = data.getValue(key);
216 return ParameterFormatter.deepToString(result);
217 }
218
219 /**
220 * Removes the element with the specified name.
221 * @param key The name of the element.
222 * @return The previous value of the element.
223 */
224 public String remove(final String key) {
225 final String result = get(key);
226 data.remove(key);
227 return result;
228 }
229
230 /**
231 * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
232 *
233 * @return The formatted String.
234 */
235 public String asString() {
236 return format((MapFormat) null, new StringBuilder()).toString();
237 }
238
239 /**
240 * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
241 *
242 * @param format The format identifier.
243 * @return The formatted String.
244 */
245 public String asString(final String format) {
246 try {
247 return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()).toString();
248 } catch (final IllegalArgumentException ex) {
249 return asString();
250 }
251 }
252
253 /**
254 * Performs the given action for each key-value pair in this data structure
255 * until all entries have been processed or the action throws an exception.
256 * <p>
257 * Some implementations may not support structural modifications (adding new elements or removing elements) while
258 * iterating over the contents. In such implementations, attempts to add or remove elements from the
259 * {@code BiConsumer}'s {@link BiConsumer#accept(Object, Object)} accept} method may cause a
260 * {@code ConcurrentModificationException} to be thrown.
261 * </p>
262 *
263 * @param action The action to be performed for each key-value pair in this collection
264 * @param <CV> type of the consumer value
265 * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
266 * to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
267 * {@link #forEach(TriConsumer, Object)}.
268 * @see ReadOnlyStringMap#forEach(BiConsumer)
269 * @since 2.9
270 */
271 public <CV> void forEach(final BiConsumer<String, ? super CV> action) {
272 data.forEach(action);
273 }
274
275 /**
276 * Performs the given action for each key-value pair in this data structure
277 * until all entries have been processed or the action throws an exception.
278 * <p>
279 * The third parameter lets callers pass in a stateful object to be modified with the key-value pairs,
280 * so the TriConsumer implementation itself can be stateless and potentially reusable.
281 * </p>
282 * <p>
283 * Some implementations may not support structural modifications (adding new elements or removing elements) while
284 * iterating over the contents. In such implementations, attempts to add or remove elements from the
285 * {@code TriConsumer}'s {@link TriConsumer#accept(Object, Object, Object) accept} method may cause a
286 * {@code ConcurrentModificationException} to be thrown.
287 * </p>
288 *
289 * @param action The action to be performed for each key-value pair in this collection
290 * @param state the object to be passed as the third parameter to each invocation on the specified
291 * triconsumer
292 * @param <CV> type of the consumer value
293 * @param <S> type of the third parameter
294 * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
295 * to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
296 * {@link #forEach(TriConsumer, Object)}.
297 * @see ReadOnlyStringMap#forEach(TriConsumer, Object)
298 * @since 2.9
299 */
300 public <CV, S> void forEach(final TriConsumer<String, ? super CV, S> action, final S state) {
301 data.forEach(action, state);
302 }
303
304 /**
305 * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
306 *
307 * @param format The format identifier.
308 * @return The formatted String.
309 */
310 private StringBuilder format(final MapFormat format, final StringBuilder sb) {
311 if (format == null) {
312 appendMap(sb);
313 } else {
314 switch (format) {
315 case XML : {
316 asXml(sb);
317 break;
318 }
319 case JSON : {
320 asJson(sb);
321 break;
322 }
323 case JAVA : {
324 asJava(sb);
325 break;
326 }
327 default : {
328 appendMap(sb);
329 }
330 }
331 }
332 return sb;
333 }
334
335 /**
336 * Formats this message as an XML fragment String into the given builder.
337 *
338 * @param sb format into this builder.
339 */
340 public void asXml(final StringBuilder sb) {
341 sb.append("<Map>\n");
342 for (int i = 0; i < data.size(); i++) {
343 sb.append(" <Entry key=\"")
344 .append(data.getKeyAt(i))
345 .append("\">");
346 int size = sb.length();
347 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
348 StringBuilders.escapeXml(sb, size);
349 sb.append("</Entry>\n");
350 }
351 sb.append("</Map>");
352 }
353
354 /**
355 * Formats the message and return it.
356 * @return the formatted message.
357 */
358 @Override
359 public String getFormattedMessage() {
360 return asString();
361 }
362
363 /**
364 *
365 * @param formats
366 * An array of Strings that provide extra information about how to format the message. MapMessage uses
367 * the first format specifier it recognizes. The supported formats are XML, JSON, and JAVA. The default
368 * format is key1="value1" key2="value2" as required by <a href="https://tools.ietf.org/html/rfc5424">RFC
369 * 5424</a> messages.
370 *
371 * @return The formatted message.
372 */
373 @Override
374 public String getFormattedMessage(final String[] formats) {
375 return format(getFormat(formats), new StringBuilder()).toString();
376 }
377
378 private MapFormat getFormat(final String[] formats) {
379 if (formats == null || formats.length == 0) {
380 return null;
381 }
382 for (int i = 0; i < formats.length; i++) {
383 final MapFormat mapFormat = MapFormat.lookupIgnoreCase(formats[i]);
384 if (mapFormat != null) {
385 return mapFormat;
386 }
387 }
388 return null;
389 }
390
391 protected void appendMap(final StringBuilder sb) {
392 for (int i = 0; i < data.size(); i++) {
393 if (i > 0) {
394 sb.append(' ');
395 }
396 sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE);
397 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
398 sb.append(Chars.DQUOTE);
399 }
400 }
401
402 protected void asJson(final StringBuilder sb) {
403 sb.append('{');
404 for (int i = 0; i < data.size(); i++) {
405 if (i > 0) {
406 sb.append(", ");
407 }
408 sb.append(Chars.DQUOTE);
409 int start = sb.length();
410 sb.append(data.getKeyAt(i));
411 StringBuilders.escapeJson(sb, start);
412 sb.append(Chars.DQUOTE).append(':').append(Chars.DQUOTE);
413 start = sb.length();
414 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
415 StringBuilders.escapeJson(sb, start);
416 sb.append(Chars.DQUOTE);
417 }
418 sb.append('}');
419 }
420
421
422 protected void asJava(final StringBuilder sb) {
423 sb.append('{');
424 for (int i = 0; i < data.size(); i++) {
425 if (i > 0) {
426 sb.append(", ");
427 }
428 sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE);
429 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
430 sb.append(Chars.DQUOTE);
431 }
432 sb.append('}');
433 }
434
435 /**
436 * Constructs a new instance based on an existing Map.
437 * @param map The Map.
438 * @return A new MapMessage
439 */
440 @SuppressWarnings("unchecked")
441 public M newInstance(final Map<String, V> map) {
442 return (M) new MapMessage<>(map);
443 }
444
445 @Override
446 public String toString() {
447 return asString();
448 }
449
450 @Override
451 public void formatTo(final StringBuilder buffer) {
452 format((MapFormat) null, buffer);
453 }
454
455 @Override
456 public void formatTo(String[] formats, StringBuilder buffer) {
457 format(getFormat(formats), buffer);
458 }
459
460 @Override
461 public boolean equals(final Object o) {
462 if (this == o) {
463 return true;
464 }
465 if (o == null || this.getClass() != o.getClass()) {
466 return false;
467 }
468
469 final MapMessage<?, ?> that = (MapMessage<?, ?>) o;
470
471 return this.data.equals(that.data);
472 }
473
474 @Override
475 public int hashCode() {
476 return data.hashCode();
477 }
478
479 /**
480 * Always returns null.
481 *
482 * @return null
483 */
484 @Override
485 public Throwable getThrowable() {
486 return null;
487 }
488
489 /**
490 * Default implementation does nothing.
491 *
492 * @since 2.9
493 */
494 protected void validate(final String key, final boolean value) {
495 // do nothing
496 }
497
498 /**
499 * Default implementation does nothing.
500 *
501 * @since 2.9
502 */
503 protected void validate(final String key, final byte value) {
504 // do nothing
505 }
506
507 /**
508 * Default implementation does nothing.
509 *
510 * @since 2.9
511 */
512 protected void validate(final String key, final char value) {
513 // do nothing
514 }
515
516 /**
517 * Default implementation does nothing.
518 *
519 * @since 2.9
520 */
521 protected void validate(final String key, final double value) {
522 // do nothing
523 }
524
525 /**
526 * Default implementation does nothing.
527 *
528 * @since 2.9
529 */
530 protected void validate(final String key, final float value) {
531 // do nothing
532 }
533
534 /**
535 * Default implementation does nothing.
536 *
537 * @since 2.9
538 */
539 protected void validate(final String key, final int value) {
540 // do nothing
541 }
542
543 /**
544 * Default implementation does nothing.
545 *
546 * @since 2.9
547 */
548 protected void validate(final String key, final long value) {
549 // do nothing
550 }
551
552 /**
553 * Default implementation does nothing.
554 *
555 * @since 2.9
556 */
557 protected void validate(final String key, final Object value) {
558 // do nothing
559 }
560
561 /**
562 * Default implementation does nothing.
563 *
564 * @since 2.9
565 */
566 protected void validate(final String key, final short value) {
567 // do nothing
568 }
569
570 /**
571 * Default implementation does nothing.
572 *
573 * @since 2.9
574 */
575 protected void validate(final String key, final String value) {
576 // do nothing
577 }
578
579 /**
580 * Adds an item to the data Map.
581 * @param key The name of the data item.
582 * @param value The value of the data item.
583 * @return this object
584 * @since 2.9
585 */
586 @SuppressWarnings("unchecked")
587 public M with(final String key, final boolean value) {
588 validate(key, value);
589 data.putValue(key, value);
590 return (M) this;
591 }
592
593 /**
594 * Adds an item to the data Map.
595 * @param key The name of the data item.
596 * @param value The value of the data item.
597 * @return this object
598 * @since 2.9
599 */
600 @SuppressWarnings("unchecked")
601 public M with(final String key, final byte value) {
602 validate(key, value);
603 data.putValue(key, value);
604 return (M) this;
605 }
606
607 /**
608 * Adds an item to the data Map.
609 * @param key The name of the data item.
610 * @param value The value of the data item.
611 * @return this object
612 * @since 2.9
613 */
614 @SuppressWarnings("unchecked")
615 public M with(final String key, final char value) {
616 validate(key, value);
617 data.putValue(key, value);
618 return (M) this;
619 }
620
621
622 /**
623 * Adds an item to the data Map.
624 * @param key The name of the data item.
625 * @param value The value of the data item.
626 * @return this object
627 * @since 2.9
628 */
629 @SuppressWarnings("unchecked")
630 public M with(final String key, final double value) {
631 validate(key, value);
632 data.putValue(key, value);
633 return (M) this;
634 }
635
636 /**
637 * Adds an item to the data Map.
638 * @param key The name of the data item.
639 * @param value The value of the data item.
640 * @return this object
641 * @since 2.9
642 */
643 @SuppressWarnings("unchecked")
644 public M with(final String key, final float value) {
645 validate(key, value);
646 data.putValue(key, value);
647 return (M) this;
648 }
649
650 /**
651 * Adds an item to the data Map.
652 * @param key The name of the data item.
653 * @param value The value of the data item.
654 * @return this object
655 * @since 2.9
656 */
657 @SuppressWarnings("unchecked")
658 public M with(final String key, final int value) {
659 validate(key, value);
660 data.putValue(key, value);
661 return (M) this;
662 }
663
664 /**
665 * Adds an item to the data Map.
666 * @param key The name of the data item.
667 * @param value The value of the data item.
668 * @return this object
669 * @since 2.9
670 */
671 @SuppressWarnings("unchecked")
672 public M with(final String key, final long value) {
673 validate(key, value);
674 data.putValue(key, value);
675 return (M) this;
676 }
677
678 /**
679 * Adds an item to the data Map.
680 * @param key The name of the data item.
681 * @param value The value of the data item.
682 * @return this object
683 * @since 2.9
684 */
685 @SuppressWarnings("unchecked")
686 public M with(final String key, final Object value) {
687 validate(key, value);
688 data.putValue(key, value);
689 return (M) this;
690 }
691
692 /**
693 * Adds an item to the data Map.
694 * @param key The name of the data item.
695 * @param value The value of the data item.
696 * @return this object
697 * @since 2.9
698 */
699 @SuppressWarnings("unchecked")
700 public M with(final String key, final short value) {
701 validate(key, value);
702 data.putValue(key, value);
703 return (M) this;
704 }
705
706 /**
707 * Adds an item to the data Map in fluent style.
708 * @param key The name of the data item.
709 * @param value The value of the data item.
710 * @return {@code this}
711 */
712 @SuppressWarnings("unchecked")
713 public M with(final String key, final String value) {
714 put(key, value);
715 return (M) this;
716 }
717
718 }