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.Collections;
020import java.util.Map;
021import java.util.TreeMap;
022
023import org.apache.logging.log4j.util.BiConsumer;
024import org.apache.logging.log4j.util.Chars;
025import org.apache.logging.log4j.util.EnglishEnums;
026import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
027import org.apache.logging.log4j.util.IndexedStringMap;
028import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable;
029import org.apache.logging.log4j.util.PerformanceSensitive;
030import org.apache.logging.log4j.util.ReadOnlyStringMap;
031import org.apache.logging.log4j.util.SortedArrayStringMap;
032import org.apache.logging.log4j.util.StringBuilders;
033import org.apache.logging.log4j.util.Strings;
034import org.apache.logging.log4j.util.TriConsumer;
035
036/**
037 * Represents a Message that consists of a Map.
038 * <p>
039 * Thread-safety note: the contents of this message can be modified after construction.
040 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
041 * logged, because it is undefined whether the logged message string will contain the old values or the modified
042 * values.
043 * </p>
044 * <p>
045 * This class was pulled up from {@link StringMapMessage} to allow for Objects as values.
046 * </p>
047 * @param <M> Allow subclasses to use fluent APIs and override methods that return instances of subclasses.
048 * @param <V> The value type
049 */
050@PerformanceSensitive("allocation")
051@AsynchronouslyFormattable
052public class MapMessage<M extends MapMessage<M, V>, V> implements MultiFormatStringBuilderFormattable {
053
054    private static final long serialVersionUID = -5031471831131487120L;
055
056    /**
057     * When set as the format specifier causes the Map to be formatted as XML.
058     */
059    public enum MapFormat {
060        
061        /** The map should be formatted as XML. */
062        XML,
063        
064        /** The map should be formatted as JSON. */
065        JSON,
066        
067        /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */
068        JAVA;
069
070        /**
071         * Maps a format name to an {@link MapFormat} while ignoring case.
072         * 
073         * @param format a MapFormat name
074         * @return a MapFormat
075         */
076        public static MapFormat lookupIgnoreCase(final String format) {
077            return XML.name().equalsIgnoreCase(format) ? XML //
078                    : JSON.name().equalsIgnoreCase(format) ? JSON //
079                    : JAVA.name().equalsIgnoreCase(format) ? JAVA //
080                    : null;
081        }
082
083        /**
084         * All {@code MapFormat} names.
085         * 
086         * @return All {@code MapFormat} names.
087         */
088        public static String[] names() {
089            return new String[] {XML.name(), JSON.name(), JAVA.name()};
090        }
091    }
092
093    private final IndexedStringMap data;
094
095    /**
096     * Constructs a new instance.
097     */
098    public MapMessage() {
099        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}