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