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.Locale;
021    import java.util.Map;
022    
023    import org.apache.logging.log4j.message.MapMessage.MapFormat;
024    import org.apache.logging.log4j.util.EnglishEnums;
025    
026    /**
027     * Represents a Message that conforms to RFC 5424 (http://tools.ietf.org/html/rfc5424).
028     */
029    public class StructuredDataMessage extends MapMessage implements MultiformatMessage, Serializable {
030    
031        private static final long serialVersionUID = 1703221292892071920L;
032        private static final int MAX_LENGTH = 32;
033        private static final int HASHVAL = 31;
034    
035        private StructuredDataId id;
036    
037        private String message;
038    
039        private String type;
040    
041        /**
042         * Supported formats.
043         */
044        public enum Format {
045            /** The map should be formatted as XML. */
046            XML,
047            /** Full message format includes the type and message. */
048            FULL
049        }
050    
051        /**
052         * Constructor based on a String id.
053         * @param id The String id.
054         * @param msg The message.
055         * @param type The message type.
056         */
057        public StructuredDataMessage(final String id, final String msg, final String type) {
058            this.id = new StructuredDataId(id, null, null);
059            this.message = msg;
060            this.type = type;
061        }
062        /**
063         * Constructor based on a String id.
064         * @param id The String id.
065         * @param msg The message.
066         * @param type The message type.
067         * @param data The StructuredData map.
068         */
069        public StructuredDataMessage(final String id, final String msg, final String type,
070                                     Map<String, String> data) {
071            super(data);
072            this.id = new StructuredDataId(id, null, null);
073            this.message = msg;
074            this.type = type;
075        }
076    
077        /**
078         * Constructor based on a StructuredDataId.
079         * @param id The StructuredDataId.
080         * @param msg The message.
081         * @param type The message type.
082         */
083        public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
084            this.id = id;
085            this.message = msg;
086            this.type = type;
087        }
088    
089        /**
090         * Constructor based on a StructuredDataId.
091         * @param id The StructuredDataId.
092         * @param msg The message.
093         * @param type The message type.
094         * @param data The StructuredData map.
095         */
096        public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
097                                     Map<String, String> data) {
098            super(data);
099            this.id = id;
100            this.message = msg;
101            this.type = type;
102        }
103    
104    
105        /**
106         * Constructor based on a StructuredDataMessage.
107         * @param msg The StructuredDataMessage.
108         * @param map The StructuredData map.
109         */
110        private StructuredDataMessage(StructuredDataMessage msg, Map<String, String> map) {
111            super(map);
112            this.id = msg.id;
113            this.message = msg.message;
114            this.type = msg.type;
115        }
116    
117    
118        /**
119         * Basic constructor.
120         */
121        protected StructuredDataMessage() {
122    
123        }
124    
125        /**
126         * Returns the supported formats.
127         * @return An array of the supported format names.
128         */
129        @Override
130        public String[] getFormats() {
131            String[] formats = new String[Format.values().length];
132            int i = 0;
133            for (Format format : Format.values()) {
134                formats[i++] = format.name();
135            }
136            return formats;
137        }
138    
139        /**
140         * Returns the id.
141         * @return the StructuredDataId.
142         */
143        public StructuredDataId getId() {
144            return id;
145        }
146    
147        /**
148         * Set the id from a String.
149         * @param id The String id.
150         */
151        protected void setId(String id) {
152            this.id = new StructuredDataId(id, null, null);
153        }
154    
155        /**
156         * Set the id.
157         * @param id The StructuredDataId.
158         */
159        protected void setId(StructuredDataId id) {
160            this.id = id;
161        }
162    
163        /**
164         * Set the type.
165         * @return the type.
166         */
167        public String getType() {
168            return type;
169        }
170    
171        protected void setType(String type) {
172            if (type.length() > MAX_LENGTH) {
173                throw new IllegalArgumentException("Structured data type exceeds maximum length of 32 characters: " + type);
174            }
175            this.type = type;
176        }
177        /**
178         * Returns the message.
179         * @return the message.
180         */
181        @Override
182        public String getFormat() {
183            return message;
184        }
185    
186        protected void setMessageFormat(String msg) {
187            this.message = msg;
188        }
189    
190    
191        @Override
192        protected void validate(String key, String value) {
193            if (value.length() > MAX_LENGTH) {
194                throw new IllegalArgumentException("Structured data values are limited to 32 characters. key: " + key +
195                    " value: " + value);
196            }
197        }
198    
199        /**
200         * Format the Structured data as described in RFC 5424.
201         *
202         * @return The formatted String.
203         */
204        @Override
205        public String asString() {
206            return asString(Format.FULL, null);
207        }
208    
209        /**
210         * Format the Structured data as described in RFC 5424.
211         *
212         * @param format The format identifier. Ignored in this implementation.
213         * @return The formatted String.
214         */
215    
216        @Override
217        public String asString(String format) {
218            try {
219                return asString(EnglishEnums.valueOf(Format.class, format), null);
220            } catch (IllegalArgumentException ex) {
221                return asString();
222            }
223        }
224    
225        /**
226         * Format the Structured data as described in RFC 5424.
227         *
228         * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
229         *                         described in RFC 5424
230         * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
231         *                         will be used.
232         * @return The formatted String.
233         */
234        public final String asString(Format format, StructuredDataId structuredDataId) {
235            StringBuilder sb = new StringBuilder();
236            boolean full = Format.FULL.equals(format);
237            if (full) {
238                String type = getType();
239                if (type == null) {
240                    return sb.toString();
241                }
242                sb.append(getType()).append(" ");
243            }
244            StructuredDataId id = getId();
245            if (id != null) {
246                id = id.makeId(structuredDataId);
247            } else {
248                id = structuredDataId;
249            }
250            if (id == null || id.getName() == null) {
251                return sb.toString();
252            }
253            sb.append("[");
254            sb.append(id);
255            sb.append(" ");
256            appendMap(sb);
257            sb.append("]");
258            if (full) {
259                String msg = getFormat();
260                if (msg != null) {
261                    sb.append(" ").append(msg);
262                }
263            }
264            return sb.toString();
265        }
266    
267        /**
268         * Format the message and return it.
269         * @return the formatted message.
270         */
271        @Override
272        public String getFormattedMessage() {
273            return asString(Format.FULL, null);
274        }
275    
276        /**
277         * Format the message according the the specified format.
278         * @param formats An array of Strings that provide extra information about how to format the message.
279         * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
280         * prepended and the event message to be appended. Specifying any other value will cause only the
281         * StructuredData to be included. The default is "FULL".
282         *
283         * @return the formatted message.
284         */
285        @Override
286        public String getFormattedMessage(String[] formats) {
287            if (formats != null && formats.length > 0) {
288                for (String format : formats) {
289                    if (Format.XML.name().equalsIgnoreCase(format)) {
290                        return asXML();
291                    } else if (Format.FULL.name().equalsIgnoreCase(format)) {
292                        return asString(Format.FULL, null);
293                    }
294                }
295                return asString(null, null);
296            } else {
297                return asString(Format.FULL, null);
298            }
299        }
300    
301        private String asXML() {
302            StringBuilder sb = new StringBuilder();
303            StructuredDataId id = getId();
304            if (id == null || id.getName() == null || type == null) {
305                return sb.toString();
306            }
307            sb.append("<StructuredData>\n");
308            sb.append("<type>").append(type).append("</type>\n");
309            sb.append("<id>").append(id).append("</id>\n");
310            super.asXML(sb);
311            sb.append("</StructuredData>\n");
312            return sb.toString();
313        }
314    
315        @Override
316        public String toString() {
317            return asString(null, null);
318        }
319    
320    
321        @Override
322        public MapMessage newInstance(Map<String, String> map) {
323            return new StructuredDataMessage(this, map);
324        }
325    
326        @Override
327        public boolean equals(Object o) {
328            if (this == o) {
329                return true;
330            }
331            if (o == null || getClass() != o.getClass()) {
332                return false;
333            }
334    
335            StructuredDataMessage that = (StructuredDataMessage) o;
336    
337            if (!super.equals(o)) {
338                return false;
339            }
340            if (type != null ? !type.equals(that.type) : that.type != null) {
341                return false;
342            }
343            if (id != null ? !id.equals(that.id) : that.id != null) {
344                return false;
345            }
346            if (message != null ? !message.equals(that.message) : that.message != null) {
347                return false;
348            }
349    
350            return true;
351        }
352    
353        @Override
354        public int hashCode() {
355            int result = super.hashCode();
356            result = HASHVAL * result + (type != null ? type.hashCode() : 0);
357            result = HASHVAL * result + (id != null ? id.hashCode() : 0);
358            result = HASHVAL * result + (message != null ? message.hashCode() : 0);
359            return result;
360        }
361    }