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         * Set 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         * Set the id.
156         * @param id The StructuredDataId.
157         */
158        protected void setId(final StructuredDataId id) {
159            this.id = id;
160        }
161    
162        /**
163         * Set 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         * Returns the message.
178         * @return the message.
179         */
180        @Override
181        public String getFormat() {
182            return message;
183        }
184    
185        protected void setMessageFormat(final String msg) {
186            this.message = msg;
187        }
188    
189    
190        @Override
191        protected void validate(final String key, final String value) {
192            validateKey(key);
193        }
194    
195        private void validateKey(final String key) {
196            if (key.length() > MAX_LENGTH) {
197                throw new IllegalArgumentException("Structured data keys are limited to 32 characters. key: " + key);
198            }
199            char[] chars = key.toCharArray();
200            for (char c : chars) {
201                if (c < 33 || c > 126 || c == '=' || c == ']' || c == '"') {
202                    throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters and may not contain a space, =, ], or \"");
203                }
204            }
205        }
206    
207        /**
208         * Format the Structured data as described in RFC 5424.
209         *
210         * @return The formatted String.
211         */
212        @Override
213        public String asString() {
214            return asString(Format.FULL, null);
215        }
216    
217        /**
218         * Format the Structured data as described in RFC 5424.
219         *
220         * @param format The format identifier. Ignored in this implementation.
221         * @return The formatted String.
222         */
223    
224        @Override
225        public String asString(final String format) {
226            try {
227                return asString(EnglishEnums.valueOf(Format.class, format), null);
228            } catch (final IllegalArgumentException ex) {
229                return asString();
230            }
231        }
232    
233        /**
234         * Format the Structured data as described in RFC 5424.
235         *
236         * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
237         *                         described in RFC 5424
238         * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
239         *                         will be used.
240         * @return The formatted String.
241         */
242        public final String asString(final Format format, final StructuredDataId structuredDataId) {
243            final StringBuilder sb = new StringBuilder();
244            final boolean full = Format.FULL.equals(format);
245            if (full) {
246                final String type = getType();
247                if (type == null) {
248                    return sb.toString();
249                }
250                sb.append(getType()).append(" ");
251            }
252            StructuredDataId id = getId();
253            if (id != null) {
254                id = id.makeId(structuredDataId);
255            } else {
256                id = structuredDataId;
257            }
258            if (id == null || id.getName() == null) {
259                return sb.toString();
260            }
261            sb.append("[");
262            sb.append(id);
263            sb.append(" ");
264            appendMap(sb);
265            sb.append("]");
266            if (full) {
267                final String msg = getFormat();
268                if (msg != null) {
269                    sb.append(" ").append(msg);
270                }
271            }
272            return sb.toString();
273        }
274    
275        /**
276         * Format the message and return it.
277         * @return the formatted message.
278         */
279        @Override
280        public String getFormattedMessage() {
281            return asString(Format.FULL, null);
282        }
283    
284        /**
285         * Format the message according the the specified format.
286         * @param formats An array of Strings that provide extra information about how to format the message.
287         * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
288         * prepended and the event message to be appended. Specifying any other value will cause only the
289         * StructuredData to be included. The default is "FULL".
290         *
291         * @return the formatted message.
292         */
293        @Override
294        public String getFormattedMessage(final String[] formats) {
295            if (formats != null && formats.length > 0) {
296                for (final String format : formats) {
297                    if (Format.XML.name().equalsIgnoreCase(format)) {
298                        return asXML();
299                    } else if (Format.FULL.name().equalsIgnoreCase(format)) {
300                        return asString(Format.FULL, null);
301                    }
302                }
303                return asString(null, null);
304            } else {
305                return asString(Format.FULL, null);
306            }
307        }
308    
309        private String asXML() {
310            final StringBuilder sb = new StringBuilder();
311            final StructuredDataId id = getId();
312            if (id == null || id.getName() == null || type == null) {
313                return sb.toString();
314            }
315            sb.append("<StructuredData>\n");
316            sb.append("<type>").append(type).append("</type>\n");
317            sb.append("<id>").append(id).append("</id>\n");
318            super.asXML(sb);
319            sb.append("</StructuredData>\n");
320            return sb.toString();
321        }
322    
323        @Override
324        public String toString() {
325            return asString(null, null);
326        }
327    
328    
329        @Override
330        public MapMessage newInstance(final Map<String, String> map) {
331            return new StructuredDataMessage(this, map);
332        }
333    
334        @Override
335        public boolean equals(final Object o) {
336            if (this == o) {
337                return true;
338            }
339            if (o == null || getClass() != o.getClass()) {
340                return false;
341            }
342    
343            final StructuredDataMessage that = (StructuredDataMessage) o;
344    
345            if (!super.equals(o)) {
346                return false;
347            }
348            if (type != null ? !type.equals(that.type) : that.type != null) {
349                return false;
350            }
351            if (id != null ? !id.equals(that.id) : that.id != null) {
352                return false;
353            }
354            if (message != null ? !message.equals(that.message) : that.message != null) {
355                return false;
356            }
357    
358            return true;
359        }
360    
361        @Override
362        public int hashCode() {
363            int result = super.hashCode();
364            result = HASHVAL * result + (type != null ? type.hashCode() : 0);
365            result = HASHVAL * result + (id != null ? id.hashCode() : 0);
366            result = HASHVAL * result + (message != null ? message.hashCode() : 0);
367            return result;
368        }
369    }