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