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 026 /** 027 * Represents a Message that consists of a Map. 028 */ 029 public class MapMessage implements MultiformatMessage { 030 /** 031 * When set as the format specifier causes the Map to be formatted as XML. 032 */ 033 034 public enum MapFormat { 035 /** The map should be formatted as XML. */ 036 XML, 037 /** The map should be formatted as JSON. */ 038 JSON, 039 /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */ 040 JAVA 041 } 042 043 private static final long serialVersionUID = -5031471831131487120L; 044 045 private final SortedMap<String, String> data; 046 047 /** 048 * Constructor. 049 */ 050 public MapMessage() { 051 data = new TreeMap<String, String>(); 052 } 053 054 /** 055 * Constructor based on an existing Map. 056 * @param map The Map. 057 */ 058 public MapMessage(final Map<String, String> map) { 059 this.data = map instanceof SortedMap ? (SortedMap<String, String>) map : new TreeMap<String, String>(map); 060 } 061 062 @Override 063 public String[] getFormats() { 064 final String[] formats = new String[MapFormat.values().length]; 065 int i = 0; 066 for (final MapFormat format : MapFormat.values()) { 067 formats[i++] = format.name(); 068 } 069 return formats; 070 } 071 072 /** 073 * Returns the data elements as if they were parameters on the logging event. 074 * @return the data elements. 075 */ 076 @Override 077 public Object[] getParameters() { 078 return data.values().toArray(); 079 } 080 081 /** 082 * Returns the message. 083 * @return the message. 084 */ 085 @Override 086 public String getFormat() { 087 return ""; 088 } 089 090 /** 091 * Returns the message data as an unmodifiable Map. 092 * @return the message data as an unmodifiable map. 093 */ 094 public Map<String, String> getData() { 095 return Collections.unmodifiableMap(data); 096 } 097 098 /** 099 * Clear the data. 100 */ 101 public void clear() { 102 data.clear(); 103 } 104 105 /** 106 * Add an item to the data Map. 107 * @param key The name of the data item. 108 * @param value The value of the data item. 109 */ 110 public void put(final String key, final String value) { 111 if (value == null) { 112 throw new IllegalArgumentException("No value provided for key " + key); 113 } 114 validate(key, value); 115 data.put(key, value); 116 } 117 118 protected void validate(final String key, final String value) { 119 120 } 121 122 /** 123 * Add all the elements from the specified Map. 124 * @param map The Map to add. 125 */ 126 public void putAll(final Map<String, String> map) { 127 data.putAll(map); 128 } 129 130 /** 131 * Retrieve the value of the element with the specified key or null if the key is not present. 132 * @param key The name of the element. 133 * @return The value of the element or null if the key is not present. 134 */ 135 public String get(final String key) { 136 return data.get(key); 137 } 138 139 /** 140 * Remove the element with the specified name. 141 * @param key The name of the element. 142 * @return The previous value of the element. 143 */ 144 public String remove(final String key) { 145 return data.remove(key); 146 } 147 148 /** 149 * Format the Structured data as described in RFC 5424. 150 * 151 * @return The formatted String. 152 */ 153 public String asString() { 154 return asString((MapFormat) null); 155 } 156 157 public String asString(final String format) { 158 try { 159 return asString(EnglishEnums.valueOf(MapFormat.class, format)); 160 } catch (final IllegalArgumentException ex) { 161 return asString(); 162 } 163 } 164 /** 165 * Format the Structured data as described in RFC 5424. 166 * 167 * @param format The format identifier. Ignored in this implementation. 168 * @return The formatted String. 169 */ 170 private String asString(final MapFormat format) { 171 final StringBuilder sb = new StringBuilder(); 172 if (format == null) { 173 appendMap(sb); 174 } else { 175 switch (format) { 176 case XML : { 177 asXML(sb); 178 break; 179 } 180 case JSON : { 181 asJSON(sb); 182 break; 183 } 184 case JAVA : { 185 asJava(sb); 186 break; 187 } 188 default : { 189 appendMap(sb); 190 } 191 } 192 } 193 return sb.toString(); 194 } 195 196 public void asXML(final StringBuilder sb) { 197 sb.append("<Map>\n"); 198 for (final Map.Entry<String, String> entry : data.entrySet()) { 199 sb.append(" <Entry key=\"").append(entry.getKey()).append("\">").append(entry.getValue()) 200 .append("</Entry>\n"); 201 } 202 sb.append("</Map>"); 203 } 204 205 /** 206 * Format the message and return it. 207 * @return the formatted message. 208 */ 209 @Override 210 public String getFormattedMessage() { 211 return asString(); 212 } 213 214 /** 215 * 216 * @param formats An array of Strings that provide extra information about how to format the message. 217 * MapMessage uses the first format specifier it recognizes. The supported formats are XML, JSON, and 218 * JAVA. The default format is key1="value1" key2="value2" as required by RFC 5424 messages. 219 * 220 * @return The formatted message. 221 */ 222 @Override 223 public String getFormattedMessage(final String[] formats) { 224 if (formats == null || formats.length == 0) { 225 return asString(); 226 } 227 for (final String format : formats) { 228 for (final MapFormat mapFormat : MapFormat.values()) { 229 if (mapFormat.name().equalsIgnoreCase(format)) { 230 return asString(mapFormat); 231 } 232 } 233 } 234 return asString(); 235 236 } 237 238 protected void appendMap(final StringBuilder sb) { 239 boolean first = true; 240 for (final Map.Entry<String, String> entry : data.entrySet()) { 241 if (!first) { 242 sb.append(" "); 243 } 244 first = false; 245 sb.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\""); 246 } 247 } 248 249 protected void asJSON(final StringBuilder sb) { 250 boolean first = true; 251 sb.append("{"); 252 for (final Map.Entry<String, String> entry : data.entrySet()) { 253 if (!first) { 254 sb.append(", "); 255 } 256 first = false; 257 sb.append("\"").append(entry.getKey()).append("\":"); 258 sb.append("\"").append(entry.getValue()).append("\""); 259 } 260 sb.append("}"); 261 } 262 263 264 protected void asJava(final StringBuilder sb) { 265 boolean first = true; 266 sb.append("{"); 267 for (final Map.Entry<String, String> entry : data.entrySet()) { 268 if (!first) { 269 sb.append(", "); 270 } 271 first = false; 272 sb.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\""); 273 } 274 sb.append("}"); 275 } 276 277 public MapMessage newInstance(final Map<String, String> map) { 278 return new MapMessage(map); 279 } 280 281 @Override 282 public String toString() { 283 return asString(); 284 } 285 286 @Override 287 public boolean equals(final Object o) { 288 if (this == o) { 289 return true; 290 } 291 if (o == null || this.getClass() != o.getClass()) { 292 return false; 293 } 294 295 final MapMessage that = (MapMessage) o; 296 297 return this.data.equals(that.data); 298 } 299 300 @Override 301 public int hashCode() { 302 return data.hashCode(); 303 } 304 305 /** 306 * Always returns null. 307 * 308 * @return null 309 */ 310 @Override 311 public Throwable getThrowable() { 312 return null; 313 } 314 }