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 */
017package org.apache.logging.log4j.message;
018
019import java.util.Map;
020
021import 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 */
028public 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}