java.lang.RuntimeException: wXb6Vnl31u :: Ошибка для HTML= 001 003 004 005 006 007 008 009
010

Bracket - как составлять фильтры с помощью 011 бракетов?

012 013
014

В этой статье мы рассмотрим.

015 016
    017
  1. Структура бракета 018 019
      020
    • Пример бракета
    • 021
    022
  2. 023 024
  3. Структура dynamicFilters 025 026
      027
    • Пример бракета с динамичным фильтром для текстового поля
    • 028
    029
  4. 030 031
  5. Структура nativeFilters 032 033
      034
    • Пример бракетом с нативным фильтром для автора
    • 035
    036
  6. 037
038 039
040

Структура бракета:

041 042

Бракеты используется для получения данных с определённым 043 фильтром

044 045 049 050
051  var JSON = {
052      "brackets": [
053          {
054              "id": "iCNZfgb0",
055              "parentId": null,
056              "parentTreeIds": [{"..."}],
057              "connectionType": "",
058              "brackets": [{"..."}],
059              "dynamicFilters": [{"..."}],
060              "nativeFilters": [{"..."}],
061          }]
062  }
063  
064  
065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 085 086 087 088 089 093 094 095 096 097 100 101 102 103 104 107 108 109 110 111 115 116 117 118 119 122 123 124
Атрибут входного JSONОписание
id(String): Уникальный идентификатор бракета.
parentId(String или null): Идентификатор родительского бракета, если 083 таковой имеется. Если бракет является верхним уровнем, то это поле 084 может быть null.
parentTreeIds(Массив String): Массив идентификаторов родительских бракетов 090 данного бракета в дереве. Простыми словами это массив хранящий id 091 родительского бракета, родителя родительского и т.д пока они 092 имеются.
connectionType(String): Тип соединения между условиями внутри бракета. 098 Например, "AND" или "OR". Этот тип определяет, какие условия должны 099 выполняться внутри бракета.
brackets(Массив объектов): Массив вложенных бракетов, которые содержат 105 дополнительные сложные фильтры. Это позволяет строить сложные 106 логические выражения из бракетов.
dynamicFilters(Массив объектов): Массив фильтров, которые выполняют 112 фильтрацию по динамичным полям бизнес-объекта. Подробнее об структуре 114 dynamicFilters
nativeFilters(Массив объектов): Массив фильтров, которые могут быть 120 применены к нативным данным (полям) бизнес-объекта. Подробнее об структуре nativeFilters
125 126

Как вы возможно уже поняли dynamicFilters и nativeFilters 127 фильтруют по полям бизнес-объекта, а Бракеты содержат эти фильтры в 128 себе, соединяя их через булеву логику (И / ИЛИ). Также бракеты 129 содержат другие бракеты, создавая структуру где все выполняется 130 постепенно по приоритету.

131 132

Например, чтобы было наглядно создадим БО 133 "Задачи" в системе MyBPM, и сделаем следующий фильтр:

134 135

136 137

Простое объяснение:

138 139
    140
  1. Внутри оранжевого бракета ищем задачи, где автор либо текущий 141 пользователь, либо Цыпленков Денис, но статус задачи In Work или 142 Автор последнего изменения задачи Темирлан Жумагулов
  2. 143 144
  3. Затем смотрим чтобы данные проходящие оранжевый бракет имели 145 дату создания с 01.09 - 30.09
  4. 146
147 148

Техническое объяснение:

149 150

Предупреждение: Может быть изначально 151 непонятным если вы не ознакомились со структурами dynamicFilters и 152 nativeFilters (Прочитайте их сперва)

