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

Как настроить входящую миграцию через 011 PostgreSQL с нуля

012 013

В этой статье вы прочитаете вначале общую концепцию входящей 014 миграции, потом общий алгоритм настройке входящей миграции, а потом 015 каждый пункт входящей миграции будет расписан детально.

016 017

Общая концепция миграции

018 019
    020
  1. Нужно в PostgreSQL БД создать таблицы нужного формата.
  2. 021 022
  3. Потом в них будет сторонняя система заливать данные в эти 023 таблицы (если эти таблицы будут пустыми, то миграция СРАБОТАЕТ, но 024 впустую).
  4. 025 026
  5. После заливки всех данных в in-таблицы сторонняя система должна 027 зачистить специальную маркерную таблицу.
  6. 028 029
  7. Как только маркерная таблица будет зачищена, система поймёт что 030 данные в in-таблицах готовы и миграция запустится.
  8. 031
032 033

Что нужно знать, чтобы пользоваться этим 034 руководством

035 036
    037
  1. Где находится директория логирования
  2. 038 039
  3. Где находятся файлы конфигурации
  4. 040
041 042

Алгоритм настройки

043 044

Для настройки входящей миграции через PostgreSQL необходимо 045 выполнить следующие пункты:

046 047
    048
  1. Отметить поля в бизнес-объектах, которые нужно мигрировать 049 из-вне на платформе MyBPM;
  2. 050 051
  3. Подключиться к базе-данных PostgreSQL например с помощью 052 pgAdmin - далее будем это БД называть импортной БД;
  4. 053 054
  5. Получить DDL-таблиц из платформы MyBPM;
  6. 055 056
  7. Накатить полеченные DDL-ы в импортной БД;
  8. 057 058
  9. Настроить подключение платформы MyBPM к импортной БД;
  10. 059 060
  11. Активировать миграцию и провести холостой пуск;
  12. 061 062
  13. Загрузить данные и смотреть процесс миграции;
  14. 063
064 065

Пункт 1. Отметить поля в бизнес-объектах

066 067

Давайте в качестве примера создадим простейший набор 068 бизнес-объектов, в котором будут все возможные сочетания полей.

069 070

Бизнес-объекты создаются в режиме редактирования нажатием на 071 кнопку с плюсом и выбором пункта "Бизнес-объект" как показано на 072 картинке:

073 074

pic001.png

075 076

Давайте создадим бизнес-объект "Физическое лицо" как показано на 077 картинке:

078 079

pic001.png

080 081

И укажем ему код: NaturalPerson как показано на 082 картинке:

083 084

pic002.png

085 086

Теперь зададим код Surname для поля 087 Фамилия как показано на картинке:

088 089

pic003.png

090 091

pic004.png

092 093

Остальные поля также, в итоге должно получиться два 094 бизнес-объекта с такими полями:

095 096
097  Бизнес-объект "Физическое лицо" с кодом "NaturalPerson"
098  И полями:  Фамилия        с кодом Surname       тип "Текстовое поле"
099             Имя            с кодом Name          тип "Текстовое поле"
100             ИИН            с кодом IIN           тип "Текстовое поле"
101             Дата рождения  с кодом BirthDate     тип "Дата"
102  
103  Бизнес-объект "Юридическое лицо" с кодом "LegalPerson"
104  И полями:  Имя            с кодом Name          тип "Текстовое поле"
105             БИН            с кодом BIN           тип "Текстовое поле"
106             Дата создания  с кодом CreateDate    тип "Дата"
107  
108  
109  
110  
111 112

Теперь создадим составной бизнес-объект, как показано на 113 картинке:

114 115

pic006.png

116 117

Выберем для его формирования бизнес-объекты Физическое и 118 Юридическое лица, как показано на рисунке:

119 120

pic007.png

121 122

Сформируем поля этого составного объекта-как показано на 123 рисунке:

124 125

pic008.png

126 127

Так же необходимо задать коды.

128 129

Должен получиться такой составной бизнес-объект:

130 131
132  Состановй бизнес-объект: "ЦО Клиент" с кодом "Client"
133  С полями:   "Дата рожд./созд." с кодом "CrDate"  связанный с полями: "Дата рождения" и "Дата создания"
134              "ИИН/БИН"          с кодом "BIIN"    связанный с полями: "ИИН" и "БИН"
135              "Имя"              с кодом "Name"    связанный с полями: "Имя" и "Имя"
136  
137  
138  
139  
140 141

