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.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 }