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