View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.message;
19  
20  import java.util.Map;
21  
22  import org.apache.logging.log4j.util.EnglishEnums;
23  import org.apache.logging.log4j.util.StringBuilderFormattable;
24  import org.apache.logging.log4j.util.StringBuilders;
25  
26  /**
27   * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message.
28   * <p>
29   * Thread-safety note: the contents of this message can be modified after construction.
30   * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
31   * logged, because it is undefined whether the logged message string will contain the old values or the modified
32   * values.
33   * </p>
34   *
35   * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
36   */
37  @AsynchronouslyFormattable
38  public class StructuredDataMessage extends MapMessage implements StringBuilderFormattable {
39  
40      private static final long serialVersionUID = 1703221292892071920L;
41      private static final int MAX_LENGTH = 32;
42      private static final int HASHVAL = 31;
43  
44      private StructuredDataId id;
45  
46      private String message;
47  
48      private String type;
49  
50      /**
51       * Supported formats.
52       */
53      public enum Format {
54          /** The map should be formatted as XML. */
55          XML,
56          /** Full message format includes the type and message. */
57          FULL
58      }
59  
60      /**
61       * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters).
62       * @param id The String id.
63       * @param msg The message.
64       * @param type The message type.
65       */
66      public StructuredDataMessage(final String id, final String msg, final String type) {
67          this.id = new StructuredDataId(id, null, null);
68          this.message = msg;
69          this.type = type;
70      }
71      /**
72       * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an
73       * initial map of structured data to include.
74       * @param id The String id.
75       * @param msg The message.
76       * @param type The message type.
77       * @param data The StructuredData map.
78       */
79      public StructuredDataMessage(final String id, final String msg, final String type,
80                                   final Map<String, String> data) {
81          super(data);
82          this.id = new StructuredDataId(id, null, null);
83          this.message = msg;
84          this.type = type;
85      }
86  
87      /**
88       * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
89       * @param id The StructuredDataId.
90       * @param msg The message.
91       * @param type The message type.
92       */
93      public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
94          this.id = id;
95          this.message = msg;
96          this.type = type;
97      }
98  
99      /**
100      * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
101      * of structured data to include.
102      * @param id The StructuredDataId.
103      * @param msg The message.
104      * @param type The message type.
105      * @param data The StructuredData map.
106      */
107     public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
108                                  final Map<String, String> data) {
109         super(data);
110         this.id = id;
111         this.message = msg;
112         this.type = type;
113     }
114 
115 
116     /**
117      * Constructor based on a StructuredDataMessage.
118      * @param msg The StructuredDataMessage.
119      * @param map The StructuredData map.
120      */
121     private StructuredDataMessage(final StructuredDataMessage msg, final Map<String, String> map) {
122         super(map);
123         this.id = msg.id;
124         this.message = msg.message;
125         this.type = msg.type;
126     }
127 
128 
129     /**
130      * Basic constructor.
131      */
132     protected StructuredDataMessage() {
133 
134     }
135 
136     /**
137      * Add an item to the data Map in fluent style.
138      * @param key The name of the data item.
139      * @param value The value of the data item.
140      * @return {@code this}
141      */
142     @Override
143     public StructuredDataMessage with(final String key, final String value) {
144         put(key, value);
145         return this;
146     }
147 
148     /**
149      * Returns the supported formats.
150      * @return An array of the supported format names.
151      */
152     @Override
153     public String[] getFormats() {
154         final String[] formats = new String[Format.values().length];
155         int i = 0;
156         for (final Format format : Format.values()) {
157             formats[i++] = format.name();
158         }
159         return formats;
160     }
161 
162     /**
163      * Returns this message id.
164      * @return the StructuredDataId.
165      */
166     public StructuredDataId getId() {
167         return id;
168     }
169 
170     /**
171      * Sets the id from a String. This ID can be at most 32 characters long.
172      * @param id The String id.
173      */
174     protected void setId(final String id) {
175         this.id = new StructuredDataId(id, null, null);
176     }
177 
178     /**
179      * Sets the id.
180      * @param id The StructuredDataId.
181      */
182     protected void setId(final StructuredDataId id) {
183         this.id = id;
184     }
185 
186     /**
187      * Returns this message type.
188      * @return the type.
189      */
190     public String getType() {
191         return type;
192     }
193 
194     protected void setType(final String type) {
195         if (type.length() > MAX_LENGTH) {
196             throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type);
197         }
198         this.type = type;
199     }
200 
201     @Override
202     public void formatTo(final StringBuilder buffer) {
203         asString(Format.FULL, null, buffer);
204     }
205 
206     /**
207      * Returns the message.
208      * @return the message.
209      */
210     @Override
211     public String getFormat() {
212         return message;
213     }
214 
215     protected void setMessageFormat(final String msg) {
216         this.message = msg;
217     }
218 
219 
220     @Override
221     protected void validate(final String key, final String value) {
222         validateKey(key);
223     }
224 
225     private void validateKey(final String key) {
226         if (key.length() > MAX_LENGTH) {
227             throw new IllegalArgumentException("Structured data keys are limited to 32 characters. key: " + key);
228         }
229         for (int i = 0; i < key.length(); i++) {
230             final char c = key.charAt(i);
231             if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') {
232                 throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" +
233                         "and may not contain a space, =, ], or \"");
234             }
235         }
236     }
237 
238     /**
239      * Formats the structured data as described in RFC 5424.
240      *
241      * @return The formatted String.
242      */
243     @Override
244     public String asString() {
245         return asString(Format.FULL, null);
246     }
247 
248     /**
249      * Formats the structured data as described in RFC 5424.
250      *
251      * @param format The format identifier. Ignored in this implementation.
252      * @return The formatted String.
253      */
254 
255     @Override
256     public String asString(final String format) {
257         try {
258             return asString(EnglishEnums.valueOf(Format.class, format), null);
259         } catch (final IllegalArgumentException ex) {
260             return asString();
261         }
262     }
263 
264     /**
265      * Formats the structured data as described in RFC 5424.
266      *
267      * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
268      *                         described in RFC 5424
269      * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
270      *                         will be used.
271      * @return The formatted String.
272      */
273     public final String asString(final Format format, final StructuredDataId structuredDataId) {
274         final StringBuilder sb = new StringBuilder();
275         asString(format, structuredDataId, sb);
276         return sb.toString();
277     }
278 
279     /**
280      * Formats the structured data as described in RFC 5424.
281      *
282      * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
283      *                         described in RFC 5424
284      * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
285      *                         will be used.
286      * @param sb The StringBuilder to append the formatted message to.
287      */
288     public final void asString(final Format format, final StructuredDataId structuredDataId, final StringBuilder sb) {
289         final boolean full = Format.FULL.equals(format);
290         if (full) {
291             final String myType = getType();
292             if (myType == null) {
293                 return;
294             }
295             sb.append(getType()).append(' ');
296         }
297         StructuredDataId sdId = getId();
298         if (sdId != null) {
299             sdId = sdId.makeId(structuredDataId); // returns sdId if structuredDataId is null
300         } else {
301             sdId = structuredDataId;
302         }
303         if (sdId == null || sdId.getName() == null) {
304             return;
305         }
306         sb.append('[');
307         StringBuilders.appendValue(sb, sdId); // avoids toString if implements StringBuilderFormattable
308         sb.append(' ');
309         appendMap(sb);
310         sb.append(']');
311         if (full) {
312             final String msg = getFormat();
313             if (msg != null) {
314                 sb.append(' ').append(msg);
315             }
316         }
317     }
318 
319     /**
320      * Formats the message and return it.
321      * @return the formatted message.
322      */
323     @Override
324     public String getFormattedMessage() {
325         return asString(Format.FULL, null);
326     }
327 
328     /**
329      * Formats the message according the the specified format.
330      * @param formats An array of Strings that provide extra information about how to format the message.
331      * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
332      * prepended and the event message to be appended. Specifying any other value will cause only the
333      * StructuredData to be included. The default is "FULL".
334      *
335      * @return the formatted message.
336      */
337     @Override
338     public String getFormattedMessage(final String[] formats) {
339         if (formats != null && formats.length > 0) {
340             for (int i = 0; i < formats.length; i++) {
341                 final String format = formats[i];
342                 if (Format.XML.name().equalsIgnoreCase(format)) {
343                     return asXml();
344                 } else if (Format.FULL.name().equalsIgnoreCase(format)) {
345                     return asString(Format.FULL, null);
346                 }
347             }
348             return asString(null, null);
349         }
350         return asString(Format.FULL, null);
351     }
352 
353     private String asXml() {
354         final StringBuilder sb = new StringBuilder();
355         final StructuredDataId sdId = getId();
356         if (sdId == null || sdId.getName() == null || type == null) {
357             return sb.toString();
358         }
359         sb.append("<StructuredData>\n");
360         sb.append("<type>").append(type).append("</type>\n");
361         sb.append("<id>").append(sdId).append("</id>\n");
362         super.asXml(sb);
363         sb.append("</StructuredData>\n");
364         return sb.toString();
365     }
366 
367     @Override
368     public String toString() {
369         return asString(null, null);
370     }
371 
372 
373     @Override
374     public MapMessage newInstance(final Map<String, String> map) {
375         return new StructuredDataMessage(this, map);
376     }
377 
378     @Override
379     public boolean equals(final Object o) {
380         if (this == o) {
381             return true;
382         }
383         if (o == null || getClass() != o.getClass()) {
384             return false;
385         }
386 
387         final StructuredDataMessage that = (StructuredDataMessage) o;
388 
389         if (!super.equals(o)) {
390             return false;
391         }
392         if (type != null ? !type.equals(that.type) : that.type != null) {
393             return false;
394         }
395         if (id != null ? !id.equals(that.id) : that.id != null) {
396             return false;
397         }
398         if (message != null ? !message.equals(that.message) : that.message != null) {
399             return false;
400         }
401 
402         return true;
403     }
404 
405     @Override
406     public int hashCode() {
407         int result = super.hashCode();
408         result = HASHVAL * result + (type != null ? type.hashCode() : 0);
409         result = HASHVAL * result + (id != null ? id.hashCode() : 0);
410         result = HASHVAL * result + (message != null ? message.hashCode() : 0);
411         return result;
412     }
413 }