153 154
    155
  1. У нас есть один начальный, главный массив brackets, который 156 содержит в себе два бракета: оранжевый бракет и 157 прозрачный бракет с нативным фильтром "Дата 158 создания", соединенные через условие И. В итоге будут отображаться 159 данные которые прошли через фильтры оранжевого бракета И чья дата 160 создания попадает в указанный период. 161 162
    163  {
    164  "boCode": "TEST", // поле описано в таблице внутри "Получение списка инстанций бизнес-объектов"
    165  "paging": { // поле описано в таблице внутри "Получение списка инстанций бизнес-объектов"
    166      "offset": 0,
    167      "limit": 20
    168  },
    169  "brackets" :  [ // ВНИМАНИЕ! Вот здесь наши бракеты будут расположены:
    170      {
    171          "id": "id_1", // Это id оранжевого бракета (Так как фильтров нет можем не указывать их)
    172          "parentId": null,
    173          "parentTreeIds": [], 
    174          "connectionType": "AND", // Связь со следующим соседним бракетом в списке
    175          "brackets": [
    176           {...}, // Рассмотрим ниже
    177           {...}, // Рассмотрим ниже
    178           {...} // Рассмотрим ниже
    179          ]
    180      },
    181      {
    182          "id": "id_2", // Это id прозрачного бракета с нативным фильтром
    183          "parentId": null,
    184          "parentTreeIds": [],
    185          "nativeFilters": [{ // Нативный фильтр на дату создания задачи с "dateFrom" по "dateTo"
    186              "id": "n1",
    187              "type": "CREATED_AT",
    188              "dateFrom": "2023-09-01T00:00:00.000Z",
    189              "dateTo": "2023-09-30T00:00:00.000Z"
    190          }],
    191          "connectionType": "AND",
    192          "brackets": []
    193      }
    194  ],
    195  "state": "ALL", // поле описано в таблице внутри "Получение списка инстанций бизнес-объектов"
    196  }
    197  
    198  
    199
  2. 200 201
  3. Внутри оранжевого бракета у нас есть: прозрачный 202 бракет с нативным фильтром "Автор", фиолетовый 203 бракет и прозрачный бракет с фильтром 204 нативным фильтром "Автор последнего изменения", соединенные через 205 условие ИЛИ. Бракет будет фильтровать задачи (данные) по Автору 206 "Текущий пользователь" ИЛИ по фильтрам фиолетового бракета ИЛИ по 207 Автору последнего изменения "Темирлан Жумагулов". 208 209
    210   {
    211       "id": "id_1", // Это id оранжевого бракета (Так как фильтров нет можем не указывать их)
    212       "parentId": null,
    213       "parentTreeIds": [],
    214       "connectionType": "AND", // Связь со следующим соседним бракетом в списке
    215       "brackets": [
    216          {
    217              "id": "id_1_1", // Это id первого бракета вунтри оранжевого бракета
    218              "parentId": "id_1",
    219              "parentTreeIds": ["id_1"], 
    220              "nativeFilters": [{ // С фильтром на нативное поле Автор
    221                 "id": "n2",
    222                 "type": "CREATED_BY",
    223                 "isCurrentUser": true // По текущему пользователю 
    224              }],
    225              "connectionType": "OR" // Связь со следующим соседним бракетом в списке
    226          }, 
    227          {
    228              "id": "id_1_2", // Это id фиолетвого бракета нужного для группировки и порядка выполнения 
    229              "parentId": "id_1",
    230              "parentTreeIds": ["id_1"], 
    231              "brackets": [
    232                  {...}, // Рассмотрим ниже
    233                  {...} // Рассмотрим ниже
    234              ],
    235              "connectionType": "OR" // Связь со следующим соседним бракетом в списке
    236          }, 
    237          {
    238              "id": "id_1_3",
    239              "parentId": "id_1",
    240              "parentTreeIds": ["id_1"], 
    241              "nativeFilters": [{ // С фильтром на нативное поле Автор
    242                 "id": "n3",
    243                 "type": "CREATED_BY",
    244                 "isCurrentUser": true // По текущему пользователю 
    245              }],
    246              "connectionType": "AND" // Дефолтная связь (Нужно ли вообще ставить тогда?)
    247          } 
    248       ]
    249   }
    250  
    251  
    252
  4. 253 254
  5. Внутри фиолетового бракета у нас есть два прозрачных 255 бракета: первый с нативным фильтром на 256 поле "Автор", а второй с динамичным фильтром на 257 динамическое поле "Статус" созданного внутри БО "Задачи", 258 соединенные через условие И. Бракет будет фильтровать данные по 259 Авторам "Денис Цыпленков" и "qwe qwe" И чей Статус задачи в работе 260 "In work". 261 262
    263  {
    264    "id": "id_1_2", // Это id фиолетвого бракета нужного для группировки и порядка выполнения 
    265    "parentId": "id_1",
    266    "parentTreeIds": ["id_1"], 
    267    "brackets": [
    268        {
    269            "id": "id_1_2_1", // Это id первого бракета внутри фиолетеового бракета
    270            "parentId": "id_1_2",
    271            "parentTreeIds": ["id_1", "id_1_2"],
    272            "nativeFilters": [{ // С нативным фильтром на поле "Автор"
    273                "id": "n3",
    274                "type": "CREATED_BY",
    275                "orgUnitRecords": [
    276                    {
    277                      "id":"dPLgJxgojYu2Q0OR", // Для пользователя с этим id
    278                      "type":"PERSON" 
    279                    }
    280                ]
    281            }],
    282            "connectionType": "AND" 
    283        },
    284        {
    285            "id": "id_1_2_2", // Это id второго бракета внутри фиолетового бракета
    286            "parentId": "id_1_2",
    287            "parentTreeIds": ["id_1", "id_1_2"],
    288            "dynamicFilters": [{ // С фильтром на динамичное поле Статус
    289                "id": "n2",
    290                "fieldId": "sn4jBFXH9SSinZEZ", // С этим id поля
    291                "type": "RADIO_BUTTON_GROUP", // С типос "Единичный список"
    292                "value": "TYmt8hUli27oCoP4" // Где значения идентификатор option-а
    293            }],
    294            "connectionType": "AND"
    295        },
    296    ],
    297    "connectionType": "OR" // Связь со следующим соседним бракетом в списке
    298  }
    299  
    300  
    301
  6. 302
