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