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 */ 017 package org.apache.logging.log4j.message; 018 019 import java.util.Collections; 020 import java.util.Map; 021 import java.util.SortedMap; 022 import java.util.TreeMap; 023 024 import org.apache.logging.log4j.util.EnglishEnums; 025 import org.apache.logging.log4j.util.Strings; 026 027 /** 028 * Represents a Message that consists of a Map. 029 * <p> 030 * Thread-safety note: the contents of this message can be modified after construction. 031 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is 032 * logged, because it is undefined whether the logged message string will contain the old values or the modified 033 * values. 034 */ 035 public class MapMessage implements MultiformatMessage { 036 /** 037 * When set as the format specifier causes the Map to be formatted as XML. 038 */ 039 040 public enum MapFormat { 041 /** The map should be formatted as XML. */ 042 XML, 043 /** The map should be formatted as JSON. */ 044 JSON, 045 /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */ 046 JAVA 047 } 048 049 private static final long serialVersionUID = -5031471831131487120L; 050 051 private final SortedMap<String, String> data; 052 053 /** 054 * Constructor. 055 */ 056 public MapMessage() { 057 data = new TreeMap<String, String>(); 058 } 059 060 /** 061 * Constructor based on an existing Map. 062 * @param map The Map. 063 */ 064 public MapMessage(final Map<String, String> map) { 065 this.data = map instanceof SortedMap ? (SortedMap<String, String>) map : new TreeMap<String, String>(map); 066 } 067 068 @Override 069 public String[] getFormats() { 070 final String[] formats = new String[MapFormat.values().length]; 071 int i = 0; 072 for (final MapFormat format : MapFormat.values()) { 073 formats[i++] = format.name(); 074 } 075 return formats; 076 } 077 078 /** 079 * Returns the data elements as if they were parameters on the logging event. 080 * @return the data elements. 081 */ 082 @Override 083 public Object[] getParameters() { 084 return data.values().toArray(); 085 } 086 087 /** 088 * Returns the message. 089 * @return the message. 090 */ 091 @Override 092 public String getFormat() { 093 return Strings.EMPTY; 094 } 095 096 /** 097 * Returns the message data as an unmodifiable Map. 098 * @return the message data as an unmodifiable map. 099 */ 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 }