Создадим ещё бизнес-объект "Заявка на клиента" (стрелочкой 142 обозначен перенос мышкой):

143 144

009.png

145 146

И создадим последний бизнес объект "Заявка на физическое лицо" 147 (стрелочкой обозначен перенос мышкой):

148 149

010.png

150 151

Теперь давайте перечислим всю созданную структуру 152 бизнес-объектов:

153 154
155  Бизнес-объект "Физическое лицо" с кодом "NaturalPerson"
156  И полями:  Фамилия        с кодом Surname       тип "Текстовое поле"
157             Имя            с кодом Name          тип "Текстовое поле"
158             ИИН            с кодом IIN           тип "Текстовое поле"
159             Дата рождения  с кодом BirthDate     тип "Дата"
160  
161  
162  
163 164

 

165 166
167  Бизнес-объект "Юридическое лицо" с кодом "LegalPerson"
168  И полями:  Имя            с кодом Name          тип "Текстовое поле"
169             БИН            с кодом BIN           тип "Текстовое поле"
170             Дата создания  с кодом CreateDate    тип "Дата"
171  
172  
173  
174 175

 

176 177
178  Составной бизнес-объект: "ЦО Клиент" с кодом "Client"
179  С полями:   "Дата рожд./созд." с кодом "CrDate"  связанный с полями: "Дата рождения" и "Дата создания"
180              "ИИН/БИН"          с кодом "BIIN"    связанный с полями: "ИИН" и "БИН"
181              "Имя"              с кодом "Name"    связанный с полями: "Имя" и "Имя"
182  
183  
184  
185 186

 

187 188
189  Бизнес-объект "Заявка на клиента" с кодом "ClientClaim"
190  И полями:  Название       с кодом Name          тип "Текстовое поле"
191             Описание       с кодом Descr         тип "Текстовый блок"
192             ЦО Клиент      с кодом CoClient      ссылается на ЦО "ЦО Клиент"
193  
194  
195  
196 197

 

198 199
200  Бизнес-объект "Заявка на физическое лицо" с кодом "NaturalClaim"
201  И полями:  Наименование     с кодом Name          тип "Текстовое поле"
202             Описание         с кодом Descr         тип "Текстовый блок"
203             Физическое лицо  с кодом Natural       ссылается на БО "Физическое лицо"
204  
205  
206  
207  
208 209

Теперь все поля этих бизнес объектов необходимо отметить, что 210 они участвуют в in-migration как показано на рисунке:

211 212

011.png

213 214

Это нужно сделать для всех полей простых бизнес-объектов. Для 215 составных бизнес-объектов это делать не нужно, так как в составном 216 бизнес-объекте данны нет - они распределены по составляющим этого 217 БО.

218 219

Не забывайте сохранять изменённые данные.

220 221

Пункт 2. Подключение к БД PostgreSQL

222 223

Описание этого пункта не входит в обязанность данного 224 руководства. Вам необходимо заранее ознакомиться с подобными 225 руководствами.

226 227

Единственное отметим, что для подключения необходимо знать 228 следующие параметры:

229 230
    231
  1. Хост или IP-адрес сервера базы данных, на котором размещена БД. 232 Например: 192.168.32.31 - это IP-адрес, или db01.mybpm.local
  2. 233 234
  3. Порт подключения - это число например 5432
  4. 235 236
  5. Имя базы данных - например mybpm_out
  6. 237 238
  7. Имя схемы в базе данных - например in_tables
  8. 239 240
  9. Имя пользователя - например mybpm
  10. 241 242
  11. Пароль пользователя - например 6dfxHTESrO
  12. 243
244 245

Эти данные необходимо получить от администратора баз данных. 246 Дальше их ввести в pgAdmin-е при создании подключения, и, если 247 подключение будет успешно, то они правильные.

248 249

Дальше по тексту мы будет пользоваться следующими 250 значениями:

251 252
253  IP-адрес сервера базы данных = 192.168.11.23
254  Порт подключения             = 10018
255  Имя базы данных              = migration
256  Имя схемы в базе данных      = in_tables
257  Имя пользователя             = mybpm_migration
258  Пароль пользователя          = 6dfxHTESrO
259  
260  
261  
262  
263 264

Пункт 3. Получить DDL-таблиц из платформы 265 MyBPM

266 267

В начале в конфиге нужно прописать схему базы данных. Для этого 268 откройте конфиг

269 270
271  /mybpm/configs/InMigrationPostgresConfig.txt
272  
273  
274  
275  
276 277

