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.SortedMap;
    22  import java.util.TreeMap;
    23
    24  import org.apache.logging.log4j.util.EnglishEnums;
    25  import org.apache.logging.log4j.util.StringBuilders;
    26  import org.apache.logging.log4j.util.Strings;
    27
    28  /**
    29   * Represents a Message that consists of a Map.
    30   * <p>
    31   * Thread-safety note: the contents of this message can be modified after construction.
    32   * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
    33   * logged, because it is undefined whether the logged message string will contain the old values or the modified
    34   * values.
    35   */
    36  public class MapMessage implements MultiformatMessage {
    37      /**
    38       * When set as the format specifier causes the Map to be formatted as XML.
    39       */
    40
    41      public enum MapFormat {
    42          /** The map should be formatted as XML. */
    43          XML,
    44          /** The map should be formatted as JSON. */
    45          JSON,
    46          /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */
    47          JAVA
    48      }
    49
    50      private static final long serialVersionUID = -5031471831131487120L;
    51
    52      private final SortedMap<String, String> data;
    53
    54      /**
    55       * Constructor.
    56       */
    57      public MapMessage() {
    58          data = new TreeMap<String, String>();
    59      }
    60
    61      /**
    62       * Constructor based on an existing Map.
    63       * @param map The Map.
    64       */
    65      public MapMessage(final Map<String, String> map) {
    66          this.data = map instanceof SortedMap ? (SortedMap<String, String>) map : new TreeMap<String, String>(map);
    67      }
    68
    69      @Override
    70      public String[] getFormats() {
    71          final String[] formats = new String[MapFormat.values().length];
    72          int i = 0;
    73          for (final MapFormat format : MapFormat.values()) {
    74              formats[i++] = format.name();
    75          }
    76          return formats;
    77      }
    78
    79      /**
    80       * Returns the data elements as if they were parameters on the logging event.
    81       * @return the data elements.
    82       */
    83      @Override
    84      public Object[] getParameters() {
    85          return data.values().toArray();
    86      }
    87
    88      /**
    89       * Returns the message.
    90       * @return the message.
    91       */
    92      @Override
    93      public String getFormat() {
    94          return Strings.EMPTY;
    95      }
    96
    97      /**
    98       * Returns the message data as an unmodifiable Map.
    99       * @return the message data as an unmodifiable map.
    100      */
    101     public Map<String, String> getData() {
    102         return Collections.unmodifiableMap(data);
    103     }
    104
    105     /**
    106      * Clear the data.
    107      */
    108     public void clear() {
    109         data.clear();
    110     }
    111
    112     /**
    113      * Add an item to the data Map.
    114      * @param key The name of the data item.
    115      * @param value The value of the data item.
    116      */
    117     public void put(final String key, final String value) {
    118         if (value == null) {
    119             throw new IllegalArgumentException("No value provided for key " + key);
    120         }
    121         validate(key, value);
    122         data.put(key, value);
    123     }
    124
    125     protected void validate(final String key, final String value) {
    126
    127     }
    128
    129     /**
    130      * Add all the elements from the specified Map.
    131      * @param map The Map to add.
    132      */
    133     public void putAll(final Map<String, String> map) {
    134         data.putAll(map);
    135     }
    136
    137     /**
    138      * Retrieve the value of the element with the specified key or null if the key is not present.
    139      * @param key The name of the element.
    140      * @return The value of the element or null if the key is not present.
    141      */
    142     public String get(final String key) {
    143         return data.get(key);
    144     }
    145
    146     /**
    147      * Remove the element with the specified name.
    148      * @param key The name of the element.
    149      * @return The previous value of the element.
    150      */
    151     public String remove(final String key) {
    152         return data.remove(key);
    153     }
    154
    155     /**
    156      * Format the Structured data as described in RFC 5424.
    157      *
    158      * @return The formatted String.
    159      */
    160     public String asString() {
    161         return asString((MapFormat) null);
    162     }
    163
    164     public String asString(final String format) {
    165         try {
    166             return asString(EnglishEnums.valueOf(MapFormat.class, format));
    167         } catch (final IllegalArgumentException ex) {
    168             return asString();
    169         }
    170     }
    171     /**
    172      * Format the Structured data as described in RFC 5424.
    173      *
    174      * @param format The format identifier. Ignored in this implementation.
    175      * @return The formatted String.
    176      */
    177     private String asString(final MapFormat format) {
    178         final StringBuilder sb = new StringBuilder();
    179         if (format == null) {
    180             appendMap(sb);
    181         } else {
    182             switch (format) {
    183                 case XML : {
    184                     asXml(sb);
    185                     break;
    186                 }
    187                 case JSON : {
    188                     asJson(sb);
    189                     break;
    190                 }
    191                 case JAVA : {
    192                     asJava(sb);
    193                     break;
    194                 }
    195                 default : {
    196                     appendMap(sb);
    197                 }
    198             }
    199         }
    200         return sb.toString();
    201     }
    202
    203     public void asXml(final StringBuilder sb) {
    204         sb.append("<Map>\n");
    205         for (final Map.Entry<String, String> entry : data.entrySet()) {
    206             sb.append("  <Entry key=\"").append(entry.getKey()).append("\">").append(entry.getValue())
    207               .append("</Entry>\n");
    208         }
    209         sb.append("</Map>");
    210     }
    211
    212     /**
    213      * Format the message and return it.
    214      * @return the formatted message.
    215      */
    216     @Override
    217     public String getFormattedMessage() {
    218         return asString();
    219     }
    220
    221     /**
    222      *
    223      * @param formats An array of Strings that provide extra information about how to format the message.
    224      * MapMessage uses the first format specifier it recognizes. The supported formats are XML, JSON, and
    225      * JAVA. The default format is key1="value1" key2="value2" as required by RFC 5424 messages.
    226      *
    227      * @return The formatted message.
    228      */
    229     @Override
    230     public String getFormattedMessage(final String[] formats) {
    231         if (formats == null || formats.length == 0) {
    232             return asString();
    233         }
    234         for (final String format : formats) {
    235             for (final MapFormat mapFormat : MapFormat.values()) {
    236                 if (mapFormat.name().equalsIgnoreCase(format)) {
    237                     return asString(mapFormat);
    238                 }
    239             }
    240         }
    241         return asString();
    242
    243     }
    244
    245     protected void appendMap(final StringBuilder sb) {
    246         boolean first = true;
    247         for (final Map.Entry<String, String> entry : data.entrySet()) {
    248             if (!first) {
    249                 sb.append(' ');
    250             }
    251             first = false;
    252             StringBuilders.appendKeyDqValue(sb, entry);
    253         }
    254     }
    255
    256     protected void asJson(final StringBuilder sb) {
    257         boolean first = true;
    258         sb.append('{');
    259         for (final Map.Entry<String, String> entry : data.entrySet()) {
    260             if (!first) {
    261                 sb.append(", ");
    262             }
    263             first = false;
    264             StringBuilders.appendDqValue(sb, entry.getKey()).append(':');
    265             StringBuilders.appendDqValue(sb, entry.getValue());
    266         }
    267         sb.append('}');
    268     }
    269
    270
    271     protected void asJava(final StringBuilder sb) {
    272         boolean first = true;
    273         sb.append('{');
    274         for (final Map.Entry<String, String> entry : data.entrySet()) {
    275             if (!first) {
    276                 sb.append(", ");
    277             }
    278             first = false;
    279             StringBuilders.appendKeyDqValue(sb, entry);
    280         }
    281         sb.append('}');
    282     }
    283
    284     public MapMessage newInstance(final Map<String, String> map) {
    285         return new MapMessage(map);
    286     }
    287
    288     @Override
    289     public String toString() {
    290         return asString();
    291     }
    292
    293     @Override
    294     public boolean equals(final Object o) {
    295         if (this == o) {
    296             return true;
    297         }
    298         if (o == null || this.getClass() != o.getClass()) {
    299             return false;
    300         }
    301
    302         final MapMessage that = (MapMessage) o;
    303
    304         return this.data.equals(that.data);
    305     }
    306
    307     @Override
    308     public int hashCode() {
    309         return data.hashCode();
    310     }
    311
    312     /**
    313      * Always returns null.
    314      *
    315      * @return null
    316      */
    317     @Override
    318     public Throwable getThrowable() {
    319         return null;
    320     }
    321 }