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<>();
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<>(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 in fluent style.
114      * @param key The name of the data item.
115      * @param value The value of the data item.
116      * @return {@code this}
117      */
118     public MapMessage with(final String key, final String value) {
119         put(key, value);
120         return this;
121     }
122 
123     /**
124      * Add an item to the data Map.
125      * @param key The name of the data item.
126      * @param value The value of the data item.
127      */
128     public void put(final String key, final String value) {
129         if (value == null) {
130             throw new IllegalArgumentException("No value provided for key " + key);
131         }
132         validate(key, value);
133         data.put(key, value);
134     }
135 
136     protected void validate(final String key, final String value) {
137 
138     }
139 
140     /**
141      * Add all the elements from the specified Map.
142      * @param map The Map to add.
143      */
144     public void putAll(final Map<String, String> map) {
145         data.putAll(map);
146     }
147 
148     /**
149      * Retrieve the value of the element with the specified key or null if the key is not present.
150      * @param key The name of the element.
151      * @return The value of the element or null if the key is not present.
152      */
153     public String get(final String key) {
154         return data.get(key);
155     }
156 
157     /**
158      * Remove the element with the specified name.
159      * @param key The name of the element.
160      * @return The previous value of the element.
161      */
162     public String remove(final String key) {
163         return data.remove(key);
164     }
165 
166     /**
167      * Format the Structured data as described in RFC 5424.
168      *
169      * @return The formatted String.
170      */
171     public String asString() {
172         return asString((MapFormat) null);
173     }
174 
175     public String asString(final String format) {
176         try {
177             return asString(EnglishEnums.valueOf(MapFormat.class, format));
178         } catch (final IllegalArgumentException ex) {
179             return asString();
180         }
181     }
182     /**
183      * Format the Structured data as described in RFC 5424.
184      *
185      * @param format The format identifier. Ignored in this implementation.
186      * @return The formatted String.
187      */
188     private String asString(final MapFormat format) {
189         final StringBuilder sb = new StringBuilder();
190         if (format == null) {
191             appendMap(sb);
192         } else {
193             switch (format) {
194                 case XML : {
195                     asXml(sb);
196                     break;
197                 }
198                 case JSON : {
199                     asJson(sb);
200                     break;
201                 }
202                 case JAVA : {
203                     asJava(sb);
204                     break;
205                 }
206                 default : {
207                     appendMap(sb);
208                 }
209             }
210         }
211         return sb.toString();
212     }
213 
214     public void asXml(final StringBuilder sb) {
215         sb.append("<Map>\n");
216         for (final Map.Entry<String, String> entry : data.entrySet()) {
217             sb.append("  <Entry key=\"").append(entry.getKey()).append("\">").append(entry.getValue())
218               .append("</Entry>\n");
219         }
220         sb.append("</Map>");
221     }
222 
223     /**
224      * Format the message and return it.
225      * @return the formatted message.
226      */
227     @Override
228     public String getFormattedMessage() {
229         return asString();
230     }
231 
232     /**
233      *
234      * @param formats An array of Strings that provide extra information about how to format the message.
235      * MapMessage uses the first format specifier it recognizes. The supported formats are XML, JSON, and
236      * JAVA. The default format is key1="value1" key2="value2" as required by RFC 5424 messages.
237      *
238      * @return The formatted message.
239      */
240     @Override
241     public String getFormattedMessage(final String[] formats) {
242         if (formats == null || formats.length == 0) {
243             return asString();
244         }
245         for (final String format : formats) {
246             for (final MapFormat mapFormat : MapFormat.values()) {
247                 if (mapFormat.name().equalsIgnoreCase(format)) {
248                     return asString(mapFormat);
249                 }
250             }
251         }
252         return asString();
253 
254     }
255 
256     protected void appendMap(final StringBuilder sb) {
257         boolean first = true;
258         for (final Map.Entry<String, String> entry : data.entrySet()) {
259             if (!first) {
260                 sb.append(' ');
261             }
262             first = false;
263             StringBuilders.appendKeyDqValue(sb, entry);
264         }
265     }
266 
267     protected void asJson(final StringBuilder sb) {
268         boolean first = true;
269         sb.append('{');
270         for (final Map.Entry<String, String> entry : data.entrySet()) {
271             if (!first) {
272                 sb.append(", ");
273             }
274             first = false;
275             StringBuilders.appendDqValue(sb, entry.getKey()).append(':');
276             StringBuilders.appendDqValue(sb, entry.getValue());
277         }
278         sb.append('}');
279     }
280 
281 
282     protected void asJava(final StringBuilder sb) {
283         boolean first = true;
284         sb.append('{');
285         for (final Map.Entry<String, String> entry : data.entrySet()) {
286             if (!first) {
287                 sb.append(", ");
288             }
289             first = false;
290             StringBuilders.appendKeyDqValue(sb, entry);
291         }
292         sb.append('}');
293     }
294 
295     public MapMessage newInstance(final Map<String, String> map) {
296         return new MapMessage(map);
297     }
298 
299     @Override
300     public String toString() {
301         return asString();
302     }
303 
304     @Override
305     public boolean equals(final Object o) {
306         if (this == o) {
307             return true;
308         }
309         if (o == null || this.getClass() != o.getClass()) {
310             return false;
311         }
312 
313         final MapMessage that = (MapMessage) o;
314 
315         return this.data.equals(that.data);
316     }
317 
318     @Override
319     public int hashCode() {
320         return data.hashCode();
321     }
322 
323     /**
324      * Always returns null.
325      *
326      * @return null
327      */
328     @Override
329     public Throwable getThrowable() {
330         return null;
331     }
332 }