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