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.StringBuilders;
026 import 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 */
036 public 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<String, String>();
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<String, String>(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.
114 * @param key The name of the data item.
115 * @param value The value of the data item.
116 */
117 public void put(final String key, final String value) {
118 if (value == null) {
119 throw new IllegalArgumentException("No value provided for key " + key);
120 }
121 validate(key, value);
122 data.put(key, value);
123 }
124
125 protected void validate(final String key, final String value) {
126
127 }
128
129 /**
130 * Add all the elements from the specified Map.
131 * @param map The Map to add.
132 */
133 public void putAll(final Map<String, String> map) {
134 data.putAll(map);
135 }
136
137 /**
138 * Retrieve the value of the element with the specified key or null if the key is not present.
139 * @param key The name of the element.
140 * @return The value of the element or null if the key is not present.
141 */
142 public String get(final String key) {
143 return data.get(key);
144 }
145
146 /**
147 * Remove the element with the specified name.
148 * @param key The name of the element.
149 * @return The previous value of the element.
150 */
151 public String remove(final String key) {
152 return data.remove(key);
153 }
154
155 /**
156 * Format the Structured data as described in RFC 5424.
157 *
158 * @return The formatted String.
159 */
160 public String asString() {
161 return asString((MapFormat) null);
162 }
163
164 public String asString(final String format) {
165 try {
166 return asString(EnglishEnums.valueOf(MapFormat.class, format));
167 } catch (final IllegalArgumentException ex) {
168 return asString();
169 }
170 }
171 /**
172 * Format the Structured data as described in RFC 5424.
173 *
174 * @param format The format identifier. Ignored in this implementation.
175 * @return The formatted String.
176 */
177 private String asString(final MapFormat format) {
178 final StringBuilder sb = new StringBuilder();
179 if (format == null) {
180 appendMap(sb);
181 } else {
182 switch (format) {
183 case XML : {
184 asXml(sb);
185 break;
186 }
187 case JSON : {
188 asJson(sb);
189 break;
190 }
191 case JAVA : {
192 asJava(sb);
193 break;
194 }
195 default : {
196 appendMap(sb);
197 }
198 }
199 }
200 return sb.toString();
201 }
202
203 public void asXml(final StringBuilder sb) {
204 sb.append("<Map>\n");
205 for (final Map.Entry<String, String> entry : data.entrySet()) {
206 sb.append(" <Entry key=\"").append(entry.getKey()).append("\">").append(entry.getValue())
207 .append("</Entry>\n");
208 }
209 sb.append("</Map>");
210 }
211
212 /**
213 * Format the message and return it.
214 * @return the formatted message.
215 */
216 @Override
217 public String getFormattedMessage() {
218 return asString();
219 }
220
221 /**
222 *
223 * @param formats An array of Strings that provide extra information about how to format the message.
224 * MapMessage uses the first format specifier it recognizes. The supported formats are XML, JSON, and
225 * JAVA. The default format is key1="value1" key2="value2" as required by RFC 5424 messages.
226 *
227 * @return The formatted message.
228 */
229 @Override
230 public String getFormattedMessage(final String[] formats) {
231 if (formats == null || formats.length == 0) {
232 return asString();
233 }
234 for (final String format : formats) {
235 for (final MapFormat mapFormat : MapFormat.values()) {
236 if (mapFormat.name().equalsIgnoreCase(format)) {
237 return asString(mapFormat);
238 }
239 }
240 }
241 return asString();
242
243 }
244
245 protected void appendMap(final StringBuilder sb) {
246 boolean first = true;
247 for (final Map.Entry<String, String> entry : data.entrySet()) {
248 if (!first) {
249 sb.append(' ');
250 }
251 first = false;
252 StringBuilders.appendKeyDqValue(sb, entry);
253 }
254 }
255
256 protected void asJson(final StringBuilder sb) {
257 boolean first = true;
258 sb.append('{');
259 for (final Map.Entry<String, String> entry : data.entrySet()) {
260 if (!first) {
261 sb.append(", ");
262 }
263 first = false;
264 StringBuilders.appendDqValue(sb, entry.getKey()).append(':');
265 StringBuilders.appendDqValue(sb, entry.getValue());
266 }
267 sb.append('}');
268 }
269
270
271 protected void asJava(final StringBuilder sb) {
272 boolean first = true;
273 sb.append('{');
274 for (final Map.Entry<String, String> entry : data.entrySet()) {
275 if (!first) {
276 sb.append(", ");
277 }
278 first = false;
279 StringBuilders.appendKeyDqValue(sb, entry);
280 }
281 sb.append('}');
282 }
283
284 public MapMessage newInstance(final Map<String, String> map) {
285 return new MapMessage(map);
286 }
287
288 @Override
289 public String toString() {
290 return asString();
291 }
292
293 @Override
294 public boolean equals(final Object o) {
295 if (this == o) {
296 return true;
297 }
298 if (o == null || this.getClass() != o.getClass()) {
299 return false;
300 }
301
302 final MapMessage that = (MapMessage) o;
303
304 return this.data.equals(that.data);
305 }
306
307 @Override
308 public int hashCode() {
309 return data.hashCode();
310 }
311
312 /**
313 * Always returns null.
314 *
315 * @return null
316 */
317 @Override
318 public Throwable getThrowable() {
319 return null;
320 }
321 }