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