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 */ 017package org.apache.logging.log4j.message; 018 019import java.util.Collections; 020import java.util.Map; 021import java.util.TreeMap; 022 023import org.apache.logging.log4j.util.BiConsumer; 024import org.apache.logging.log4j.util.Chars; 025import org.apache.logging.log4j.util.EnglishEnums; 026import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; 027import org.apache.logging.log4j.util.IndexedStringMap; 028import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable; 029import org.apache.logging.log4j.util.PerformanceSensitive; 030import org.apache.logging.log4j.util.ReadOnlyStringMap; 031import org.apache.logging.log4j.util.SortedArrayStringMap; 032import org.apache.logging.log4j.util.StringBuilders; 033import org.apache.logging.log4j.util.Strings; 034import org.apache.logging.log4j.util.TriConsumer; 035 036/** 037 * Represents a Message that consists of a Map. 038 * <p> 039 * Thread-safety note: the contents of this message can be modified after construction. 040 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is 041 * logged, because it is undefined whether the logged message string will contain the old values or the modified 042 * values. 043 * </p> 044 * <p> 045 * This class was pulled up from {@link StringMapMessage} to allow for Objects as values. 046 * </p> 047 * @param <M> Allow subclasses to use fluent APIs and override methods that return instances of subclasses. 048 * @param <V> The value type 049 */ 050@PerformanceSensitive("allocation") 051@AsynchronouslyFormattable 052public class MapMessage<M extends MapMessage<M, V>, V> implements MultiFormatStringBuilderFormattable { 053 054 private static final long serialVersionUID = -5031471831131487120L; 055 056 /** 057 * When set as the format specifier causes the Map to be formatted as XML. 058 */ 059 public enum MapFormat { 060 061 /** The map should be formatted as XML. */ 062 XML, 063 064 /** The map should be formatted as JSON. */ 065 JSON, 066 067 /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */ 068 JAVA; 069 070 /** 071 * Maps a format name to an {@link MapFormat} while ignoring case. 072 * 073 * @param format a MapFormat name 074 * @return a MapFormat 075 */ 076 public static MapFormat lookupIgnoreCase(final String format) { 077 return XML.name().equalsIgnoreCase(format) ? XML // 078 : JSON.name().equalsIgnoreCase(format) ? JSON // 079 : JAVA.name().equalsIgnoreCase(format) ? JAVA // 080 : null; 081 } 082 083 /** 084 * All {@code MapFormat} names. 085 * 086 * @return All {@code MapFormat} names. 087 */ 088 public static String[] names() { 089 return new String[] {XML.name(), JSON.name(), JAVA.name()}; 090 } 091 } 092 093 private final IndexedStringMap data; 094 095 /** 096 * Constructs a new instance. 097 */ 098 public MapMessage() { 099 this.data = new SortedArrayStringMap(); 100 } 101 102 /** 103 * Constructs a new instance. 104 * 105 * @param initialCapacity the initial capacity. 106 */ 107 public MapMessage(final int initialCapacity) { 108 this.data = new SortedArrayStringMap(initialCapacity); 109 } 110 111 /** 112 * Constructs a new instance based on an existing Map. 113 * @param map The Map. 114 */ 115 public MapMessage(final Map<String, V> map) { 116 this.data = new SortedArrayStringMap(map); 117 } 118 119 @Override 120 public String[] getFormats() { 121 return MapFormat.names(); 122 } 123 124 /** 125 * Returns the data elements as if they were parameters on the logging event. 126 * @return the data elements. 127 */ 128 @Override 129 public Object[] getParameters() { 130 final Object[] result = new Object[data.size()]; 131 for (int i = 0; i < data.size(); i++) { 132 result[i] = data.getValueAt(i); 133 } 134 return result; 135 } 136 137 /** 138 * Returns the message. 139 * @return the message. 140 */ 141 @Override 142 public String getFormat() { 143 return Strings.EMPTY; 144 } 145 146 /** 147 * Returns the message data as an unmodifiable Map. 148 * @return the message data as an unmodifiable map. 149 */ 150 @SuppressWarnings("unchecked") 151 public Map<String, V> getData() { 152 final TreeMap<String, V> result = new TreeMap<>(); // returned map must be sorted 153 for (int i = 0; i < data.size(); i++) { 154 // The Eclipse compiler does not need the typecast to V, but the Oracle compiler sure does. 155 result.put(data.getKeyAt(i), (V) data.getValueAt(i)); 156 } 157 return Collections.unmodifiableMap(result); 158 } 159 160 /** 161 * Returns a read-only view of the message data. 162 * @return the read-only message data. 163 */ 164 public IndexedReadOnlyStringMap getIndexedReadOnlyStringMap() { 165 return data; 166 } 167 168 /** 169 * Clear the data. 170 */ 171 public void clear() { 172 data.clear(); 173 } 174 175 /** 176 * Returns {@code true} if this data structure contains the specified key, {@code false} otherwise. 177 * 178 * @param key the key whose presence to check. May be {@code null}. 179 * @return {@code true} if this data structure contains the specified key, {@code false} otherwise 180 * @since 2.9 181 */ 182 public boolean containsKey(final String key) { 183 return data.containsKey(key); 184 } 185 186 /** 187 * Adds an item to the data Map. 188 * @param key The name of the data item. 189 * @param value The value of the data item. 190 */ 191 public void put(final String key, final String value) { 192 if (value == null) { 193 throw new IllegalArgumentException("No value provided for key " + key); 194 } 195 validate(key, value); 196 data.putValue(key, value); 197 } 198 199 /** 200 * Adds all the elements from the specified Map. 201 * @param map The Map to add. 202 */ 203 public void putAll(final Map<String, String> map) { 204 for (final Map.Entry<String, String> entry : map.entrySet()) { 205 data.putValue(entry.getKey(), entry.getValue()); 206 } 207 } 208 209 /** 210 * Retrieves the value of the element with the specified key or null if the key is not present. 211 * @param key The name of the element. 212 * @return The value of the element or null if the key is not present. 213 */ 214 public String get(final String key) { 215 Object result = data.getValue(key); 216 return ParameterFormatter.deepToString(result); 217 } 218 219 /** 220 * Removes the element with the specified name. 221 * @param key The name of the element. 222 * @return The previous value of the element. 223 */ 224 public String remove(final String key) { 225 final String result = get(key); 226 data.remove(key); 227 return result; 228 } 229 230 /** 231 * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>. 232 * 233 * @return The formatted String. 234 */ 235 public String asString() { 236 return format((MapFormat) null, new StringBuilder()).toString(); 237 } 238 239 /** 240 * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>. 241 * 242 * @param format The format identifier. 243 * @return The formatted String. 244 */ 245 public String asString(final String format) { 246 try { 247 return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()).toString(); 248 } catch (final IllegalArgumentException ex) { 249 return asString(); 250 } 251 } 252 253 /** 254 * Performs the given action for each key-value pair in this data structure 255 * until all entries have been processed or the action throws an exception. 256 * <p> 257 * Some implementations may not support structural modifications (adding new elements or removing elements) while 258 * iterating over the contents. In such implementations, attempts to add or remove elements from the 259 * {@code BiConsumer}'s {@link BiConsumer#accept(Object, Object)} accept} method may cause a 260 * {@code ConcurrentModificationException} to be thrown. 261 * </p> 262 * 263 * @param action The action to be performed for each key-value pair in this collection 264 * @param <CV> type of the consumer value 265 * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications 266 * to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or 267 * {@link #forEach(TriConsumer, Object)}. 268 * @see ReadOnlyStringMap#forEach(BiConsumer) 269 * @since 2.9 270 */ 271 public <CV> void forEach(final BiConsumer<String, ? super CV> action) { 272 data.forEach(action); 273 } 274 275 /** 276 * Performs the given action for each key-value pair in this data structure 277 * until all entries have been processed or the action throws an exception. 278 * <p> 279 * The third parameter lets callers pass in a stateful object to be modified with the key-value pairs, 280 * so the TriConsumer implementation itself can be stateless and potentially reusable. 281 * </p> 282 * <p> 283 * Some implementations may not support structural modifications (adding new elements or removing elements) while 284 * iterating over the contents. In such implementations, attempts to add or remove elements from the 285 * {@code TriConsumer}'s {@link TriConsumer#accept(Object, Object, Object) accept} method may cause a 286 * {@code ConcurrentModificationException} to be thrown. 287 * </p> 288 * 289 * @param action The action to be performed for each key-value pair in this collection 290 * @param state the object to be passed as the third parameter to each invocation on the specified 291 * triconsumer 292 * @param <CV> type of the consumer value 293 * @param <S> type of the third parameter 294 * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications 295 * to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or 296 * {@link #forEach(TriConsumer, Object)}. 297 * @see ReadOnlyStringMap#forEach(TriConsumer, Object) 298 * @since 2.9 299 */ 300 public <CV, S> void forEach(final TriConsumer<String, ? super CV, S> action, final S state) { 301 data.forEach(action, state); 302 } 303 304 /** 305 * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>. 306 * 307 * @param format The format identifier. 308 * @return The formatted String. 309 */ 310 private StringBuilder format(final MapFormat format, final StringBuilder sb) { 311 if (format == null) { 312 appendMap(sb); 313 } else { 314 switch (format) { 315 case XML : { 316 asXml(sb); 317 break; 318 } 319 case JSON : { 320 asJson(sb); 321 break; 322 } 323 case JAVA : { 324 asJava(sb); 325 break; 326 } 327 default : { 328 appendMap(sb); 329 } 330 } 331 } 332 return sb; 333 } 334 335 /** 336 * Formats this message as an XML fragment String into the given builder. 337 * 338 * @param sb format into this builder. 339 */ 340 public void asXml(final StringBuilder sb) { 341 sb.append("<Map>\n"); 342 for (int i = 0; i < data.size(); i++) { 343 sb.append(" <Entry key=\"") 344 .append(data.getKeyAt(i)) 345 .append("\">"); 346 int size = sb.length(); 347 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); 348 StringBuilders.escapeXml(sb, size); 349 sb.append("</Entry>\n"); 350 } 351 sb.append("</Map>"); 352 } 353 354 /** 355 * Formats the message and return it. 356 * @return the formatted message. 357 */ 358 @Override 359 public String getFormattedMessage() { 360 return asString(); 361 } 362 363 /** 364 * 365 * @param formats 366 * An array of Strings that provide extra information about how to format the message. MapMessage uses 367 * the first format specifier it recognizes. The supported formats are XML, JSON, and JAVA. The default 368 * format is key1="value1" key2="value2" as required by <a href="https://tools.ietf.org/html/rfc5424">RFC 369 * 5424</a> messages. 370 * 371 * @return The formatted message. 372 */ 373 @Override 374 public String getFormattedMessage(final String[] formats) { 375 return format(getFormat(formats), new StringBuilder()).toString(); 376 } 377 378 private MapFormat getFormat(final String[] formats) { 379 if (formats == null || formats.length == 0) { 380 return null; 381 } 382 for (int i = 0; i < formats.length; i++) { 383 final MapFormat mapFormat = MapFormat.lookupIgnoreCase(formats[i]); 384 if (mapFormat != null) { 385 return mapFormat; 386 } 387 } 388 return null; 389 } 390 391 protected void appendMap(final StringBuilder sb) { 392 for (int i = 0; i < data.size(); i++) { 393 if (i > 0) { 394 sb.append(' '); 395 } 396 sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE); 397 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); 398 sb.append(Chars.DQUOTE); 399 } 400 } 401 402 protected void asJson(final StringBuilder sb) { 403 sb.append('{'); 404 for (int i = 0; i < data.size(); i++) { 405 if (i > 0) { 406 sb.append(", "); 407 } 408 sb.append(Chars.DQUOTE); 409 int start = sb.length(); 410 sb.append(data.getKeyAt(i)); 411 StringBuilders.escapeJson(sb, start); 412 sb.append(Chars.DQUOTE).append(':').append(Chars.DQUOTE); 413 start = sb.length(); 414 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); 415 StringBuilders.escapeJson(sb, start); 416 sb.append(Chars.DQUOTE); 417 } 418 sb.append('}'); 419 } 420 421 422 protected void asJava(final StringBuilder sb) { 423 sb.append('{'); 424 for (int i = 0; i < data.size(); i++) { 425 if (i > 0) { 426 sb.append(", "); 427 } 428 sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE); 429 ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); 430 sb.append(Chars.DQUOTE); 431 } 432 sb.append('}'); 433 } 434 435 /** 436 * Constructs a new instance based on an existing Map. 437 * @param map The Map. 438 * @return A new MapMessage 439 */ 440 @SuppressWarnings("unchecked") 441 public M newInstance(final Map<String, V> map) { 442 return (M) new MapMessage<>(map); 443 } 444 445 @Override 446 public String toString() { 447 return asString(); 448 } 449 450 @Override 451 public void formatTo(final StringBuilder buffer) { 452 format((MapFormat) null, buffer); 453 } 454 455 @Override 456 public void formatTo(String[] formats, StringBuilder buffer) { 457 format(getFormat(formats), buffer); 458 } 459 460 @Override 461 public boolean equals(final Object o) { 462 if (this == o) { 463 return true; 464 } 465 if (o == null || this.getClass() != o.getClass()) { 466 return false; 467 } 468 469 final MapMessage<?, ?> that = (MapMessage<?, ?>) o; 470 471 return this.data.equals(that.data); 472 } 473 474 @Override 475 public int hashCode() { 476 return data.hashCode(); 477 } 478 479 /** 480 * Always returns null. 481 * 482 * @return null 483 */ 484 @Override 485 public Throwable getThrowable() { 486 return null; 487 } 488 /** 489 * @since 2.9 490 */ 491 protected void validate(final String key, final boolean value) { 492 // do nothing 493 } 494 495 /** 496 * @since 2.9 497 */ 498 protected void validate(final String key, final byte value) { 499 // do nothing 500 } 501 502 /** 503 * @since 2.9 504 */ 505 protected void validate(final String key, final char value) { 506 // do nothing 507 } 508 509 /** 510 * @since 2.9 511 */ 512 protected void validate(final String key, final double value) { 513 // do nothing 514 } 515 516 /** 517 * @since 2.9 518 */ 519 protected void validate(final String key, final float value) { 520 // do nothing 521 } 522 523 /** 524 * @since 2.9 525 */ 526 protected void validate(final String key, final int value) { 527 // do nothing 528 } 529 530 /** 531 * @since 2.9 532 */ 533 protected void validate(final String key, final long value) { 534 // do nothing 535 } 536 537 /** 538 * @since 2.9 539 */ 540 protected void validate(final String key, final Object value) { 541 // do nothing 542 } 543 544 /** 545 * @since 2.9 546 */ 547 protected void validate(final String key, final short value) { 548 // do nothing 549 } 550 551 /** 552 * @since 2.9 553 */ 554 protected void validate(final String key, final String value) { 555 // do nothing 556 } 557 558 /** 559 * Adds an item to the data Map. 560 * @param key The name of the data item. 561 * @param value The value of the data item. 562 * @return this object 563 * @since 2.9 564 */ 565 @SuppressWarnings("unchecked") 566 public M with(final String key, final boolean value) { 567 validate(key, value); 568 data.putValue(key, value); 569 return (M) this; 570 } 571 572 /** 573 * Adds an item to the data Map. 574 * @param key The name of the data item. 575 * @param value The value of the data item. 576 * @return this object 577 * @since 2.9 578 */ 579 @SuppressWarnings("unchecked") 580 public M with(final String key, final byte value) { 581 validate(key, value); 582 data.putValue(key, value); 583 return (M) this; 584 } 585 586 /** 587 * Adds an item to the data Map. 588 * @param key The name of the data item. 589 * @param value The value of the data item. 590 * @return this object 591 * @since 2.9 592 */ 593 @SuppressWarnings("unchecked") 594 public M with(final String key, final char value) { 595 validate(key, value); 596 data.putValue(key, value); 597 return (M) this; 598 } 599 600 601 /** 602 * Adds an item to the data Map. 603 * @param key The name of the data item. 604 * @param value The value of the data item. 605 * @return this object 606 * @since 2.9 607 */ 608 @SuppressWarnings("unchecked") 609 public M with(final String key, final double value) { 610 validate(key, value); 611 data.putValue(key, value); 612 return (M) this; 613 } 614 615 /** 616 * Adds an item to the data Map. 617 * @param key The name of the data item. 618 * @param value The value of the data item. 619 * @return this object 620 * @since 2.9 621 */ 622 @SuppressWarnings("unchecked") 623 public M with(final String key, final float value) { 624 validate(key, value); 625 data.putValue(key, value); 626 return (M) this; 627 } 628 629 /** 630 * Adds an item to the data Map. 631 * @param key The name of the data item. 632 * @param value The value of the data item. 633 * @return this object 634 * @since 2.9 635 */ 636 @SuppressWarnings("unchecked") 637 public M with(final String key, final int value) { 638 validate(key, value); 639 data.putValue(key, value); 640 return (M) this; 641 } 642 643 /** 644 * Adds an item to the data Map. 645 * @param key The name of the data item. 646 * @param value The value of the data item. 647 * @return this object 648 * @since 2.9 649 */ 650 @SuppressWarnings("unchecked") 651 public M with(final String key, final long value) { 652 validate(key, value); 653 data.putValue(key, value); 654 return (M) this; 655 } 656 657 /** 658 * Adds an item to the data Map. 659 * @param key The name of the data item. 660 * @param value The value of the data item. 661 * @return this object 662 * @since 2.9 663 */ 664 @SuppressWarnings("unchecked") 665 public M with(final String key, final Object value) { 666 validate(key, value); 667 data.putValue(key, value); 668 return (M) this; 669 } 670 671 /** 672 * Adds an item to the data Map. 673 * @param key The name of the data item. 674 * @param value The value of the data item. 675 * @return this object 676 * @since 2.9 677 */ 678 @SuppressWarnings("unchecked") 679 public M with(final String key, final short value) { 680 validate(key, value); 681 data.putValue(key, value); 682 return (M) this; 683 } 684 685 /** 686 * Adds an item to the data Map in fluent style. 687 * @param key The name of the data item. 688 * @param value The value of the data item. 689 * @return {@code this} 690 */ 691 @SuppressWarnings("unchecked") 692 public M with(final String key, final String value) { 693 put(key, value); 694 return (M) this; 695 } 696 697}