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    }