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
018package org.apache.logging.log4j.message;
019
020import java.util.Map;
021
022import org.apache.logging.log4j.util.EnglishEnums;
023import org.apache.logging.log4j.util.StringBuilders;
024
025/**
026 * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message.
027 * <p>
028 * Thread-safety note: the contents of this message can be modified after construction.
029 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
030 * logged, because it is undefined whether the logged message string will contain the old values or the modified
031 * values.
032 * </p>
033 *
034 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
035 */
036@AsynchronouslyFormattable
037public class StructuredDataMessage extends MapMessage<StructuredDataMessage, String> {
038
039    private static final long serialVersionUID = 1703221292892071920L;
040    private static final int MAX_LENGTH = 32;
041    private static final int HASHVAL = 31;
042
043    private StructuredDataId id;
044
045    private String message;
046
047    private String type;
048
049    private final int maxLength;
050
051    /**
052     * Supported formats.
053     */
054    public enum Format {
055        /** The map should be formatted as XML. */
056        XML,
057        /** Full message format includes the type and message. */
058        FULL
059    }
060
061    /**
062     * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters).
063     * @param id The String id.
064     * @param msg The message.
065     * @param type The message type.
066     */
067    public StructuredDataMessage(final String id, final String msg, final String type) {
068        this(id, msg, type, MAX_LENGTH);
069    }
070
071    /**
072     * Creates a StructuredDataMessage using an ID (user specified max characters), message, and type (user specified
073     * maximum number of characters).
074     * @param id The String id.
075     * @param msg The message.
076     * @param type The message type.
077     * @param maxLength The maximum length of keys;
078     * @since 2.9
079     */
080    public StructuredDataMessage(final String id, final String msg, final String type, final int maxLength) {
081        this.id = new StructuredDataId(id, null, null, maxLength);
082        this.message = msg;
083        this.type = type;
084        this.maxLength = maxLength;
085    }
086    
087    /**
088     * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an
089     * initial map of structured data to include.
090     * @param id The String id.
091     * @param msg The message.
092     * @param type The message type.
093     * @param data The StructuredData map.
094     */
095    public StructuredDataMessage(final String id, final String msg, final String type,
096                                 final Map<String, String> data) {
097        this(id, msg, type, data, MAX_LENGTH);
098    }
099
100    /**
101     * Creates a StructuredDataMessage using an (user specified max characters), message, and type (user specified
102     * maximum number of characters, and an initial map of structured data to include.
103     * @param id The String id.
104     * @param msg The message.
105     * @param type The message type.
106     * @param data The StructuredData map.
107     * @param maxLength The maximum length of keys;
108     * @since 2.9
109     */
110    public StructuredDataMessage(final String id, final String msg, final String type,
111                                 final Map<String, String> data, final int maxLength) {
112        super(data);
113        this.id = new StructuredDataId(id, null, null, maxLength);
114        this.message = msg;
115        this.type = type;
116        this.maxLength = maxLength;
117    }
118
119    /**
120     * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
121     * @param id The StructuredDataId.
122     * @param msg The message.
123     * @param type The message type.
124     */
125    public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
126        this(id, msg, type, MAX_LENGTH);
127    }
128
129    /**
130     * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
131     * @param id The StructuredDataId.
132     * @param msg The message.
133     * @param type The message type.
134     * @param maxLength The maximum length of keys;
135     * @since 2.9
136     */
137    public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, final int maxLength) {
138        this.id = id;
139        this.message = msg;
140        this.type = type;
141        this.maxLength = maxLength;
142    }
143
144    /**
145     * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
146     * of structured data to include.
147     * @param id The StructuredDataId.
148     * @param msg The message.
149     * @param type The message type.
150     * @param data The StructuredData map.
151     */
152    public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
153                                 final Map<String, String> data) {
154        this(id, msg, type, data, MAX_LENGTH);
155    }
156
157    /**
158     * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
159     * of structured data to include.
160     * @param id The StructuredDataId.
161     * @param msg The message.
162     * @param type The message type.
163     * @param data The StructuredData map.
164     * @param maxLength The maximum length of keys;
165     * @since 2.9
166     */
167    public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
168                                 final Map<String, String> data, final int maxLength) {
169        super(data);
170        this.id = id;
171        this.message = msg;
172        this.type = type;
173        this.maxLength = maxLength;
174    }
175
176
177    /**
178     * Constructor based on a StructuredDataMessage.
179     * @param msg The StructuredDataMessage.
180     * @param map The StructuredData map.
181     */
182    private StructuredDataMessage(final StructuredDataMessage msg, final Map<String, String> map) {
183        super(map);
184        this.id = msg.id;
185        this.message = msg.message;
186        this.type = msg.type;
187        this.maxLength = MAX_LENGTH;
188    }
189
190    /**
191     * Basic constructor.
192     */
193    protected StructuredDataMessage() {
194        maxLength = MAX_LENGTH;
195    }
196
197    /**
198     * Returns the supported formats.
199     * @return An array of the supported format names.
200     */
201    @Override
202    public String[] getFormats() {
203        final String[] formats = new String[Format.values().length];
204        int i = 0;
205        for (final Format format : Format.values()) {
206            formats[i++] = format.name();
207        }
208        return formats;
209    }
210
211    /**
212     * Returns this message id.
213     * @return the StructuredDataId.
214     */
215    public StructuredDataId getId() {
216        return id;
217    }
218
219    /**
220     * Sets the id from a String. This ID can be at most 32 characters long.
221     * @param id The String id.
222     */
223    protected void setId(final String id) {
224        this.id = new StructuredDataId(id, null, null);
225    }
226
227    /**
228     * Sets the id.
229     * @param id The StructuredDataId.
230     */
231    protected void setId(final StructuredDataId id) {
232        this.id = id;
233    }
234
235    /**
236     * Returns this message type.
237     * @return the type.
238     */
239    public String getType() {
240        return type;
241    }
242
243    protected void setType(final String type) {
244        if (type.length() > MAX_LENGTH) {
245            throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type);
246        }
247        this.type = type;
248    }
249
250    @Override
251    public void formatTo(final StringBuilder buffer) {
252        asString(Format.FULL, null, buffer);
253    }
254
255    @Override
256    public void formatTo(String[] formats, StringBuilder buffer) {
257        asString(getFormat(formats), null, buffer);
258    }
259
260    /**
261     * Returns the message.
262     * @return the message.
263     */
264    @Override
265    public String getFormat() {
266        return message;
267    }
268
269    protected void setMessageFormat(final String msg) {
270        this.message = msg;
271    }
272
273    /**
274     * Formats the structured data as described in RFC 5424.
275     *
276     * @return The formatted String.
277     */
278    @Override
279    public String asString() {
280        return asString(Format.FULL, null);
281    }
282
283    /**
284     * Formats the structured data as described in RFC 5424.
285     *
286     * @param format The format identifier. Ignored in this implementation.
287     * @return The formatted String.
288     */
289
290    @Override
291    public String asString(final String format) {
292        try {
293            return asString(EnglishEnums.valueOf(Format.class, format), null);
294        } catch (final IllegalArgumentException ex) {
295            return asString();
296        }
297    }
298
299    /**
300     * Formats the structured data as described in RFC 5424.
301     *
302     * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
303     *                         described in RFC 5424
304     * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
305     *                         will be used.
306     * @return The formatted String.
307     */
308    public final String asString(final Format format, final StructuredDataId structuredDataId) {
309        final StringBuilder sb = new StringBuilder();
310        asString(format, structuredDataId, sb);
311        return sb.toString();
312    }
313
314    /**
315     * Formats the structured data as described in RFC 5424.
316     *
317     * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
318     *                         described in RFC 5424
319     * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
320     *                         will be used.
321     * @param sb The StringBuilder to append the formatted message to.
322     */
323    public final void asString(final Format format, final StructuredDataId structuredDataId, final StringBuilder sb) {
324        final boolean full = Format.FULL.equals(format);
325        if (full) {
326            final String myType = getType();
327            if (myType == null) {
328                return;
329            }
330            sb.append(getType()).append(' ');
331        }
332        StructuredDataId sdId = getId();
333        if (sdId != null) {
334            sdId = sdId.makeId(structuredDataId); // returns sdId if structuredDataId is null
335        } else {
336            sdId = structuredDataId;
337        }
338        if (sdId == null || sdId.getName() == null) {
339            return;
340        }
341        if (Format.XML.equals(format)) {
342            asXml(sdId, sb);
343            return;
344        }
345        sb.append('[');
346        StringBuilders.appendValue(sb, sdId); // avoids toString if implements StringBuilderFormattable
347        sb.append(' ');
348        appendMap(sb);
349        sb.append(']');
350        if (full) {
351            final String msg = getFormat();
352            if (msg != null) {
353                sb.append(' ').append(msg);
354            }
355        }
356    }
357
358    private void asXml(StructuredDataId structuredDataId, StringBuilder sb) {
359        sb.append("<StructuredData>\n");
360        sb.append("<type>").append(type).append("</type>\n");
361        sb.append("<id>").append(structuredDataId).append("</id>\n");
362        super.asXml(sb);
363        sb.append("\n</StructuredData>\n");
364    }
365
366    /**
367     * Formats the message and return it.
368     * @return the formatted message.
369     */
370    @Override
371    public String getFormattedMessage() {
372        return asString(Format.FULL, null);
373    }
374
375    /**
376     * Formats the message according the the specified format.
377     * @param formats An array of Strings that provide extra information about how to format the message.
378     * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
379     * prepended and the event message to be appended. Specifying any other value will cause only the
380     * StructuredData to be included. The default is "FULL".
381     *
382     * @return the formatted message.
383     */
384    @Override
385    public String getFormattedMessage(final String[] formats) {
386        return asString(getFormat(formats), null);
387    }
388
389    private Format getFormat(String[] formats) {
390        if (formats != null && formats.length > 0) {
391            for (int i = 0; i < formats.length; i++) {
392                final String format = formats[i];
393                if (Format.XML.name().equalsIgnoreCase(format)) {
394                    return Format.XML;
395                } else if (Format.FULL.name().equalsIgnoreCase(format)) {
396                    return Format.FULL;
397                }
398            }
399            return null;
400        }
401        return Format.FULL;
402    }
403
404    @Override
405    public String toString() {
406        return asString(null, null);
407    }
408
409
410    @Override
411    public StructuredDataMessage newInstance(final Map<String, String> map) {
412        return new StructuredDataMessage(this, map);
413    }
414
415    @Override
416    public boolean equals(final Object o) {
417        if (this == o) {
418            return true;
419        }
420        if (o == null || getClass() != o.getClass()) {
421            return false;
422        }
423
424        final StructuredDataMessage that = (StructuredDataMessage) o;
425
426        if (!super.equals(o)) {
427            return false;
428        }
429        if (type != null ? !type.equals(that.type) : that.type != null) {
430            return false;
431        }
432        if (id != null ? !id.equals(that.id) : that.id != null) {
433            return false;
434        }
435        if (message != null ? !message.equals(that.message) : that.message != null) {
436            return false;
437        }
438
439        return true;
440    }
441
442    @Override
443    public int hashCode() {
444        int result = super.hashCode();
445        result = HASHVAL * result + (type != null ? type.hashCode() : 0);
446        result = HASHVAL * result + (id != null ? id.hashCode() : 0);
447        result = HASHVAL * result + (message != null ? message.hashCode() : 0);
448        return result;
449    }
450
451    @Override
452    protected void validate(final String key, final boolean value) {
453        validateKey(key);
454    }
455
456    /**
457     * @since 2.9
458     */
459    @Override
460    protected void validate(final String key, final byte value) {
461        validateKey(key);
462    }
463
464    /**
465     * @since 2.9
466     */
467    @Override
468    protected void validate(final String key, final char value) {
469        validateKey(key);
470    }
471    
472    /**
473     * @since 2.9
474     */
475    @Override
476    protected void validate(final String key, final double value) {
477        validateKey(key);
478    }
479    
480    /**
481     * @since 2.9
482     */
483    @Override
484    protected void validate(final String key, final float value) {
485        validateKey(key);
486    }
487
488    /**
489     * @since 2.9
490     */
491    @Override
492    protected void validate(final String key, final int value) {
493        validateKey(key);
494    }
495
496    /**
497     * @since 2.9
498     */
499    @Override
500    protected void validate(final String key, final long value) {
501        validateKey(key);
502    }
503
504    /**
505     * @since 2.9
506     */
507    @Override
508    protected void validate(final String key, final Object value) {
509        validateKey(key);
510    }
511
512    /**
513     * @since 2.9
514     */
515    @Override
516    protected void validate(final String key, final short value) {
517        validateKey(key);
518    }
519
520    @Override
521    protected void validate(final String key, final String value) {
522        validateKey(key);
523    }
524
525    protected void validateKey(final String key) {
526        if (maxLength > 0 && key.length() > maxLength) {
527            throw new IllegalArgumentException("Structured data keys are limited to " + maxLength +
528                    " characters. key: " + key);
529        }
530        for (int i = 0; i < key.length(); i++) {
531            final char c = key.charAt(i);
532            if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') {
533                throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" +
534                        "and may not contain a space, =, ], or \"");
535            }
536        }
537    }
538
539}