001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.message;
018
019import java.util.AbstractMap;
020import java.util.Collections;
021import java.util.Map;
022import java.util.TreeMap;
023
024import org.apache.logging.log4j.util.BiConsumer;
025import org.apache.logging.log4j.util.Chars;
026import org.apache.logging.log4j.util.EnglishEnums;
027import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
028import org.apache.logging.log4j.util.IndexedStringMap;
029import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable;
030import org.apache.logging.log4j.util.PerformanceSensitive;
031import org.apache.logging.log4j.util.ReadOnlyStringMap;
032import org.apache.logging.log4j.util.SortedArrayStringMap;
033import org.apache.logging.log4j.util.StringBuilders;
034import org.apache.logging.log4j.util.Strings;
035import org.apache.logging.log4j.util.TriConsumer;
036
037/**
038 * Represents a Message that consists of a Map.
039 * <p>
040 * Thread-safety note: the contents of this message can be modified after construction.
041 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
042 * logged, because it is undefined whether the logged message string will contain the old values or the modified
043 * values.
044 * </p>
045 * <p>
046 * This class was pulled up from {@link StringMapMessage} to allow for Objects as values.
047 * </p>
048 * @param <M> Allow subclasses to use fluent APIs and override methods that return instances of subclasses.
049 * @param <V> The value type
050 */
051@PerformanceSensitive("allocation")
052@AsynchronouslyFormattable
053public class MapMessage<M extends MapMessage<M, V>, V> implements MultiFormatStringBuilderFormattable {
054
055    private static final long serialVersionUID = -5031471831131487120L;
056
057    /**
058     * When set as the format specifier causes the Map to be formatted as XML.
059     */
060    public enum MapFormat {
061        
062        /** The map should be formatted as XML. */
063        XML,
064        
065        /** The map should be formatted as JSON. */
066        JSON,
067        
068        /** The map should be formatted the same as documented by {@link AbstractMap#toString()}. */
069        JAVA,
070
071        /**
072         * The map should be formatted the same as documented by {@link AbstractMap#toString()} but without quotes.
073         *
074         * @since 2.11.2
075         */
076        JAVA_UNQUOTED;
077
078        /**
079         * Maps a format name to an {@link MapFormat} while ignoring case.
080         * 
081         * @param format a MapFormat name
082         * @return a MapFormat
083         */
084        public static MapFormat lookupIgnoreCase(final String format) {
085            return XML.name().equalsIgnoreCase(format) ? XML //
086                    : JSON.name().equalsIgnoreCase(format) ? JSON //
087                    : JAVA.name().equalsIgnoreCase(format) ? JAVA //
088                    : JAVA_UNQUOTED.name().equalsIgnoreCase(format) ? JAVA_UNQUOTED //
089                    : null;
090        }
091
092        /**
093         * All {@code MapFormat} names.
094         * 
095         * @return All {@code MapFormat} names.
096         */
097        public static String[] names() {
098            return new String[] {XML.name(), JSON.name(), JAVA.name(), JAVA_UNQUOTED.name()};
099        }
100    }
101
102    private final IndexedStringMap data;
103
104    /**
105     * Constructs a new instance.
106     */
107    public MapMessage() {
108        this.data = new SortedArrayStringMap();
109    }
110
111    /**
112     * Constructs a new instance.
113     * 
114     * @param  initialCapacity the initial capacity.
115     */
116    public MapMessage(final int initialCapacity) {
117        this.data = new SortedArrayStringMap(initialCapacity);
118    }
119
120    /**
121     * Constructs a new instance based on an existing {@link Map}.
122     * @param map The Map.
123     */
124    public MapMessage(final Map<String, V> map) {
125        this.data = new SortedArrayStringMap(map);
126    }
127
128    @Override
129    public String[] getFormats() {
130        return MapFormat.names();
131    }
132
133    /**
134     * Returns the data elements as if they were parameters on the logging event.
135     * @return the data elements.
136     */
137    @Override
138    public Object[] getParameters() {
139        final Object[] result = new Object[data.size()];
140        for (int i = 0; i < data.size(); i++) {
141            result[i] = data.getValueAt(i);
142        }
143        return result;
144    }
145
146    /**
147     * Returns the message.
148     * @return the message.
149     */
150    @Override
151    public String getFormat() {
152        return Strings.EMPTY;
153    }
154
155    /**
156     * Returns the message data as an unmodifiable Map.
157     * @return the message data as an unmodifiable map.
158     */
159    @SuppressWarnings("unchecked")
160    public Map<String, V> getData() {
161        final TreeMap<String, V> result = new TreeMap<>(); // returned map must be sorted
162        for (int i = 0; i < data.size(); i++) {
163            // The Eclipse compiler does not need the typecast to V, but the Oracle compiler sure does.
164            result.put(data.getKeyAt(i), (V) data.getValueAt(i));
165        }
166        return Collections.unmodifiableMap(result);
167    }
168
169    /**
170     * Returns a read-only view of the message data.
171     * @return the read-only message data.
172     */
173    public IndexedReadOnlyStringMap getIndexedReadOnlyStringMap() {
174        return data;
175    }
176
177    /**
178     * Clear the data.
179     */
180    public void clear() {
181        data.clear();
182    }
183
184    /**
185     * Returns {@code true} if this data structure contains the specified key, {@code false} otherwise.
186     *
187     * @param key the key whose presence to check. May be {@code null}.
188     * @return {@code true} if this data structure contains the specified key, {@code false} otherwise
189     * @since 2.9
190     */
191    public boolean containsKey(final String key) {
192        return data.containsKey(key);
193    }
194
195    /**
196     * Adds an item to the data Map.
197     * @param key The name of the data item.
198     * @param value The value of the data item.
199     */
200    public void put(final String key, final String value) {
201        if (value == null) {
202            throw new IllegalArgumentException("No value provided for key " + key);
203        }
204        validate(key, value);
205        data.putValue(key, value);
206    }
207
208    /**
209     * Adds all the elements from the specified Map.
210     * @param map The Map to add.
211     */
212    public void putAll(final Map<String, String> map) {
213        for (final Map.Entry<String, String> entry : map.entrySet()) {
214            data.putValue(entry.getKey(), entry.getValue());
215        }
216    }
217
218    /**
219     * Retrieves the value of the element with the specified key or null if the key is not present.
220     * @param key The name of the element.
221     * @return The value of the element or null if the key is not present.
222     */
223    public String get(final String key) {
224        final Object result = data.getValue(key);
225        return ParameterFormatter.deepToString(result);
226    }
227
228    /**
229     * Removes the element with the specified name.
230     * @param key The name of the element.
231     * @return The previous value of the element.
232     */
233    public String remove(final String key) {
234        final String result = get(key);
235        data.remove(key);
236        return result;
237    }
238
239    /**
240     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
241     *
242     * @return The formatted String.
243     */
244    public String asString() {
245        return format((MapFormat) null, new StringBuilder()).toString();
246    }
247
248    /**
249     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
250     *
251     * @param format The format identifier.
252     * @return The formatted String.
253     */
254    public String asString(final String format) {
255        try {
256            return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()).toString();
257        } catch (final IllegalArgumentException ex) {
258            return asString();
259        }
260    }
261    
262    /**
263     * Performs the given action for each key-value pair in this data structure
264     * until all entries have been processed or the action throws an exception.
265     * <p>
266     * Some implementations may not support structural modifications (adding new elements or removing elements) while
267     * iterating over the contents. In such implementations, attempts to add or remove elements from the
268     * {@code BiConsumer}'s {@link BiConsumer#accept(Object, Object)} accept} method may cause a
269     * {@code ConcurrentModificationException} to be thrown.
270     * </p>
271     *
272     * @param action The action to be performed for each key-value pair in this collection
273     * @param <CV> type of the consumer value
274     * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
275     *          to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
276     *          {@link #forEach(TriConsumer, Object)}.
277     * @see ReadOnlyStringMap#forEach(BiConsumer)
278     * @since 2.9
279     */
280    public <CV> void forEach(final BiConsumer<String, ? super CV> action) {
281        data.forEach(action);
282    }
283
284    /**
285     * Performs the given action for each key-value pair in this data structure
286     * until all entries have been processed or the action throws an exception.
287     * <p>
288     * The third parameter lets callers pass in a stateful object to be modified with the key-value pairs,
289     * so the TriConsumer implementation itself can be stateless and potentially reusable.
290     * </p>
291     * <p>
292     * Some implementations may not support structural modifications (adding new elements or removing elements) while
293     * iterating over the contents. In such implementations, attempts to add or remove elements from the
294     * {@code TriConsumer}'s {@link TriConsumer#accept(Object, Object, Object) accept} method may cause a
295     * {@code ConcurrentModificationException} to be thrown.
296     * </p>
297     *
298     * @param action The action to be performed for each key-value pair in this collection
299     * @param state the object to be passed as the third parameter to each invocation on the specified
300     *          triconsumer
301     * @param <CV> type of the consumer value
302     * @param <S> type of the third parameter
303     * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
304     *          to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
305     *          {@link #forEach(TriConsumer, Object)}.
306     * @see ReadOnlyStringMap#forEach(TriConsumer, Object)
307     * @since 2.9
308     */
309    public <CV, S> void forEach(final TriConsumer<String, ? super CV, S> action, final S state) {
310        data.forEach(action, state);
311    }
312    
313    /**
314     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
315     *
316     * @param format The format identifier.
317     * @return The formatted String.
318     */
319    private StringBuilder format(final MapFormat format, final StringBuilder sb) {
320        if (format == null) {
321            appendMap(sb);
322        } else {
323            switch (format) {
324                case XML : {
325                    asXml(sb);
326                    break;
327                }
328                case JSON : {
329                    asJson(sb);
330                    break;
331                }
332                case JAVA : {
333                    asJava(sb);
334                    break;
335                }
336                case JAVA_UNQUOTED:
337                    asJavaUnquoted(sb);
338                    break;
339                default : {
340                    appendMap(sb);
341                }
342            }
343        }
344        return sb;
345    }
346
347    /**
348     * Formats this message as an XML fragment String into the given builder.
349     *
350     * @param sb format into this builder.
351     */
352    public void asXml(final StringBuilder sb) {
353        sb.append("<Map>\n");
354        for (int i = 0; i < data.size(); i++) {
355            sb.append("  <Entry key=\"")
356                    .append(data.getKeyAt(i))
357                    .append("\">");
358            final int size = sb.length();
359            ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
360            StringBuilders.escapeXml(sb, size);
361            sb.append("</Entry>\n");
362        }
363        sb.append("</Map>");
364    }
365
366    /**
367     * Formats the message and return it.
368     * @return the formatted message.
369     */
370    @Override
371    public String getFormattedMessage() {
372        return asString();
373    }
374
375    /**
376     *
377     * @param formats
378     *            An array of Strings that provide extra information about how to format the message. MapMessage uses
379     *            the first format specifier it recognizes. The supported formats are XML, JSON, and JAVA. The default
380     *            format is key1="value1" key2="value2" as required by <a href="https://tools.ietf.org/html/rfc5424">RFC
381     *            5424</a> messages.
382     *
383     * @return The formatted message.
384     */
385    @Override
386    public String getFormattedMessage(final String[] formats) {
387        return format(getFormat(formats), new StringBuilder()).toString();
388    }
389
390    private MapFormat getFormat(final String[] formats) {
391        if (formats == null || formats.length == 0) {
392            return null;
393        }
394        for (int i = 0; i < formats.length; i++) {
395            final MapFormat mapFormat = MapFormat.lookupIgnoreCase(formats[i]);
396            if (mapFormat != null) {
397                return mapFormat;
398            }
399        }
400        return null;
401    }
402
403    protected void appendMap(final StringBuilder sb) {
404        for (int i = 0; i < data.size(); i++) {
405            if (i > 0) {
406                sb.append(' ');
407            }
408            sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE);
409            ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
410            sb.append(Chars.DQUOTE);
411        }
412    }
413
414    protected void asJson(final StringBuilder sb) {
415        sb.append('{');
416        for (int i = 0; i < data.size(); i++) {
417            if (i > 0) {
418                sb.append(", ");
419            }
420            sb.append(Chars.DQUOTE);
421            int start = sb.length();
422            sb.append(data.getKeyAt(i));
423            StringBuilders.escapeJson(sb, start);
424            sb.append(Chars.DQUOTE).append(':').append(Chars.DQUOTE);
425            start = sb.length();
426            ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
427            StringBuilders.escapeJson(sb, start);
428            sb.append(Chars.DQUOTE);
429        }
430        sb.append('}');
431    }
432
433    protected void asJavaUnquoted(final StringBuilder sb) {
434        asJava(sb, false);
435    }
436
437    protected void asJava(final StringBuilder sb) {
438        asJava(sb, true);
439    }
440
441    private void asJava(final StringBuilder sb, boolean quoted) {
442        sb.append('{');
443        for (int i = 0; i < data.size(); i++) {
444            if (i > 0) {
445                sb.append(", ");
446            }
447            sb.append(data.getKeyAt(i)).append(Chars.EQ);
448            if (quoted) {
449                sb.append(Chars.DQUOTE);
450            }
451            ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
452            if (quoted) {
453                sb.append(Chars.DQUOTE);
454            }
455        }
456        sb.append('}');
457    }
458
459    /**
460     * Constructs a new instance based on an existing Map.
461     * @param map The Map.
462     * @return A new MapMessage
463     */
464    @SuppressWarnings("unchecked")
465    public M newInstance(final Map<String, V> map) {
466        return (M) new MapMessage<>(map);
467    }
468
469    @Override
470    public String toString() {
471        return asString();
472    }
473
474    @Override
475    public void formatTo(final StringBuilder buffer) {
476        format((MapFormat) null, buffer);
477    }
478
479    @Override
480    public void formatTo(final String[] formats, final StringBuilder buffer) {
481        format(getFormat(formats), buffer);
482    }
483
484    @Override
485    public boolean equals(final Object o) {
486        if (this == o) {
487            return true;
488        }
489        if (o == null || this.getClass() != o.getClass()) {
490            return false;
491        }
492
493        final MapMessage<?, ?> that = (MapMessage<?, ?>) o;
494
495        return this.data.equals(that.data);
496    }
497
498    @Override
499    public int hashCode() {
500        return data.hashCode();
501    }
502
503    /**
504     * Always returns null.
505     *
506     * @return null
507     */
508    @Override
509    public Throwable getThrowable() {
510        return null;
511    }
512    
513    /**
514     * Default implementation does nothing.
515     * 
516     * @since 2.9
517     */
518    protected void validate(final String key, final boolean value) {
519        // do nothing
520    }
521
522    /**
523     * Default implementation does nothing.
524     * 
525     * @since 2.9
526     */
527    protected void validate(final String key, final byte value) {
528        // do nothing
529    }
530
531    /**
532     * Default implementation does nothing.
533     * 
534     * @since 2.9
535     */
536    protected void validate(final String key, final char value) {
537        // do nothing
538    }
539
540    /**
541     * Default implementation does nothing.
542     * 
543     * @since 2.9
544     */
545    protected void validate(final String key, final double value) {
546        // do nothing
547    }
548
549    /**
550     * Default implementation does nothing.
551     * 
552     * @since 2.9
553     */
554    protected void validate(final String key, final float value) {
555        // do nothing
556    }
557    
558    /**
559     * Default implementation does nothing.
560     * 
561     * @since 2.9
562     */
563    protected void validate(final String key, final int value) {
564        // do nothing
565    }
566
567    /**
568     * Default implementation does nothing.
569     * 
570     * @since 2.9
571     */
572    protected void validate(final String key, final long value) {
573        // do nothing
574    }
575    
576    /**
577     * Default implementation does nothing.
578     * 
579     * @since 2.9
580     */
581    protected void validate(final String key, final Object value) {
582        // do nothing
583    }
584
585    /**
586     * Default implementation does nothing.
587     * 
588     * @since 2.9
589     */
590    protected void validate(final String key, final short value) {
591        // do nothing
592    }
593
594    /**
595     * Default implementation does nothing.
596     * 
597     * @since 2.9
598     */
599    protected void validate(final String key, final String value) {
600        // do nothing
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 boolean 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 byte 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 char value) {
640        validate(key, value);
641        data.putValue(key, value);
642        return (M) this;
643    }
644
645
646    /**
647     * Adds an item to the data Map.
648     * @param key The name of the data item.
649     * @param value The value of the data item.
650     * @return this object
651     * @since 2.9
652     */
653    @SuppressWarnings("unchecked")
654    public M with(final String key, final double value) {
655        validate(key, value);
656        data.putValue(key, value);
657        return (M) this;
658    }
659
660    /**
661     * Adds an item to the data Map.
662     * @param key The name of the data item.
663     * @param value The value of the data item.
664     * @return this object
665     * @since 2.9
666     */
667    @SuppressWarnings("unchecked")
668    public M with(final String key, final float value) {
669        validate(key, value);
670        data.putValue(key, value);
671        return (M) this;
672    }
673
674    /**
675     * Adds an item to the data Map.
676     * @param key The name of the data item.
677     * @param value The value of the data item.
678     * @return this object
679     * @since 2.9
680     */
681    @SuppressWarnings("unchecked")
682    public M with(final String key, final int value) {
683        validate(key, value);
684        data.putValue(key, value);
685        return (M) this;
686    }
687
688    /**
689     * Adds an item to the data Map.
690     * @param key The name of the data item.
691     * @param value The value of the data item.
692     * @return this object
693     * @since 2.9
694     */
695    @SuppressWarnings("unchecked")
696    public M with(final String key, final long value) {
697        validate(key, value);
698        data.putValue(key, value);
699        return (M) this;
700    }
701
702    /**
703     * Adds an item to the data Map.
704     * @param key The name of the data item.
705     * @param value The value of the data item.
706     * @return this object
707     * @since 2.9
708     */
709    @SuppressWarnings("unchecked")
710    public M with(final String key, final Object value) {
711        validate(key, value);
712        data.putValue(key, value);
713        return (M) this;
714    }
715
716    /**
717     * Adds an item to the data Map.
718     * @param key The name of the data item.
719     * @param value The value of the data item.
720     * @return this object
721     * @since 2.9
722     */
723    @SuppressWarnings("unchecked")
724    public M with(final String key, final short value) {
725        validate(key, value);
726        data.putValue(key, value);
727        return (M) this;
728    }
729
730    /**
731     * Adds an item to the data Map in fluent style.
732     * @param key The name of the data item.
733     * @param value The value of the data item.
734     * @return {@code this}
735     */
736    @SuppressWarnings("unchecked")
737    public M with(final String key, final String value) {
738        put(key, value);
739        return (M) this;
740    }
741
742}