И в нём укажите схему базы данных, в которой будут помещены 278 таблицы для миграции:

279 280

012.png

281 282

Теперь можно получить DDL-для создания входящих таблиц. Для 283 этого нужно выполнить Rest-запрос:

284 285
286  GET http://localhost:1313/web/migration/generate-in-ddl?pass=PASSWORD&companyCode=greetgo
287  
288  
289  
290  
291 292

Здесь вместо greetgo вам необходимо указать код 293 того аккаунта, в котором вы создали бизнес-объекты.

294 295

А вместо http://localhost:1313 нужно указать адрес 296 установленной платформы MyBPM.

297 298

А вместо PASSWORD нужно указать пароль, который 299 задан в конфиге

300 301
302  /mybpm/configs/InMigrationConfig.txt
303  
304  
305  
306  
307 308

В параметре

309 310
311  # Пароль для сервисов, чтобы их другие не могли вызывать, кто этого пароля не знает.
312  # Если указано меньше шести (6) символов, то считается что пароля нет, и эти сервисы вызвать невозможно.
313  passwordToEnableServices=SuperPuperSecret
314  
315  
316  
317  
318 319

Вот пример как это сделано в Postman-е:

320 321

013.png

322 323

На выходе этого запроса получиться список команд create 324 table ..., в которых будут созданы все необходимые таблицы, 325 через которые будет происходить миграция.

326 327

Пункт 4. Накатить полеченные DDL-ы в импортной 328 БД

329 330

Эти DDL-и нужно применить для импортной базы данных, чтобы в ней 331 все эти таблицы появились. Вот пример как это сделано в 332 DataGrip:

333 334

014.png

335 336

Сгенерировались следующие таблицы:

337 338
339  in_naturalperson - данные простых полей NaturalPerson
340  in_legalperson   - данные простых полей LegalPerson
341  in_naturalclaim  - данные простых полей NaturalClaim
342  in_clientclaim   - данные простых полей ClientClaim
343  in_naturalclaim_natural - связка NaturalClaim через поле NaturalClaim.Natural на бизнес-объект NaturalClaim
344  in_clientclaim_coclient_neturalperson - связка ClientClaim через поле ClientClaim.CoClient на бизнес-объект NaturalPerson
345  in_clientclaim_coclient_legalperson   - связка ClientClaim через поле ClientClaim.CoClient на бизнес-объект LegalPerson
346  in_filestorage - файлы для полей с файлами
347  process_tracking - индикация начала запуска
348  
349  
350  
351  
352 353

Пункт 5. Настроить подключение платформы MyBPM к 354 импортной БД;

355 356

Настройка подключения к импортной БД осуществляется в 357 конфиге:

358 359
360  /mybpm/configs/InMigrationPostgresConfig.txt
361  
362  
363  
364  
365 366

Там ранее уже прописали параметр schemaName. Теперь 367 нужно остальные прописать.

368 369
370  host=192.168.11.23
371  port=10018
372  dbName=migration
373  username=mybpm_migration
374  password=6dfxHTESrO
375  schemaName=in_tables
376  hasInMigration=true
377  
378  
379  
380  
381 382

В данном случае пароль и имя пользователя прописаны в открытом 383 виде. Если это недопустимо, то можно определить переменные 384 окружения, например такие:

385 386
387  IN_MIGRATION_DATABASE=migration
388  IN_MIGRATION_USERNAME=mybpm_migration
389  IN_MIGRATION_PASSWORD=6dfxHTESrO
390  
391  
392  
393  
394 395

И теперь прописать в конфиге 396 /mybpm/configs/InMigrationPostgresConfig.txt ссылки на 397 эти переменные окружения:

398 399
400  host=192.168.11.23
401  port=10018
402  dbName:ENV=IN_MIGRATION_DATABASE
403  username:ENV=IN_MIGRATION_USERNAME
404  password:ENV=IN_MIGRATION_PASSWORD
405  schemaName=in_tables
406  hasInMigration=true
407  
408  
409  
410  
411 412

В результате у конфигурационном файле нет отрытых паролей.

413 414

Пункт 6. Активировать миграцию и провести холостой 415 пуск

416 417

Теперь давайте активируем миграцию и проведём холостой пуск, 418 т.е. пуск на пустые таблицы, чтобы убедиться, что всё работает.

419 420

Информация о миграции кидается в категорию 421 in_pg_migration.

422 423

Вначале настроим информационный журнал миграции. Для этого 424 необходимо зайти в конфиг настройки журналирования:

