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.json; 037 038 039 040import java.text.ParseException; 041import java.util.ArrayList; 042import java.util.Collections; 043import java.util.Date; 044import java.util.LinkedHashMap; 045import java.util.LinkedHashSet; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049 050import com.unboundid.ldap.sdk.unboundidds.logs.LogException; 051import com.unboundid.ldap.sdk.unboundidds.logs.v2.LogField; 052import com.unboundid.ldap.sdk.unboundidds.logs.v2.LogMessage; 053import com.unboundid.util.Debug; 054import com.unboundid.util.NotExtensible; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.json.JSONArray; 061import com.unboundid.util.json.JSONBoolean; 062import com.unboundid.util.json.JSONNumber; 063import com.unboundid.util.json.JSONObject; 064import com.unboundid.util.json.JSONString; 065import com.unboundid.util.json.JSONValue; 066 067import static com.unboundid.ldap.sdk.unboundidds.logs.v2.json.JSONLogMessages.*; 068 069 070 071/** 072 * This class provides a data structure that holds information about a 073 * JSON-formatted log message. 074 * <BR> 075 * <BLOCKQUOTE> 076 * <B>NOTE:</B> This class, and other classes within the 077 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 078 * supported for use against Ping Identity, UnboundID, and 079 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 080 * for proprietary functionality or for external specifications that are not 081 * considered stable or mature enough to be guaranteed to work in an 082 * interoperable way with other types of LDAP servers. 083 * </BLOCKQUOTE> 084 */ 085@NotExtensible() 086@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE) 087public abstract class JSONLogMessage 088 implements LogMessage 089{ 090 /** 091 * The serial version UID for this serializable class. 092 */ 093 private static final long serialVersionUID = 997950529069507711L; 094 095 096 097 // The JSON object that contains an encoded representation of this log 098 // message. 099 @NotNull private final JSONObject jsonObject; 100 101 // The timestamp value for this log message. 102 private final long timestampValue; 103 104 // A map of the fields in this log message. 105 @NotNull private final Map<String,List<String>> logFields; 106 107 // A string representation of this log message. 108 @NotNull private final String logMessageString; 109 110 // The log type for this log message. 111 @Nullable private final String logType; 112 113 114 115 /** 116 * Creates a new JSON log message from the provided JSON object. 117 * 118 * @param jsonObject The JSON object that contains an encoded representation 119 * of this log message. It must not be {@code null}. 120 * 121 * @throws LogException If the provided JSON object cannot be parsed as a 122 * valid log message. 123 */ 124 protected JSONLogMessage(@NotNull final JSONObject jsonObject) 125 throws LogException 126 { 127 this.jsonObject = jsonObject; 128 logMessageString = jsonObject.toSingleLineString(); 129 130 final JSONValue timestampJSONValue = jsonObject.getField( 131 JSONFormattedAccessLogFields.TIMESTAMP.getFieldName()); 132 if (timestampJSONValue == null) 133 { 134 throw new LogException(logMessageString, 135 ERR_JSON_LOG_MESSAGE_MISSING_TIMESTAMP.get(logMessageString, 136 JSONFormattedAccessLogFields.TIMESTAMP.getFieldName())); 137 } 138 139 if (! (timestampJSONValue instanceof JSONString)) 140 { 141 throw new LogException(logMessageString, 142 ERR_JSON_LOG_MESSAGE_TIMESTAMP_NOT_STRING.get(logMessageString, 143 JSONFormattedAccessLogFields.TIMESTAMP.getFieldName())); 144 } 145 146 try 147 { 148 timestampValue = StaticUtils.decodeRFC3339Time( 149 ((JSONString) timestampJSONValue).stringValue()).getTime(); 150 } 151 catch (final ParseException e) 152 { 153 Debug.debugException(e); 154 throw new LogException(logMessageString, 155 ERR_JSON_LOG_MESSAGE_MALFORMED_TIMESTAMP.get(logMessageString, 156 JSONFormattedAccessLogFields.TIMESTAMP.getFieldName()), 157 e); 158 } 159 160 161 final Map<String,List<String>> fieldMap = new LinkedHashMap<>(); 162 for (final Map.Entry<String,JSONValue> e : 163 jsonObject.getFields().entrySet()) 164 { 165 fieldMap.put(e.getKey(), valueToStrings(e.getValue())); 166 } 167 168 logFields = Collections.unmodifiableMap(fieldMap); 169 170 logType = getString(JSONFormattedAccessLogFields.LOG_TYPE); 171 } 172 173 174 175 /** 176 * Retrieves a list of the string representations of the values represented by 177 * the provided JSON value. 178 * 179 * @param value The JSON value for which to obtain the string 180 * representations. It must not be {@code null}. 181 * 182 * @return A list of the string representations of the values represented by 183 * the provided JSON value. 184 */ 185 @NotNull() 186 static List<String> valueToStrings(@NotNull final JSONValue value) 187 { 188 if (value instanceof JSONArray) 189 { 190 final JSONArray a = (JSONArray) value; 191 final List<JSONValue> valueList = a.getValues(); 192 final List<String> valueStrings = new ArrayList<>(valueList.size()); 193 for (final JSONValue v : valueList) 194 { 195 if (v instanceof JSONString) 196 { 197 valueStrings.add(((JSONString) v).stringValue()); 198 } 199 else 200 { 201 valueStrings.add(v.toSingleLineString()); 202 } 203 } 204 205 return Collections.unmodifiableList(valueStrings); 206 } 207 else if (value instanceof JSONString) 208 { 209 return Collections.singletonList(((JSONString) value).stringValue()); 210 } 211 else 212 { 213 return Collections.singletonList(value.toSingleLineString()); 214 } 215 } 216 217 218 219 /** 220 * Retrieves the JSON object that contains an encoded representation of this 221 * log message. 222 * 223 * @return The JSON object that contains an encoded representation of this 224 * log message. 225 */ 226 @NotNull() 227 public final JSONObject getJSONObject() 228 { 229 return jsonObject; 230 } 231 232 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override() 238 @NotNull() 239 public final Date getTimestamp() 240 { 241 return new Date(timestampValue); 242 } 243 244 245 246 /** 247 * Retrieves the type of logger with which this message is associated. 248 * 249 * @return The type of logger with which this message is associated, or 250 * {@code null} if it is not included in the log message. 251 */ 252 @Nullable() 253 public final String getLogType() 254 { 255 return logType; 256 } 257 258 259 260 /** 261 * {@inheritDoc} 262 */ 263 @Override() 264 @NotNull() 265 public final Map<String,List<String>> getFields() 266 { 267 return logFields; 268 } 269 270 271 /** 272 * {@inheritDoc} 273 */ 274 @Override() 275 @Nullable() 276 public final Boolean getBoolean(@NotNull final LogField logField) 277 throws LogException 278 { 279 final JSONValue fieldValue = getFirstValue(logField); 280 if (fieldValue == null) 281 { 282 return null; 283 } 284 285 if (fieldValue instanceof JSONBoolean) 286 { 287 return ((JSONBoolean) fieldValue).booleanValue(); 288 } 289 else if (fieldValue instanceof JSONString) 290 { 291 final String stringValue = ((JSONString) fieldValue).stringValue(); 292 if (stringValue.equalsIgnoreCase("true")) 293 { 294 return Boolean.TRUE; 295 } 296 else if (stringValue.equalsIgnoreCase("false")) 297 { 298 return Boolean.FALSE; 299 } 300 else 301 { 302 throw new LogException(logMessageString, 303 ERR_JSON_LOG_MESSAGE_VALUE_NOT_BOOLEAN.get(logField.getFieldName(), 304 logMessageString)); 305 } 306 } 307 else 308 { 309 throw new LogException(logMessageString, 310 ERR_JSON_LOG_MESSAGE_VALUE_NOT_BOOLEAN.get(logField.getFieldName(), 311 logMessageString)); 312 } 313 } 314 315 316 317 /** 318 * Retrieves the Boolean value of the specified field. 319 * 320 * @param logField The field for which to retrieve the Boolean value. 321 * 322 * @return The Boolean value of the specified field, or {@code null} if the 323 * field does not exist in the log message or cannot be parsed as a 324 * Boolean. 325 */ 326 @Nullable() 327 final Boolean getBooleanNoThrow(@NotNull final LogField logField) 328 { 329 try 330 { 331 return getBoolean(logField); 332 } 333 catch (final LogException e) 334 { 335 Debug.debugException(e); 336 return null; 337 } 338 } 339 340 341 342 /** 343 * {@inheritDoc} 344 */ 345 @Override() 346 @Nullable() 347 public final Date getGeneralizedTime(@NotNull final LogField logField) 348 throws LogException 349 { 350 final JSONValue fieldValue = getFirstValue(logField); 351 if (fieldValue == null) 352 { 353 return null; 354 } 355 356 if (fieldValue instanceof JSONString) 357 { 358 final String stringValue = ((JSONString) fieldValue).stringValue(); 359 try 360 { 361 return StaticUtils.decodeGeneralizedTime(stringValue); 362 } 363 catch (final Exception e) 364 { 365 Debug.debugException(e); 366 throw new LogException(logMessageString, 367 ERR_JSON_LOG_MESSAGE_VALUE_NOT_GENERALIZED_TIME.get( 368 logField.getFieldName(), logMessageString), 369 e); 370 } 371 } 372 else 373 { 374 throw new LogException(logMessageString, 375 ERR_JSON_LOG_MESSAGE_VALUE_NOT_GENERALIZED_TIME.get( 376 logField.getFieldName(), logMessageString)); 377 } 378 } 379 380 381 382 /** 383 * Retrieves the generalized time value of the specified field. 384 * 385 * @param logField The field for which to retrieve the generalized time 386 * value. 387 * 388 * @return The generalized time value of the specified field, or {@code null} 389 * if the field does not exist in the log message or cannot be parsed 390 * as a timestamp in the generalized time format. 391 */ 392 @Nullable() 393 final Date getGeneralizedTimeNoThrow(@NotNull final LogField logField) 394 { 395 try 396 { 397 return getGeneralizedTime(logField); 398 } 399 catch (final LogException e) 400 { 401 Debug.debugException(e); 402 return null; 403 } 404 } 405 406 407 408 /** 409 * {@inheritDoc} 410 */ 411 @Override() 412 @Nullable() 413 public final Double getDouble(@NotNull final LogField logField) 414 throws LogException 415 { 416 final JSONValue fieldValue = getFirstValue(logField); 417 if (fieldValue == null) 418 { 419 return null; 420 } 421 422 if (fieldValue instanceof JSONNumber) 423 { 424 return ((JSONNumber) fieldValue).getValue().doubleValue(); 425 } 426 else if (fieldValue instanceof JSONString) 427 { 428 final String stringValue = ((JSONString) fieldValue).stringValue(); 429 try 430 { 431 return Double.valueOf(stringValue); 432 } 433 catch (final Exception e) 434 { 435 Debug.debugException(e); 436 throw new LogException(logMessageString, 437 ERR_JSON_LOG_MESSAGE_VALUE_NOT_FLOATING_POINT.get( 438 logField.getFieldName(), logMessageString), 439 e); 440 } 441 } 442 else 443 { 444 throw new LogException(logMessageString, 445 ERR_JSON_LOG_MESSAGE_VALUE_NOT_FLOATING_POINT.get( 446 logField.getFieldName(), logMessageString)); 447 } 448 } 449 450 451 452 /** 453 * Retrieves the floating-point value of the specified field. 454 * 455 * @param logField The field for which to retrieve the floating-point value. 456 * 457 * @return The floating-point value of the specified field, or {@code null} 458 * if the field does not exist in the log message or cannot be parsed 459 * as a Double. 460 */ 461 @Nullable() 462 final Double getDoubleNoThrow(@NotNull final LogField logField) 463 { 464 try 465 { 466 return getDouble(logField); 467 } 468 catch (final LogException e) 469 { 470 Debug.debugException(e); 471 return null; 472 } 473 } 474 475 476 477 /** 478 * {@inheritDoc} 479 */ 480 @Override() 481 @Nullable() 482 public final Integer getInteger(@NotNull final LogField logField) 483 throws LogException 484 { 485 final JSONValue fieldValue = getFirstValue(logField); 486 if (fieldValue == null) 487 { 488 return null; 489 } 490 491 if (fieldValue instanceof JSONNumber) 492 { 493 try 494 { 495 return ((JSONNumber) fieldValue).getValue().intValueExact(); 496 } 497 catch (final Exception e) 498 { 499 Debug.debugException(e); 500 throw new LogException(logMessageString, 501 ERR_JSON_LOG_MESSAGE_VALUE_NOT_INTEGER.get( 502 logField.getFieldName(), logMessageString), 503 e); 504 } 505 } 506 else if (fieldValue instanceof JSONString) 507 { 508 final String stringValue = ((JSONString) fieldValue).stringValue(); 509 try 510 { 511 return Integer.parseInt(stringValue); 512 } 513 catch (final Exception e) 514 { 515 Debug.debugException(e); 516 throw new LogException(logMessageString, 517 ERR_JSON_LOG_MESSAGE_VALUE_NOT_INTEGER.get( 518 logField.getFieldName(), logMessageString), 519 e); 520 } 521 } 522 else 523 { 524 throw new LogException(logMessageString, 525 ERR_JSON_LOG_MESSAGE_VALUE_NOT_INTEGER.get( 526 logField.getFieldName(), logMessageString)); 527 } 528 } 529 530 531 532 /** 533 * Retrieves the integer value of the specified field. 534 * 535 * @param logField The field for which to retrieve the integer value. 536 * 537 * @return The integer value of the specified field, or {@code null} if the 538 * field does not exist in the log message or cannot be parsed as an 539 * {@code Integer}. 540 */ 541 @Nullable() 542 final Integer getIntegerNoThrow(@NotNull final LogField logField) 543 { 544 try 545 { 546 return getInteger(logField); 547 } 548 catch (final LogException e) 549 { 550 Debug.debugException(e); 551 return null; 552 } 553 } 554 555 556 557 /** 558 * {@inheritDoc} 559 */ 560 @Override() 561 @Nullable() 562 public final Long getLong(@NotNull final LogField logField) 563 throws LogException 564 { 565 final JSONValue fieldValue = getFirstValue(logField); 566 if (fieldValue == null) 567 { 568 return null; 569 } 570 571 if (fieldValue instanceof JSONNumber) 572 { 573 try 574 { 575 return ((JSONNumber) fieldValue).getValue().longValueExact(); 576 } 577 catch (final Exception e) 578 { 579 Debug.debugException(e); 580 throw new LogException(logMessageString, 581 ERR_JSON_LOG_MESSAGE_VALUE_NOT_INTEGER.get( 582 logField.getFieldName(), logMessageString), 583 e); 584 } 585 } 586 else if (fieldValue instanceof JSONString) 587 { 588 final String stringValue = ((JSONString) fieldValue).stringValue(); 589 try 590 { 591 return Long.parseLong(stringValue); 592 } 593 catch (final Exception e) 594 { 595 Debug.debugException(e); 596 throw new LogException(logMessageString, 597 ERR_JSON_LOG_MESSAGE_VALUE_NOT_INTEGER.get( 598 logField.getFieldName(), logMessageString), 599 e); 600 } 601 } 602 else 603 { 604 throw new LogException(logMessageString, 605 ERR_JSON_LOG_MESSAGE_VALUE_NOT_INTEGER.get( 606 logField.getFieldName(), logMessageString)); 607 } 608 } 609 610 611 612 /** 613 * Retrieves the integer value of the specified field. 614 * 615 * @param logField The field for which to retrieve the integer value. 616 * 617 * @return The integer value of the specified field, or {@code null} if the 618 * field does not exist in the log message or cannot be parsed as a 619 * {@code Long}. 620 */ 621 @Nullable() 622 final Long getLongNoThrow(@NotNull final LogField logField) 623 { 624 try 625 { 626 return getLong(logField); 627 } 628 catch (final LogException e) 629 { 630 Debug.debugException(e); 631 return null; 632 } 633 } 634 635 636 637 /** 638 * {@inheritDoc} 639 */ 640 @Override() 641 @Nullable() 642 public final Date getRFC3339Timestamp(@NotNull final LogField logField) 643 throws LogException 644 { 645 final JSONValue fieldValue = getFirstValue(logField); 646 if (fieldValue == null) 647 { 648 return null; 649 } 650 651 if (fieldValue instanceof JSONString) 652 { 653 final String stringValue = ((JSONString) fieldValue).stringValue(); 654 try 655 { 656 return StaticUtils.decodeRFC3339Time(stringValue); 657 } 658 catch (final Exception e) 659 { 660 Debug.debugException(e); 661 throw new LogException(logMessageString, 662 ERR_JSON_LOG_MESSAGE_VALUE_NOT_RFC_3339_TIME.get( 663 logField.getFieldName(), logMessageString), 664 e); 665 } 666 } 667 else 668 { 669 throw new LogException(logMessageString, 670 ERR_JSON_LOG_MESSAGE_VALUE_NOT_RFC_3339_TIME.get( 671 logField.getFieldName(), logMessageString)); 672 } 673 } 674 675 676 677 /** 678 * Retrieves the RFC 3339 timestamp value of the specified field. 679 * 680 * @param logField The field for which to retrieve the RFC 3339 timestamp 681 * value. 682 * 683 * @return The RFC 3339 timestamp value of the specified field, or 684 * {@code null} if the field does not exist in the log message or 685 * cannot be parsed as a timestamp in the RFC 3339 format. 686 */ 687 @Nullable() 688 final Date getRFC3339TimestampNoThrow(@NotNull final LogField logField) 689 { 690 try 691 { 692 return getRFC3339Timestamp(logField); 693 } 694 catch (final LogException e) 695 { 696 Debug.debugException(e); 697 return null; 698 } 699 } 700 701 702 703 /** 704 * {@inheritDoc} 705 */ 706 @Override() 707 @Nullable() 708 public final String getString(@NotNull final LogField logField) 709 { 710 final JSONValue fieldValue = getFirstValue(logField); 711 if (fieldValue == null) 712 { 713 return null; 714 } 715 716 if (fieldValue instanceof JSONString) 717 { 718 return ((JSONString) fieldValue).stringValue(); 719 } 720 else 721 { 722 return fieldValue.toSingleLineString(); 723 } 724 } 725 726 727 728 /** 729 * Retrieves the list of values for the specified field as a list of strings. 730 * The values are expected to be in a JSON array whose values are all strings. 731 * 732 * @param logField The field for which to retrieve the list of values. 733 * 734 * @return The list of values for the specified field as a list of strings, 735 * or an empty list if the field is not present in the JSON object or 736 * if it is not an array of strings. 737 */ 738 @NotNull() 739 final List<String> getStringList(@NotNull final LogField logField) 740 { 741 final JSONValue fieldValue = jsonObject.getField(logField.getFieldName()); 742 if (fieldValue == null) 743 { 744 return Collections.emptyList(); 745 } 746 747 if (fieldValue instanceof JSONString) 748 { 749 return Collections.singletonList(((JSONString) fieldValue).stringValue()); 750 } 751 else if (fieldValue instanceof JSONArray) 752 { 753 final List<JSONValue> values = ((JSONArray) fieldValue).getValues(); 754 final List<String> stringValues = new ArrayList<>(values.size()); 755 for (final JSONValue v : values) 756 { 757 if (v instanceof JSONString) 758 { 759 stringValues.add(((JSONString) v).stringValue()); 760 } 761 } 762 763 return Collections.unmodifiableList(stringValues); 764 } 765 766 return Collections.emptyList(); 767 } 768 769 770 771 /** 772 * Retrieves the set of values for the specified field as a set of strings. 773 * The values are expected to be in a JSON array whose values are all strings. 774 * 775 * @param logField The field for which to retrieve the set of values. 776 * 777 * @return The set of values for the specified field as a set of strings, or 778 * an empty set if the field is not present in the JSON object or if 779 * it is not an array of strings. 780 */ 781 @NotNull() 782 final Set<String> getStringSet(@NotNull final LogField logField) 783 { 784 return Collections.unmodifiableSet( 785 new LinkedHashSet<>(getStringList(logField))); 786 } 787 788 789 790 /** 791 * Retrieves the first value of the specified field from the log message 792 * object. 793 * 794 * @param logField The field for which to retrieve the first value. 795 * 796 * @return The first value of the specified field, or {@code null} if the 797 * field is not present in the log message object, or if its value is 798 * an empty array. 799 */ 800 @Nullable() 801 JSONValue getFirstValue(@NotNull final LogField logField) 802 { 803 final JSONValue value = jsonObject.getField(logField.getFieldName()); 804 if (value == null) 805 { 806 return null; 807 } 808 809 if (value instanceof JSONArray) 810 { 811 final List<JSONValue> arrayValues = ((JSONArray) value).getValues(); 812 if (arrayValues.isEmpty()) 813 { 814 return null; 815 } 816 else 817 { 818 return arrayValues.get(0); 819 } 820 } 821 else 822 { 823 return value; 824 } 825 } 826 827 828 829 /** 830 * {@inheritDoc} 831 */ 832 @Override() 833 @NotNull() 834 public final String toString() 835 { 836 return logMessageString; 837 } 838}