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