425 426
427  /mybpm/logging/structure.txt
428  
429  
430  
431  
432 433

В этом файле найти запись (примечание, в редакторе 434 ZooNavigator-а есть горячая кнопа Ctrl+H для поиска)

435 436
437  category tracer_in_pg_migration
438    level TRACE
439    assign_to tracer_in_pg_migration
440  
441  
442  
443  
444 445

Эта категория отфильтровывает логи до уровня TRACE и отсылает их 446 к приёмнику tracer_in_pg_migration. Посмотрим на этот 447 приёмник. В этом же файле нужно найти запись (в zoonavigator-е для 448 поиска можно использовать горячую клавишу Ctrl+H)

449 450
451  destination tracer_in_pg_migration to_big_file traces/in_pg_migration
452    layout trace
453    level TRACE
454  
455  
456  
457  
458 459

Как видим этот приёмник кладёт логи в файл 460 migration/in_pg_migration.log. Найдём этот файл и 461 будем за ним наблюдать.

462 463
464

Миграция активируется в двух местах: активация факта миграции, 465 расписание запуска миграции, таблица 466 in_table.process_tracking

467 468

Сам факт миграции активируется в конфигурационном файле:

469 470
471  /mybpm/configs/InMigrationPostgresConfig.txt
472  
473  
474  
475  
476 477

В параметре:

478 479
480  hasInMigration=true
481  
482  
483  
484  
485 486

Ранее мы уже его поставили в состояние true, т.е. 487 миграция активирована. Проверьте это на всякий случай.

488 489
490

Чтобы миграция запускалась необходимо, чтобы таблица 491 in_table.process_tracking была пустой. Так как мы её 492 только что создали, то она должна быть пустой. Проверьте это. Если 493 там есть запись, то удалите её.

494 495

Запись в этой таблице показывает, что миграция уже отработала 496 или ещё работает, и повторную миграцию запускать не нужно.

497 498
499

Теперь нужно настроить расписание запуска миграции. Оно 500 находиться в конфигурационном файле:

501 502
503  /mybpm/scheduler/core/InMigrationScheduler.scheduler-config.txt
504  
505  
506  
507  
508 509

В параметре:

510 511
512  tryToStartMigration = repeat every 1 minute
513  
514  
515  
516  
517 518

Давайте сделаем чтобы инициация запуска миграции происходила 519 каждую минуту. Не беспокойтесь, параллельно вторая миграция не 520 запуститься, так как это не даст таблица 521 in_table.process_tracking. При запуске миграции в ней 522 появляется запись, которая блокирует последующий запуск миграции 523 параллельно.

524 525

Дальше смотрим ранее указанный лог. Там в течении минуты должны 526 появиться записи. Если там появилась ошибка

527 528
529  java.lang.RuntimeException: nYz1en475t :: config.hasInMigration() = true
530  
531  
532  
533  
534 535

То нужно перезапустить сервер. Изменения в конфиге не 536 применились.

537 538

При старте сервера может вылететь ошибка:

539 540
541  Caused by: org.postgresql.util.PSQLException: FATAL: password authentication failed for user "mybpm_migration"
542  
543  
544  
545  
546 547

Это обозначает что система не смогла подключиться к базе данных. 548 Проверьте правильность параметров в конфиге:

549 550
551  /mybpm/configs/InMigrationPostgresConfig.txt
552  
553  
554  
555  
556 557

И запустите сервер mybpm-api заново.

558 559

Если сервер запустился, значит доступ к БД настроен верно.

560 561

Вот нормальный лог миграции:

