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