303 304
305

Структура dynamicFilters

306 307

dynamicFilters используется для динамичных полей, такие 308 как текстовое поле, их структура объясняется ниже: дата, число, 309 время и тд.

310 311
312  var dynamicFieldFilter = {
313      {
314          "id": "",
315          "fieldId": "",
316          "type": "",
317          "value": "",
318          "numberFrom": null,
319          "numberTo": null,
320          "dateFrom": null,
321          "dateTo": null,
322          "isCurrentUser": false,
323          "businessObjectId": "", 
324          "boiIds": [{"..."}],
325          "fromFields": [{"..."}],
326          "label": "",
327          "options": [{"..."}],
328          "businessFields": [{"..."}],
329          "selectedBois": [{"..."}],
330          "topLeftLat": 0,
331          "topLeftLon": 0,
332          "bottomRightLat": 0,
333          "bottomRightLon": 0
334      }
335  }
336  
337  
338  
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 359 360 361 362 363 366 367 368 369 370 373 374 375 376 377 380 381 382 383 384 387 388 389 390 391 393 394 395 396 397 399 400 401 402 403 407 408 409 410 411 413 414 415 416 417 423 424 425 426 427 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 445 446 447 448 449 451 452 453 454 455 457 458 459 460 461 463 464 465 466 467 470 471 472 473 474 477 478 479
Атрибут входного JSONОписание
id(String): Уникальный идентификатор фильтра.
fieldId(String или null): Идентификатор поле для который применяется 357 данный фильтр (Как получить id 358 поля?)
type(String): Тип поле, пример INPUT_TEXT. Подробнее о всех типах полей и какие 365 значения им передавать.
value(String или null): Искомое значение для данного поле.
371 Работает только для текстовых полей или для пойска по 372 id.
numberFrom(Number): Начальное числовое значение, связанное с 378 бракетом.
379 Работает только для числового поля.
numberTo(Number): Конечное числовое значение, связанное с 385 бракетом.
386 Работает только для числового поля.
dateFrom(Date или null): Начальная дата, связанная с бракетом.
392 Работает только для полей с датой.
dateTo(Date или null): Конечная дата, связанная с бракетом.
398 Работает только для полей с датой.
isCurrentUser(Boolean): Признак того, нужно ли составлять фильтр еще и по 404 текущему пользователю помимо других пользователей.
405 Работает только для полей с пользователями (Organization 406 Units).
businessObjectId(String или null): Идентификатор вложенного БО внутри вашего 412 БО. При указании будут показываться его .
boiIds(Массив String): Идентификаторы инстанций (вложенного) 418 бизнес-объекта, который является полем внутри вашего 419 бизнес-объекта.
420 Работает только с вложенными бизнес-объектами. Проверяет id БО 421 поля у всех инстанции вашего БО на содержание хотя бы одного 422 значения из массива boiIds.
fromFields(Массив объектов): Составление для вложенных бизнес 428 объектов
label(String): Текстовое обозначение поле (название)
options(Массив объектов): Текстовое обозначение поле (название)
businessFields(Массив объектов): Массив бизнес-полей, которые могут быть 444 связаны с бракетом.
selectedBois(Массив объектов): Массив выбранных инстанций бизнес-объектов, 450 связанных с этим бракетом.
topLeftLat(Double): Широта верхнего левого угла квадрата (1/4 поле, 456 для работы фильтра по карте должны быть все 4 поля)
topLeftLon(Double): Долгота верхнего левого угла квадрата (2/4 поле, 462 для работы фильтра по карте должны быть все 4 поля)
bottomRightLat(Double): Широта нижнего правого угла квадрата. Это 468 поле должно быть меньше чем topLeftLat. (3/4 поле, для 469 работы фильтра по карте должны быть все 4 поля)
bottomRightLon(Double): Долгота нижнего правого угла квадрата. Это 475 поле должно быть меньше чем topLeftLon. (4/4 поле, для 476 работы фильтра по карте должны быть все 4 поля)
480 481

