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