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