Пример бракета с динамичным фильтром для текстового 482 поля

483 484

1) Для того чтобы составить фильтр по текстовому полю, сперва мы 485 должны присвоить идентификатор бракету, идентификаторам будет 486 "DTb445uu".

487 488
489  var bracketJson = {
490      "id" : "DTb445uu"
491  }
492  
493  
494 495

2) Так как это простой фильтр (не вложенный) "parentId" 496 и "parentTreeIds" можем присвойть null.

497 498
499  var bracketJson = {
500      "id" : "DTb445uu",
501      "parentId": null,
502      "parentTreeIds": []
503  } 
504  
505  
506 507

3) Третьем этапом мы должны определить тип связи "AND", 508 "OR".

509 510
511  var bracketJson = {
512      "id" : "DTb445uu",
513      "parentId": null,
514      "parentTreeIds": [],
515      "connectionType": "AND"
516  } 
517  
518  
519 520

4) Объявление вложенных бракетов, мы пишем простой фильтр 521 поэтому он нам не нужен.

522 523
524  var bracketJson = {
525      "id" : "DTb445uu",
526      "parentId": null,
527      "parentTreeIds": [],
528      "connectionType": "AND",
529      "brackets": []
530  } 
531  
532  
533 534

5) Определяем dynamicFilters:

535 536

id уникальное строковое значение будет 537 "nl4Fcy3Z"

538 539

fieldId нужно взять из редактора БО:

540 541

543 544

Нажимает на шестиугольник.

545 546

548 549

Как мы видим идентификатор данного поле равен 550 "nTCVOX8zaetMLFZF", а типом является INPUT_TEXT.

551 552

Зная всю эту информацию, можем составить фильтр.

553 554

Результат:

555 556
557  var bracketJson = {
558      {
559          "id": "DTb445uu",
560          "parentId": null,
561          "parentTreeIds": [],
562          "connectionType": "AND",
563          "brackets": [],
564          "dynamicFilters": [
565          {
566              "id": "nl4Fcy3Z",
567              "fieldId": "nTCVOX8zaetMLFZF",
568              "type": "INPUT_TEXT",
569              "value": "qwe"
570          }]
571      }
572  }
573  
574  
575 576

Объяснение простыми словами: Ищем у БО с 577 текстовым полем который ровняется к qwe.

578 579

Внимание! Полученный JSON вставляем в массив brackets в 580 запросе для получения списка. 581 Пример:

