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