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}