582 583
584  {
585      "boCode": "TEST", // Код БО
586      "headCodes": ["NUMBER", "TEXT"], // Поле описано в таблице внутри "Получения списка"
587      "orderingFieldCode": "NUMBER", // Код Поля
588      "orderState": "ASC", // Поле описано в таблице внутри "Получения списка"
589      "paging": { // Поле описано в таблице для "Получения списка"
590          "offset": 0, 
591          "limit": 20 
592      },
593      // "filters": [{...}] // Поле описано в таблице внутри "Получения списка"
594      // "search": "1", // Поле описано в таблице внутри "Получения списка"
595      "state": "ALL",
596      "brackets" :  [{ // Вставляем сделанный запрос сюда
597          "id": "DTb445uu", 
598          "parentId": null,
599          "parentTreeIds": [],
600          "connectionType": "AND",
601          "brackets": [],
602          "dynamicFilters": [
603          {
604              "id": "nl4Fcy3Z",
605              "fieldId": "kUVz8yNpVG@NuISC",
606              "type": "INPUT_TEXT",
607              "value": "123"
608          }]
609      }]
610  }
611  
612  
613 614

Структура nativeFilters

615 616
617  var nativeFieldFilter = {
618     "id": "",
619     "type": "",
620     "value": "",
621     "dateFrom": null,
622     "dateTo": null,
623     "isCurrentUser": false,
624     "orgUnitRecords": [
625        {
626            "id": "user_id",
627            "type": "PERSON" // Другие: DEPARTMENT (Департамент), GROUP (Рабочая группа)
628        }
629     ],
630  }
631  
632  
633  
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 655 656 657 658 659 661 662 663 664 665 667 668 669 670 671 675 676 677 678 679 684 685 686 687 688 691 692 693 694 695 698 699 700
Атрибут входного JSONОписание
id(String): Уникальный идентификатор фильтра.
type(String): Тип навтивного поле, пример 652 CREATED_BY. Подробнее о всех типах 654 нативных полей.
dateFrom(Date или null): Начальная дата, связанная с бракетом.
660 Работает только для полей с датой.
dateTo(Date или null): Конечная дата, связанная с бракетом.
666 Работает только для полей с датой.
isCurrentUser(Boolean): Признак того, нужно ли составлять фильтр еще и по 672 текущему пользователю помимо других пользователей.
673 Работает если только в type PARTICIPANTS или 674 CREATED_BY
orgUnitRecords(Массив Объектов): Организационные единицы (Пользователи, 680 Департаменты, Рабочие группы) по которым нужно сделать фильтрацию 681 на авторов или участников инстанций.
682 Работает если только в type PARTICIPANTS или 683 CREATED_BY
orgUnitRecords.id*(String): Идентификатор из 24 символов конкретного 689 пользователя, департамента или рабочей группы. Подробнее 690 о том, как получить данные идентификаторы.
orgUnitRecords.type*(String): Перечисление, указывающая к чему относится id: PERSON 696 (Пользователь), DEPARTMENT (Департамент), GROUP (Рабочая 697 группа)
701 702

Пример бракета с нативным фильтром на поле Автор

703 704

1) Для того чтобы составить фильтр по полю Автор, сперва мы 705 должны присвоить идентификатор бракету, идентификаторам будет 706 "DTb445uu".

707 708
709  var bracketJson = {
710      "id" : "DTb445uu"
711  }
712  
713  
714 715

2) Так как это простой фильтр (не вложенный) "parentId" 716 и "parentTreeIds" можем присвойть null.

717 718
719  var bracketJson = {
720      "id" : "DTb445uu",
721      "parentId": null,
722      "parentTreeIds": []
723  } 
724  
725  
726 727

3) Третьем этапом мы должны определить тип связи "AND", 728 "OR".

729 730
731  var bracketJson = {
732      "id" : "DTb445uu",
733      "parentId": null,
734      "parentTreeIds": [],
735      "connectionType": "AND"
736  } 
737  
738  
739 740

4) Объявление вложенных бракетов, мы пишем простой фильтр 741 поэтому он нам не нужен.

742 743
744  var bracketJson = {
745      "id" : "DTb445uu",
746      "parentId": null,
747      "parentTreeIds": [],
748      "connectionType": "AND",
749      "brackets": []
750  } 
751  
752  
753 754

