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