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