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;
025
026/**
027 * Represents a Message that consists of a Map.
028 */
029public 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}