001/* 002 * Copyright 2022-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2022-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2022-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.HashSet; 044import java.util.LinkedHashMap; 045import java.util.List; 046import java.util.Map; 047import java.util.Set; 048 049import com.unboundid.util.ByteStringBuffer; 050import com.unboundid.util.Debug; 051import com.unboundid.util.NotNull; 052import com.unboundid.util.Nullable; 053import com.unboundid.util.StaticUtils; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.json.JSONArray; 057import com.unboundid.util.json.JSONBuffer; 058import com.unboundid.util.json.JSONField; 059import com.unboundid.util.json.JSONObject; 060import com.unboundid.util.json.JSONString; 061import com.unboundid.util.json.JSONValue; 062 063import static com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax. 064 LogSyntaxMessages.*; 065 066 067 068/** 069 * This class defines a log field syntax for values that are JSON objects. This 070 * syntax allows individual field values to be redacted or tokenized within the 071 * JSON objects. If a JSON object is completely redacted, then the redacted 072 * representation will be <code>{ "redacted":"{REDACTED}" }</code>. If a JSON 073 * object is completely tokenized, then the tokenized representation will be 074 * <code>{ "tokenized":"{TOKENIZED:token-value}" }</code>", where token-value 075 * will be replaced with a generated value. 076 * <BR> 077 * <BLOCKQUOTE> 078 * <B>NOTE:</B> This class, and other classes within the 079 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 080 * supported for use against Ping Identity, UnboundID, and 081 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 082 * for proprietary functionality or for external specifications that are not 083 * considered stable or mature enough to be guaranteed to work in an 084 * interoperable way with other types of LDAP servers. 085 * </BLOCKQUOTE> 086 */ 087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 088public final class JSONLogFieldSyntax 089 extends LogFieldSyntax<JSONObject> 090{ 091 /** 092 * The name for this syntax. 093 */ 094 @NotNull public static final String SYNTAX_NAME = "json"; 095 096 097 098 /** 099 * A JSON object that represents a completely redacted value. 100 */ 101 @NotNull private static final JSONObject REDACTED_JSON_OBJECT = 102 new JSONObject(new JSONField("redacted", REDACTED_STRING)); 103 104 105 106 /** 107 * The string representation that will be used for a JSON object that is 108 * completely redacted. 109 */ 110 @NotNull private static final String REDACTED_JSON_OBJECT_STRING = 111 REDACTED_JSON_OBJECT.toSingleLineString(); 112 113 114 115 /** 116 * The string representation that will be used for a JSON object that is 117 * completely redacted. 118 */ 119 @NotNull private static final String 120 REDACTED_JSON_OBJECT_STRING_WITH_REPLACED_QUOTES = 121 REDACTED_JSON_OBJECT_STRING.replace('"', '\''); 122 123 124 125 // Indicates whether all fields should be considered sensitive when redacting 126 // or tokenizing components. 127 private final boolean allFieldsAreSensitive; 128 129 // The set of the names and OIDs for the specific fields whose values should 130 // not be redacted or tokenized. 131 @NotNull private final Set<String> excludedSensitiveFields; 132 133 // The set of the names and OIDs for the specific fields whose values should 134 // be redacted or tokenized. 135 @NotNull private final Set<String> includedSensitiveFields; 136 137 138 139 /** 140 * Creates a new JSON log field syntax instance that can optionally define 141 * specific fields to include in or exclude from redaction or tokenization. 142 * If any include fields are specified, then only the values of those fields 143 * will be considered sensitive and will have their values tokenized or 144 * redacted. If any exclude fields are specified, then the values of any 145 * fields except those will be considered sensitive. If no include fields and 146 * no exclude fields are specified, then all fields will be considered 147 * sensitive and will have their values tokenized or redacted. 148 * 149 * @param maxStringLengthCharacters The maximum length (in characters) to 150 * use for strings within values. Strings 151 * that are longer than this should be 152 * truncated before inclusion in the log. 153 * This value must be greater than or equal 154 * to zero. 155 * @param includedSensitiveFields The names for the JSON fields whose 156 * values should be considered sensitive 157 * and should have their values redacted or 158 * tokenized by methods that operate on 159 * value components. This may be 160 * {@code null} or empty if no included 161 * sensitive fields should be defined. 162 * @param excludedSensitiveFields The names for the specific fields whose 163 * values should not be considered 164 * sensitive and should not have their 165 * values redacted or tokenized by methods 166 * that operate on value components. This 167 * may be {@code null} or empty if no 168 * excluded sensitive fields should be 169 * defined. 170 */ 171 public JSONLogFieldSyntax( 172 final int maxStringLengthCharacters, 173 @Nullable final Collection<String> includedSensitiveFields, 174 @Nullable final Collection<String> excludedSensitiveFields) 175 { 176 super(maxStringLengthCharacters); 177 178 this.includedSensitiveFields = getLowercaseNames(includedSensitiveFields); 179 this.excludedSensitiveFields = getLowercaseNames(excludedSensitiveFields); 180 allFieldsAreSensitive = this.includedSensitiveFields.isEmpty() && 181 this.excludedSensitiveFields.isEmpty(); 182 } 183 184 185 186 /** 187 * Retrieves a set containing the lowercase representations of the provided 188 * names. 189 * 190 * @param names The set of names to be converted to lowercase. It may be 191 * {@code null} or empty. 192 * 193 * @return A set containing the lowercase representations of the provided 194 * names, or an empty set if the given collection is {@code null} or 195 * empty. 196 */ 197 @NotNull() 198 private static Set<String> getLowercaseNames( 199 @Nullable final Collection<String> names) 200 { 201 if (names == null) 202 { 203 return Collections.emptySet(); 204 } 205 else 206 { 207 final Set<String> lowercaseNames = new HashSet<>(); 208 for (final String name : names) 209 { 210 lowercaseNames.add(StaticUtils.toLowerCase(name)); 211 } 212 213 return Collections.unmodifiableSet(lowercaseNames); 214 } 215 } 216 217 218 219 /** 220 * Retrieves the names of the JSON fields whose values should be considered 221 * sensitive and should have their values redacted or tokenized by methods 222 * that operate on value components. 223 * 224 * @return The names of the JSON fields whose values should be considered 225 * sensitive, or an empty list if no included sensitive field names 226 * have been defined. 227 */ 228 @NotNull() 229 public Set<String> getIncludedSensitiveFields() 230 { 231 return includedSensitiveFields; 232 } 233 234 235 236 /** 237 * Retrieves the names of the JSON fields whose values should not be 238 * considered sensitive and should not have their values redacted or tokenized 239 * by methods that operate on value components. 240 * 241 * @return The names of the JSON fields whose values should not be considered 242 * sensitive, or an empty list if no excluded sensitive field names 243 * have been defined. 244 */ 245 @NotNull() 246 public Set<String> getExcludedSensitiveFields() 247 { 248 return excludedSensitiveFields; 249 } 250 251 252 253 /** 254 * {@inheritDoc} 255 */ 256 @Override() 257 @NotNull() 258 public String getSyntaxName() 259 { 260 return SYNTAX_NAME; 261 } 262 263 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override() 269 public void valueToSanitizedString(@NotNull final JSONObject value, 270 @NotNull final ByteStringBuffer buffer) 271 { 272 buffer.append(sanitize(value).toSingleLineString()); 273 } 274 275 276 277 /** 278 * Sanitizes the provided JSON value. 279 * 280 * @param value The value to be sanitized. It must not be {@code null}. 281 * 282 * @return A sanitized representation of the provided JSON value. 283 */ 284 @NotNull() 285 private JSONValue sanitize(@NotNull final JSONValue value) 286 { 287 if (value instanceof JSONObject) 288 { 289 final Map<String,JSONValue> originalFields = 290 ((JSONObject) value).getFields(); 291 final Map<String,JSONValue> sanitizedFields = 292 new LinkedHashMap<>(StaticUtils.computeMapCapacity( 293 originalFields.size())); 294 for (final Map.Entry<String,JSONValue> e : originalFields.entrySet()) 295 { 296 sanitizedFields.put(e.getKey(), sanitize(e.getValue())); 297 } 298 return new JSONObject(sanitizedFields); 299 } 300 else if (value instanceof JSONArray) 301 { 302 final List<JSONValue> originalValues = ((JSONArray) value).getValues(); 303 final List<JSONValue> sanitizedValues = 304 new ArrayList<>(originalValues.size()); 305 for (final JSONValue v : originalValues) 306 { 307 sanitizedValues.add(sanitize(v)); 308 } 309 return new JSONArray(sanitizedValues); 310 } 311 else if (value instanceof JSONString) 312 { 313 final String stringValue = ((JSONString) value).stringValue(); 314 return new JSONString(sanitize(stringValue)); 315 } 316 else 317 { 318 return value; 319 } 320 } 321 322 323 324 /** 325 * {@inheritDoc} 326 */ 327 @Override() 328 public void logSanitizedFieldToTextFormattedLog( 329 @NotNull final String fieldName, 330 @NotNull final JSONObject fieldValue, 331 @NotNull final ByteStringBuffer buffer) 332 { 333 buffer.append(' '); 334 buffer.append(fieldName); 335 buffer.append("=\""); 336 buffer.append(valueToSanitizedString(fieldValue).replace('"', '\'')); 337 buffer.append('"'); 338 } 339 340 341 342 /** 343 * {@inheritDoc} 344 */ 345 @Override() 346 public void logSanitizedFieldToJSONFormattedLog( 347 @NotNull final String fieldName, 348 @NotNull final JSONObject fieldValue, 349 @NotNull final JSONBuffer buffer) 350 { 351 buffer.appendValue(fieldName, sanitize(fieldValue)); 352 } 353 354 355 356 /** 357 * {@inheritDoc} 358 */ 359 @Override() 360 public void logSanitizedValueToJSONFormattedLog( 361 @NotNull final JSONObject value, 362 @NotNull final JSONBuffer buffer) 363 { 364 buffer.appendValue(sanitize(value)); 365 } 366 367 368 369 /** 370 * {@inheritDoc} 371 */ 372 @Override() 373 @NotNull() 374 public JSONObject parseValue(@NotNull final String valueString) 375 throws RedactedValueException, TokenizedValueException, 376 LogSyntaxException 377 { 378 try 379 { 380 return new JSONObject(valueString); 381 } 382 catch (final Exception e) 383 { 384 Debug.debugException(e); 385 386 if (valueStringIsCompletelyRedacted(valueString)) 387 { 388 throw new RedactedValueException( 389 ERR_JSON_LOG_SYNTAX_CANNOT_PARSE_REDACTED.get(), e); 390 } 391 else if (valueStringIsCompletelyTokenized(valueString)) 392 { 393 throw new TokenizedValueException( 394 ERR_JSON_LOG_SYNTAX_CANNOT_PARSE_TOKENIZED.get(), e); 395 } 396 else 397 { 398 throw new LogSyntaxException( 399 ERR_JSON_LOG_SYNTAX_CANNOT_PARSE.get(), e); 400 } 401 } 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 public boolean valueStringIsCompletelyRedacted( 411 @NotNull final String valueString) 412 { 413 return valueString.equals(REDACTED_STRING) || 414 valueString.equals(REDACTED_JSON_OBJECT_STRING); 415 } 416 417 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override() 423 public boolean completelyRedactedValueConformsToSyntax() 424 { 425 return true; 426 } 427 428 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override() 434 public void redactEntireValue(@NotNull final ByteStringBuffer buffer) 435 { 436 buffer.append(REDACTED_JSON_OBJECT_STRING); 437 } 438 439 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override() 445 public void logCompletelyRedactedFieldToTextFormattedLog( 446 @NotNull final String fieldName, 447 @NotNull final ByteStringBuffer buffer) 448 { 449 buffer.append(' '); 450 buffer.append(fieldName); 451 buffer.append("=\""); 452 buffer.append(REDACTED_JSON_OBJECT_STRING_WITH_REPLACED_QUOTES); 453 buffer.append('"'); 454 } 455 456 457 458 /** 459 * {@inheritDoc} 460 */ 461 @Override() 462 public void logCompletelyRedactedFieldToJSONFormattedLog( 463 @NotNull final String fieldName, 464 @NotNull final JSONBuffer buffer) 465 { 466 buffer.appendValue(fieldName, REDACTED_JSON_OBJECT); 467 } 468 469 470 471 /** 472 * {@inheritDoc} 473 */ 474 @Override() 475 public void logCompletelyRedactedValueToJSONFormattedLog( 476 @NotNull final JSONBuffer buffer) 477 { 478 buffer.appendValue(REDACTED_JSON_OBJECT); 479 } 480 481 482 483 /** 484 * {@inheritDoc} 485 */ 486 @Override() 487 public boolean supportsRedactedComponents() 488 { 489 return true; 490 } 491 492 493 494 /** 495 * {@inheritDoc} 496 */ 497 @Override() 498 public boolean valueWithRedactedComponentsConformsToSyntax() 499 { 500 return true; 501 } 502 503 504 505 /** 506 * {@inheritDoc} 507 */ 508 @Override() 509 public void redactComponents(@NotNull final JSONObject value, 510 @NotNull final ByteStringBuffer buffer) 511 { 512 buffer.append(redactValue(value).toString()); 513 } 514 515 516 517 /** 518 * Retrieves a redacted representation of the provided JSON value. 519 * 520 * @param value The value to be redacted. 521 * 522 * @return A redacted representation of the provided JSON value. 523 */ 524 @NotNull() 525 private JSONValue redactValue(@NotNull final JSONValue value) 526 { 527 if (value instanceof JSONObject) 528 { 529 final Map<String,JSONValue> originalFields = 530 ((JSONObject) value).getFields(); 531 final Map<String,JSONValue> redactedFields = 532 new LinkedHashMap<>(StaticUtils.computeMapCapacity( 533 originalFields.size())); 534 for (final Map.Entry<String,JSONValue> e : originalFields.entrySet()) 535 { 536 final String fieldName = e.getKey(); 537 if (shouldRedactOrTokenize(fieldName)) 538 { 539 redactedFields.put(fieldName, new JSONString(REDACTED_STRING)); 540 } 541 else 542 { 543 redactedFields.put(fieldName, redactValue(e.getValue())); 544 } 545 } 546 return new JSONObject(redactedFields); 547 } 548 else if (value instanceof JSONArray) 549 { 550 final List<JSONValue> originalValues = ((JSONArray) value).getValues(); 551 final List<JSONValue> redactedValues = 552 new ArrayList<>(originalValues.size()); 553 for (final JSONValue v : originalValues) 554 { 555 redactedValues.add(redactValue(v)); 556 } 557 return new JSONArray(redactedValues); 558 } 559 else 560 { 561 return sanitize(value); 562 } 563 } 564 565 566 567 /** 568 * Indicates whether values of the specified field should be redacted or 569 * tokenized. 570 * 571 * @param fieldName The name of the field for which to make the 572 * determination. 573 * 574 * @return {@code true} if values of the specified field should be redacted 575 * or tokenized, or {@code false} if not. 576 */ 577 private boolean shouldRedactOrTokenize(@NotNull final String fieldName) 578 { 579 if (allFieldsAreSensitive) 580 { 581 return true; 582 } 583 584 final String lowerName = StaticUtils.toLowerCase(fieldName); 585 if (includedSensitiveFields.contains(lowerName)) 586 { 587 return true; 588 } 589 590 if (excludedSensitiveFields.isEmpty()) 591 { 592 return false; 593 } 594 else 595 { 596 return (! excludedSensitiveFields.contains(lowerName)); 597 } 598 } 599 600 601 602 /** 603 * {@inheritDoc} 604 */ 605 @Override() 606 public void logRedactedComponentsFieldToTextFormattedLog( 607 @NotNull final String fieldName, 608 @NotNull final JSONObject fieldValue, 609 @NotNull final ByteStringBuffer buffer) 610 { 611 buffer.append(' '); 612 buffer.append(fieldName); 613 buffer.append("=\""); 614 buffer.append(redactComponents(fieldValue).replace('"', '\'')); 615 buffer.append('"'); 616 } 617 618 619 620 /** 621 * {@inheritDoc} 622 */ 623 @Override() 624 public void logRedactedComponentsFieldToJSONFormattedLog( 625 @NotNull final String fieldName, 626 @NotNull final JSONObject fieldValue, 627 @NotNull final JSONBuffer buffer) 628 { 629 buffer.appendValue(fieldName, redactValue(fieldValue)); 630 } 631 632 633 634 /** 635 * {@inheritDoc} 636 */ 637 @Override() 638 public void logRedactedComponentsValueToJSONFormattedLog( 639 @NotNull final JSONObject value, 640 @NotNull final JSONBuffer buffer) 641 { 642 buffer.appendValue(redactValue(value)); 643 } 644 645 646 647 /** 648 * {@inheritDoc} 649 */ 650 @Override() 651 public boolean valueStringIsCompletelyTokenized( 652 @NotNull final String valueString) 653 { 654 if (super.valueStringIsCompletelyTokenized(valueString)) 655 { 656 return true; 657 } 658 659 try 660 { 661 final JSONObject jsonObject = new JSONObject(valueString); 662 final Map<String,JSONValue> fields = jsonObject.getFields(); 663 return ((fields.size() == 1) && 664 fields.containsKey("tokenized")); 665 } 666 catch (final Exception e) 667 { 668 Debug.debugException(e); 669 return false; 670 } 671 } 672 673 674 675 /** 676 * {@inheritDoc} 677 */ 678 @Override() 679 public boolean completelyTokenizedValueConformsToSyntax() 680 { 681 return true; 682 } 683 684 685 686 /** 687 * {@inheritDoc} 688 */ 689 @Override() 690 public void tokenizeEntireValue(@NotNull final JSONObject value, 691 @NotNull final byte[] pepper, 692 @NotNull final ByteStringBuffer buffer) 693 { 694 final JSONObject tokenizedObject = new JSONObject( 695 new JSONField("tokenized", 696 tokenize(value.toNormalizedString(), pepper))); 697 buffer.append(tokenizedObject.toSingleLineString()); 698 } 699 700 701 702 /** 703 * {@inheritDoc} 704 */ 705 @Override() 706 public void logCompletelyTokenizedFieldToTextFormattedLog( 707 @NotNull final String fieldName, 708 @NotNull final JSONObject fieldValue, 709 @NotNull final byte[] pepper, 710 @NotNull final ByteStringBuffer buffer) 711 { 712 buffer.append(' '); 713 buffer.append(fieldName); 714 buffer.append("=\""); 715 buffer.append(tokenizeEntireValue(fieldValue, pepper).replace('"', '\'')); 716 buffer.append('"'); 717 } 718 719 720 721 /** 722 * {@inheritDoc} 723 */ 724 @Override() 725 public void logCompletelyTokenizedFieldToJSONFormattedLog( 726 @NotNull final String fieldName, 727 @NotNull final JSONObject fieldValue, 728 @NotNull final byte[] pepper, 729 @NotNull final JSONBuffer buffer) 730 { 731 buffer.appendValue(fieldName, 732 new JSONObject(new JSONField("tokenized", 733 tokenize(fieldValue.toNormalizedString(), pepper)))); 734 } 735 736 737 738 /** 739 * {@inheritDoc} 740 */ 741 @Override() 742 public void logCompletelyTokenizedValueToJSONFormattedLog( 743 @NotNull final JSONObject value, 744 @NotNull final byte[] pepper, 745 @NotNull final JSONBuffer buffer) 746 { 747 buffer.appendValue(new JSONObject(new JSONField("tokenized", 748 tokenize(value.toNormalizedString(), pepper)))); 749 } 750 751 752 753 /** 754 * {@inheritDoc} 755 */ 756 @Override() 757 public boolean supportsTokenizedComponents() 758 { 759 return true; 760 } 761 762 763 764 /** 765 * {@inheritDoc} 766 */ 767 @Override() 768 public boolean valueWithTokenizedComponentsConformsToSyntax() 769 { 770 return true; 771 } 772 773 774 775 /** 776 * {@inheritDoc} 777 */ 778 @Override() 779 public void tokenizeComponents(@NotNull final JSONObject value, 780 @NotNull final byte[] pepper, 781 @NotNull final ByteStringBuffer buffer) 782 { 783 buffer.append(tokenizeValue(value, pepper).toString()); 784 } 785 786 787 788 /** 789 * Retrieves a tokenized representation of the provided JSON value. 790 * 791 * @param value The value to be tokenized. 792 * @param pepper A pepper used to provide brute-force protection for the 793 * resulting token. The pepper value should be kept secret so 794 * that it is not available to unauthorized users who might be 795 * able to view log information, although the same pepper 796 * value should be consistently provided when tokenizing 797 * values so that the same value will consistently yield the 798 * same token. It must not be {@code null} and should not be 799 * empty. 800 * 801 * @return A tokenized representation of the provided JSON value. 802 */ 803 @NotNull() 804 private JSONValue tokenizeValue(@NotNull final JSONValue value, 805 @NotNull final byte[] pepper) 806 { 807 if (value instanceof JSONObject) 808 { 809 final Map<String,JSONValue> originalFields = 810 ((JSONObject) value).getFields(); 811 final Map<String,JSONValue> tokenizedFields = 812 new LinkedHashMap<>(StaticUtils.computeMapCapacity( 813 originalFields.size())); 814 for (final Map.Entry<String,JSONValue> e : originalFields.entrySet()) 815 { 816 final String fieldName = e.getKey(); 817 final JSONValue fieldValue = e.getValue(); 818 if (shouldRedactOrTokenize(fieldName)) 819 { 820 final String tokenizedValue = 821 tokenize(fieldValue.toNormalizedString(), pepper); 822 tokenizedFields.put(fieldName, new JSONString(tokenizedValue)); 823 } 824 else 825 { 826 tokenizedFields.put(fieldName, tokenizeValue(fieldValue, pepper)); 827 } 828 } 829 return new JSONObject(tokenizedFields); 830 } 831 else if (value instanceof JSONArray) 832 { 833 final List<JSONValue> originalValues = ((JSONArray) value).getValues(); 834 final List<JSONValue> tokenizedValues = 835 new ArrayList<>(originalValues.size()); 836 for (final JSONValue v : originalValues) 837 { 838 tokenizedValues.add(tokenizeValue(v, pepper)); 839 } 840 return new JSONArray(tokenizedValues); 841 } 842 else 843 { 844 return sanitize(value); 845 } 846 } 847 848 849 850 /** 851 * {@inheritDoc} 852 */ 853 @Override() 854 public void logTokenizedComponentsFieldToTextFormattedLog( 855 @NotNull final String fieldName, 856 @NotNull final JSONObject fieldValue, 857 @NotNull final byte[] pepper, 858 @NotNull final ByteStringBuffer buffer) 859 { 860 buffer.append(' '); 861 buffer.append(fieldName); 862 buffer.append("=\""); 863 buffer.append(tokenizeComponents(fieldValue, pepper).replace('"', '\'')); 864 buffer.append('"'); 865 } 866 867 868 869 /** 870 * {@inheritDoc} 871 */ 872 @Override() 873 public void logTokenizedComponentsFieldToJSONFormattedLog( 874 @NotNull final String fieldName, 875 @NotNull final JSONObject fieldValue, 876 @NotNull final byte[] pepper, 877 @NotNull final JSONBuffer buffer) 878 { 879 buffer.appendValue(fieldName, tokenizeValue(fieldValue, pepper)); 880 } 881 882 883 884 /** 885 * {@inheritDoc} 886 */ 887 @Override() 888 public void logTokenizedComponentsValueToJSONFormattedLog( 889 @NotNull final JSONObject value, 890 @NotNull final byte[] pepper, 891 @NotNull final JSONBuffer buffer) 892 { 893 buffer.appendValue(tokenizeValue(value, pepper)); 894 } 895}