5) Определяем nativeFilters:

755 756

id уникальное строковое значение будет 757 "nl4Fcy3Z"

758 759

type будет CREATED_BY (Это вы можете узнать 760 в таблице выше)

761 762

orgUnitRecords будет содержать пользователя, но 763 как вариант возможно взять целый департамент или рабочую группу. 764 Но, мы берем одного конкретного автора и не текущего, поэтому нам 765 нужно получить его 766 id и type будет Person

767 768

Зная всю эту информацию, можем составить фильтр:

769 770
771  var bracketJson = {
772      {
773          "id": "DTb445uu",
774          "parentId": null,
775          "parentTreeIds": [],
776          "connectionType": "AND",
777          "brackets": [], 
778          "nativeFilters": [{ // Нативный фильтр на поле "Автор"
779              "id": "nl4Fcy3Z",
780              "type": "CREATED_BY",
781              "orgUnitRecords": [
782                  {
783                      "id":"dPLgJxgojYu2Q0OR", // id нашего пользователя
784                      "type":"PERSON" 
785                  }
786              ]
787          }]
788      }
789  }
790  
791  
792 793

Объяснение простыми словами: Ищем у БО 794 инстанцию, которую создал пользователь с id "dPLgJxgojYu2Q0OR".

795 796

Внимание! Полученный JSON вставляем в массив brackets в 797 запросе для получения списка. 798 Пример:

799 800
801  {
802      "boCode": "TEST", // Код БО
803      "headCodes": ["NUMBER", "TEXT"], // Поле описано в таблице внутри "Получения списка"
804      "orderingFieldCode": "NUMBER", // Код Поля
805      "orderState": "ASC", // Поле описано в таблице внутри "Получения списка"
806      "paging": { // Поле описано в таблице для "Получения списка"
807          "offset": 0, 
808          "limit": 20 
809      },
810      // "filters": [{...}] // Поле описано в таблице внутри "Получения списка"
811      // "search": "1", // Поле описано в таблице внутри "Получения списка"
812      "state": "ALL",
813      "brackets" :  [{ // Вставляем сделанный запрос сюда
814          "id": "DTb445uu", 
815          "parentId": null,
816          "parentTreeIds": [],
817          "connectionType": "AND",
818          "brackets": [],
819          "nativeFilters": [{ // С нативным фильтром на поле "Автор"
820              "id": "nl4Fcy3Z",
821              "type": "CREATED_BY",
822              "orgUnitRecords": [
823                  {
824                      "id":"dPLgJxgojYu2Q0OR", // Для пользователя с этим id
825                      "type":"PERSON" 
826                  }
827              ]
828          }]
829      }]
830  }
831  
832  
833
834 835 at kz.greetgo.md_reader.util.MdUtil.xmlTextToDoc(MdUtil.java:80) at kz.greetgo.md_reader.core.MdConverter.prepareHtmlFileFrom(MdConverter.java:136) at kz.greetgo.md_reader.core.MdConverter.convert(MdConverter.java:208) at kz.greetgo.md_reader.controller.RenderController.downloadToc(RenderController.java:360) at kz.greetgo.md_reader.controller.RenderController.request(RenderController.java:108) at jdk.internal.reflect.GeneratedMethodAccessor3.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at kz.greetgo.md_reader.interceptors.TextReplaceFilter.doFilter(TextReplaceFilter.java:36) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: java.io.IOException: Server returned HTTP response code: 429 for URL: http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:2000) at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:677) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1397) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1333) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.startPE(XMLDTDScannerImpl.java:732) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.skipSeparator(XMLDTDScannerImpl.java:2101) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDecls(XMLDTDScannerImpl.java:2064) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDTDExternalSubset(XMLDTDScannerImpl.java:299) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.dispatch(XMLDocumentScannerImpl.java:1165) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.next(XMLDocumentScannerImpl.java:1040) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:943) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825) at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) at java.xml/com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:247) at java.xml/com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:342) at java.xml/javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:122) at kz.greetgo.md_reader.util.MdUtil.xmlTextToDoc(MdUtil.java:71) ... 48 more