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    }