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