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  package org.apache.logging.log4j.message;
18  
19  import java.util.Map;
20  
21  import org.apache.logging.log4j.util.EnglishEnums;
22  
23  /**
24   * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message.
25   *
26   * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
27   */
28  public class StructuredDataMessage extends MapMessage {
29  
30      private static final long serialVersionUID = 1703221292892071920L;
31      private static final int MAX_LENGTH = 32;
32      private static final int HASHVAL = 31;
33  
34      private StructuredDataId id;
35  
36      private String message;
37  
38      private String type;
39  
40      /**
41       * Supported formats.
42       */
43      public enum Format {
44          /** The map should be formatted as XML. */
45          XML,
46          /** Full message format includes the type and message. */
47          FULL
48      }
49  
50      /**
51       * Constructor based on a String id.
52       * @param id The String id.
53       * @param msg The message.
54       * @param type The message type.
55       */
56      public StructuredDataMessage(final String id, final String msg, final String type) {
57          this.id = new StructuredDataId(id, null, null);
58          this.message = msg;
59          this.type = type;
60      }
61      /**
62       * Constructor based on a String id.
63       * @param id The String id.
64       * @param msg The message.
65       * @param type The message type.
66       * @param data The StructuredData map.
67       */
68      public StructuredDataMessage(final String id, final String msg, final String type,
69                                   final Map<String, String> data) {
70          super(data);
71          this.id = new StructuredDataId(id, null, null);
72          this.message = msg;
73          this.type = type;
74      }
75  
76      /**
77       * Constructor based on a StructuredDataId.
78       * @param id The StructuredDataId.
79       * @param msg The message.
80       * @param type The message type.
81       */
82      public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
83          this.id = id;
84          this.message = msg;
85          this.type = type;
86      }
87  
88      /**
89       * Constructor based on a StructuredDataId.
90       * @param id The StructuredDataId.
91       * @param msg The message.
92       * @param type The message type.
93       * @param data The StructuredData map.
94       */
95      public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
96                                   final Map<String, String> data) {
97          super(data);
98          this.id = id;
99          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 }