562 563
564  2024-12-26T08:59:33.261 INFO  Q inMigration g5mP3u7hF4 :: cleanErrorsTable start
565  2024-12-26T08:59:33.264 INFO  Q inMigration mX9xgIdrQt :: clean days = 10, limit = 10000
566  2024-12-26T08:59:33.268 INFO  Q inMigration vhhSIlN1W5 :: started saving data to kafka
567  2024-12-26T08:59:33.269 INFO  Q inMigration 1RHJ7aF2vu :: errors cleaner deleted 0rows in 6 ms.
568  2024-12-26T08:59:33.285 INFO  Q inMigration w7qP9r57 :: start migration to Kafka by companyId=6f27ebce5e49a79d69e522a8
569  2024-12-26T08:59:33.290 INFO  Q inMigration 27TtdVdLGj :: periodic_update_time updated
570  2024-12-26T08:59:33.298 INFO  Q inMigration uENuePJ3M1 :: start to vacuum system tables
571  2024-12-26T08:59:33.347 INFO  Q inMigration uENuePJ3M1 :: finish to vacuum system tables
572  2024-12-26T08:59:33.440 INFO  Q inMigration 7cUnbAdw29 :: in migration worker returned boStructure as null for Аккаунт
573  2024-12-26T08:59:33.445 INFO  Q inMigration 7cUnbAdw29 :: in migration worker returned boStructure as null for Пользователи
574  2024-12-26T08:59:33.448 INFO  Q inMigration 7cUnbAdw29 :: in migration worker returned boStructure as null for Департамент
575  2024-12-26T08:59:33.450 INFO  Q inMigration 7cUnbAdw29 :: in migration worker returned boStructure as null for Рабочая группа
576  2024-12-26T08:59:33.465 INFO  Q inMigration uAhZ1PFqMe :: copyInTableData start
577  2024-12-26T08:59:33.469 INFO  Q inMigration Gf4MsN7oi5 :: copyInTableData for table = in_NaturalPerson start
578  2024-12-26T08:59:33.479 INFO  Q inMigration 6ri5uiI970 :: copy table with name copy_2024_12_26_in_NaturalPerson is already exists, will be skipped
579  2024-12-26T08:59:33.479 INFO  Q inMigration 9o0WErV28h :: copyInTableData for table = in_NaturalPerson finish
580  2024-12-26T08:59:33.479 INFO  Q inMigration XvCndCKLGg :: extra table is empty, so no copy table will be created for extra
581  2024-12-26T08:59:33.480 INFO  Q inMigration AI0KjWef :: start setting status ON_WORK to in_tables.in_NaturalPerson occupied_id=612308124825686351
582  2024-12-26T08:59:33.489 INFO  Q inMigration CHWarE4y :: finish setting status ON_WORK to in_tables.in_NaturalPerson occupied_id=612308124825686351
583  2024-12-26T08:59:33.496 INFO  Q inMigration uAhZ1PFqMe :: copyInTableData start
584  2024-12-26T08:59:33.497 INFO  Q inMigration Gf4MsN7oi5 :: copyInTableData for table = in_LegalPerson start
585  2024-12-26T08:59:33.497 INFO  Q inMigration 6ri5uiI970 :: copy table with name copy_2024_12_26_in_LegalPerson is already exists, will be skipped
586  2024-12-26T08:59:33.497 INFO  Q inMigration 9o0WErV28h :: copyInTableData for table = in_LegalPerson finish
587  2024-12-26T08:59:33.497 INFO  Q inMigration XvCndCKLGg :: extra table is empty, so no copy table will be created for extra
588  2024-12-26T08:59:33.497 INFO  Q inMigration AI0KjWef :: start setting status ON_WORK to in_tables.in_LegalPerson occupied_id=612308124825686351
589  2024-12-26T08:59:33.502 INFO  Q inMigration CHWarE4y :: finish setting status ON_WORK to in_tables.in_LegalPerson occupied_id=612308124825686351
590  2024-12-26T08:59:33.505 INFO  Q inMigration 7cUnbAdw29 :: in migration worker returned boStructure as null for ЦО Клиент
591  2024-12-26T08:59:33.511 INFO  Q inMigration uAhZ1PFqMe :: copyInTableData start
592  
593  
594  
595  
596 597

При запуске миграции, в таблице:

598 599
600  in_table.process_tracking
601  
602  
603  
604  
605 606

Появиться одна запись, которая будет блокировать дальнейшие 607 запуски миграции.

608 609

Пункт 7. Загрузить данные и смотреть процесс 610 миграции

611 612

Теперь можно заполнять эти таблицы данными.

613 614

После заполнения этих таблиц нужно удалить запись в таблице 615 in_table.process_tracking и миграция запуститься в 616 течении минуты.

617 618

При миграции будет создана таблица:

619 620
621  in_tables.err
622  
623  
624  
625  
626 627

В которой будут помещаться ошибки при миграции. Так же будет 628 создана таблица:

629 630
631  in_table.task
632  
633  
634  
635  
636 637

В которой будут помещаться задания для миграции. Миграция 638 разбивается на задания, и помещаются в эту таблицу. При запуске 639 система исполняет эти задания. Там вначале помещаются задания для 640 миграции простых полей. А потом задания для миграции связей между 641 бизнес-объектами.

642
643 644 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.GeneratedMethodAccessor7.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: 403 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