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 */ 030 public class MapMessage implements MultiformatMessage { 031 /** 032 * When set as the format specifier causes the Map to be formatted as XML. 033 */ 034 035 public enum MapFormat { 036 /** The map should be formatted as XML. */ 037 XML, 038 /** The map should be formatted as JSON. */ 039 JSON, 040 /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */ 041 JAVA 042 } 043 044 private static final long serialVersionUID = -5031471831131487120L; 045 046 private final SortedMap<String, String> data; 047 048 /** 049 * Constructor. 050 */ 051 public MapMessage() { 052 data = new TreeMap<String, String>(); 053 } 054 055 /** 056 * Constructor based on an existing Map. 057 * @param map The Map. 058 */ 059 public MapMessage(final Map<String, String> map) { 060 this.data = map instanceof SortedMap ? (SortedMap<String, String>) map : new TreeMap<String, String>(map); 061 } 062 063 @Override 064 public String[] getFormats() { 065 final String[] formats = new String[MapFormat.values().length]; 066 int i = 0; 067 for (final MapFormat format : MapFormat.values()) { 068 formats[i++] = format.name(); 069 } 070 return formats; 071 } 072 073 /** 074 * Returns the data elements as if they were parameters on the logging event. 075 * @return the data elements. 076 */ 077 @Override 078 public Object[] getParameters() { 079 return data.values().toArray(); 080 } 081 082 /** 083 * Returns the message. 084 * @return the message. 085 */ 086 @Override 087 public String getFormat() { 088 return Strings.EMPTY; 089 } 090 091 /** 092 * Returns the message data as an unmodifiable Map. 093 * @return the message data as an unmodifiable map. 094 */ 095 public Map<String, String> getData() { 096 return Collections.unmodifiableMap(data); 097 } 098 099 /** 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 }