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 {@link 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 /** 490 * Default implementation does nothing. 491 * 492 * @since 2.9 493 */ 494 protected void validate(final String key, final boolean value) { 495 // do nothing 496 } 497 498 /** 499 * Default implementation does nothing. 500 * 501 * @since 2.9 502 */ 503 protected void validate(final String key, final byte value) { 504 // do nothing 505 } 506 507 /** 508 * Default implementation does nothing. 509 * 510 * @since 2.9 511 */ 512 protected void validate(final String key, final char value) { 513 // do nothing 514 } 515 516 /** 517 * Default implementation does nothing. 518 * 519 * @since 2.9 520 */ 521 protected void validate(final String key, final double value) { 522 // do nothing 523 } 524 525 /** 526 * Default implementation does nothing. 527 * 528 * @since 2.9 529 */ 530 protected void validate(final String key, final float value) { 531 // do nothing 532 } 533 534 /** 535 * Default implementation does nothing. 536 * 537 * @since 2.9 538 */ 539 protected void validate(final String key, final int value) { 540 // do nothing 541 } 542 543 /** 544 * Default implementation does nothing. 545 * 546 * @since 2.9 547 */ 548 protected void validate(final String key, final long value) { 549 // do nothing 550 } 551 552 /** 553 * Default implementation does nothing. 554 * 555 * @since 2.9 556 */ 557 protected void validate(final String key, final Object value) { 558 // do nothing 559 } 560 561 /** 562 * Default implementation does nothing. 563 * 564 * @since 2.9 565 */ 566 protected void validate(final String key, final short value) { 567 // do nothing 568 } 569 570 /** 571 * Default implementation does nothing. 572 * 573 * @since 2.9 574 */ 575 protected void validate(final String key, final String value) { 576 // do nothing 577 } 578 579 /** 580 * Adds an item to the data Map. 581 * @param key The name of the data item. 582 * @param value The value of the data item. 583 * @return this object 584 * @since 2.9 585 */ 586 @SuppressWarnings("unchecked") 587 public M with(final String key, final boolean value) { 588 validate(key, value); 589 data.putValue(key, value); 590 return (M) this; 591 } 592 593 /** 594 * Adds an item to the data Map. 595 * @param key The name of the data item. 596 * @param value The value of the data item. 597 * @return this object 598 * @since 2.9 599 */ 600 @SuppressWarnings("unchecked") 601 public M with(final String key, final byte value) { 602 validate(key, value); 603 data.putValue(key, value); 604 return (M) this; 605 } 606 607 /** 608 * Adds an item to the data Map. 609 * @param key The name of the data item. 610 * @param value The value of the data item. 611 * @return this object 612 * @since 2.9 613 */ 614 @SuppressWarnings("unchecked") 615 public M with(final String key, final char value) { 616 validate(key, value); 617 data.putValue(key, value); 618 return (M) this; 619 } 620 621 622 /** 623 * Adds an item to the data Map. 624 * @param key The name of the data item. 625 * @param value The value of the data item. 626 * @return this object 627 * @since 2.9 628 */ 629 @SuppressWarnings("unchecked") 630 public M with(final String key, final double value) { 631 validate(key, value); 632 data.putValue(key, value); 633 return (M) this; 634 } 635 636 /** 637 * Adds an item to the data Map. 638 * @param key The name of the data item. 639 * @param value The value of the data item. 640 * @return this object 641 * @since 2.9 642 */ 643 @SuppressWarnings("unchecked") 644 public M with(final String key, final float value) { 645 validate(key, value); 646 data.putValue(key, value); 647 return (M) this; 648 } 649 650 /** 651 * Adds an item to the data Map. 652 * @param key The name of the data item. 653 * @param value The value of the data item. 654 * @return this object 655 * @since 2.9 656 */ 657 @SuppressWarnings("unchecked") 658 public M with(final String key, final int value) { 659 validate(key, value); 660 data.putValue(key, value); 661 return (M) this; 662 } 663 664 /** 665 * Adds an item to the data Map. 666 * @param key The name of the data item. 667 * @param value The value of the data item. 668 * @return this object 669 * @since 2.9 670 */ 671 @SuppressWarnings("unchecked") 672 public M with(final String key, final long value) { 673 validate(key, value); 674 data.putValue(key, value); 675 return (M) this; 676 } 677 678 /** 679 * Adds an item to the data Map. 680 * @param key The name of the data item. 681 * @param value The value of the data item. 682 * @return this object 683 * @since 2.9 684 */ 685 @SuppressWarnings("unchecked") 686 public M with(final String key, final Object value) { 687 validate(key, value); 688 data.putValue(key, value); 689 return (M) this; 690 } 691 692 /** 693 * Adds an item to the data Map. 694 * @param key The name of the data item. 695 * @param value The value of the data item. 696 * @return this object 697 * @since 2.9 698 */ 699 @SuppressWarnings("unchecked") 700 public M with(final String key, final short value) { 701 validate(key, value); 702 data.putValue(key, value); 703 return (M) this; 704 } 705 706 /** 707 * Adds an item to the data Map in fluent style. 708 * @param key The name of the data item. 709 * @param value The value of the data item. 710 * @return {@code this} 711 */ 712 @SuppressWarnings("unchecked") 713 public M with(final String key, final String value) { 714 put(key, value); 715 return (M) this; 716 } 717 718}