From 5476e4bceef31d51378abef49bc1bbbc0e8927a3 Mon Sep 17 00:00:00 2001 From: latlon team <info@lat-lon.de> Date: Mon, 12 Aug 2024 05:54:55 +0000 Subject: [PATCH] Code drop - XPLANBOX-3161 update docker-maven-plugin to 0.45.0, fix 2x dependency (0d14a3b57) - XPLANBOX-3169 - disabled assertions of status tests (195ab596c) - XPLANBOX-2765 - enhanced xxplan-manager-api application.properties (e56ec22ad) - XPLANBOX-2765 - improved readme, fixed formatting (c9fd908e1) - XPLANBOX-3169 - improved assertions of FINISHED test (0403eccfb) - XPLANBOX-2765 - send public events at start & end of validation (eef02bb6a) - XPLANBOX-3171 - Modify variable baseUrlValidatorApi to not expect content path (f3b6898f7) - XPLANBOX-3169 - Make Groovy test steps fail after 20 attempts and deactivate failing test steps (1e0e8498c) - Revert "XPLANBOX-3113 - Removed basic authentication of v2 openAPI and showConfig test cases" (358f0fe06) - XPLANBOX-3170 - Remove HTTP status code 422 from XPlanValidatorAPI version 2 (1a0332347) - XPLANBOX-3169 - Implement SoapUI test for status FAILED (f76db4e4c) - XPLANBOX-3113 - Removed basic authentication of v2 openAPI and showConfig test cases (48ade652d) - XPLANBOX-3113 - Added basic authorization for v2 testcases (913a4fe17) - XPLANBOX-3153 - status instead of validation result in ValidationFinishedEvent (88fc95234) - XPLANBOX-3131 - exception handling v2 (6279de9b9) - XPLANBOX-3113 - Fixed two assertions (ed4d113f0) - XPLANBOX-3158 - access to xplan-validator-workspace in xplan-validator-executor (98f4e9dff) - XPLANBOX-3113 - Updated and unified naming of expected error testcases (521b1071c) - XPLANBOX-2756 don't process plans concurrently (d1fa67237) - XPLANBOX-3122 unit test & fix (e375073f4) - XPLANBOX-3113 - Updated and unified naming of expected error testcases (b66cafd32) - XPLANBOX-3144 - replaced endpoint with baseUrlValidatorApi after changes in soapui project (388ae6844) - XPLANBOX-3122 - removed ValidationHandler (moved to xplan-validator-executor), moved ValidationHandlerTest to xplan-validator-executor (54949480b) - XPLANBOX-3153 - described exceptions in /status/{uuid}, handle 404 (7671dc74d) - XPLANBOX-3113 - added soapui test /status/{uuid} -> GET BP 6.0.2 XX retrieveStatus invalidAcceptHeader expectError (bb7a8189b) - XPLANBOX-3149 - fixed formatting (77dec47c9) - XPLANBOX-3149 add validation unit tests previously in validation-api (29111a229) - XPLANBOX-3150 remove s3mock shutdown & fix renaming of s3.* properties (ca23b7c43) - fix unit test (4b99a7348) - XPLANBOX-3113 - Set descriptions in SoapUI tests for XPlanValidatorAPI version 2 (edb0018bf) - XPLANBOX-3113 - Implement SoapUI tests for XPlanValidatorAPI version 2 (9f6e0d23b) - XPLANBOX-3143 - Remove @Hidden from /status path (344fe4f96) - XPLANBOX-3141 - fixed property name; fixed links (a38558b9e) - XPLANBOX-3114 fix env property name (d3d68f799) - XPLANBOX-3141 move configuration to application properties (8af7d8aaa) - XPLANBOX-3118 remove rabbit profile (80495c1c2) - XPLANBOX-2762 - restored dependencies to regeln (otherwise they are missin in /info) (ce9e50cd8) - XPLANBOX-2762 - fixed xml, zip and pdf response (c887857c4) - XPLANBOX-2762 - added ValidationTimeout exception (91420603c) - XPLANBOX-2762 - added de.xleitstelle.xplanung.regeln to xplan-validator-executor; removed regeln and profile dependencies from xplan-validator-api (2df6fea82) - XPLANBOX-2762 v1 validation starts wrapping async validation, cleanup (0df0273d4) - XPLANBOX-3121 - added link to pdf report (d870f375e) - XPLANBOX-2847 - implemented removed files afte validation; added missing pom (6ec852418) - XPLANBOX-2847 - enhanced link to report, added IT (70282a255) - XPLANBOX-2847 - set expiration days for bucket XPLAN_S3_BUCKET_NAME_EXECUTION, return expiration date for validation report (8679e9e6f) - XPLANBOX-3115 - added links; fixed tests; fixes s3 implementation (f841b2e25) - XPLANBOX-3115 - implemented /v2/status/{uuid} (8344523d1) - XPLANBOX-2758 - added spring-boot-starter-actuator and enabled health.probes (e325d84a2) - XPLANBOX-2758 - added modules xplan-validator-storage and xplan-validator-executor (2b0f5f69f) - XPLANBOX-2761 - fixed/enabled unit test by adding getContextId (c51061fb6) - XPLANBOX-2759 - implemented /v2/validate with GML (f2176dd0f) - XPLANBOX-2761 - seperated ResourceConfigs to fix mixing OpenAPI documents (553fc1b61) - XPLANBOX-2756 - implemented XPlanValidatorAPI v2 (1ca148f5d) - XPLANBOX-2760 - renamed PlanStorage and profile; added variable to set directory; implemented required methods in S3PlanValidationExecutionStorage (241972135) - XPLANBOX-2761/2759 - receive validation request event, validate & send validation ready (2e7acfcba) - XPLANBOX-2761 - api v2, send event, consume event (bfe02763d) - XPLANBOX-2756 - WIP - store XPlanArchive in S3 or filesystem (fac7c26ef) Co-authored-by: Dirk Stenger <stenger@lat-lon.de> Co-authored-by: Julian Zilz <zilz@lat-lon.de> Co-authored-by: Lyn Elisa Goltz <goltz@lat-lon.de> Co-authored-by: Marc Guillemot <guillemot@lat-lon.de> Dropped from commit: d9d8dd45563c252aa396eb40bedb99d8eb42c406 --- jenkinsfiles/test.Jenkinsfile | 8 +- pom.xml | 13 +- .../api/commons/openapi/OpenApiFilter.java | 2 +- .../api/commons/v1/model/AbstractLink.java | 163 ++ xplan-core/xplan-core-commons/pom.xml | 4 + .../latlon/xplan/commons}/s3/S3Metadata.java | 2 +- .../de/latlon/xplan/commons}/s3/S3Object.java | 2 +- .../latlon/xplan/commons}/s3/S3Storage.java | 48 +- .../xplan/commons/s3}/StorageException.java | 7 +- xplan-core/xplan-core-manager/pom.xml | 1 - .../manager/document/DocumentStorage.java | 2 +- .../document/XPlanDocumentManager.java | 2 +- .../document/s3/S3DocumentStorage.java | 6 +- .../AmazonS3DocumentStorageContext.java | 2 +- .../storage/StorageCleanUpManager.java | 2 +- .../xplan/manager/storage/StorageEvent.java | 2 +- .../FilesystemStorageCleanUpManager.java | 2 +- .../storage/s3/S3StorageCleanUpManager.java | 4 +- .../storage/s3/config/AmazonS3Context.java | 15 +- .../s3/listener/S3TransactionListener.java | 6 +- .../service/XPlanDeleteService.java | 2 +- .../transaction/service/XPlanEditService.java | 2 +- .../service/XPlanInsertService.java | 2 +- .../wmsconfig/raster/XPlanRasterManager.java | 2 +- .../raster/storage/RasterStorage.java | 1 + .../raster/storage/s3/S3RasterStorage.java | 7 +- .../config/AmazonS3RasterStorageContext.java | 2 +- .../document/s3/S3DocumentStorageIT.java | 2 +- .../s3/S3StorageCleanUpManagerTest.java | 6 +- .../storage/s3/S3StorageTestManual.java | 5 +- .../s3/config/AmazonS3TestContext.java | 11 +- .../s3/config/S3StorageTestContext.java | 4 +- .../storage/s3/S3RasterStorageTest.java | 43 +- .../storage/s3/S3RasterStorageTestManual.java | 4 +- .../src/test/resources/s3Mock.properties | 10 +- ...nRequestNotifier.java => EventSender.java} | 12 +- .../core/validator/events/RabbitConfig.java | 57 +- .../validator/events/RabbitEmitterConfig.java | 39 +- .../validator/events/RabbitEventSender.java | 81 + .../events/RabbitReceiverConfig.java | 41 +- .../core/validator/events/RabbitSettings.java | 32 +- .../events/ValidationFinishedEvent.java | 62 + .../events/ValidationRequestedEvent.java | 74 +- .../events/ValidationTaskQueueConfig.java | 13 + .../events/XPlanboxInternalEvent.java | 11 + .../events/XPlanboxInternalFanoutEvent.java | 11 + .../events/XPlanboxInternalTaskEvent.java | 11 + .../validator/events/XPlanboxPublicEvent.java | 11 + .../events/v1/XPlanboxPublicV1Event.java | 78 + .../events/ValidationFinishedEventTest.java} | 29 +- .../events/ValidationRequestedEventTest.java | 5 +- .../events/v1/XPlanboxPublicV1EventTest.java | 39 + .../xplan-docker-volume-init/setupVolumes.sh | 2 - .../src/main/asciidoc/s3storage.adoc | 2 - .../dokumente/config/S3DocumentContext.java | 4 +- .../dokumente/handler/DocumentHandler.java | 3 +- .../dokumente/service/DocumentService.java | 2 +- .../service/s3/S3DocumentService.java | 8 +- .../xplanbox/api/manager/PlanInfoBuilder.java | 33 +- .../xplanbox/api/manager/v1/model/Link.java | 153 +- .../src/main/resources/application.properties | 13 + xplan-manager/xplan-manager-web/pom.xml | 5 - xplan-tests/xplan-tests-selenium/README.md | 9 +- xplan-tests/xplan-tests-selenium/pom.xml | 27 +- .../runAllSeleniumTests.sh | 1 + .../rabbit/ValidationPublicEventsIT.java | 219 +++ .../validator/rabbit/ValidatorApiV2.java | 33 + xplan-tests/xplan-tests-soapui/README.md | 2 +- xplan-tests/xplan-tests-soapui/pom.xml | 6 +- .../xplan-tests-soapui/runAllSoapUiTests.sh | 2 +- .../xplan-validator-api-soapui-project.xml | 1234 ++++++++++-- xplan-validator/pom.xml | 2 + .../xplan-validator-api/Dockerfile | 5 +- xplan-validator/xplan-validator-api/README.md | 22 +- xplan-validator/xplan-validator-api/pom.xml | 10 +- .../xplanbox/api/validator/SpringBootApp.java | 40 +- .../AbstractApiConfig.java} | 99 +- .../api/validator/api/v1/ApiV1Config.java | 78 + .../api/validator/api/v2/ApiV2Config.java | 81 + .../api/validator/api/v2/ApiV2Filter.java | 22 + .../validator/config/ApplicationContext.java | 37 +- .../validator/config/RabbitConfiguration.java | 3 +- .../exception/InvalidValidationUuid.java | 44 + .../ValidationExecutionExceptionMapper.java | 44 + .../exception/ValidationTimeout.java | 44 + .../handler/AsyncValidationWrapper.java | 102 + .../xplanbox/api/validator/v1/DefaultApi.java | 16 +- .../xplanbox/api/validator/v1/InfoApi.java | 2 + .../api/validator/v1/ValidateApi.java | 158 +- .../api/validator/v2/DefaultApi2.java | 72 + .../xplanbox/api/validator/v2/InfoApi2.java | 62 + .../xplanbox/api/validator/v2/StatusApi.java | 83 + .../api/validator/v2/ValidateApi2.java | 197 ++ .../xplanbox/api/validator/v2/model/Link.java | 144 ++ .../api/validator/v2/model/StatusEnum.java | 21 + .../v2/model/StatusNotification.java | 94 + .../validator/v2/model/ValidationReceipt.java | 41 +- .../main/resources/application-dev.properties | 23 + .../src/main/resources/application.properties | 13 + .../config/FakeAsyncValidationWrapper.java | 51 + .../api/validator/config/TestContext.java | 22 +- .../handler/AsyncValidationWrapperTest.java | 136 ++ .../validator/handler/ConfigHandlerTest.java | 3 +- .../api/validator/v1/DefaultApiTest.java | 16 +- .../validator/v1/ValidateApiJerseyTest.java | 236 +++ .../api/validator/v1/ValidateApiTest.java | 223 +-- .../api/validator/v2/DefaultApi2Test.java | 139 ++ .../api/validator/v2/InfoApi2Test.java | 92 + .../api/validator/v2/StatusApi2Test.java | 80 + .../api/validator/v2/ValidateApi2Test.java | 147 ++ .../api/validator/v1/validation-report1.json | 1 + .../api/validator/v1/validation-report2.xml | 1 + .../xplan-validator-executor/Dockerfile | 44 + .../xplan-validator-executor/README.md | 11 + .../xplan-validator-executor/pom.xml | 217 +++ .../xplan-validator-executor/run.sh | 10 + .../validator/executor/PlanValidator.java | 155 ++ .../validator/executor/SpringBootApp.java | 54 + .../executor/config/ApplicationContext.java | 162 ++ .../config/ExecutorRabbitConfiguration.java | 31 +- .../executor}/handler/ValidationHandler.java | 2 +- .../executor/messagingrabbitmq/Receiver.java | 71 + .../main/resources/application-dev.properties | 23 + .../src/main/resources/application.properties | 50 + .../src/main/resources/log4j2.yaml | 82 + .../validator/executor/PlanValidatorTest.java | 196 ++ .../handler/ValidationHandlerTest.java | 29 +- .../src/test/resources/bplan_valid_41.zip | Bin 0 -> 1652 bytes .../resources/config/application.properties | 26 + .../validator/executor/report1.expected.json | 1697 +++++++++++++++++ .../validator/executor/report2.expected.xml | 1 + .../validator/executor/report6.expected.json | 1 + .../validator/executor/report7.expected.json | 1 + .../src/test/resources/xplan.gml | 100 + .../xplan-validator-storage/pom.xml | 71 + .../xplanbox/validator/storage/ErrorType.java | 34 + .../xplanbox/validator/storage/Status.java | 77 + .../validator/storage/StatusType.java | 21 + .../storage/StoredValidationReport.java | 46 + .../validator/storage/ValidationDetails.java | 29 + .../storage/ValidationExecutionException.java | 20 + .../storage/ValidationExecutionStorage.java | 238 +++ .../storage/config/AmazonS3Context.java | 92 + .../FileSystemValidationExecutionStorage.java | 89 + .../s3/S3PlanValidationExecutionStorage.java | 84 + ...ValidationExecutionStorageStorageTest.java | 75 + .../storage/s3/AmazonS3TestContext.java | 88 + .../s3/S3ValidationExecutionStorageIT.java | 87 + .../s3/S3ValidationExecutionStorageTest.java | 111 ++ .../src/test/resources/BPlan002_5-3.zip | Bin 0 -> 60474 bytes .../src/test/resources/s3Mock.properties | 25 + .../src/test/resources/xplan.gml | 100 + .../xplan/validator/wms/libs.expected.txt | 7 + 153 files changed, 8786 insertions(+), 937 deletions(-) create mode 100644 xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/v1/model/AbstractLink.java rename xplan-core/{xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage => xplan-core-commons/src/main/java/de/latlon/xplan/commons}/s3/S3Metadata.java (97%) rename xplan-core/{xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage => xplan-core-commons/src/main/java/de/latlon/xplan/commons}/s3/S3Object.java (96%) rename xplan-core/{xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage => xplan-core-commons/src/main/java/de/latlon/xplan/commons}/s3/S3Storage.java (82%) rename xplan-core/{xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage => xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3}/StorageException.java (89%) rename xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/{ValidationRequestNotifier.java => EventSender.java} (87%) create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEventSender.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationFinishedEvent.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationTaskQueueConfig.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalEvent.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalFanoutEvent.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalTaskEvent.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxPublicEvent.java create mode 100644 xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1Event.java rename xplan-core/xplan-core-validator-events/src/{main/java/de/latlon/core/validator/events/RabbitValidationRequestNotifier.java => test/java/de/latlon/core/validator/events/ValidationFinishedEventTest.java} (57%) create mode 100644 xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1EventTest.java create mode 100644 xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidationPublicEventsIT.java create mode 100644 xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidatorApiV2.java rename xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/{config/JerseyConfig.java => api/AbstractApiConfig.java} (70%) create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v1/ApiV1Config.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Config.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Filter.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/InvalidValidationUuid.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationExecutionExceptionMapper.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationTimeout.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapper.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/InfoApi2.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/StatusApi.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/Link.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusEnum.java create mode 100644 xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusNotification.java rename xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationMessageReceiver.java => xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/ValidationReceipt.java (57%) create mode 100644 xplan-validator/xplan-validator-api/src/main/resources/application-dev.properties create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/FakeAsyncValidationWrapper.java create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapperTest.java create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiJerseyTest.java create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2Test.java create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/InfoApi2Test.java create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/StatusApi2Test.java create mode 100644 xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2Test.java create mode 100644 xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report1.json create mode 100644 xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report2.xml create mode 100644 xplan-validator/xplan-validator-executor/Dockerfile create mode 100644 xplan-validator/xplan-validator-executor/README.md create mode 100644 xplan-validator/xplan-validator-executor/pom.xml create mode 100755 xplan-validator/xplan-validator-executor/run.sh create mode 100644 xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/PlanValidator.java create mode 100644 xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/SpringBootApp.java create mode 100644 xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ApplicationContext.java rename xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/messagingrabbitmq/Receiver.java => xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ExecutorRabbitConfiguration.java (56%) rename xplan-validator/{xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator => xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor}/handler/ValidationHandler.java (99%) create mode 100644 xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/messagingrabbitmq/Receiver.java create mode 100644 xplan-validator/xplan-validator-executor/src/main/resources/application-dev.properties create mode 100644 xplan-validator/xplan-validator-executor/src/main/resources/application.properties create mode 100644 xplan-validator/xplan-validator-executor/src/main/resources/log4j2.yaml create mode 100644 xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/PlanValidatorTest.java rename xplan-validator/{xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator => xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor}/handler/ValidationHandlerTest.java (91%) create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/bplan_valid_41.zip create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/config/application.properties create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report1.expected.json create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report2.expected.xml create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report6.expected.json create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report7.expected.json create mode 100644 xplan-validator/xplan-validator-executor/src/test/resources/xplan.gml create mode 100644 xplan-validator/xplan-validator-storage/pom.xml create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ErrorType.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/Status.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StatusType.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StoredValidationReport.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationDetails.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionException.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionStorage.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/config/AmazonS3Context.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorage.java create mode 100644 xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/s3/S3PlanValidationExecutionStorage.java create mode 100644 xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorageStorageTest.java create mode 100644 xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/AmazonS3TestContext.java create mode 100644 xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageIT.java create mode 100644 xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageTest.java create mode 100644 xplan-validator/xplan-validator-storage/src/test/resources/BPlan002_5-3.zip create mode 100644 xplan-validator/xplan-validator-storage/src/test/resources/s3Mock.properties create mode 100644 xplan-validator/xplan-validator-storage/src/test/resources/xplan.gml diff --git a/jenkinsfiles/test.Jenkinsfile b/jenkinsfiles/test.Jenkinsfile index 783068c56f..2d06afdbe0 100644 --- a/jenkinsfiles/test.Jenkinsfile +++ b/jenkinsfiles/test.Jenkinsfile @@ -8,7 +8,7 @@ pipeline { string(name: 'BRANCH', defaultValue: "main", description: 'Set branch') string(name: 'CREDENTIALS_ID', defaultValue: "xplanbox.lat-lon.de", description: 'Set id of Jenkins credentials to login to environment (BASIC AUTH)') string(name: 'SERVICES_API_KEY', defaultValue: "xplanbox", description: 'Set ApiKey to access /config of XPlanDienste') - string(name: 'ENDPOINT_VALIDATOR_API', defaultValue: "https://xplanbox.lat-lon.de/xvalidator/api/v1", description: 'Set endpoint of XPlanValidatorAPI') + string(name: 'BASE_URL_VALIDATOR_API', defaultValue: "https://xplanbox.lat-lon.de", description: 'Set base URL of XPlanValidatorAPI') string(name: 'BASE_URL_DIENSTE', defaultValue: "https://xplanbox.lat-lon.de", description: 'Set base URL of XPlanDienste') string(name: 'BASE_URL_INSPIRE_PLU', defaultValue: "https://xplanbox.lat-lon.de", description: 'Set base URL of INSPIRE PLU') string(name: 'BASE_URL_MANAGER_API', defaultValue: "https://xplanbox.lat-lon.de", description: 'Set base URL of XPlanManagerAPI') @@ -44,7 +44,7 @@ pipeline { usernamePassword(credentialsId:"${CREDENTIALS_ID}", passwordVariable: 'Password', usernameVariable: 'Username') ]) { sh 'mvn test -pl :xplan-tests-soapui -Psystem-tests -DtestFileName=xplan-validator-api-soapui-project.xml \ - -Dendpoint=${ENDPOINT_VALIDATOR_API} -Dusername=$Username -Dpassword=$Password' + -DbaseUrlValidatorApi=${BASE_URL_VALIDATOR_API} -Dusername=$Username -Dpassword=$Password' } } } @@ -79,7 +79,9 @@ pipeline { withCredentials([ usernamePassword(credentialsId:"${CREDENTIALS_ID}", passwordVariable: 'Password', usernameVariable: 'Username') ]) { - sh 'mvn integration-test -pl :xplan-tests-selenium -Psystem-tests -DbaseUrlValidatorWeb=${BASE_URL_VALIDATOR_WEB} -Dusername=$Username -Dpassword=$Password' + withEnv(['SKIP_RABBIT_TESTS=true']) { + sh 'mvn integration-test -pl :xplan-tests-selenium -Psystem-tests -DbaseUrlValidatorWeb=${BASE_URL_VALIDATOR_WEB} -Dusername=$Username -Dpassword=$Password' + } } } } diff --git a/pom.xml b/pom.xml index 47c1a68d7d..e7e52fef6a 100644 --- a/pom.xml +++ b/pom.xml @@ -759,7 +759,7 @@ <plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> - <version>0.44.0</version> + <version>0.45.0</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> @@ -1095,6 +1095,11 @@ <artifactId>xplan-core-validator-events</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>xplan-validator-storage</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>xplan-core-schemas</artifactId> @@ -1150,6 +1155,12 @@ <version>1.1.1</version> <scope>test</scope> </dependency> + <dependency> + <groupId>io.findify</groupId> + <artifactId>s3mock_2.13</artifactId> + <version>0.2.6</version> + <scope>test</scope> + </dependency> <!-- pdf report --> <dependency> <groupId>com.github.librepdf</groupId> diff --git a/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/openapi/OpenApiFilter.java b/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/openapi/OpenApiFilter.java index 9161f5a09b..1ca5b88e40 100644 --- a/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/openapi/OpenApiFilter.java +++ b/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/openapi/OpenApiFilter.java @@ -88,7 +88,7 @@ public class OpenApiFilter extends AbstractSpecFilter { paths.putAll(filteredPathItems); } - private String createNewKey(String path) { + protected String createNewKey(String path) { Pattern pattern = Pattern.compile("\\/(xvalidator|xmanager|xdokumente)\\/api\\/v[\\d_\\-\\.]*(\\/|)"); Matcher matcher = pattern.matcher(path); return matcher.replaceFirst("/"); diff --git a/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/v1/model/AbstractLink.java b/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/v1/model/AbstractLink.java new file mode 100644 index 0000000000..29caeb95b4 --- /dev/null +++ b/xplan-core/xplan-core-api/src/main/java/de/latlon/xplanbox/api/commons/v1/model/AbstractLink.java @@ -0,0 +1,163 @@ +/*- + * #%L + * xplan-manager-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.commons.v1.model; + +import java.net.URI; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; + +/** + * Datatype for Link. A AbstractLink to a resource related to the resource such as + * XPlanWerkWMS or the resource itself. + * + * @since 4.0 + */ +@Schema(description = "Link to a resource related to the resource such as XPlanWerkWMS or the resource itself") +@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", + date = "2020-08-28T13:42:47.160+02:00[Europe/Berlin]") +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class AbstractLink { + + private @Valid URI href; + + private @Valid String type; + + private @Valid String hreflang; + + private @Valid String title; + + private @Valid Integer length; + + public AbstractLink href(URI href) { + this.href = href; + return this; + } + + @Schema(example = "https://xplanbox.lat-lon.de/xmanager/api/v1/plan/123", required = true) + @JsonProperty("href") + @NotNull + public URI getHref() { + return href; + } + + public void setHref(URI href) { + this.href = href; + } + + public AbstractLink type(String type) { + this.type = type; + return this; + } + + @Schema(example = "application/json") + @JsonProperty("type") + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public AbstractLink hreflang(String hreflang) { + this.hreflang = hreflang; + return this; + } + + @Schema(example = "en") + @JsonProperty("hreflang") + public String getHreflang() { + return hreflang; + } + + public void setHreflang(String hreflang) { + this.hreflang = hreflang; + } + + public AbstractLink title(String title) { + this.title = title; + return this; + } + + @Schema(example = "Othmarschen 3, Hamburg") + @JsonProperty("title") + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public AbstractLink length(Integer length) { + this.length = length; + return this; + } + + @Schema + @JsonProperty("length") + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractLink link = (AbstractLink) o; + return Objects.equals(this.href, link.href) && Objects.equals(this.type, link.type) + && Objects.equals(this.hreflang, link.hreflang) && Objects.equals(this.title, link.title) + && Objects.equals(this.length, link.length); + } + + @Override + public int hashCode() { + return Objects.hash(href, type, hreflang, title, length); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the + * first line). + */ + protected String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} diff --git a/xplan-core/xplan-core-commons/pom.xml b/xplan-core/xplan-core-commons/pom.xml index 44119c28e0..afda0793b6 100644 --- a/xplan-core/xplan-core-commons/pom.xml +++ b/xplan-core/xplan-core-commons/pom.xml @@ -120,6 +120,10 @@ <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-s3</artifactId> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Metadata.java b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Metadata.java similarity index 97% rename from xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Metadata.java rename to xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Metadata.java index 74d7a5dcf3..df5eccda75 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Metadata.java +++ b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Metadata.java @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplan.manager.storage.s3; +package de.latlon.xplan.commons.s3; /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Object.java b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Object.java similarity index 96% rename from xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Object.java rename to xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Object.java index b9cb4d69d6..5ae595abbc 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Object.java +++ b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Object.java @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplan.manager.storage.s3; +package de.latlon.xplan.commons.s3; /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Storage.java b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Storage.java similarity index 82% rename from xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Storage.java rename to xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Storage.java index 65cee68860..e16095e7c9 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3Storage.java +++ b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/S3Storage.java @@ -18,29 +18,33 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplan.manager.storage.s3; +package de.latlon.xplan.commons.s3; + +import static com.amazonaws.services.s3.model.BucketLifecycleConfiguration.ENABLED; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.Bucket; +import com.amazonaws.services.s3.model.BucketLifecycleConfiguration; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectResult; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.amazonaws.services.s3.model.lifecycle.LifecycleFilter; import de.latlon.xplan.commons.archive.ArchiveEntry; import de.latlon.xplan.commons.archive.XPlanArchiveContentAccess; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.List; - /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> * @since 7.0 @@ -58,13 +62,24 @@ public class S3Storage { this.bucketName = bucketName; } + public void setBucketExpirationDate(String id, int expirationInDays) throws StorageException { + createBucketIfNotExists(); + BucketLifecycleConfiguration bucketLifecycleConfig = new BucketLifecycleConfiguration(); + BucketLifecycleConfiguration.Rule rule = new BucketLifecycleConfiguration.Rule().withId(id) + .withExpirationInDays(expirationInDays) + .withStatus(ENABLED) + .withFilter(new LifecycleFilter()); + bucketLifecycleConfig.withRules(rule); + client.setBucketLifecycleConfiguration(bucketName, bucketLifecycleConfig); + } + /** * @param key of the object to return * @return the S3Object with the passed key, never <code>null</code> * @throws StorageException if an error occurred requesting the object or an object * with the passed key was not found */ - public de.latlon.xplan.manager.storage.s3.S3Object getObject(String key) throws StorageException { + public de.latlon.xplan.commons.s3.S3Object getObject(String key) throws StorageException { S3Object object = null; try { LOG.info("Get object with key {} from bucket {}.", key, bucketName); @@ -74,7 +89,7 @@ public class S3Storage { objectMetadata.getContentLength()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); object.getObjectContent().transferTo(bos); - return new de.latlon.xplan.manager.storage.s3.S3Object(s3Metadata, bos.toByteArray()); + return new de.latlon.xplan.commons.s3.S3Object(s3Metadata, bos.toByteArray()); } catch (AmazonServiceException | IOException e) { throw new StorageException("Could not get object with key " + key + " from bucket " + bucketName + ".", e); @@ -107,8 +122,9 @@ public class S3Storage { return objectsToDelete.getObjectSummaries(); } - protected String insertObject(int planId, String entryName, XPlanArchiveContentAccess archive) + public String insertObject(int planId, String entryName, XPlanArchiveContentAccess archive) throws StorageException { + createBucketIfNotExists(); String key = createKey(planId, entryName); try { LOG.info("Insert object with key {} in bucket {}.", key, bucketName); @@ -127,17 +143,19 @@ public class S3Storage { } } - protected void insertObject(String key, Path file) throws StorageException { + public PutObjectResult insertObject(String key, Path file) throws StorageException { + createBucketIfNotExists(); try { LOG.info("Insert object with key {} in bucket {}.", key, bucketName); - client.putObject(bucketName, key, file.toFile()); + return client.putObject(bucketName, key, file.toFile()); } catch (AmazonServiceException e) { throw new StorageException("Could not insert object with key " + key + " in bucket " + bucketName + ".", e); } } - public void insertObject(de.latlon.xplan.manager.storage.s3.S3Object object) throws StorageException { + public void insertObject(de.latlon.xplan.commons.s3.S3Object object) throws StorageException { + createBucketIfNotExists(); String key = object.getS3Metadata().getKey(); try { LOG.info("Insert object with key {} in bucket {}.", key, bucketName); diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/StorageException.java b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/StorageException.java similarity index 89% rename from xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/StorageException.java rename to xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/StorageException.java index ff5dc2d202..198f42ef45 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/StorageException.java +++ b/xplan-core/xplan-core-commons/src/main/java/de/latlon/xplan/commons/s3/StorageException.java @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplan.manager.wmsconfig.raster.storage; +package de.latlon.xplan.commons.s3; import com.amazonaws.AmazonServiceException; @@ -35,6 +35,11 @@ public class StorageException extends Exception { this.statusCode = ((AmazonServiceException) e).getStatusCode(); } + public StorageException(String message, int statusCode) { + super(message); + this.statusCode = statusCode; + } + public int getStatusCode() { return statusCode; } diff --git a/xplan-core/xplan-core-manager/pom.xml b/xplan-core/xplan-core-manager/pom.xml index 0805384960..2870fd8959 100644 --- a/xplan-core/xplan-core-manager/pom.xml +++ b/xplan-core/xplan-core-manager/pom.xml @@ -164,7 +164,6 @@ <dependency> <groupId>io.findify</groupId> <artifactId>s3mock_2.13</artifactId> - <version>0.2.6</version> <scope>test</scope> </dependency> <dependency> diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/DocumentStorage.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/DocumentStorage.java index 56806e16b0..906d814935 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/DocumentStorage.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/DocumentStorage.java @@ -22,7 +22,7 @@ package de.latlon.xplan.manager.document; import de.latlon.xplan.commons.archive.XPlanArchive; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import java.nio.file.Path; import java.util.List; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/XPlanDocumentManager.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/XPlanDocumentManager.java index 8fde067de2..1764ae100f 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/XPlanDocumentManager.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/XPlanDocumentManager.java @@ -25,7 +25,7 @@ import de.latlon.xplan.commons.reference.ExternalReference; import de.latlon.xplan.commons.reference.ExternalReferenceInfo; import de.latlon.xplan.manager.edit.EditedArtefacts; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/S3DocumentStorage.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/S3DocumentStorage.java index 17ea9de039..318db1a9ce 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/S3DocumentStorage.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/S3DocumentStorage.java @@ -24,9 +24,9 @@ import com.amazonaws.services.s3.AmazonS3; import de.latlon.xplan.commons.archive.XPlanArchive; import de.latlon.xplan.manager.document.DocumentStorage; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.storage.s3.S3Object; -import de.latlon.xplan.manager.storage.s3.S3Storage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; +import de.latlon.xplan.commons.s3.StorageException; import java.nio.file.Path; import java.util.ArrayList; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/config/AmazonS3DocumentStorageContext.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/config/AmazonS3DocumentStorageContext.java index d6d362679c..313ac42727 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/config/AmazonS3DocumentStorageContext.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/document/s3/config/AmazonS3DocumentStorageContext.java @@ -42,7 +42,7 @@ public class AmazonS3DocumentStorageContext { @Bean public S3DocumentStorage documentStorage(AmazonS3 s3Client, - @Value("${s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { + @Value("${xplanbox.s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { return new S3DocumentStorage(s3Client, bucketName); } diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageCleanUpManager.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageCleanUpManager.java index d342cd6f91..2b0278093e 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageCleanUpManager.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageCleanUpManager.java @@ -20,7 +20,7 @@ */ package de.latlon.xplan.manager.storage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageEvent.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageEvent.java index 688e8e0e5d..e81d45b641 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageEvent.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/StorageEvent.java @@ -20,7 +20,7 @@ */ package de.latlon.xplan.manager.storage; -import de.latlon.xplan.manager.storage.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Object; import java.nio.file.Path; import java.util.ArrayList; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/filesystem/FilesystemStorageCleanUpManager.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/filesystem/FilesystemStorageCleanUpManager.java index cf12833a2d..2898356c89 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/filesystem/FilesystemStorageCleanUpManager.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/filesystem/FilesystemStorageCleanUpManager.java @@ -23,7 +23,7 @@ package de.latlon.xplan.manager.storage.filesystem; import de.latlon.xplan.manager.storage.StorageCleanUpManager; import de.latlon.xplan.manager.storage.StorageEvent; import de.latlon.xplan.manager.wmsconfig.raster.storage.FileSystemStorage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManager.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManager.java index bf213e712e..73673267d2 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManager.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManager.java @@ -22,9 +22,11 @@ package de.latlon.xplan.manager.storage.s3; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.S3ObjectSummary; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; import de.latlon.xplan.manager.storage.StorageCleanUpManager; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import java.util.List; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3Context.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3Context.java index b91778c0f3..8087bbe556 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3Context.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3Context.java @@ -27,7 +27,7 @@ import com.amazonaws.client.builder.AwsClientBuilder; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import de.latlon.xplan.manager.storage.StorageCleanUpManager; -import de.latlon.xplan.manager.storage.s3.S3Storage; +import de.latlon.xplan.commons.s3.S3Storage; import de.latlon.xplan.manager.storage.s3.S3StorageCleanUpManager; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -49,8 +49,8 @@ public class AmazonS3Context { @Bean(destroyMethod = "shutdown") public AmazonS3 s3Client(AWSCredentials credentials, - @Value("${s3.region:#{environment.XPLAN_S3_REGION}}") String signingRegion, - @Value("${s3.endpoint.url:#{environment.XPLAN_S3_ENDPOINT}}") String endpointUrl) { + @Value("${xplanbox.s3.region:#{environment.XPLAN_S3_REGION}}") String signingRegion, + @Value("${xplanbox.s3.endpoint.url:#{environment.XPLAN_S3_ENDPOINT}}") String endpointUrl) { AmazonS3 client; // TODO refactoring if/else to @ConditionalOnExpression with SpringBoot into 2 // SpringBeans @@ -72,20 +72,21 @@ public class AmazonS3Context { } @Bean - public AWSCredentials credentials(@Value("${s3.accessKeyId:#{environment.XPLAN_S3_ACCESS_KEY}}") String accessKeyId, - @Value("${s3.secretKey:#{environment.XPLAN_S3_SECRET_ACCESS_KEY}}") String secretKey) { + public AWSCredentials credentials( + @Value("${xplanbox.s3.accessKeyId:#{environment.XPLAN_S3_ACCESS_KEY}}") String accessKeyId, + @Value("${xplanbox.s3.secretKey:#{environment.XPLAN_S3_SECRET_ACCESS_KEY}}") String secretKey) { return new BasicAWSCredentials(accessKeyId, secretKey); } @Bean public StorageCleanUpManager storageCleanUpManager(AmazonS3 s3Client, - @Value("${s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { + @Value("${xplanbox.s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { return new S3StorageCleanUpManager(s3Client, bucketName); } @Bean public S3Storage rollbackStorage(AmazonS3 s3Client, - @Value("${s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { + @Value("${xplanbox.s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { return new S3Storage(s3Client, bucketName); } diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/listener/S3TransactionListener.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/listener/S3TransactionListener.java index d4fc106de3..c5b02d943d 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/listener/S3TransactionListener.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/storage/s3/listener/S3TransactionListener.java @@ -21,9 +21,9 @@ package de.latlon.xplan.manager.storage.s3.listener; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.storage.s3.S3Object; -import de.latlon.xplan.manager.storage.s3.S3Storage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; +import de.latlon.xplan.commons.s3.StorageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanDeleteService.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanDeleteService.java index 06b8f29388..93282c6cd8 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanDeleteService.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanDeleteService.java @@ -23,7 +23,7 @@ package de.latlon.xplan.manager.transaction.service; import de.latlon.xplan.manager.database.XPlanManagerDao; import de.latlon.xplan.manager.storage.StorageCleanUpManager; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.springframework.context.ApplicationEventPublisher; import jakarta.transaction.Transactional; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanEditService.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanEditService.java index 76a21df86e..1b86828c46 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanEditService.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanEditService.java @@ -26,7 +26,7 @@ import de.latlon.xplan.manager.document.XPlanDocumentManager; import de.latlon.xplan.manager.edit.EditedArtefacts; import de.latlon.xplan.manager.web.shared.PlanStatus; import de.latlon.xplan.manager.web.shared.XPlan; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.deegree.feature.FeatureCollection; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanInsertService.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanInsertService.java index e2c8846c35..41cc2a7f5c 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanInsertService.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/transaction/service/XPlanInsertService.java @@ -29,7 +29,7 @@ import de.latlon.xplan.manager.database.XPlanManagerDao; import de.latlon.xplan.manager.document.XPlanDocumentManager; import de.latlon.xplan.manager.transaction.PlanImportData; import de.latlon.xplan.manager.web.shared.PlanStatus; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/XPlanRasterManager.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/XPlanRasterManager.java index 9b50e576fc..2683ed0906 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/XPlanRasterManager.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/XPlanRasterManager.java @@ -28,7 +28,7 @@ import de.latlon.xplan.manager.storage.StorageEvent; import de.latlon.xplan.manager.web.shared.PlanStatus; import de.latlon.xplan.manager.wmsconfig.raster.config.RasterConfigManager; import de.latlon.xplan.manager.wmsconfig.raster.storage.RasterStorage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplan.manager.workspace.WorkspaceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/RasterStorage.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/RasterStorage.java index 73d108de24..420216fc45 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/RasterStorage.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/RasterStorage.java @@ -21,6 +21,7 @@ package de.latlon.xplan.manager.wmsconfig.raster.storage; import de.latlon.xplan.commons.archive.XPlanArchiveContentAccess; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplan.manager.storage.StorageEvent; import java.io.IOException; diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorage.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorage.java index 1a2e939b7a..2414e71ccf 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorage.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorage.java @@ -23,10 +23,10 @@ package de.latlon.xplan.manager.wmsconfig.raster.storage.s3; import com.amazonaws.services.s3.AmazonS3; import de.latlon.xplan.commons.archive.XPlanArchiveContentAccess; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.storage.s3.S3Object; -import de.latlon.xplan.manager.storage.s3.S3Storage; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; import de.latlon.xplan.manager.wmsconfig.raster.storage.RasterStorage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -45,7 +45,6 @@ public class S3RasterStorage extends S3Storage implements RasterStorage { @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN") public String addRasterFile(int planId, String referenceEntryName, String georefEntryName, XPlanArchiveContentAccess archive, StorageEvent storageEvent) throws StorageException { - createBucketIfNotExists(); String objectKey = insertObject(planId, referenceEntryName, archive); storageEvent.addInsertedKey(objectKey); if (georefEntryName != null) { diff --git a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/config/AmazonS3RasterStorageContext.java b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/config/AmazonS3RasterStorageContext.java index a10f3c7403..0305d17b65 100644 --- a/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/config/AmazonS3RasterStorageContext.java +++ b/xplan-core/xplan-core-manager/src/main/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/config/AmazonS3RasterStorageContext.java @@ -47,7 +47,7 @@ public class AmazonS3RasterStorageContext { @Bean public S3RasterStorage rasterStorage(AmazonS3 s3Client, - @Value("${s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { + @Value("${xplanbox.s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { return new S3RasterStorage(s3Client, bucketName); } diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/document/s3/S3DocumentStorageIT.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/document/s3/S3DocumentStorageIT.java index b697547fa8..498a1d7f98 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/document/s3/S3DocumentStorageIT.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/document/s3/S3DocumentStorageIT.java @@ -25,7 +25,7 @@ import de.latlon.xplan.commons.archive.XPlanArchiveCreator; import de.latlon.xplan.manager.document.s3.config.AmazonS3DocumentStorageContext; import de.latlon.xplan.manager.storage.StorageEvent; import de.latlon.xplan.manager.storage.s3.config.AmazonS3TestContext; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManagerTest.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManagerTest.java index 95714db597..07ced67fa9 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManagerTest.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageCleanUpManagerTest.java @@ -27,7 +27,7 @@ import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectInputStream; import com.amazonaws.services.s3.model.S3ObjectSummary; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -74,8 +74,8 @@ public class S3StorageCleanUpManagerTest { verify(client).listObjects(BUCKET_NAME, "1_"); verify(client).deleteObject(BUCKET_NAME, "1_test.png"); - ArgumentCaptor<de.latlon.xplan.manager.storage.s3.S3Object> argument = ArgumentCaptor - .forClass(de.latlon.xplan.manager.storage.s3.S3Object.class); + ArgumentCaptor<de.latlon.xplan.commons.s3.S3Object> argument = ArgumentCaptor + .forClass(de.latlon.xplan.commons.s3.S3Object.class); verify(storageEvent).addDeletedKey(argument.capture()); assertThat(argument.getValue().getS3Metadata().getKey(), is("1_test.png")); } diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageTestManual.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageTestManual.java index 8f9cdd8636..453f98b547 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageTestManual.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/S3StorageTestManual.java @@ -22,9 +22,12 @@ package de.latlon.xplan.manager.storage.s3; import de.latlon.xplan.commons.archive.XPlanArchiveContentAccess; import de.latlon.xplan.commons.archive.XPlanArchiveCreator; +import de.latlon.xplan.commons.s3.S3Metadata; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; import de.latlon.xplan.manager.storage.s3.config.AmazonS3TestContext; import de.latlon.xplan.manager.storage.s3.config.S3StorageTestContext; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3TestContext.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3TestContext.java index c69428f645..e989e049de 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3TestContext.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/AmazonS3TestContext.java @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% @@ -66,8 +66,10 @@ public class AmazonS3TestContext { @Bean @Primary @Profile("mock") - public AmazonS3 s3TestClient(@Value("${s3.region}") String signingRegion, - @Value("${s3.bucketName}") String bucketName, @Value("${s3.endpoint.url}") String url) { + public AmazonS3 s3TestClient(@Value("${xplanbox.s3.region}") String signingRegion, + @Value("${xplanbox.s3.bucketName}") String bucketName, @Value("${xplanbox.s3.endpoint.url}") String url) { + + System.out.println("region: " + signingRegion + ", bucket: " + bucketName + ", url: " + url); AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(url + ":" + port, signingRegion); AmazonS3 client = AmazonS3ClientBuilder.standard() @@ -84,7 +86,6 @@ public class AmazonS3TestContext { s3TestClient.shutdown(); if (s3Mock != null) { s3Mock.stop(); - s3Mock.shutdown(); } } diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/S3StorageTestContext.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/S3StorageTestContext.java index 1a8b23ca61..f65645921d 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/S3StorageTestContext.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/storage/s3/config/S3StorageTestContext.java @@ -21,7 +21,7 @@ package de.latlon.xplan.manager.storage.s3.config; import com.amazonaws.services.s3.AmazonS3; -import de.latlon.xplan.manager.storage.s3.S3Storage; +import de.latlon.xplan.commons.s3.S3Storage; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,7 +34,7 @@ public class S3StorageTestContext { @Bean public S3Storage rollbackStorage(AmazonS3 s3Client, - @Value("${s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { + @Value("${xplanbox.s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { return new S3Storage(s3Client, bucketName); } diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTest.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTest.java index 5bc5da6cc0..1f8e76df11 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTest.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTest.java @@ -20,36 +20,25 @@ */ package de.latlon.xplan.manager.wmsconfig.raster.storage.s3; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.InputStream; +import java.util.Collections; +import java.util.List; + import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.ObjectListing; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; -import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.amazonaws.services.s3.model.*; import de.latlon.xplan.commons.archive.ArchiveEntry; import de.latlon.xplan.commons.archive.XPlanArchiveContentAccess; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; import org.junit.Test; import org.mockito.ArgumentCaptor; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; - -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> */ @@ -58,7 +47,7 @@ public class S3RasterStorageTest { private static final String BUCKET_NAME = "xplanbox"; @Test - public void testAddRasterFile() throws IOException, StorageException { + public void testAddRasterFile() throws StorageException { AmazonS3 client = spy(AmazonS3.class); S3RasterStorage s3RasterStorage = new S3RasterStorage(client, BUCKET_NAME); XPlanArchiveContentAccess archive = mockArchive(); @@ -67,7 +56,7 @@ public class S3RasterStorageTest { String key = s3RasterStorage.addRasterFile(1, "test.png", "test.png.aux.xml", archive, storageEvent); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); assertThat(key, is("1_test.png")); - verify(client).doesBucketExistV2(eq(BUCKET_NAME)); + verify(client, times(2)).doesBucketExistV2(eq(BUCKET_NAME)); verify(client, times(2)).putObject(eq(BUCKET_NAME), captor.capture(), nullable(InputStream.class), any(ObjectMetadata.class)); assertThat(captor.getAllValues(), hasItems("1_test.png", "1_test.png.aux.xml")); @@ -99,8 +88,8 @@ public class S3RasterStorageTest { s3RasterStorage.deleteRasterFile(1, "test.png", storageEvent); verify(client).deleteObject(BUCKET_NAME, "1_test.png"); - ArgumentCaptor<de.latlon.xplan.manager.storage.s3.S3Object> argument = ArgumentCaptor - .forClass(de.latlon.xplan.manager.storage.s3.S3Object.class); + ArgumentCaptor<de.latlon.xplan.commons.s3.S3Object> argument = ArgumentCaptor + .forClass(de.latlon.xplan.commons.s3.S3Object.class); verify(storageEvent).addDeletedKey(argument.capture()); assertThat(argument.getValue().getS3Metadata().getKey(), is("1_test.png")); } diff --git a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTestManual.java b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTestManual.java index 90706d7dc0..fb0fb651cb 100644 --- a/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTestManual.java +++ b/xplan-core/xplan-core-manager/src/test/java/de/latlon/xplan/manager/wmsconfig/raster/storage/s3/S3RasterStorageTestManual.java @@ -23,9 +23,9 @@ package de.latlon.xplan.manager.wmsconfig.raster.storage.s3; import de.latlon.xplan.commons.archive.XPlanArchiveContentAccess; import de.latlon.xplan.commons.archive.XPlanArchiveCreator; import de.latlon.xplan.manager.storage.StorageEvent; -import de.latlon.xplan.manager.storage.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Object; import de.latlon.xplan.manager.storage.s3.config.AmazonS3TestContext; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplan.manager.wmsconfig.raster.storage.s3.config.AmazonS3RasterStorageContext; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/xplan-core/xplan-core-manager/src/test/resources/s3Mock.properties b/xplan-core/xplan-core-manager/src/test/resources/s3Mock.properties index 3edcb5508b..419aaa99b2 100644 --- a/xplan-core/xplan-core-manager/src/test/resources/s3Mock.properties +++ b/xplan-core/xplan-core-manager/src/test/resources/s3Mock.properties @@ -18,8 +18,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # #L% ### -s3.accessKeyId= -s3.secretKey= -s3.bucketName=latlonxpb -s3.region=eu-central-1 -s3.endpoint.url=http://localhost +xplanbox.s3.accessKeyId= +xplanbox.s3.secretKey= +xplanbox.s3.bucketName=latlonxpb +xplanbox.s3.region=eu-central-1 +xplanbox.s3.endpoint.url=http://localhost diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestNotifier.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/EventSender.java similarity index 87% rename from xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestNotifier.java rename to xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/EventSender.java index e88f76163b..5feb9fe096 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestNotifier.java +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/EventSender.java @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% @@ -22,10 +22,12 @@ package de.latlon.core.validator.events; /** * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> - * @since 7.2 + * @since 8.0 */ -public interface ValidationRequestNotifier { +public interface EventSender { - void sendEvent(ValidationRequestedEvent event); + void sendEvent(XPlanboxInternalEvent e); + + void sendPublicEvent(XPlanboxPublicEvent e); } diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitConfig.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitConfig.java index 1c15bcc22f..a89b1713b6 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitConfig.java +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitConfig.java @@ -8,25 +8,30 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.core.validator.events; -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.Queue; -import org.springframework.amqp.core.TopicExchange; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; /** @@ -34,33 +39,47 @@ import org.springframework.context.annotation.Profile; * @since 7.2 */ @Configuration -@Profile("rabbit") +@Import({ ValidationTaskQueueConfig.class }) +@Profile("!test") public class RabbitConfig { - @Bean - RabbitSettings rabbitSettings() { - return new RabbitSettings(); - } + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + @interface InternalFanout { - @Bean - Queue queue(RabbitSettings rabbitSettings) { - return new Queue(rabbitSettings.getInternalQueueName(), true); } - @Bean - TopicExchange exchange(RabbitSettings rabbitSettings) { - return new TopicExchange(rabbitSettings.getInternalExchangeName()); + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + @interface PublicFanout { + } @Bean - Binding binding(Queue queue, TopicExchange exchange) { - return BindingBuilder.bind(queue).to(exchange).with("foo.bar.#"); + @ConditionalOnMissingBean(RabbitSettings.class) + RabbitSettings rabbitSettings() { + return new RabbitSettings(); } @Bean + @ConditionalOnMissingBean(Jackson2JsonMessageConverter.class) public Jackson2JsonMessageConverter jsonMessageConverter() { Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter(); return jsonConverter; } + @Bean + @InternalFanout + public FanoutExchange internalFanoutExchange(RabbitSettings rabbitSettings) { + return new FanoutExchange(rabbitSettings.getInternalFanoutName()); + } + + @Bean + @PublicFanout + public FanoutExchange publicFanoutExchange(RabbitSettings rabbitSettings) { + return new FanoutExchange(rabbitSettings.getPublicFanoutName()); + } + } diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEmitterConfig.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEmitterConfig.java index b6440f7f6b..d511e5bc04 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEmitterConfig.java +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEmitterConfig.java @@ -8,57 +8,42 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.core.validator.events; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; -@Configuration -@Import({ RabbitConfig.class }) +import de.latlon.core.validator.events.RabbitConfig.InternalFanout; +import de.latlon.core.validator.events.RabbitConfig.PublicFanout; + /** * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> * @since 7.2 */ +@Configuration +@Import({ RabbitConfig.class }) +@Profile("!test") public class RabbitEmitterConfig { @Bean - @Profile("rabbit") - public RabbitTemplate rabbitTemplate(Jackson2JsonMessageConverter msgConverter, ConnectionFactory connectionFactory, + EventSender validationRequestNotifier(RabbitTemplate rabbitTemplate, + @InternalFanout FanoutExchange internalFanoutExchange, @PublicFanout FanoutExchange publicFanoutExchange, RabbitSettings rabbitSettings) { - RabbitTemplate template = new RabbitTemplate(); - template.setMessageConverter(msgConverter); - template.setConnectionFactory(connectionFactory); - template.setExchange(rabbitSettings.getInternalExchangeName()); - - return template; - } - - @Bean - @Profile("rabbit") - ValidationRequestNotifier validationRequestNotifier(RabbitTemplate rabbitTemplate) { - return new RabbitValidationRequestNotifier(rabbitTemplate); - } - - @Bean - @Profile("!rabbit") - ValidationRequestNotifier noOpValidationRequestNotifier(RabbitTemplate rabbitTemplate) { - return (ValidationRequestedEvent event) -> { - }; + return new RabbitEventSender(rabbitTemplate, internalFanoutExchange, publicFanoutExchange, rabbitSettings); } } diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEventSender.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEventSender.java new file mode 100644 index 0000000000..82ca44ac29 --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitEventSender.java @@ -0,0 +1,81 @@ +/*- + * #%L + * xplan-core-validator-events - Modul zur Gruppierung der Kernmodule + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.core.validator.events; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + +/** + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 7.2 + */ +class RabbitEventSender implements EventSender { + + private static final Logger LOG = getLogger(RabbitEventSender.class); + + private final RabbitTemplate rabbitTemplate; + + private final String publicFanoutExchangeName; + + private final String internalFanoutExchangeName; + + private final Map<Class<?>, String> taskClass2QueueName = new HashMap<>(); + + public RabbitEventSender(RabbitTemplate rabbitTemplate, FanoutExchange internalFanoutExchange, + FanoutExchange publicFanoutExchange, RabbitSettings rabbitSettings) { + this.internalFanoutExchangeName = internalFanoutExchange.getName(); + this.publicFanoutExchangeName = publicFanoutExchange.getName(); + this.rabbitTemplate = rabbitTemplate; + + taskClass2QueueName.put(ValidationRequestedEvent.class, rabbitSettings.getInternalValidationWorkQueueName()); + } + + public void sendEvent(XPlanboxInternalEvent event) { + if (event instanceof XPlanboxInternalTaskEvent) { + String workQueueName = taskClass2QueueName.get(event.getClass()); + if (workQueueName == null) { + LOG.error("Ignoring task: no queue configured to send event to (" + event + ")"); + } + else { + LOG.info("Sending task event: " + event); + rabbitTemplate.convertAndSend(workQueueName, event); + } + } + else { + LOG.info("Sending fanout event: " + event); + rabbitTemplate.convertAndSend(internalFanoutExchangeName, "fanout." + event.getClass().getSimpleName(), + event); + } + } + + @Override + public void sendPublicEvent(XPlanboxPublicEvent event) { + LOG.info("Sending fanout public event: " + event); + rabbitTemplate.convertAndSend(publicFanoutExchangeName, "xplanbox", event); + } + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitReceiverConfig.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitReceiverConfig.java index 3ca256f480..0fb4ed1f40 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitReceiverConfig.java +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitReceiverConfig.java @@ -8,58 +8,49 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.core.validator.events; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.core.AnonymousQueue; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; +import de.latlon.core.validator.events.RabbitConfig.InternalFanout; + /** * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> * @since 7.2 */ @Configuration @Import({ RabbitConfig.class }) +@Profile("!test") public class RabbitReceiverConfig { @Bean - @Profile("rabbit") - SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, - MessageListenerAdapter listenerAdapter, RabbitSettings rabbitSettings) { - SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - container.setQueueNames(rabbitSettings.getInternalQueueName()); - container.setMessageListener(listenerAdapter); - return container; - } - - @Bean - @Profile("rabbit") - MessageListenerAdapter listenerAdapter(ValidationMessageReceiver receiver, - Jackson2JsonMessageConverter msgConverter) { - MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(receiver, "receiveMessage"); - listenerAdapter.setMessageConverter(msgConverter); - return listenerAdapter; + @InternalFanout + public Queue internalFanoutQueue() { + return new AnonymousQueue(); } @Bean - ValidationMessageReceiver validationMessageReceiver() { - return new ValidationMessageReceiver(); + @InternalFanout + public Binding internalFanoutBinding(@InternalFanout FanoutExchange fanout, @InternalFanout Queue fanoutQueue) { + return BindingBuilder.bind(fanoutQueue).to(fanout); } } diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitSettings.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitSettings.java index cc9e958218..06f34440aa 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitSettings.java +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitSettings.java @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% @@ -28,19 +28,37 @@ import org.springframework.beans.factory.annotation.Value; */ class RabbitSettings { - private static final String topicExchangeName = "-internal-exchange"; + private static final String suffixInternalFanoutName = "-internal-fanout"; - private static final String queueName = "-internal-queue"; + private static final String suffixPublicFanoutName = "-public-fanout"; + + private static final String suffixTopicExchangeName = "-internal-exchange"; + + private static final String suffixValidationWorkQueueName = "-internal-validation-queue"; @Value("${xplanbox.rabbitmq.internal.prefix}") private String internalPrefix; - public String getInternalQueueName() { - return internalPrefix + queueName; + @Value("${xplanbox.rabbitmq.public.fanout}") + private String publicFanoutName; + + public String getInternalValidationWorkQueueName() { + return internalPrefix + suffixValidationWorkQueueName; } public String getInternalExchangeName() { - return internalPrefix + topicExchangeName; + return internalPrefix + suffixTopicExchangeName; + } + + public String getInternalFanoutName() { + return internalPrefix + suffixInternalFanoutName; + } + + public String getPublicFanoutName() { + if (publicFanoutName != null && !publicFanoutName.isBlank()) { + return publicFanoutName; + } + return internalPrefix + suffixPublicFanoutName; } } diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationFinishedEvent.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationFinishedEvent.java new file mode 100644 index 0000000000..b4cb999c16 --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationFinishedEvent.java @@ -0,0 +1,62 @@ +/*- + * #%L + * xplan-core-validator-events - Modul zur Gruppierung der Kernmodule + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.core.validator.events; + +/** + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public class ValidationFinishedEvent extends XPlanboxInternalFanoutEvent { + + private String uuid; + + private ValidationFinishedStatus validationFinishedStatus; + + public enum ValidationFinishedStatus { + + SUCCEEDED, FAILED + + } + + public ValidationFinishedEvent() { + + } + + public ValidationFinishedEvent(String uuid, ValidationFinishedStatus validationFinishedStatus) { + this.uuid = uuid; + this.validationFinishedStatus = validationFinishedStatus; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + uuid + ", validationFinishedStatus: " + validationFinishedStatus + + ")"; + } + + public String getUuid() { + return uuid; + } + + public ValidationFinishedStatus getValidationFinishedStatus() { + return validationFinishedStatus; + } + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestedEvent.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestedEvent.java index 2caec823e6..0a5a90c09d 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestedEvent.java +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationRequestedEvent.java @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% @@ -26,25 +26,89 @@ import de.latlon.xplan.validator.web.shared.ValidationSettings; * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> * @since 7.2 */ -public class ValidationRequestedEvent { +public class ValidationRequestedEvent extends XPlanboxInternalTaskEvent { + + public enum MediaType { + + ZIP("application/zip", ".zip"), PDF("application/pdf", ".pdf"), JSON("application/json", ".json"), + XML("application/xml", ".xml"); + + private final String mimeType; + + private final String fileExtension; + + MediaType(String mimeType, String fileExtension) { + this.mimeType = mimeType; + this.fileExtension = fileExtension; + } + + public String getFileExtension() { + return fileExtension; + } + + public String getMimeType() { + return mimeType; + } + + } + + public enum OriginFile { + + ZIP, GML + + } private ValidationSettings settings; + private String uuid; + + private MediaType requestedMediaType; + + private String xFileName; + + private OriginFile originFile; + public ValidationRequestedEvent() { } - public ValidationRequestedEvent(ValidationSettings settings) { + public ValidationRequestedEvent(ValidationSettings settings, String xFileName, MediaType requestedMediaType, + OriginFile originFile) { + this(null, settings, xFileName, requestedMediaType, originFile); + } + + public ValidationRequestedEvent(String uuid, ValidationSettings settings, String xFileName, + MediaType requestedMediaType, OriginFile originFile) { + this.uuid = uuid; this.settings = settings; + this.xFileName = xFileName; + this.requestedMediaType = requestedMediaType; + this.originFile = originFile; + } + + public OriginFile getOriginFile() { + return originFile; + } + + public MediaType getRequestedMediaType() { + return requestedMediaType; } public ValidationSettings getSettings() { return settings; } + public String getUuid() { + return uuid; + } + + public String getxFileName() { + return xFileName; + } + @Override public String toString() { - return settings.toString(); + return "settings: " + settings + ", uuid: " + uuid; } } diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationTaskQueueConfig.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationTaskQueueConfig.java new file mode 100644 index 0000000000..4546be5313 --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationTaskQueueConfig.java @@ -0,0 +1,13 @@ +package de.latlon.core.validator.events; + +import org.springframework.amqp.core.Queue; +import org.springframework.context.annotation.Bean; + +public class ValidationTaskQueueConfig { + + @Bean + public Queue validationTaskQueue(RabbitSettings rabbitSettings) { + return new Queue(rabbitSettings.getInternalValidationWorkQueueName(), true); + } + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalEvent.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalEvent.java new file mode 100644 index 0000000000..b162e1b17e --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalEvent.java @@ -0,0 +1,11 @@ +package de.latlon.core.validator.events; + +/** + * Marker interface for internal events. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public sealed interface XPlanboxInternalEvent permits XPlanboxInternalFanoutEvent, XPlanboxInternalTaskEvent { + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalFanoutEvent.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalFanoutEvent.java new file mode 100644 index 0000000000..a05b1dc6a3 --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalFanoutEvent.java @@ -0,0 +1,11 @@ +package de.latlon.core.validator.events; + +/** + * Base class for internal fanout events. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public non-sealed class XPlanboxInternalFanoutEvent implements XPlanboxInternalEvent { + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalTaskEvent.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalTaskEvent.java new file mode 100644 index 0000000000..001926cde6 --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxInternalTaskEvent.java @@ -0,0 +1,11 @@ +package de.latlon.core.validator.events; + +/** + * Base class for internal task events. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public non-sealed class XPlanboxInternalTaskEvent implements XPlanboxInternalEvent { + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxPublicEvent.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxPublicEvent.java new file mode 100644 index 0000000000..bed8dedce3 --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/XPlanboxPublicEvent.java @@ -0,0 +1,11 @@ +package de.latlon.core.validator.events; + +/** + * Marker interface for public events. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public interface XPlanboxPublicEvent { + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1Event.java b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1Event.java new file mode 100644 index 0000000000..04584a233d --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1Event.java @@ -0,0 +1,78 @@ +package de.latlon.core.validator.events.v1; + +import java.util.Objects; + +import de.latlon.core.validator.events.XPlanboxPublicEvent; + +/** + * V1 public event. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public class XPlanboxPublicV1Event implements XPlanboxPublicEvent { + + public static enum EventType { + + VALIDATION_START, VALIDATION_FINISHED + + } + + private String apiVersion = "1.0"; + + private EventType eventType; + + private String uuid; + + private String message; + + public XPlanboxPublicV1Event() { + + } + + public XPlanboxPublicV1Event(EventType eventType, String uuid, String message) { + this.eventType = eventType; + this.uuid = uuid; + this.message = message; + } + + public String getApiVersion() { + return apiVersion; + } + + public EventType getEventType() { + return eventType; + } + + public String getMessage() { + return message; + } + + public String getUuid() { + return uuid; + } + + @Override + public int hashCode() { + return Objects.hash(apiVersion, eventType, message, uuid); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + XPlanboxPublicV1Event other = (XPlanboxPublicV1Event) obj; + return Objects.equals(apiVersion, other.apiVersion) && eventType == other.eventType + && Objects.equals(message, other.message) && Objects.equals(uuid, other.uuid); + } + + @Override + public String toString() { + return "XPlanboxPublicV1Event(" + eventType + ", " + uuid + ", " + message + ")"; + } + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitValidationRequestNotifier.java b/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationFinishedEventTest.java similarity index 57% rename from xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitValidationRequestNotifier.java rename to xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationFinishedEventTest.java index 192a545be9..aa0e0a6818 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/RabbitValidationRequestNotifier.java +++ b/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationFinishedEventTest.java @@ -20,28 +20,25 @@ */ package de.latlon.core.validator.events; -import static org.slf4j.LoggerFactory.getLogger; +import static de.latlon.core.validator.events.ValidationFinishedEvent.ValidationFinishedStatus.SUCCEEDED; +import static org.assertj.core.api.Assertions.assertThat; -import org.slf4j.Logger; -import org.springframework.amqp.rabbit.core.RabbitTemplate; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; -/** - * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> - * @since 7.2 - */ -class RabbitValidationRequestNotifier implements ValidationRequestNotifier { +class ValidationFinishedEventTest { - private static final Logger LOG = getLogger(RabbitValidationRequestNotifier.class); + @Test + void jsonSerializeAndDeserialize() throws Exception { + ValidationFinishedEvent event = new ValidationFinishedEvent("my-uuid", SUCCEEDED); - private RabbitTemplate rabbitTemplate; + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(event); - public RabbitValidationRequestNotifier(RabbitTemplate rabbitTemplate) { - this.rabbitTemplate = rabbitTemplate; - } + ValidationFinishedEvent valueFromJson = mapper.readValue(json, ValidationFinishedEvent.class); + + assertThat(valueFromJson).usingRecursiveComparison().isEqualTo(event); - public void sendEvent(ValidationRequestedEvent event) { - LOG.info("Sending message: " + event); - rabbitTemplate.convertAndSend("foo.bar.baz", event); } } diff --git a/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationRequestedEventTest.java b/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationRequestedEventTest.java index cb7db7d09d..f00c4d20ee 100644 --- a/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationRequestedEventTest.java +++ b/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/ValidationRequestedEventTest.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; +import de.latlon.core.validator.events.ValidationRequestedEvent.MediaType; +import de.latlon.core.validator.events.ValidationRequestedEvent.OriginFile; import de.latlon.xplan.validator.web.shared.ValidationOption; import de.latlon.xplan.validator.web.shared.ValidationSettings; import de.latlon.xplan.validator.web.shared.ValidationType; @@ -42,7 +44,8 @@ class ValidationRequestedEventTest { List<ValidationType> validationTypes = Arrays.asList(ValidationType.GEOMETRIC, ValidationType.SYNTACTIC, ValidationType.SEMANTIC); ValidationSettings settings = new ValidationSettings("validationName", validationTypes, extendedOptions); - ValidationRequestedEvent event = new ValidationRequestedEvent(settings); + ValidationRequestedEvent event = new ValidationRequestedEvent(settings, "xFileName", MediaType.PDF, + OriginFile.GML); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(event); diff --git a/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1EventTest.java b/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1EventTest.java new file mode 100644 index 0000000000..585943e68d --- /dev/null +++ b/xplan-core/xplan-core-validator-events/src/test/java/de/latlon/core/validator/events/v1/XPlanboxPublicV1EventTest.java @@ -0,0 +1,39 @@ +package de.latlon.core.validator.events.v1; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.latlon.core.validator.events.v1.XPlanboxPublicV1Event.EventType; + +class XPlanboxPublicV1EventTest { + + @Test + void jsonSerializeValidationStart() throws Exception { + XPlanboxPublicV1Event event = new XPlanboxPublicV1Event(EventType.VALIDATION_START, "uuid1", + "start validation"); + + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(event); + + String expected = "{\"apiVersion\":\"1.0\",\"eventType\":\"VALIDATION_START\",\"uuid\":\"uuid1\",\"message\":\"start validation\"}"; + + assertThat(json).isEqualTo(expected); + } + + @Test + void jsonSerializeValidationFinished() throws Exception { + XPlanboxPublicV1Event event = new XPlanboxPublicV1Event(EventType.VALIDATION_FINISHED, "uuid1", + "end validation"); + + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(event); + + String expected = "{\"apiVersion\":\"1.0\",\"eventType\":\"VALIDATION_FINISHED\",\"uuid\":\"uuid1\",\"message\":\"end validation\"}"; + + assertThat(json).isEqualTo(expected); + } + +} diff --git a/xplan-docker/xplan-docker-volume-init/setupVolumes.sh b/xplan-docker/xplan-docker-volume-init/setupVolumes.sh index 8951de68c4..77a9ef20b4 100755 --- a/xplan-docker/xplan-docker-volume-init/setupVolumes.sh +++ b/xplan-docker/xplan-docker-volume-init/setupVolumes.sh @@ -90,8 +90,6 @@ XPLAN_SERVICES_METADATA_URL="${XPLAN_SERVICES_METADATA_URL}" XPLAN_INIT_INSPIREPLU="${XPLAN_INIT_INSPIREPLU:-disabled}" -XPLAN_S3_PUBLIC_URL="${XPLAN_S3_PUBLIC_URL}" - ############################# # Update content of volumes # ############################# diff --git a/xplan-documentation/xplan-betriebshandbuch/src/main/asciidoc/s3storage.adoc b/xplan-documentation/xplan-betriebshandbuch/src/main/asciidoc/s3storage.adoc index 01a8f16d37..504fe7be2b 100644 --- a/xplan-documentation/xplan-betriebshandbuch/src/main/asciidoc/s3storage.adoc +++ b/xplan-documentation/xplan-betriebshandbuch/src/main/asciidoc/s3storage.adoc @@ -42,13 +42,11 @@ XPLAN_S3_SECRET_ACCESS_KEY=MY_SECRET_KEY <2> XPLAN_S3_BUCKET_NAME=mydocumentbucket <3> XPLAN_S3_REGION=eu-central-1 <4> XPLAN_S3_ENDPOINT=s3.eu-central-1.amazonaws.com <5> -XPLAN_S3_PUBLIC_URL=http://mydocumentbucket.s3.eu-central-1.amazonaws.com <6> ---- <1> Hier muss der S3 Accesskey gesetzt werden <2> Hier muss der S3 Secretkey gesetzt werden <3> Hier muss der Name des S3 Bucket für die Begleitdokumente gesetzt werden, hier als Beispiel `mydocumentbucket` <4> Hier muss die S3 Region gesetzt werden <5> Hier muss die S3 Endpoint URL gesetzt werden -<6> Hier kann die öffentliche S3 Endpoint URL gesetzt werden (optional), setzt sich aus (3), (4) und (5) zusammen. NOTE: Für die Aktivierung des S3 Objektspeichers für die Ablage von Begleitdokumenten muss zusätzlich auch die Umgebungsvariable `spring_profiles_active=s3doc` gesetzt werden. diff --git a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/config/S3DocumentContext.java b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/config/S3DocumentContext.java index d73f9fc46d..cc43ddb716 100644 --- a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/config/S3DocumentContext.java +++ b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/config/S3DocumentContext.java @@ -21,7 +21,7 @@ package de.latlon.xplanbox.api.dokumente.config; import com.amazonaws.services.s3.AmazonS3; -import de.latlon.xplan.manager.storage.s3.S3Storage; +import de.latlon.xplan.commons.s3.S3Storage; import de.latlon.xplan.manager.storage.s3.config.AmazonS3Context; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Value; @@ -45,7 +45,7 @@ public class S3DocumentContext { @Bean public S3Storage documentStorage(AmazonS3 s3Client, - @Value("${s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { + @Value("${xplanbox.s3.bucketName:#{environment.XPLAN_S3_BUCKET_NAME}}") String bucketName) { LOG.info("Instantiate S3Storage to manage documents"); return new S3Storage(s3Client, bucketName); } diff --git a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/handler/DocumentHandler.java b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/handler/DocumentHandler.java index 03a8024602..12adba4616 100644 --- a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/handler/DocumentHandler.java +++ b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/handler/DocumentHandler.java @@ -21,8 +21,7 @@ package de.latlon.xplanbox.api.dokumente.handler; import de.latlon.xplan.core.manager.db.repository.PlanRepository; -import de.latlon.xplan.manager.database.XPlanDao; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplanbox.api.commons.exception.InvalidPlanId; import de.latlon.xplanbox.api.commons.exception.InvalidPlanIdSyntax; import de.latlon.xplanbox.api.dokumente.exception.InvalidDocument; diff --git a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/DocumentService.java b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/DocumentService.java index 83ad260f0d..1b762bfc9e 100644 --- a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/DocumentService.java +++ b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/DocumentService.java @@ -20,7 +20,7 @@ */ package de.latlon.xplanbox.api.dokumente.service; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplanbox.api.dokumente.exception.InvalidDocument; import de.latlon.xplanbox.api.dokumente.v1.model.Document; diff --git a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/s3/S3DocumentService.java b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/s3/S3DocumentService.java index 1b7f89cddc..ee34897a6c 100644 --- a/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/s3/S3DocumentService.java +++ b/xplan-dokumente/xplan-dokumente-api/src/main/java/de/latlon/xplanbox/api/dokumente/service/s3/S3DocumentService.java @@ -21,10 +21,10 @@ package de.latlon.xplanbox.api.dokumente.service.s3; import com.amazonaws.services.s3.model.S3ObjectSummary; -import de.latlon.xplan.manager.storage.s3.S3Metadata; -import de.latlon.xplan.manager.storage.s3.S3Object; -import de.latlon.xplan.manager.storage.s3.S3Storage; -import de.latlon.xplan.manager.wmsconfig.raster.storage.StorageException; +import de.latlon.xplan.commons.s3.S3Metadata; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; +import de.latlon.xplan.commons.s3.StorageException; import de.latlon.xplanbox.api.dokumente.exception.InvalidDocument; import de.latlon.xplanbox.api.dokumente.service.DocumentHeader; import de.latlon.xplanbox.api.dokumente.service.DocumentHeaderWithStream; diff --git a/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/PlanInfoBuilder.java b/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/PlanInfoBuilder.java index 729ded25dc..2d9957d1e2 100644 --- a/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/PlanInfoBuilder.java +++ b/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/PlanInfoBuilder.java @@ -20,6 +20,18 @@ */ package de.latlon.xplanbox.api.manager; +import static de.latlon.xplanbox.api.manager.v1.model.Link.RelEnum.ALTERNATE; +import static de.latlon.xplanbox.api.manager.v1.model.Link.RelEnum.PLANWERKWMS; +import static de.latlon.xplanbox.api.manager.v1.model.Link.RelEnum.SELF; +import static org.slf4j.LoggerFactory.getLogger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + import de.latlon.xplan.manager.web.shared.PlanStatus; import de.latlon.xplan.manager.web.shared.XPlan; import de.latlon.xplan.validator.web.shared.XPlanEnvelope; @@ -35,18 +47,6 @@ import de.latlon.xplanbox.api.manager.v1.model.PlanStatusEnum; import org.apache.http.client.utils.URIBuilder; import org.slf4j.Logger; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static de.latlon.xplanbox.api.manager.v1.model.Link.RelEnum.ALTERNATE; -import static de.latlon.xplanbox.api.manager.v1.model.Link.RelEnum.PLANWERKWMS; -import static de.latlon.xplanbox.api.manager.v1.model.Link.RelEnum.SELF; -import static org.slf4j.LoggerFactory.getLogger; - /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> */ @@ -120,11 +120,14 @@ public class PlanInfoBuilder { List<Link> links = new ArrayList<>(); URI selfRef = createSelfRef(); if (selfRef != null) { - Link selfLink = new Link().href(selfRef).rel(SELF).type(selfMediaType).title(xPlan.getName()); + Link selfLink = (Link) new Link().rel(SELF).href(selfRef).type(selfMediaType).title(xPlan.getName()); links.add(selfLink); alternateMediaTypes.forEach(mediaType -> { - Link alternateLink = new Link().href(selfRef).rel(ALTERNATE).type(mediaType).title(xPlan.getName()); + Link alternateLink = (Link) new Link().rel(ALTERNATE) + .href(selfRef) + .type(mediaType) + .title(xPlan.getName()); links.add(alternateLink); }); } @@ -170,7 +173,7 @@ public class PlanInfoBuilder { pathSegments.add(xPlan.getName().replace("/", "")); uriBuilder.setPathSegments(pathSegments); URI planwerkWmsRef = uriBuilder.build(); - Link planwerkWmsLink = new Link().href(planwerkWmsRef).rel(PLANWERKWMS).title(xPlan.getName()); + Link planwerkWmsLink = (Link) new Link().rel(PLANWERKWMS).href(planwerkWmsRef).title(xPlan.getName()); return planwerkWmsLink; } catch (URISyntaxException e) { diff --git a/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/v1/model/Link.java b/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/v1/model/Link.java index f787e7788b..8264554856 100644 --- a/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/v1/model/Link.java +++ b/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/v1/model/Link.java @@ -20,18 +20,17 @@ */ package de.latlon.xplanbox.api.manager.v1.model; +import java.util.Objects; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; +import de.latlon.xplanbox.api.commons.v1.model.AbstractLink; import io.swagger.v3.oas.annotations.media.Schema; - import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlRootElement; -import java.net.URI; -import java.util.Objects; /** * Datatype for Link. A Link to a resource related to the resource such as XPlanWerkWMS or @@ -44,14 +43,11 @@ import java.util.Objects; date = "2020-08-28T13:42:47.160+02:00[Europe/Berlin]") @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) -public class Link { - - private @Valid URI href; +public class Link extends AbstractLink { public enum RelEnum { - SELF(String.valueOf("self")), ALTERNATE(String.valueOf("alternate")), - PLANWERKWMS(String.valueOf("planwerkwms")); + SELF("self"), ALTERNATE("alternate"), PLANWERKWMS("planwerkwms"); private String value; @@ -83,36 +79,6 @@ public class Link { private @Valid RelEnum rel; - private @Valid String type; - - private @Valid String hreflang; - - private @Valid String title; - - private @Valid Integer length; - - /** - * - **/ - public Link href(URI href) { - this.href = href; - return this; - } - - @Schema(example = "https://xplanbox.lat-lon.de/xmanager/api/v1/plan/123", required = true) - @JsonProperty("href") - @NotNull - public URI getHref() { - return href; - } - - public void setHref(URI href) { - this.href = href; - } - - /** - * - **/ public Link rel(RelEnum rel) { this.rel = rel; return this; @@ -128,95 +94,21 @@ public class Link { this.rel = rel; } - /** - * - **/ - public Link type(String type) { - this.type = type; - return this; - } - - @Schema(example = "application/json") - @JsonProperty("type") - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - /** - * - **/ - public Link hreflang(String hreflang) { - this.hreflang = hreflang; - return this; - } - - @Schema(example = "en") - @JsonProperty("hreflang") - public String getHreflang() { - return hreflang; - } - - public void setHreflang(String hreflang) { - this.hreflang = hreflang; - } - - /** - * - **/ - public Link title(String title) { - this.title = title; - return this; - } - - @Schema(example = "Othmarschen 3, Hamburg") - @JsonProperty("title") - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - /** - * - **/ - public Link length(Integer length) { - this.length = length; - return this; - } - - @Schema - @JsonProperty("length") - public Integer getLength() { - return length; - } - - public void setLength(Integer length) { - this.length = length; - } - @Override - public boolean equals(java.lang.Object o) { - if (this == o) { + public boolean equals(Object o) { + if (this == o) return true; - } - if (o == null || getClass() != o.getClass()) { + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) return false; - } Link link = (Link) o; - return Objects.equals(this.href, link.href) && Objects.equals(this.rel, link.rel) - && Objects.equals(this.type, link.type) && Objects.equals(this.hreflang, link.hreflang) - && Objects.equals(this.title, link.title) && Objects.equals(this.length, link.length); + return rel == link.rel; } @Override public int hashCode() { - return Objects.hash(href, rel, type, hreflang, title, length); + return Objects.hash(super.hashCode(), rel); } @Override @@ -224,25 +116,14 @@ public class Link { StringBuilder sb = new StringBuilder(); sb.append("class Link {\n"); - sb.append(" href: ").append(toIndentedString(href)).append("\n"); + sb.append(" href: ").append(toIndentedString(getHref())).append("\n"); sb.append(" rel: ").append(toIndentedString(rel)).append("\n"); - sb.append(" type: ").append(toIndentedString(type)).append("\n"); - sb.append(" hreflang: ").append(toIndentedString(hreflang)).append("\n"); - sb.append(" title: ").append(toIndentedString(title)).append("\n"); - sb.append(" length: ").append(toIndentedString(length)).append("\n"); + sb.append(" type: ").append(toIndentedString(getType())).append("\n"); + sb.append(" hreflang: ").append(toIndentedString(getHreflang())).append("\n"); + sb.append(" title: ").append(toIndentedString(getTitle())).append("\n"); + sb.append(" length: ").append(toIndentedString(getLength())).append("\n"); sb.append("}"); return sb.toString(); } - /** - * Convert the given object to string with each line indented by 4 spaces (except the - * first line). - */ - private String toIndentedString(java.lang.Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } - } diff --git a/xplan-manager/xplan-manager-api/src/main/resources/application.properties b/xplan-manager/xplan-manager-api/src/main/resources/application.properties index fb6b50101f..9492e9ccff 100644 --- a/xplan-manager/xplan-manager-api/src/main/resources/application.properties +++ b/xplan-manager/xplan-manager-api/src/main/resources/application.properties @@ -35,3 +35,16 @@ spring.rabbitmq.port=${XPLAN_RABBIT_PORT:5672} spring.rabbitmq.username=${XPLAN_RABBIT_USER:guest} xplanbox.rabbitmq.internal.prefix=${XPLAN_RABBIT_INTERNAL_PREFIX:xplanbox} +xplanbox.rabbitmq.fanout.topics=none +xplanbox.rabbitmq.public.fanout=${XPLAN_RABBIT_PUBLIC_FANOUT:} +xplanbox.rabbitmq.task.topics=none + +xplanbox.s3.accessKeyId=${XPLAN_S3_ACCESS_KEY} +xplanbox.s3.endpoint.url=${XPLAN_S3_ENDPOINT} +xplanbox.s3.region=${XPLAN_S3_REGION} +xplanbox.s3.secretKey=${XPLAN_S3_SECRET_ACCESS_KEY} + +xplanbox.validation.fsdirectory=${XPLAN_FS_DIRECTORY:/tmp/validation} +xplanbox.validation.profiles=${XPLAN_VALIDATOR_PROFILES:} +xplanbox.validation.s3.bucketName=${XPLAN_VALIDATION_S3_BUCKET_NAME:validation} +xplanbox.validation.s3.bucketPublicUrl=${XPLAN_VALIDATION_S3_BUCKET_PUBLIC_URL} diff --git a/xplan-manager/xplan-manager-web/pom.xml b/xplan-manager/xplan-manager-web/pom.xml index 7e82215a77..c84776ad06 100644 --- a/xplan-manager/xplan-manager-web/pom.xml +++ b/xplan-manager/xplan-manager-web/pom.xml @@ -174,11 +174,6 @@ <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <type>jar</type> - </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> diff --git a/xplan-tests/xplan-tests-selenium/README.md b/xplan-tests/xplan-tests-selenium/README.md index ef4181fb20..186d16d28a 100644 --- a/xplan-tests/xplan-tests-selenium/README.md +++ b/xplan-tests/xplan-tests-selenium/README.md @@ -10,7 +10,7 @@ mvn clean integration-test -Psystem-tests -DbaseUrlValidatorWeb=https://xplanbox ## Ausführung im Docker Container -Die Selenium Tests können in einem Docker Container ausgeführt werden +Die Tests können in einem Docker Container ausgeführt werden ``` docker run --env XPLAN_VALIDATOR_WEB_BASE_URL=... xplanbox/xplan-tests-selenium @@ -21,6 +21,13 @@ docker run --env XPLAN_VALIDATOR_WEB_BASE_URL=... xplanbox/xplan-tests-selenium - `XPLAN_VALIDATOR_WEB_BASE_URL` - `XPLAN_VALIDATOR_WEB_USERNAME` - `XPLAN_VALIDATOR_WEB_PASSWORD` +- `SKIP_RABBIT_TESTS`: wenn `true` oder `1` werden die Tests ignoriert, für die eine Verbindung zum RabbitMQ Server benötigt wird. +- `XPLAN_RABBIT_HOST`: der Host vom RabbitMQ Server (Default: `localhost`) +- `XPLAN_RABBIT_PORT`: der Port vom RabbitMQ Server (Default: `5672`) +- `XPLAN_RABBIT_USER`: der Benutzername für die Verbindung zum RabbitMQ Server (Default: `guest`) +- `XPLAN_RABBIT_PASSWORD`: das Passwort für die Verbindung zum RabbitMQ Server (Default: `guest`) +- `XPLAN_RABBIT_PUBLIC_FANOUT`: der Name vom Fanout Channel (Default: `xplanbox-public-fanout`) +- `XPLAN_VALIDATORAPI_URL_PUBLIC`: die Url von der validator-api Anwendung (Default: `http://localhost:8085/xplan-validator-api`) Der Report im PDF Format kann zu einem S3 Bucket hochgeladen werden, dafür müssen folgende Umgebungsvariablen gesetzt werden: diff --git a/xplan-tests/xplan-tests-selenium/pom.xml b/xplan-tests/xplan-tests-selenium/pom.xml index ab2bf8ab35..2846de24b4 100644 --- a/xplan-tests/xplan-tests-selenium/pom.xml +++ b/xplan-tests/xplan-tests-selenium/pom.xml @@ -1,4 +1,6 @@ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>xplan-tests-selenium</artifactId> @@ -47,6 +49,15 @@ <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>io.github.openfeign</groupId> + <artifactId>feign-jackson</artifactId> + <version>13.3</version> + </dependency> + <dependency> + <groupId>com.rabbitmq</groupId> + <artifactId>amqp-client</artifactId> + </dependency> </dependencies> <build> @@ -64,10 +75,10 @@ <profile> <id>system-tests</id> <properties> - <baseUrlValidatorWeb /> + <baseUrlValidatorWeb/> <maven.test.skip>false</maven.test.skip> - <password /> - <username /> + <password/> + <username/> </properties> <build> <plugins> @@ -111,11 +122,15 @@ <files> <file> <source>../pom.xml</source> - <destName>xplan-tests-pom.xml</destName> + <destName> + xplan-tests-pom.xml + </destName> </file> <file> <source>../../pom.xml</source> - <destName>xplanbox-pom.xml</destName> + <destName> + xplanbox-pom.xml + </destName> </file> </files> </inline> diff --git a/xplan-tests/xplan-tests-selenium/runAllSeleniumTests.sh b/xplan-tests/xplan-tests-selenium/runAllSeleniumTests.sh index fa2e91a834..3528bccf1a 100755 --- a/xplan-tests/xplan-tests-selenium/runAllSeleniumTests.sh +++ b/xplan-tests/xplan-tests-selenium/runAllSeleniumTests.sh @@ -38,6 +38,7 @@ function waitForRightVersion() { echo "Waiting for services with git revision $GIT_REVISION:" waitForRightVersion $XPLAN_VALIDATOR_WEB_BASE_URL/version.txt +waitForRightVersion $XPLAN_VALIDATORAPI_URL_PUBLIC/xvalidator/version.txt # start virtual buffer Xvfb :0 -screen 0 1024x768x24 & diff --git a/xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidationPublicEventsIT.java b/xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidationPublicEventsIT.java new file mode 100644 index 0000000000..f75326822d --- /dev/null +++ b/xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidationPublicEventsIT.java @@ -0,0 +1,219 @@ +package de.latlon.xplanbox.tests.validator.rabbit; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DeliverCallback; + +import de.latlon.xplanbox.tests.validator.rabbit.ValidatorApiV2.StatusResponse; +import de.latlon.xplanbox.tests.validator.rabbit.ValidatorApiV2.ValidateResponse; +import feign.Client; +import feign.Feign; +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; +import feign.jackson.JacksonDecoder; + +@DisabledIfEnvironmentVariable(named = "SKIP_RABBIT_TESTS", matches = "(1|true)") +class ValidationPublicEventsIT { + + private List<String> receivedPublicEvents = Collections.synchronizedList(new ArrayList<>()); + + record TestConfig(String apiBaseUrl, String rabbitHost, int rabbitPort, String rabbitUsername, + String rabbitPassword, String publicFanoutQueueName) { + } + + static TestConfig getTestConfig() { + String apiBaseUrl = getEnv("XPLAN_VALIDATORAPI_URL_PUBLIC", "http://localhost:8085/xplan-validator-api"); + String rabbitHost = getEnv("XPLAN_RABBIT_HOST", "localhost"); + int rabbitPort = Integer.parseInt(getEnv("XPLAN_RABBIT_PORT", "5672"), 10); + String rabbitUsername = getEnv("XPLAN_RABBIT_USER", "guest"); + String rabbitPassword = getEnv("XPLAN_RABBIT_PASSWORD", "guest"); + String publicFanoutQueueName = getEnv("XPLAN_RABBIT_PUBLIC_FANOUT", "xplanbox-public-fanout"); + return new TestConfig(apiBaseUrl, rabbitHost, rabbitPort, rabbitUsername, rabbitPassword, + publicFanoutQueueName); + } + + private static String getEnv(String key, String defaultValue) { + String value = System.getenv(key); + if (value == null || value.isBlank()) { + value = System.getProperty(key, defaultValue); + } + return value; + } + + private TestConfig testConfig = getTestConfig(); + + @Test + void validateAndVerifyPublicEvents() throws Exception { + ValidatorApiV2 v2Api = Feign.builder() // + .client(new Client.Default(getSSLSocketFactory(), getHostnameVerifier())) + // .logger(new Logger.ErrorLogger()) // + // .logLevel(Level.FULL) // + .encoder(new SimpleBodyFileEncoder()) // + .decoder(new JacksonDecoder()) // + .target(ValidatorApiV2.class, testConfig.apiBaseUrl); + + startRabbitPublicEventsReception(); + + Path f = Paths.get(getClass().getResource("/BPlan001_5-4.zip").toURI()); + ValidateResponse r = v2Api.validate(f); + + assertThat(r.uuid()).isNotBlank(); + assertThat(r.statusLink()).isNotNull(); + + // verify status (probably not yet finished but who knows...) + StatusResponse statusResponse = v2Api.status(r.uuid()); + assertThat(statusResponse.status()).isIn("REQUESTED", "FINISHED"); // .... + + // awaiting 2 events for this uuid + List<String> expectedEvents = List.of( // + "{\"apiVersion\":\"1.0\",\"eventType\":\"VALIDATION_START\",\"uuid\":\"" + r.uuid() + + "\",\"message\":\"validation started\"}", // + "{\"apiVersion\":\"1.0\",\"eventType\":\"VALIDATION_FINISHED\",\"uuid\":\"" + r.uuid() + + "\",\"message\":\"validation finished\"}"); + waitingIfNeeded(10, () -> assertThat(receivedEventsFor(r.uuid())).isEqualTo(expectedEvents)); + + // verify status + statusResponse = v2Api.status(r.uuid()); + assertThat(statusResponse.status()).isEqualTo("FINISHED"); // now surely finished + + System.out.println(receivedPublicEvents); + } + + List<String> receivedEventsFor(String uuid) { + return receivedPublicEvents.stream() // + .filter((it) -> it.contains(uuid)) // + .collect(Collectors.toList()); + } + + private void startRabbitPublicEventsReception() throws Exception { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost(testConfig.rabbitHost); + factory.setPort(testConfig.rabbitPort); + factory.setUsername(testConfig.rabbitUsername); + factory.setPassword(testConfig.rabbitPassword); + Connection connection = factory.newConnection(); + Channel channel = connection.createChannel(); + + String queueName = channel.queueDeclare().getQueue(); + channel.queueBind(queueName, testConfig.publicFanoutQueueName(), ""); + + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), "UTF-8"); + receivedPublicEvents.add(message); + }; + channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { + }); + } + + // SSLSocketFactory + // Install the all-trusting trust manager + public static SSLSocketFactory getSSLSocketFactory() { + try { + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, getTrustManager(), new SecureRandom()); + return sslContext.getSocketFactory(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + // TrustManager + // trust manager that does not validate certificate chains + private static TrustManager[] getTrustManager() { + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + } }; + return trustAllCerts; + } + + // HostnameVerifier + public static HostnameVerifier getHostnameVerifier() { + HostnameVerifier hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + }; + return hostnameVerifier; + } + + // encode @Param("bodyFile") to request body bytes + public static class SimpleBodyFileEncoder implements Encoder { + + public void encode(Object object, Type type, RequestTemplate template) throws EncodeException { + @SuppressWarnings("unchecked") + Map<String, ?> params = (Map<String, ?>) object; + Path file = (Path) params.get("bodyFile"); + byte[] bytes; + try { + bytes = Files.readAllBytes(file); + } + catch (IOException e) { + throw new RuntimeException(e); + } + template.body(bytes, StandardCharsets.UTF_8); + } + + } + + private void waitingIfNeeded(int maxWaitInSeconds, Callable<?> callable) throws Exception { + long maxWaitTime = System.currentTimeMillis() + 1_000 * maxWaitInSeconds; + while (true) { + try { + callable.call(); + return; + } + catch (Throwable t) { + if (System.currentTimeMillis() > maxWaitTime) { + throw t; + } + Thread.sleep(1_000); + } + } + + } + +} diff --git a/xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidatorApiV2.java b/xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidatorApiV2.java new file mode 100644 index 0000000000..1f4cc988fb --- /dev/null +++ b/xplan-tests/xplan-tests-selenium/src/test/java/de/latlon/xplanbox/tests/validator/rabbit/ValidatorApiV2.java @@ -0,0 +1,33 @@ +package de.latlon.xplanbox.tests.validator.rabbit; + +import java.nio.file.Path; +import java.util.List; + +import feign.Headers; +import feign.Param; +import feign.RequestLine; + +/** + * Simple interface for the API methods currently tested. All paths relative to + * "/xplan-validator-api" + */ +public interface ValidatorApiV2 { + + public record StatusResponse(String status, List<Link> links) { + } + + public record Link(String expirationDate, String href, String type, String hreflang, String title, String length, + String rel) { + } + + public record ValidateResponse(String uuid, Link statusLink) { + } + + @RequestLine("GET /api/v2/status/{uuid}") + StatusResponse status(@Param("uuid") String uuid); + + @RequestLine("POST /api/v2/validate") + @Headers("Content-Type: application/zip") + ValidateResponse validate(@Param("bodyFile") Path f); + +} \ No newline at end of file diff --git a/xplan-tests/xplan-tests-soapui/README.md b/xplan-tests/xplan-tests-soapui/README.md index b9686b6860..b79f364781 100644 --- a/xplan-tests/xplan-tests-soapui/README.md +++ b/xplan-tests/xplan-tests-soapui/README.md @@ -8,7 +8,7 @@ Details zu den einzelnen SoapUI TestSuites sind in der [README](src/main/resourc ``` mvn clean test -Psystem-tests -DtestFileName=xplan-validator-api-soapui-project.xml \ - -Dendpoint=https://xplanbox.lat-lon.de/xvalidator/api/v1 -Dusername=xplanbox -Dpassword='PWD' + -DbaseUrlValidatorApi=https://xplanbox.lat-lon.de -Dusername=xplanbox -Dpassword='PWD' ``` ### xplan-manager-api-soapui-project diff --git a/xplan-tests/xplan-tests-soapui/pom.xml b/xplan-tests/xplan-tests-soapui/pom.xml index 19c0007390..97b8c1a8e6 100644 --- a/xplan-tests/xplan-tests-soapui/pom.xml +++ b/xplan-tests/xplan-tests-soapui/pom.xml @@ -68,11 +68,11 @@ <baseUrlMapProxy /> <usernameMapProxy /> <passwordMapProxy /> + <baseUrlValidatorApi /> <username /> <password /> <apiKey /> <jdbcUrl /> - <endpoint /> </properties> <build> <plugins> @@ -104,9 +104,6 @@ <junitReport>true</junitReport> <exportAll>true</exportAll> <testFailIgnore>true</testFailIgnore> - <endpoint>${endpoint}</endpoint> - <username>${username}</username> - <password>${password}</password> <projectProperties> <projectProperty>projectDir=${basedir}/src/main/resources</projectProperty> <value>baseUrlInspirePlu=${baseUrlInspirePlu}</value> @@ -127,6 +124,7 @@ <value>baseUrlMapProxy=${baseUrlMapProxy}</value> <value>usernameMapProxy=${usernameMapProxy}</value> <value>passwordMapProxy=${passwordMapProxy}</value> + <value>baseUrlValidatorApi=${baseUrlValidatorApi}</value> <value>username=${username}</value> <value>password=${password}</value> <value>apiKey=${apiKey}</value> diff --git a/xplan-tests/xplan-tests-soapui/runAllSoapUiTests.sh b/xplan-tests/xplan-tests-soapui/runAllSoapUiTests.sh index ae69ef7ebd..6b6121f191 100755 --- a/xplan-tests/xplan-tests-soapui/runAllSoapUiTests.sh +++ b/xplan-tests/xplan-tests-soapui/runAllSoapUiTests.sh @@ -69,7 +69,7 @@ mvn test -Psystem-tests -DtestFileName=xplan-manager-api-soapui-project.xml \ -DjdbcUrl=$JDBC_URL mvn test -Psystem-tests -DtestFileName=xplan-validator-api-soapui-project.xml \ - -Dendpoint=$XPLAN_VALIDATOR_API_BASE_URL/xvalidator/api/v1 -Dusername=$XPLAN_VALIDATOR_API_USERNAME -Dpassword=$XPLAN_VALIDATOR_API_PASSWORD + -DbaseUrlValidatorApi=$XPLAN_VALIDATOR_API_BASE_URL -Dusername=$XPLAN_VALIDATOR_API_USERNAME -Dpassword=$XPLAN_VALIDATOR_API_PASSWORD if [ -z ${XPLAN_DOKUMENTE_API_BASE_URL+x} ]; then diff --git a/xplan-tests/xplan-tests-soapui/src/main/resources/xplan-validator-api-soapui-project.xml b/xplan-tests/xplan-tests-soapui/src/main/resources/xplan-validator-api-soapui-project.xml index 7657184eee..21fc65c9bf 100644 --- a/xplan-tests/xplan-tests-soapui/src/main/resources/xplan-validator-api-soapui-project.xml +++ b/xplan-tests/xplan-tests-soapui/src/main/resources/xplan-validator-api-soapui-project.xml @@ -24,6 +24,7 @@ <con:settings/> <con:definitionCache type="TEXT" rootPart=""/> <con:endpoints> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:endpoint>https://xplanbox.lat-lon.de/xvalidator/api/v1</con:endpoint> </con:endpoints> @@ -52,16 +53,28 @@ OpenAPI document</con:description> <con:params/> <con:element>html</con:element> </con:representation> + <con:representation type="FAULT"> + <con:mediaType>text/html;charset=utf-8</con:mediaType> + <con:status>404</con:status> + <con:params/> + <con:element>html</con:element> + </con:representation> + <con:representation type="FAULT"> + <con:mediaType>application/json</con:mediaType> + <con:status>404</con:status> + <con:params/> + <con:element xmlns:v1="http://localhost/xplan-validator-api/xvalidator/api/v1/">v1:Fault</con:element> + </con:representation> <con:request name="Request 1" id="17005e5b-eacc-4780-bec7-796702b4e0f4" mediaType="application/json"> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/</con:originalUri> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>No Authorization</con:selectedAuthProfile> <con:preemptive>false</con:preemptive> @@ -102,12 +115,12 @@ Show system and application configuration</con:description> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/info</con:originalUri> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>No Authorization</con:selectedAuthProfile> <con:authType>No Authorization</con:authType> @@ -678,12 +691,12 @@ Validate XPlanGML or XPlanArchive</con:description> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>No Authorization</con:selectedAuthProfile> <con:authType>No Authorization</con:authType> @@ -720,7 +733,7 @@ Validate XPlanGML or XPlanArchive</con:description> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="f3ba4686-3a8b-4dc9-8e96-9bc351221c5a" name="Valid HTTP Status Codes"> @@ -846,8 +859,8 @@ Validate XPlanGML or XPlanArchive</con:description> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>No Authorization</con:selectedAuthProfile> <con:preemptive>false</con:preemptive> @@ -871,7 +884,7 @@ Validate XPlanGML or XPlanArchive</con:description> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/info</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="0cc46e02-cf98-4922-b9a0-70051141ecfd" name="Valid HTTP Status Codes"> @@ -908,8 +921,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>No Authorization</con:selectedAuthProfile> <con:authType>No Authorization</con:authType> @@ -932,7 +945,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -941,8 +954,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -980,7 +993,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1025,8 +1038,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1064,7 +1077,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1109,8 +1122,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1149,7 +1162,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1194,8 +1207,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1234,7 +1247,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1288,8 +1301,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1328,7 +1341,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1373,8 +1386,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1413,7 +1426,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1458,8 +1471,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1498,7 +1511,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1570,8 +1583,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1614,7 +1627,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1659,8 +1672,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1699,7 +1712,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1744,8 +1757,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1783,7 +1796,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1828,8 +1841,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1867,7 +1880,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1876,8 +1889,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -1916,7 +1929,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="application/json" xmlns="http://eviware.com/soapui/config"/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -1961,8 +1974,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2000,7 +2013,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="application/xml" xmlns="http://eviware.com/soapui/config"/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2027,8 +2040,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2066,7 +2079,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="text/xml" xmlns="http://eviware.com/soapui/config"/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2093,8 +2106,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2132,7 +2145,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="application/pdf" xmlns="http://eviware.com/soapui/config"/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2148,8 +2161,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2187,7 +2200,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="application/zip" xmlns="http://eviware.com/soapui/config"/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2196,8 +2209,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2235,7 +2248,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2289,8 +2302,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2328,7 +2341,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2382,8 +2395,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2421,7 +2434,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2484,8 +2497,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2523,7 +2536,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2586,8 +2599,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2625,7 +2638,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2688,8 +2701,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2727,7 +2740,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2772,8 +2785,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2811,7 +2824,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2856,8 +2869,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2895,7 +2908,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2904,8 +2917,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2944,7 +2957,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="application/pdf" xmlns="http://eviware.com/soapui/config"/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -2953,8 +2966,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -2993,7 +3006,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3002,8 +3015,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3041,7 +3054,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3095,8 +3108,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3136,7 +3149,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3208,8 +3221,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3249,7 +3262,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3312,8 +3325,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3351,7 +3364,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3367,8 +3380,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3406,7 +3419,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3469,8 +3482,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3509,7 +3522,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3581,8 +3594,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3620,7 +3633,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3701,8 +3714,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3741,7 +3754,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3804,8 +3817,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3844,7 +3857,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3916,8 +3929,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -3955,7 +3968,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="7d16e282-0f06-406d-9b34-d11a52136ce3" name="Valid HTTP Status Codes"> @@ -3964,8 +3977,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -4012,7 +4025,7 @@ assert json.version != null</scriptText> <con:settings> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/info</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="79bf32cf-1071-4e79-ba39-ea58115c33a0" name="Valid HTTP Status Codes"> @@ -4066,8 +4079,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>No Authorization</con:selectedAuthProfile> <con:authType>No Authorization</con:authType> @@ -4106,7 +4119,7 @@ assert json.version != null</scriptText> <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> </con:settings> <con:encoding>UTF-8</con:encoding> - <con:endpoint>http://localhost:8085/xplan-validator-api/xvalidator/api/v1</con:endpoint> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> <con:request/> <con:originalUri>http://localhost/xplan-validator-api/xvalidator/api/v1/validate</con:originalUri> <con:assertion type="Valid HTTP Status Codes" id="5f4dcada-c13f-4671-84c1-8a94197dfc67" name="Valid HTTP Status Codes"> @@ -4151,8 +4164,8 @@ assert json.version != null</scriptText> </con:configuration> </con:assertion> <con:credentials> - <con:username xsi:nil="true"/> - <con:password xsi:nil="true"/> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> <con:domain xsi:nil="true"/> <con:selectedAuthProfile>Basic</con:selectedAuthProfile> <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> @@ -4203,11 +4216,968 @@ assert json.version != null</scriptText> </con:property> </con:properties> </con:testSuite> + <con:testSuite id="fa8cd647-1575-4886-b599-51ecc10029ea" name="XPlanValidatorAPIv2 TestSuite"> + <con:description>Tests für XPlanValidatorAPI version 2.</con:description> + <con:settings/> + <con:runType>SEQUENTIAL</con:runType> + <con:testCase id="8ba01cec-aea7-4882-b1b0-8d9e4647f2ef" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="/ TestCase" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""> + <con:description>Tests für den "/"-Pfad.</con:description> + <con:settings/> + <con:testStep type="httprequest" name="GET XX X.X XX openAPI" id="f77d6853-198d-4e77-ad18-72cbf741adde"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="c41918a7-8619-4507-bb66-c291e018afa5" name="GET XX X.X XX openAPI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="f3ba4686-3a8b-4dc9-8e96-9bc351221c5a" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="41304562-7812-4f95-87b1-17cf5354b417" name="openapi"> + <con:configuration> + <path>$.openapi</path> + <content>3.0.1</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="fb838b6f-0eaa-45a1-8a43-f87364021182" name="path /"> + <con:configuration> + <path>$.paths./</path> + <content>{"get":{"summary":"OpenAPI document","description":"API documentation","operationId":"openApi","responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"type":"object"}}}},"406":{"description":"Requested format is not available"}}}}</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="fb838b6f-0eaa-45a1-8a43-f87364021182" name="path /validate X-Filename parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'X-Filename')]</path> + <content>[{"name":"X-Filename","in":"header","description":"Name of the file to be uploaded","schema":{"pattern":"^[A-Za-z0-9.()_\\-]*$","type":"string"},"example":"File names such as xplan.gml, xplan.xml, xplan.zip"}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate name parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'name')]</path> + <content>[{"name":"name","in":"query","description":"Name of the validation","schema":{"pattern":"^[A-Za-z0-9.()_\\-]*$","type":"string"},"example":"xplan-1Pruefbericht_Torstrasse_10_report-4223"}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate skipSemantisch parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'skipSemantisch')]</path> + <content>[{"name":"skipSemantisch","in":"query","description":"skip semantische Validierung","schema":{"type":"boolean","default":false}}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate skipGeometrisch parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'skipGeometrisch')]</path> + <content>[{"name":"skipGeometrisch","in":"query","description":"skip geometrische Validierung","schema":{"type":"boolean","default":false}}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate skipFlaechenschluss parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'skipFlaechenschluss')]</path> + <content>[{"name":"skipFlaechenschluss","in":"query","description":"skip Flaechenschluss Ueberpruefung","schema":{"type":"boolean","default":false}}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate skipGeltungsbereich parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'skipGeltungsbereich')]</path> + <content>[{"name":"skipGeltungsbereich","in":"query","description":"skip Geltungsbereich Ueberpruefung","schema":{"type":"boolean","default":false}}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate skipLaufrichtung parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'skipLaufrichtung')]</path> + <content>[{"name":"skipLaufrichtung","in":"query","description":"skip Laufrichtung Ueberpruefung","schema":{"type":"boolean","default":false}}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="path /validate profiles parameter"> + <con:configuration> + <path>$.paths./validate.post.parameters.[?(@.name == 'profiles')]</path> + <content>[{"name":"profiles","in":"query","description":"Names of profiles which shall be additionally used for validation","explode":false,"schema":{"type":"array","items":{"type":"string"}}}]</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="schemas ValidationReport externalReferences items type" disabled="true"> + <con:configuration> + <path>$.components.schemas.ValidationReport.properties.externalReferences.items.type</path> + <content>string</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="634b1a88-6df2-49f0-9e23-8babb820f690" name="schemas ValidationReport externalReferencesResult items ref" disabled="true"> + <con:configuration> + <path>$.components.schemas.ValidationReport.properties.externalReferencesResult.items.$ref</path> + <content>#/components/schemas/ExternalReferenceResult</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="21d4a0ad-da22-4876-8ecc-ce7ebc245f39" name="schemas DocumentSummary" disabled="true"> + <con:configuration> + <path>$.components.schemas.DocumentSummary</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="3a711a3a-80f6-4f4b-92d2-412784d41ae8" name="path /status/{uuid}"> + <con:configuration> + <path>$.paths./status/{uuid}</path> + <content>{"get":{"tags":["status"],"summary":"Status of a validation","description":"Returns the status of a validation","operationId":"status","parameters":[{"name":"uuid","in":"path","description":"UUID of the validation","required":true,"schema":{"type":"string"},"example":"0a0cedbc-bf3f-4f1f-bdaf-ea0e52075540"}],"responses":{"200":{"description":"ValidationReport","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationReceipt"}}}},"404":{"description":"Invalid uuid, no validation with the passed uuid found"},"406":{"description":"Requested format is not available"}}}}</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET XX X.X XX openAPI invalidAcceptHeaderExpectError" id="a7ea3bf5-a395-42f4-8ebf-ef27b6e766c3"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="c41918a7-8619-4507-bb66-c291e018afa5" name="GET XX X.X XX openAPI invalidAcceptHeaderExpectError" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="invalid" xmlns="http://eviware.com/soapui/config"/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="f3ba4686-3a8b-4dc9-8e96-9bc351221c5a" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>406</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:properties/> + </con:testCase> + <con:testCase id="ea696cef-8d32-45ec-9cbf-65b2c190cfb7" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="/info TestCase" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""> + <con:description>Tests für den "/info"-Pfad.</con:description> + <con:settings/> + <con:testStep type="httprequest" name="GET XX X.X XX showConfig" id="2b3bc64c-6c0f-4af1-87d0-b486e93aeccc"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="70d479ab-c344-411e-a79f-167f3f70c0a9" name="GET XX X.X XX showConfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/info</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="0cc46e02-cf98-4922-b9a0-70051141ecfd" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="04006561-4031-47bd-9cd2-fb1215076b2d" name="version"> + <con:configuration> + <path>$.version</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="GroovyScriptAssertion" id="81fc7cf2-d170-45a8-ba3f-26d3c82a741a" name="versionNotNull"> + <con:configuration> + <scriptText>import groovy.json.JsonSlurper + + json = new JsonSlurper().parseText(messageExchange.response.responseContent) + assert !(json.isEmpty()) + + assert json.version != null</scriptText> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="d1a2a299-a358-4dcb-b245-b5ce405449b0" name="supportedXPlanGmlVersions"> + <con:configuration> + <path>$.supportedXPlanGmlVersions</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET XX X.X XX showConfig invalidAcceptHeaderExpectError" id="3f3d2e0c-ee50-454a-968d-322e846f120a"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="70d479ab-c344-411e-a79f-167f3f70c0a9" name="GET XX X.X XX showConfig invalidAcceptHeaderExpectError" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="invalid" xmlns="http://eviware.com/soapui/config"/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/info</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="0cc46e02-cf98-4922-b9a0-70051141ecfd" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>406</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:properties/> + </con:testCase> + <con:testCase id="6bb3bcfc-8b50-4786-ab61-966e4b29dfb6" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="/validate TestCase" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""> + <con:description>Tests für den "/validate"-Pfad.</con:description> + <con:settings/> + <con:testStep type="httprequest" name="POST BP 6.0.2 XX validatePlan" id="12aa68bf-5824-4401-bde6-18535f69548a"> + <con:settings/> + <con:config method="POST" xsi:type="con:HttpRequest" id="aa5ee7a4-a529-48ac-ad3a-eb3064f75a0f" name="POST BP 6.0.2 XX validatePlan" postQueryString="false" mediaType="application/octet-stream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/validate</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="047b0265-8e8e-45c2-b4d6-1c67ce5c4a7c" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="a14750a4-b6ee-40eb-b83b-6905d92e26ed" name="uuid"> + <con:configuration> + <path>$.uuid</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="cca268ea-ed94-467c-9fee-33c8215bf396" name="statusLink"> + <con:configuration> + <path>$.statusLink</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="cca268ea-ed94-467c-9fee-33c8215bf396" name="href"> + <con:configuration> + <path>$.statusLink.href</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:attachment> + <con:name>BP_6.0.2_valide.zip</con:name> + <con:contentType>application/octet-stream</con:contentType> + <con:contentId>BP_6.0.2_valide.zip</con:contentId> + <con:url>${projectDir}/xplan-validator-api/plans/BP_6.0.2_valide.zip</con:url> + <con:id>292d515d-99cd-4aac-99f1-c05d1d4a5851</con:id> + </con:attachment> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="transfer" name="POST BP 6.0.2 XX validatePlan Property Transfer" id="ad6e933c-44dc-4400-bd4e-61d7493afc36"> + <con:settings/> + <con:config xsi:type="con:PropertyTransfersStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:transfers setNullOnMissingSource="true" transferTextContent="true" failOnError="true" ignoreEmpty="false" transferToAll="false" entitize="false" transferChildNodes="false"> + <con:name>hrefStatus</con:name> + <con:sourceType>Response</con:sourceType> + <con:sourceStep>POST BP 6.0.2 XX validatePlan</con:sourceStep> + <con:sourcePath>$.statusLink.href</con:sourcePath> + <con:targetType>hrefStatus</con:targetType> + <con:targetStep>#TestSuite#</con:targetStep> + <con:type>JSONPATH</con:type> + <con:targetTransferType>JSONPATH</con:targetTransferType> + <con:upgraded>true</con:upgraded> + </con:transfers> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="POST BP 6.0.2 XX validatePlan mismatchingContentTypeAndFileType" id="4a261e97-6577-4e32-b3ea-536c2a239621"> + <con:settings/> + <con:config method="POST" xsi:type="con:HttpRequest" id="aa5ee7a4-a529-48ac-ad3a-eb3064f75a0f" name="POST BP 6.0.2 XX validatePlan mismatchingContentTypeAndFileType" postQueryString="false" mediaType="text/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/validate</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="047b0265-8e8e-45c2-b4d6-1c67ce5c4a7c" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="8f708cd5-ffca-4b46-bf7e-080df45e77ba" name="uuid"> + <con:configuration> + <path>$.uuid</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="8390a98f-4263-4974-9789-a23a7a134a41" name="statusLink"> + <con:configuration> + <path>$.statusLink</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="b53b1dc3-03fc-469b-8934-81e34c1e7043" name="href"> + <con:configuration> + <path>$.statusLink.href</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:attachment> + <con:name>BP_6.0.2_valide.zip</con:name> + <con:contentType>text/xml</con:contentType> + <con:contentId>BP_6.0.2_valide.zip</con:contentId> + <con:url>${projectDir}/xplan-validator-api/plans/BP_6.0.2_valide.zip</con:url> + <con:id>292d515d-99cd-4aac-99f1-c05d1d4a5851</con:id> + </con:attachment> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="transfer" name="POST BP 6.0.2 XX validatePlan mismatchingContentTypeAndFileType Property Transfer" id="deb991ae-2746-43e4-95ec-854455d33c8d"> + <con:settings/> + <con:config xsi:type="con:PropertyTransfersStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:transfers setNullOnMissingSource="true" transferTextContent="true" failOnError="true" ignoreEmpty="false" transferToAll="false" entitize="false" transferChildNodes="false"> + <con:name>hrefStatus2</con:name> + <con:sourceType>Response</con:sourceType> + <con:sourceStep>POST BP 6.0.2 XX validatePlan mismatchingContentTypeAndFileType</con:sourceStep> + <con:sourcePath>$.statusLink.href</con:sourcePath> + <con:targetType>hrefStatus2</con:targetType> + <con:targetStep>#TestSuite#</con:targetStep> + <con:type>JSONPATH</con:type> + <con:targetTransferType>JSONPATH</con:targetTransferType> + <con:upgraded>true</con:upgraded> + </con:transfers> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="POST BP 6.0.2 XX validatePlan invalidQueryParamExpectError" id="244e9447-3e31-4d44-b521-25bafd92536c"> + <con:settings/> + <con:config method="POST" xsi:type="con:HttpRequest" id="aa5ee7a4-a529-48ac-ad3a-eb3064f75a0f" name="POST BP 6.0.2 XX validatePlan invalidQueryParamExpectError" postQueryString="false" mediaType="application/octet-stream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/validate</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="047b0265-8e8e-45c2-b4d6-1c67ce5c4a7c" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>400</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:attachment> + <con:name>BP_6.0.2_valide.zip</con:name> + <con:contentType>application/octet-stream</con:contentType> + <con:contentId>BP_6.0.2_valide.zip</con:contentId> + <con:url>${projectDir}/xplan-validator-api/plans/BP_6.0.2_valide.zip</con:url> + <con:id>292d515d-99cd-4aac-99f1-c05d1d4a5851</con:id> + </con:attachment> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters> + <con:parameter> + <con:name>name</con:name> + <con:value>//</con:value> + <con:style>QUERY</con:style> + </con:parameter> + </con:parameters> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="POST BP 6.0.2 XX validatePlan invalidAcceptHeaderExpectError" id="d6ddd986-409b-45e3-b69f-13f2b9d18210"> + <con:settings/> + <con:config method="POST" xsi:type="con:HttpRequest" id="aa5ee7a4-a529-48ac-ad3a-eb3064f75a0f" name="POST BP 6.0.2 XX validatePlan invalidAcceptHeaderExpectError" postQueryString="false" mediaType="application/octet-stream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="invalid" xmlns="http://eviware.com/soapui/config"/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/validate</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="047b0265-8e8e-45c2-b4d6-1c67ce5c4a7c" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>406</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:attachment> + <con:name>BP_6.0.2_valide.zip</con:name> + <con:contentType>application/octet-stream</con:contentType> + <con:contentId>BP_6.0.2_valide.zip</con:contentId> + <con:url>${projectDir}/xplan-validator-api/plans/BP_6.0.2_valide.zip</con:url> + <con:id>292d515d-99cd-4aac-99f1-c05d1d4a5851</con:id> + </con:attachment> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="POST BP 6.0 XX validatePlan unsupportedMediaTypeZipExpectError" id="4e24cbc3-ecdc-48b1-bcd0-eb709b803334"> + <con:settings/> + <con:config method="POST" xsi:type="con:HttpRequest" id="aa5ee7a4-a529-48ac-ad3a-eb3064f75a0f" name="POST BP 6.0 XX validatePlan unsupportedMediaTypeZipExpectError" postQueryString="false" mediaType="application/octet-stream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/validate</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="047b0265-8e8e-45c2-b4d6-1c67ce5c4a7c" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>415</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:attachment> + <con:name>BP_6.0.2_valide.zip</con:name> + <con:contentType>application/octet-stream</con:contentType> + <con:contentId>BP_6.0.2_valide.zip</con:contentId> + <con:url>${projectDir}/xplan-validator-api/plans/Blankenese29_Test_60_InvalidContent.zip</con:url> + <con:id>292d515d-99cd-4aac-99f1-c05d1d4a5851</con:id> + </con:attachment> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="POST BP X.X XX validatePlan unsupportedMediaTypeOdtExpectError" id="59528b98-9523-40ef-8183-6e100065c202"> + <con:settings/> + <con:config method="POST" xsi:type="con:HttpRequest" id="aa5ee7a4-a529-48ac-ad3a-eb3064f75a0f" name="POST BP X.X XX validatePlan unsupportedMediaTypeOdtExpectError" postQueryString="false" mediaType="application/octet-stream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/validate</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="047b0265-8e8e-45c2-b4d6-1c67ce5c4a7c" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>415</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:attachment> + <con:name>BP_6.0.2_valide.zip</con:name> + <con:contentType>application/octet-stream</con:contentType> + <con:contentId>BP_6.0.2_valide.zip</con:contentId> + <con:url>${projectDir}/xplan-validator-api/plans/invalidContentType.odt</con:url> + <con:id>292d515d-99cd-4aac-99f1-c05d1d4a5851</con:id> + </con:attachment> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:properties/> + </con:testCase> + <con:testCase id="1ac50e96-08c6-4f7f-b28c-b0c00453a683" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="/status/{uuid} TestCase" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""> + <con:description>Tests für den "/status/{uuid}"-Pfad.</con:description> + <con:settings/> + <con:testStep type="httprequest" name="GET BP 6.0.2 XX retrieveStatus" id="f6b97694-bf15-4a53-8b89-d854f9bd8680"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="80a79ed8-fcfd-4604-9220-f7c650063d0c" name="GET BP 6.0.2 XX retrieveStatus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#TestSuite#hrefStatus}</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="ae2299b3-55a5-4171-b298-0c0fbf353fbb" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="9c15ee46-6bc7-4cb2-bca6-6439ca6fdd4f" name="status exists"> + <con:configuration> + <path>$.status</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="c47301d5-d89e-4a17-8345-4329bf534b29" name="status" disabled="true"> + <con:configuration> + <path>$.status</path> + <content>FINISHED</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="9c15ee46-6bc7-4cb2-bca6-6439ca6fdd4f" name="links"> + <con:configuration> + <path>$.links</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath RegEx Match" id="79d9689a-c545-4733-bf8d-4b8da83f53a3" name="link[0]" disabled="true"> + <con:configuration> + <path>$.links[0].href</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + <regEx>(.*).json|(.*).pdf</regEx> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath RegEx Match" id="79d9689a-c545-4733-bf8d-4b8da83f53a3" name="link[1]" disabled="true"> + <con:configuration> + <path>$.links[1].href</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + <regEx>(.*).json|(.*).pdf</regEx> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="groovy" name="pollStatus" id="119b8ff3-609c-44dc-895b-0399ac3383e3"> + <con:settings/> + <con:config> + <script>import groovy.json.JsonSlurper + +def testStepName = "GET BP 6.0.2 XX retrieveStatus" +def response = testRunner.testCase.getTestStepByName(testStepName).getPropertyValue("Response") +def json = new JsonSlurper().parseText(response) + +if( context.loopIndex == null ) + context.loopIndex = 0 + +if( ++context.loopIndex < 20 && json.status != "FINISHED" ){ + sleep(1000) + testRunner.gotoStepByName(testStepName) +} else if (context.loopIndex == 20) { + context.loopIndex = null + assert(false) +} else { + context.loopIndex = null +}</script> + </con:config> + </con:testStep> + <con:testStep type="groovy" name="transferUrls" id="1504923d-a693-40d9-a8c9-8ec60e856c80"> + <con:settings/> + <con:config> + <script>import groovy.json.JsonSlurper + +def response = testRunner.testCase.getTestStepByName("GET BP 6.0.2 XX retrieveStatus").getPropertyValue("Response") +def json = new JsonSlurper().parseText(response) + +def hrefJson = json.links.find { it.type == "application/json" }.href +def hrefPdf = json.links.find { it.type == "application/pdf" }.href + +testRunner.testCase.testSuite.setPropertyValue( "reportJson", hrefJson ) +testRunner.testCase.testSuite.setPropertyValue( "reportPdf", hrefPdf )</script> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET BP 6.0.2 XX retrieveStatus mismatchingContentTypeAndFileType" id="3bef223a-0426-4f62-8d72-85dc566f33fb"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="80a79ed8-fcfd-4604-9220-f7c650063d0c" name="GET BP 6.0.2 XX retrieveStatus mismatchingContentTypeAndFileType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#TestSuite#hrefStatus2}</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="ae2299b3-55a5-4171-b298-0c0fbf353fbb" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Existence Match" id="c2e1881a-b3ea-42ae-9576-ccd831867f6a" name="status exists"> + <con:configuration> + <path>$.status</path> + <content>true</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="bd70dfd2-7b9c-4737-baca-a3278a5b6fd4" name="status" disabled="true"> + <con:configuration> + <path>$.status</path> + <content>FAILED</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="1c25a6b8-17f5-48ea-9e07-eb18352886d7" name="errorMsg" disabled="true"> + <con:configuration> + <path>$.errorMsg</path> + <content>Could not read attached file as XPlanGML</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:assertion type="JsonPath Match" id="095b0bb8-5f84-4622-bce5-36deb4f29499" name="links"> + <con:configuration> + <path>$.links</path> + <content>null</content> + <allowWildcards>false</allowWildcards> + <ignoreNamspaceDifferences>false</ignoreNamspaceDifferences> + <ignoreComments>false</ignoreComments> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="groovy" name="pollStatus2" id="8ca0b5da-0bd9-44d8-aa10-5ee9285b7b34"> + <con:settings/> + <con:config> + <script>import groovy.json.JsonSlurper + +def testStepName = "GET BP 6.0.2 XX retrieveStatus mismatchingContentTypeAndFileType" +def response = testRunner.testCase.getTestStepByName(testStepName).getPropertyValue("Response") +def json = new JsonSlurper().parseText(response) + +if( context.loopIndex == null ) + context.loopIndex = 0 + +if( ++context.loopIndex < 20 && json.status != "FAILED" ){ + sleep(1000) + testRunner.gotoStepByName(testStepName) +} else if (context.loopIndex == 20) { + context.loopIndex = null + assert(false) +} else { + context.loopIndex = null +}</script> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET BP 6.0.2 XX retrieveReportJson" id="0255adc3-8e64-4a6a-8127-9e26fc88d5c4" disabled="true"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="80a79ed8-fcfd-4604-9220-f7c650063d0c" name="GET BP 6.0.2 XX retrieveReportJson" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#TestSuite#reportJson}</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="84472359-c38c-468b-b28a-f03bbd016181" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET BP 6.0.2 XX retrieveReportPdf" id="383e53c7-3a23-4fe2-9481-3c823846806b" disabled="true"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="80a79ed8-fcfd-4604-9220-f7c650063d0c" name="GET BP 6.0.2 XX retrieveReportPdf" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#TestSuite#reportPdf}</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="84472359-c38c-468b-b28a-f03bbd016181" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>200</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET BP 6.0.2 XX retrieveStatus invalidUuidExpectError" id="fe250f45-cf4c-404c-a39d-25b1b4df0012"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="80a79ed8-fcfd-4604-9220-f7c650063d0c" name="GET BP 6.0.2 XX retrieveStatus invalidUuidExpectError" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><xml-fragment/></con:setting> + </con:settings> + <con:endpoint>${#Project#baseUrlValidatorApi}/xplan-validator-api/api/v2/status/invalid</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="ae2299b3-55a5-4171-b298-0c0fbf353fbb" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>404</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:testStep type="httprequest" name="GET BP 6.0.2 XX retrieveStatus invalidAcceptHeaderExpectError" id="dbca65e9-2ba6-4e07-8cd4-20f2bfe20b01"> + <con:settings/> + <con:config method="GET" xsi:type="con:HttpRequest" id="80a79ed8-fcfd-4604-9220-f7c650063d0c" name="GET BP 6.0.2 XX retrieveStatus invalidAcceptHeaderExpectError" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:settings> + <con:setting id="com.eviware.soapui.impl.wsdl.WsdlRequest@request-headers"><entry key="Accept" value="application/xml" xmlns="http://eviware.com/soapui/config"/></con:setting> + </con:settings> + <con:endpoint>${#TestSuite#hrefStatus}</con:endpoint> + <con:request/> + <con:assertion type="Valid HTTP Status Codes" id="ae2299b3-55a5-4171-b298-0c0fbf353fbb" name="Valid HTTP Status Codes"> + <con:configuration> + <codes>406</codes> + </con:configuration> + </con:assertion> + <con:credentials> + <con:username>${#Project#username}</con:username> + <con:password>${#Project#password}</con:password> + <con:selectedAuthProfile>Basic</con:selectedAuthProfile> + <con:addedBasicAuthenticationTypes>Basic</con:addedBasicAuthenticationTypes> + <con:preemptive>true</con:preemptive> + <con:authType>Preemptive</con:authType> + </con:credentials> + <con:jmsConfig JMSDeliveryMode="PERSISTENT"/> + <con:jmsPropertyConfig/> + <con:parameters/> + </con:config> + </con:testStep> + <con:properties/> + </con:testCase> + <con:testCase id="9fd4d5d4-1361-4668-adc4-d026000695b0" failOnError="false" failTestCaseOnErrors="true" keepSession="false" maxResults="0" name="Cleanup Properties" searchProperties="true" timeout="0" wsrmEnabled="false" wsrmVersion="1.0" wsrmAckTo="" amfAuthorisation="false" amfEndpoint="" amfLogin="" amfPassword=""> + <con:description>Gesetzte Properties werden wieder geleert.</con:description> + <con:settings/> + <con:testStep type="transfer" name="Property Transfer" id="50852410-a76f-4519-9e59-556d1486eb93"> + <con:settings/> + <con:config xsi:type="con:PropertyTransfersStep" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:transfers setNullOnMissingSource="true" transferTextContent="true" failOnError="true" ignoreEmpty="false" transferToAll="false" entitize="false" transferChildNodes="false"> + <con:name>hrefStatus</con:name> + <con:sourceType>empty</con:sourceType> + <con:sourceStep>#TestCase#</con:sourceStep> + <con:targetType>hrefStatus</con:targetType> + <con:targetStep>#TestSuite#</con:targetStep> + <con:upgraded>true</con:upgraded> + </con:transfers> + <con:transfers setNullOnMissingSource="true" transferTextContent="true" failOnError="true" ignoreEmpty="false" transferToAll="false" entitize="false" transferChildNodes="false"> + <con:name>reportJson</con:name> + <con:sourceType>empty</con:sourceType> + <con:sourceStep>#TestCase#</con:sourceStep> + <con:targetType>reportJson</con:targetType> + <con:targetStep>#TestSuite#</con:targetStep> + <con:upgraded>true</con:upgraded> + </con:transfers> + <con:transfers setNullOnMissingSource="true" transferTextContent="true" failOnError="true" ignoreEmpty="false" transferToAll="false" entitize="false" transferChildNodes="false"> + <con:name>reportPdf</con:name> + <con:sourceType>empty</con:sourceType> + <con:sourceStep>#TestCase#</con:sourceStep> + <con:targetType>reportPdf</con:targetType> + <con:targetStep>#TestSuite#</con:targetStep> + <con:upgraded>true</con:upgraded> + </con:transfers> + <con:transfers setNullOnMissingSource="true" transferTextContent="true" failOnError="true" disabled="false" entitize="false" ignoreEmpty="false" transferChildNodes="false" transferToAll="false" useXQuery="false"> + <con:name>hrefStatus2</con:name> + <con:sourceType>empty</con:sourceType> + <con:sourceStep>#TestCase#</con:sourceStep> + <con:sourcePath xsi:nil="true"/> + <con:targetType>hrefStatus2</con:targetType> + <con:targetStep>#TestSuite#</con:targetStep> + <con:targetPath xsi:nil="true"/> + <con:type>XPATH</con:type> + <con:targetTransferType>XPATH</con:targetTransferType> + <con:upgraded>true</con:upgraded> + </con:transfers> + </con:config> + </con:testStep> + <con:properties> + <con:property> + <con:name>empty</con:name> + <con:value/> + </con:property> + </con:properties> + </con:testCase> + <con:properties> + <con:property> + <con:name>hrefStatus</con:name> + <con:value/> + </con:property> + <con:property> + <con:name>hrefStatus2</con:name> + <con:value/> + </con:property> + <con:property> + <con:name>reportJson</con:name> + <con:value/> + </con:property> + <con:property> + <con:name>reportPdf</con:name> + <con:value/> + </con:property> + </con:properties> + </con:testSuite> + <con:endpointStrategy xsi:type="con:DefaultEndpointStrategy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <con:endpoint mode="COMPLEMENT" username="${#Project#username}" password="${#Project#password}">${#Project#baseUrlValidatorApi}/xplan-validator-api/xvalidator/api/v1</con:endpoint> + </con:endpointStrategy> <con:properties> <con:property> <con:name>null</con:name> <con:value/> </con:property> + <con:property> + <con:name>baseUrlValidatorApi</con:name> + <con:value>http://localhost:8085</con:value> + </con:property> + <con:property> + <con:name>username</con:name> + <con:value/> + </con:property> + <con:property> + <con:name>password</con:name> + <con:value/> + </con:property> </con:properties> <con:wssContainer/> <con:oAuth2ProfileContainer/> diff --git a/xplan-validator/pom.xml b/xplan-validator/pom.xml index 2e04209b7f..2815d249ca 100644 --- a/xplan-validator/pom.xml +++ b/xplan-validator/pom.xml @@ -13,6 +13,8 @@ <modules> <module>xplan-validator-api</module> <module>xplan-validator-config</module> + <module>xplan-validator-executor</module> + <module>xplan-validator-storage</module> <module>xplan-validator-web</module> </modules> diff --git a/xplan-validator/xplan-validator-api/Dockerfile b/xplan-validator/xplan-validator-api/Dockerfile index 1b148be282..355030cf78 100644 --- a/xplan-validator/xplan-validator-api/Dockerfile +++ b/xplan-validator/xplan-validator-api/Dockerfile @@ -30,12 +30,11 @@ COPY --from=builder $JMX_EXPORTER_DIR $JMX_EXPORTER_DIR ENV JAVA_ADDITIONAL_ARG_JAVA17_EXPORTS="--add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED" \ JAVA_ADDITIONAL_ARG_JMX_EXPORTER='-javaagent:$JMX_EXPORTER_DIR/jmx_prometheus_javaagent-1.0.1.jar=12345:$JMX_EXPORTER_DIR/jmx-exporter.config.yaml' - # set environment variables ENV DEEGREE_WORKSPACE_ROOT=/xplanbox/deegree \ - JAVA_ADDITIONAL_ARG_APP="-Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -Djts.overlay=ng -Duser.timezone=Europe/Berlin" \ - XPLANBOX_CONFIG="/xplanbox/xplan-validator-config/" + JAVA_ADDITIONAL_ARG_APP="-Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -Duser.timezone=Europe/Berlin" \ + XPLANBOX_CONFIG="/xplanbox/xplan-validator-config/" COPY ${WAR_FILE} /xplanbox/app.war COPY run.sh /xplanbox/ diff --git a/xplan-validator/xplan-validator-api/README.md b/xplan-validator/xplan-validator-api/README.md index 9ebfe953d7..c2510183f3 100644 --- a/xplan-validator/xplan-validator-api/README.md +++ b/xplan-validator/xplan-validator-api/README.md @@ -1,9 +1,29 @@ -## xPlanValidatorAPI +# xPlanValidatorAPI OpenAPI: #host#/xplan-validator-api/xvalidator/api/v1/ +## Development + +The application can be started locally with + +``` +mvn spring-boot:run -Dspring-boot.run.profiles=dev +``` + +(RabbitMQ required on localhost:5672 with credentials guest/guest) + +The application starts on port 8085: + +- v1 API: http://localhost:8085/xplan-validator-api/xvalidator/api/v1 + +- v2 API: http://localhost:8085/xplan-validator-api/api/v2 + ## Examples with curl +``` +export XPLAN_VALIDATOR_API_HOST=http://localhost:8085 +``` + ### validation ``` diff --git a/xplan-validator/xplan-validator-api/pom.xml b/xplan-validator/xplan-validator-api/pom.xml index de6d8d5546..16a78ab6a9 100755 --- a/xplan-validator/xplan-validator-api/pom.xml +++ b/xplan-validator/xplan-validator-api/pom.xml @@ -153,6 +153,10 @@ <groupId>de.latlon.product.xplanbox</groupId> <artifactId>xplan-core-api</artifactId> </dependency> + <dependency> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-validator-storage</artifactId> + </dependency> <dependency> <groupId>de.latlon.product.xplanbox</groupId> <artifactId>xplan-core-security</artifactId> @@ -329,6 +333,10 @@ <groupId>de.latlon.product.xplanbox</groupId> <artifactId>xplan-core-validator-events</artifactId> </dependency> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-s3</artifactId> + </dependency> </dependencies> <profiles> @@ -336,7 +344,7 @@ <id>docker</id> <properties> <docker-image.skip>false</docker-image.skip> - <docker-contextTarFile.expectedSizeInMat10pct>85</docker-contextTarFile.expectedSizeInMat10pct> + <docker-contextTarFile.expectedSizeInMat10pct>88</docker-contextTarFile.expectedSizeInMat10pct> </properties> <dependencies> <dependency> <!-- to copy jmx exporter stuff from docker image --> diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/SpringBootApp.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/SpringBootApp.java index c8388b2511..07c72e0208 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/SpringBootApp.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/SpringBootApp.java @@ -8,32 +8,39 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.xplanbox.api.validator; +import de.latlon.xplanbox.api.validator.api.v1.ApiV1Config; +import de.latlon.xplanbox.api.validator.api.v2.ApiV2Config; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; - +import jakarta.ws.rs.core.Context; +import org.glassfish.jersey.servlet.ServletContainer; +import org.glassfish.jersey.servlet.ServletProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication -@ComponentScan(basePackages = { "de.latlon.xplanbox.api.validator.config" }) +@ComponentScan(basePackages = { "de.latlon.xplanbox.api.validator.config", + "de.latlon.xplanbox.validator.storage.config", "de.latlon.xplanbox.validator.executor" }) @EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class }) public class SpringBootApp extends SpringBootServletInitializer { @@ -51,10 +58,27 @@ public class SpringBootApp extends SpringBootServletInitializer { return application.sources(SpringBootApp.class); } - @Override - public void onStartup(ServletContext servletContext) throws ServletException { - // TODO Auto-generated method stub + @Bean + public ServletRegistrationBean v1config(@Context ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration) { + ServletRegistrationBean v1config = new ServletRegistrationBean( + new ServletContainer(new ApiV1Config(servletContext, validatorApiConfiguration)), + "/xvalidator/api/v1/*"); + v1config.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, ApiV1Config.class.getName()); + v1config.setName(ApiV1Config.class.getName()); + v1config.setLoadOnStartup(1); + return v1config; + } + @Bean + public ServletRegistrationBean v2config(@Context ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration) { + ServletRegistrationBean v2config = new ServletRegistrationBean( + new ServletContainer(new ApiV2Config(servletContext, validatorApiConfiguration)), "/api/v2/*"); + v2config.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, ApiV2Config.class.getName()); + v2config.setName(ApiV2Config.class.getName()); + v2config.setLoadOnStartup(2); + return v2config; } } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/JerseyConfig.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/AbstractApiConfig.java similarity index 70% rename from xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/JerseyConfig.java rename to xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/AbstractApiConfig.java index 5010b71060..9e6668e3f7 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/JerseyConfig.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/AbstractApiConfig.java @@ -8,17 +8,19 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplanbox.api.validator.config; +package de.latlon.xplanbox.api.validator.api; + +import java.util.Collections; import de.latlon.xplanbox.api.commons.ObjectMapperContextResolver; import de.latlon.xplanbox.api.commons.converter.StringListConverterProvider; @@ -26,11 +28,9 @@ import de.latlon.xplanbox.api.commons.exception.ConstraintViolationExceptionMapp import de.latlon.xplanbox.api.commons.exception.UnsupportedContentTypeExceptionMapper; import de.latlon.xplanbox.api.commons.exception.ValidatorExceptionMapper; import de.latlon.xplanbox.api.commons.exception.XPlanApiExceptionMapper; -import de.latlon.xplanbox.api.commons.openapi.OpenApiFilter; -import de.latlon.xplanbox.api.validator.v1.DefaultApi; -import de.latlon.xplanbox.api.validator.v1.InfoApi; -import de.latlon.xplanbox.api.validator.v1.ValidateApi; -import io.swagger.v3.oas.integration.SwaggerConfiguration; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import de.latlon.xplanbox.api.validator.exception.ValidationExecutionExceptionMapper; +import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource; import io.swagger.v3.oas.models.ExternalDocumentation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; @@ -38,20 +38,10 @@ import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.tags.Tag; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.ServerProperties; -import org.slf4j.Logger; -import org.springframework.context.annotation.Configuration; - import jakarta.servlet.ServletContext; -import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Context; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.slf4j.LoggerFactory.getLogger; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; /** * Application configuration for XPlanValidator REST API. Example mapping for proxy @@ -62,56 +52,53 @@ import static org.slf4j.LoggerFactory.getLogger; * * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> */ -@ApplicationPath("/xvalidator/api/v1") -@Configuration -public class JerseyConfig extends ResourceConfig { - - private static final Logger LOG = getLogger(JerseyConfig.class); - - private static final String APP_PATH = "xvalidator/api/v1"; +public abstract class AbstractApiConfig extends ResourceConfig { - public JerseyConfig(@Context ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration) { + public AbstractApiConfig(@Context ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration) { property(ServerProperties.WADL_FEATURE_DISABLE, true); register(new ObjectMapperContextResolver()); - register(InfoApi.class); - register(ValidateApi.class); + BaseOpenApiResource openApiResource = createDefaultApi(servletContext, validatorApiConfiguration); + register(openApiResource); + register(ConstraintViolationExceptionMapper.class); register(UnsupportedContentTypeExceptionMapper.class); + register(ValidationExecutionExceptionMapper.class); register(ValidatorExceptionMapper.class); register(XPlanApiExceptionMapper.class); register(StringListConverterProvider.class); + } + + protected abstract BaseOpenApiResource createDefaultApi(ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration); + protected OpenAPI createOpenAPI(ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration, + String apiPath) { OpenAPI openApi = new OpenAPI(); - openApi.setInfo(new Info().title("XPlanValidatorAPI") - .version("1.3.0") - .description("XPlanValidator REST API") - .termsOfService(getTermsOfService(validatorApiConfiguration)) - .license(new License().name("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html"))); + addInfo(openApi, validatorApiConfiguration); addContact(openApi, validatorApiConfiguration); - openApi.servers(servers(servletContext, validatorApiConfiguration)); - Tag tag = createTag(validatorApiConfiguration); - openApi.tags(Collections.singletonList(tag)); - - DefaultApi openApiResource = new DefaultApi(); - SwaggerConfiguration oasConfig = new SwaggerConfiguration().openAPI(openApi) - .filterClass(OpenApiFilter.class.getCanonicalName()) - .prettyPrint(true) - .resourcePackages(Stream.of("de.latlon.xplanbox.api.validator.v1").collect(Collectors.toSet())); - - openApiResource.setOpenApiConfiguration(oasConfig); - register(openApiResource); - LOG.info("XPlanApiValidator successfully initialized"); + addServers(openApi, servletContext, validatorApiConfiguration, apiPath); + addTag(openApi, validatorApiConfiguration); + return openApi; } - private Tag createTag(ValidatorApiConfiguration validatorApiConfiguration) { + private void addTag(OpenAPI openApi, ValidatorApiConfiguration validatorApiConfiguration) { Tag tag = new Tag().name("validate").description("Validate XPlanGML documents"); if (validatorApiConfiguration != null && validatorApiConfiguration.getDocumentationUrl() != null) { tag.externalDocs(new ExternalDocumentation().description("xPlanBox") .url(validatorApiConfiguration.getDocumentationUrl())); } - return tag; + openApi.tags(Collections.singletonList(tag)); + } + + private void addInfo(OpenAPI openApi, ValidatorApiConfiguration validatorApiConfiguration) { + openApi.setInfo(new Info().title("XPlanValidatorAPI") + .version("1.3.0") + .description("XPlanValidator REST API") + .termsOfService(getTermsOfService(validatorApiConfiguration)) + .license(new License().name("Apache 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html"))); } private void addContact(OpenAPI openApi, ValidatorApiConfiguration validatorApiConfiguration) { @@ -121,13 +108,15 @@ public class JerseyConfig extends ResourceConfig { } } - private List<Server> servers(ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration) { - String serverUrl = getServerUrl(servletContext, validatorApiConfiguration); + private void addServers(OpenAPI openApi, ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration, String apiPath) { + String serverUrl = getServerUrl(servletContext, validatorApiConfiguration, apiPath); Server server = new Server().url(serverUrl); - return Collections.singletonList(server); + openApi.servers(Collections.singletonList(server)); } - private String getServerUrl(ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration) { + private String getServerUrl(ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration, + String apiPath) { StringBuilder serverUrl = new StringBuilder(); if (validatorApiConfiguration != null && validatorApiConfiguration.getApiUrl() != null) { String apiEndpoint = validatorApiConfiguration.getApiUrl().toString(); @@ -138,7 +127,7 @@ public class JerseyConfig extends ResourceConfig { } if (!serverUrl.toString().endsWith("/")) serverUrl.append("/"); - serverUrl.append(APP_PATH); + serverUrl.append(apiPath); return serverUrl.toString(); } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v1/ApiV1Config.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v1/ApiV1Config.java new file mode 100644 index 0000000000..989a1a3b2b --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v1/ApiV1Config.java @@ -0,0 +1,78 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.api.v1; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Set; + +import de.latlon.xplanbox.api.commons.openapi.OpenApiFilter; +import de.latlon.xplanbox.api.validator.api.AbstractApiConfig; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import de.latlon.xplanbox.api.validator.v1.DefaultApi; +import de.latlon.xplanbox.api.validator.v1.InfoApi; +import de.latlon.xplanbox.api.validator.v1.ValidateApi; +import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import jakarta.servlet.ServletContext; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Context; +import org.slf4j.Logger; + +/** + * Application configuration for XPlanValidator REST API. Example mapping for proxy + * mapping: http://xplanbox.lat-lon.de/xvalidator/api/v1/ -> + * http://host:8080/xplan-api-validator/xvalidator/api/v1/ Public address: + * http://xplanbox.lat-lon.de/xvalidator/ Internal address: + * http://host:8080/xplan-api-validator/xvalidator/ + * + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + */ +@ApplicationPath("/xvalidator/api/v1") +public class ApiV1Config extends AbstractApiConfig { + + private static final Logger LOG = getLogger(ApiV1Config.class); + + private static final String APP_PATH = "xvalidator/api/v1"; + + public ApiV1Config(@Context ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration) { + super(servletContext, validatorApiConfiguration); + + register(InfoApi.class); + register(ValidateApi.class); + + LOG.info("XPlanApiValidator v1 successfully initialized"); + } + + public BaseOpenApiResource createDefaultApi(ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration) { + DefaultApi openApiResourceV1 = new DefaultApi(); + OpenAPI v1OpenApi = createOpenAPI(servletContext, validatorApiConfiguration, APP_PATH); + SwaggerConfiguration oasConfigV1 = new SwaggerConfiguration().openAPI(v1OpenApi) + .filterClass(OpenApiFilter.class.getCanonicalName()) + .prettyPrint(true) + .resourcePackages(Set.of("de.latlon.xplanbox.api.validator.v1")); + openApiResourceV1.setOpenApiConfiguration(oasConfigV1); + return openApiResourceV1; + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Config.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Config.java new file mode 100644 index 0000000000..97e57359f2 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Config.java @@ -0,0 +1,81 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.api.v2; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Set; + +import de.latlon.xplanbox.api.validator.api.AbstractApiConfig; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import de.latlon.xplanbox.api.validator.api.v2.ApiV2Filter; +import de.latlon.xplanbox.api.validator.v2.DefaultApi2; +import de.latlon.xplanbox.api.validator.v2.InfoApi2; +import de.latlon.xplanbox.api.validator.v2.StatusApi; +import de.latlon.xplanbox.api.validator.v2.ValidateApi2; +import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import jakarta.servlet.ServletContext; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Context; +import org.slf4j.Logger; + +/** + * Application configuration for XPlanValidator REST API. Example mapping for proxy + * mapping: http://xplanbox.lat-lon.de/xvalidator/api/v1/ -> + * http://host:8080/xplan-api-validator/xvalidator/api/v1/ Public address: + * http://xplanbox.lat-lon.de/xvalidator/ Internal address: + * http://host:8080/xplan-api-validator/xvalidator/ + * + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + */ +@ApplicationPath("/api/v2") +public class ApiV2Config extends AbstractApiConfig { + + private static final Logger LOG = getLogger(ApiV2Config.class); + + private static final String APP_PATH = "api/v2"; + + public ApiV2Config(@Context ServletContext servletContext, ValidatorApiConfiguration validatorApiConfiguration) { + super(servletContext, validatorApiConfiguration); + + register(InfoApi2.class); + register(ValidateApi2.class); + register(StatusApi.class); + + LOG.info("XPlanApiValidator v2 successfully initialized"); + } + + @Override + protected BaseOpenApiResource createDefaultApi(ServletContext servletContext, + ValidatorApiConfiguration validatorApiConfiguration) { + DefaultApi2 openApiResourceV2 = new DefaultApi2(); + OpenAPI v2OpenApi = createOpenAPI(servletContext, validatorApiConfiguration, APP_PATH); + SwaggerConfiguration oasConfigV2 = new SwaggerConfiguration().openAPI(v2OpenApi) + .filterClass(ApiV2Filter.class.getCanonicalName()) + .prettyPrint(true) + .resourcePackages(Set.of("de.latlon.xplanbox.api.validator.v2")); + openApiResourceV2.setOpenApiConfiguration(oasConfigV2); + return openApiResourceV2; + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Filter.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Filter.java new file mode 100644 index 0000000000..1311ea46ca --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/api/v2/ApiV2Filter.java @@ -0,0 +1,22 @@ +package de.latlon.xplanbox.api.validator.api.v2; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.latlon.xplanbox.api.commons.openapi.OpenApiFilter; + +/** + * Filter to keep only v2 Api elements. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +public class ApiV2Filter extends OpenApiFilter { + + protected String createNewKey(String path) { + Pattern pattern = Pattern.compile("\\/api\\/v[\\d_\\-\\.]*(\\/|)"); + Matcher matcher = pattern.matcher(path); + return matcher.replaceFirst("/"); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/ApplicationContext.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/ApplicationContext.java index 5478f45abc..a416556599 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/ApplicationContext.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/ApplicationContext.java @@ -8,18 +8,27 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.xplanbox.api.validator.config; +import static java.nio.file.Files.createTempDirectory; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + import de.latlon.xplan.commons.configuration.PropertiesLoader; import de.latlon.xplan.commons.configuration.SystemPropertyPropertiesLoader; import de.latlon.xplan.manager.web.shared.ConfigurationException; @@ -39,31 +48,26 @@ import de.latlon.xplan.validator.semantic.profile.SemanticProfilesCreator; import de.latlon.xplan.validator.semantic.xquery.XQuerySemanticValidator; import de.latlon.xplan.validator.syntactic.SyntacticValidator; import de.latlon.xplan.validator.syntactic.SyntacticValidatorImpl; -import de.latlon.xplan.validator.wms.config.ValidatorWmsContext; import de.latlon.xplanbox.api.commons.handler.SystemConfigHandler; import de.latlon.xplanbox.security.config.SecurityContext; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.config.AmazonS3Context; +import de.latlon.xplanbox.validator.storage.filesystem.FileSystemValidationExecutionStorage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.ResourceLoader; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static java.nio.file.Files.createTempDirectory; - /** * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> */ @Configuration @ComponentScan(basePackages = { "de.latlon.xplanbox.api.validator.handler", "de.latlon.xplanbox.api.validator.v1" }) -@Import({ SecurityContext.class, ValidatorWmsContext.class }) +@Import({ SecurityContext.class, AmazonS3Context.class }) public class ApplicationContext { @Autowired @@ -111,7 +115,7 @@ public class ApplicationContext { @Bean public SemanticProfiles semanticProfiles(ValidatorConfiguration validatorConfiguration, PropertiesLoader validatorPropertiesLoader, - @Value("#{environment.XPLAN_VALIDATOR_PROFILES}") String activatedProfiles) throws ConfigurationException { + @Value("${xplanbox.validation.profiles}") String activatedProfiles) throws ConfigurationException { List<String> activatedProfilesList = activatedProfiles != null ? Arrays.asList(activatedProfiles.split(",")) : Collections.emptyList(); SemanticProfilesCreator semanticProfilesCreator = new SemanticProfilesCreator(validatorConfiguration, @@ -155,4 +159,11 @@ public class ApplicationContext { return new ReportWriter(); } + @Bean + @Profile("!s3execution") + public ValidationExecutionStorage validationExecutionStorage( + @Value("${xplanbox.validation.fsdirectory}") Optional<Path> fsExecutionDir) throws IOException { + return new FileSystemValidationExecutionStorage(fsExecutionDir); + } + } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/RabbitConfiguration.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/RabbitConfiguration.java index 2e1cdec46b..b0bbd5fb88 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/RabbitConfiguration.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/config/RabbitConfiguration.java @@ -24,9 +24,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import de.latlon.core.validator.events.RabbitEmitterConfig; +import de.latlon.core.validator.events.RabbitReceiverConfig; @Configuration -@Import({ RabbitEmitterConfig.class }) +@Import({ RabbitEmitterConfig.class, RabbitReceiverConfig.class }) public class RabbitConfiguration { } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/InvalidValidationUuid.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/InvalidValidationUuid.java new file mode 100644 index 0000000000..2d4702c56b --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/InvalidValidationUuid.java @@ -0,0 +1,44 @@ +/*- + * #%L + * xplan-core-api - Modul zur Gruppierung der Kernmodule + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.exception; + +import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; + +import de.latlon.xplanbox.api.commons.exception.XPlanApiException; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class InvalidValidationUuid extends XPlanApiException { + + private static final String EXCEPTION_MESSAGE = "Validation with uuid %s is not available!"; + + public InvalidValidationUuid(String uuid) { + super(String.format(EXCEPTION_MESSAGE, uuid)); + } + + @Override + public int getStatusCode() { + return NOT_FOUND.getStatusCode(); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationExecutionExceptionMapper.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationExecutionExceptionMapper.java new file mode 100644 index 0000000000..8bbc0ca70f --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationExecutionExceptionMapper.java @@ -0,0 +1,44 @@ +/*- + * #%L + * xplan-core-api - Modul zur Gruppierung der Kernmodule + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.exception; + +import de.latlon.xplanbox.validator.storage.ValidationExecutionException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +@Provider +public class ValidationExecutionExceptionMapper implements ExceptionMapper<ValidationExecutionException> { + + @Override + public Response toResponse(ValidationExecutionException exception) { + return Response.status(mapStatusCode(exception)).entity(exception.getMessage()).build(); + } + + private int mapStatusCode(ValidationExecutionException exception) { + return exception.getErrorType().getStatusCode(); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationTimeout.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationTimeout.java new file mode 100644 index 0000000000..1017667584 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/exception/ValidationTimeout.java @@ -0,0 +1,44 @@ +/*- + * #%L + * xplan-core-api - Modul zur Gruppierung der Kernmodule + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.exception; + +import static jakarta.ws.rs.core.Response.Status.REQUEST_TIMEOUT; + +import de.latlon.xplanbox.api.commons.exception.XPlanApiException; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class ValidationTimeout extends XPlanApiException { + + private static final String EXCEPTION_MESSAGE = "Timeout of validation %s"; + + public ValidationTimeout(String uuid) { + super(String.format(EXCEPTION_MESSAGE, uuid)); + } + + @Override + public int getStatusCode() { + return REQUEST_TIMEOUT.getStatusCode(); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapper.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapper.java new file mode 100644 index 0000000000..402320200c --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapper.java @@ -0,0 +1,102 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.handler; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import de.latlon.core.validator.events.EventSender; +import de.latlon.core.validator.events.ValidationFinishedEvent; +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.xplanbox.api.validator.exception.ValidationTimeout; +import jakarta.inject.Singleton; +import org.joda.time.Instant; +import org.slf4j.Logger; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Component +@Singleton +public class AsyncValidationWrapper { + + private static final Logger LOG = getLogger(AsyncValidationWrapper.class); + + private final EventSender validationRequestNotifier; + + private final Map<String, Boolean> waitingUuids = Collections.synchronizedMap(new HashMap<>()); + + protected AsyncValidationWrapper(EventSender validationRequestNotifier) { + this.validationRequestNotifier = validationRequestNotifier; + } + + public boolean validate(ValidationRequestedEvent event) throws ValidationTimeout { + validationRequestNotifier.sendEvent(event); + + String uuid = event.getUuid(); + LOG.info("Event sent. Waiting for validation of {}", uuid); + waitingUuids.put(uuid, null); + + long maxWaitDate = Long.MAX_VALUE; // TODO: which value would be correct? + boolean waitFinished = false; + while (!waitFinished) { + synchronized (waitingUuids) { + try { + waitingUuids.wait(); + } + catch (InterruptedException e) { + // ignoring + } + } + + if (waitingUuids.get(uuid) != null) { + LOG.info("Finished waiting for validation of {}", uuid); + waitFinished = true; + } + else if (Instant.now().isAfter(maxWaitDate)) { + throw new ValidationTimeout(uuid); + } + else { + LOG.info("Still waiting for validation of {}", uuid); + } + } + + return waitingUuids.remove(uuid); + } + + @RabbitListener(queues = "#{internalFanoutQueue.name}") + public void someValidationFinished(ValidationFinishedEvent t) { + String uuid = t.getUuid(); + if (waitingUuids.containsKey(uuid)) { + LOG.info("Notifying waiting threads for finished validation of {}", uuid); + waitingUuids.put(uuid, Boolean.TRUE); + synchronized (waitingUuids) { + waitingUuids.notifyAll(); + } + } + else { + LOG.info("No waiting threads for {}. Ignoring", uuid); + } + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/DefaultApi.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/DefaultApi.java index f2f2f28f69..f1a30ced83 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/DefaultApi.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/DefaultApi.java @@ -20,10 +20,12 @@ */ package de.latlon.xplanbox.api.validator.v1; +import static io.swagger.v3.oas.integration.api.OpenApiContext.OPENAPI_CONTEXT_ID_DEFAULT; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; - import jakarta.servlet.ServletConfig; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -34,15 +36,15 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; - /** * Controller class for handling access to the default resource returning the OpenAPI * document. * * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> * @since 4.0 + * @deprecated since 8.0 use API V2 instead */ +@Deprecated @Path("/") @jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2020-08-27T12:32:04.497+02:00[Europe/Berlin]") @@ -63,4 +65,12 @@ public class DefaultApi extends BaseOpenApiResource { return super.getOpenApi(headers, this.config, this.app, uriInfo, APPLICATION_JSON); } + @Override + protected String getContextId(ServletConfig config) { + String contextId = super.getContextId(config); + if (OPENAPI_CONTEXT_ID_DEFAULT.equals(contextId)) + return contextId + getClass().getSimpleName(); + return contextId; + } + } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/InfoApi.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/InfoApi.java index 733b6a05bb..01882062c0 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/InfoApi.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/InfoApi.java @@ -40,7 +40,9 @@ import java.io.IOException; * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> * @since 4.0 + * @deprecated since 8.0 use API V2 instead */ +@Deprecated @Path("/info") @jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2020-08-27T12:32:04.497+02:00[Europe/Berlin]") diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/ValidateApi.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/ValidateApi.java index 0c88a02c1d..f89dec7028 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/ValidateApi.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v1/ValidateApi.java @@ -8,31 +8,45 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.xplanbox.api.validator.v1; -import de.latlon.core.validator.events.ValidationRequestNotifier; +import static de.latlon.xplan.commons.util.ContentTypeChecker.checkContentTypesOfXPlanArchiveOrGml; +import static de.latlon.xplan.commons.util.TextPatternConstants.SIMPLE_NAME_PATTERN; +import static de.latlon.xplanbox.api.commons.ValidatorConverter.createValidationSettings; +import static de.latlon.xplanbox.api.commons.ValidatorConverter.detectOrCreateValidationName; +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_PDF; +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_PDF_TYPE; +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_ZIP; +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_ZIP_TYPE; +import static io.swagger.v3.oas.annotations.enums.Explode.FALSE; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static jakarta.ws.rs.core.MediaType.APPLICATION_XML; +import static jakarta.ws.rs.core.MediaType.APPLICATION_XML_TYPE; +import static jakarta.ws.rs.core.MediaType.TEXT_XML; +import static jakarta.ws.rs.core.MediaType.TEXT_XML_TYPE; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; import de.latlon.core.validator.events.ValidationRequestedEvent; -import de.latlon.xplan.commons.archive.XPlanArchive; -import de.latlon.xplan.commons.util.UnsupportedContentTypeException; -import de.latlon.xplan.validator.ValidatorException; -import de.latlon.xplan.validator.report.ValidatorReport; import de.latlon.xplan.validator.web.shared.ValidationSettings; -import de.latlon.xplanbox.api.commons.ValidationReportBuilder; -import de.latlon.xplanbox.api.commons.exception.InvalidXPlanGmlOrArchive; -import de.latlon.xplanbox.api.commons.exception.UnsupportedHeaderValue; -import de.latlon.xplanbox.api.commons.exception.UnsupportedParameterValue; +import de.latlon.xplanbox.api.commons.ObjectMapperContextResolver; import de.latlon.xplanbox.api.commons.v1.model.ValidationReport; -import de.latlon.xplanbox.api.validator.handler.ValidationHandler; +import de.latlon.xplanbox.api.validator.handler.AsyncValidationWrapper; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -40,9 +54,6 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; -import org.apache.commons.io.FileUtils; -import org.springframework.beans.factory.annotation.Autowired; - import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DefaultValue; @@ -56,28 +67,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Request; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Variant; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.util.List; - -import static de.latlon.xplan.commons.util.ContentTypeChecker.checkContentTypesOfXPlanArchiveOrGml; -import static de.latlon.xplan.commons.util.TextPatternConstants.SIMPLE_NAME_PATTERN; -import static de.latlon.xplanbox.api.commons.ValidatorConverter.createValidationSettings; -import static de.latlon.xplanbox.api.commons.ValidatorConverter.detectOrCreateValidationName; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_PDF; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_PDF_TYPE; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_ZIP; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_ZIP_TYPE; -import static io.swagger.v3.oas.annotations.enums.Explode.FALSE; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; -import static jakarta.ws.rs.core.MediaType.APPLICATION_XML; -import static jakarta.ws.rs.core.MediaType.APPLICATION_XML_TYPE; -import static jakarta.ws.rs.core.MediaType.TEXT_XML; -import static jakarta.ws.rs.core.MediaType.TEXT_XML_TYPE; +import org.springframework.beans.factory.annotation.Autowired; /** * Controller class for handling access to the validate resource. @@ -85,17 +75,17 @@ import static jakarta.ws.rs.core.MediaType.TEXT_XML_TYPE; * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> * @since 4.0 + * @deprecated since 8.0 use API V2 instead */ +@Deprecated @Path("/validate") -@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", - date = "2020-08-26T09:59:16.298+02:00[Europe/Berlin]") public class ValidateApi { @Autowired - private ValidationHandler validationHandler; + ValidationExecutionStorage validationExecutionStorage; @Autowired - private ValidationRequestNotifier validationRequestNotifier; + AsyncValidationWrapper validationHandler; @POST @Consumes({ "text/xml", "application/gml+xml" }) @@ -157,14 +147,13 @@ public class ValidateApi { @QueryParam("profiles") @Parameter( description = "Names of profiles which shall be additionaly used for validation", explode = FALSE) List<String> profiles) - throws IOException, ValidatorException, URISyntaxException, InvalidXPlanGmlOrArchive, - UnsupportedContentTypeException, UnsupportedParameterValue, UnsupportedHeaderValue { + throws Exception { checkContentTypesOfXPlanArchiveOrGml(body.toPath()); String validationName = detectOrCreateValidationName(xFilename, name); - XPlanArchive archive = validationHandler.createArchiveFromGml(body, validationName); - return validate(request, xFilename, validationName, skipSemantisch, skipGeometrisch, skipFlaechenschluss, - skipGeltungsbereich, skipLaufrichtung, profiles, archive); + return doValidate(body, request, xFilename, validationName, skipSemantisch, skipGeometrisch, + skipFlaechenschluss, skipGeltungsbereich, skipLaufrichtung, profiles, + ValidationRequestedEvent.OriginFile.GML); } @POST @@ -182,47 +171,55 @@ public class ValidateApi { @QueryParam("profiles") @Parameter( description = "Names of profiles which shall be additionaly used for validation", explode = FALSE) List<String> profiles) - throws IOException, ValidatorException, URISyntaxException, InvalidXPlanGmlOrArchive, - UnsupportedContentTypeException, UnsupportedParameterValue, UnsupportedHeaderValue { - checkContentTypesOfXPlanArchiveOrGml(body.toPath()); - String validationName = detectOrCreateValidationName(xFilename, name); - XPlanArchive archive = validationHandler.createArchiveFromZip(body, validationName); + throws Exception { - return validate(request, xFilename, validationName, skipSemantisch, skipGeometrisch, skipFlaechenschluss, - skipGeltungsbereich, skipLaufrichtung, profiles, archive); + return doValidate(body, request, xFilename, name, skipSemantisch, skipGeometrisch, skipFlaechenschluss, + skipGeltungsbereich, skipLaufrichtung, profiles, ValidationRequestedEvent.OriginFile.ZIP); } - private Response validate(Request request, String xFileName, String validationName, Boolean skipSemantisch, + private Response doValidate(File body, Request request, String xFilename, String name, Boolean skipSemantisch, Boolean skipGeometrisch, Boolean skipFlaechenschluss, Boolean skipGeltungsbereich, Boolean skipLaufrichtung, - List<String> profiles, XPlanArchive archive) throws ValidatorException, IOException { + List<String> profiles, ValidationRequestedEvent.OriginFile originFile) throws Exception { + checkContentTypesOfXPlanArchiveOrGml(body.toPath()); + + String uuid = validationExecutionStorage.addPlanToValidate(body.toPath()); + String validationName = detectOrCreateValidationName(xFilename, name); MediaType mediaType = detectRequestedMediaType(request); + final ValidationRequestedEvent.MediaType requestedMediaType = detectValidationEventMediaType(mediaType); ValidationSettings settings = createValidationSettings(validationName, skipGeometrisch, skipSemantisch, skipFlaechenschluss, skipGeltungsbereich, skipLaufrichtung, profiles); - validationRequestNotifier.sendEvent(new ValidationRequestedEvent(settings)); + ValidationRequestedEvent event = new ValidationRequestedEvent(uuid, settings, xFilename, requestedMediaType, + originFile); - ValidatorReport validatorReport = validationHandler.validate(archive, xFileName, settings); - if (APPLICATION_ZIP_TYPE.equals(mediaType)) { - java.nio.file.Path report = validationHandler.zipReports(validatorReport); - return Response.ok(FileUtils.readFileToByteArray(report.toFile())) - .type(APPLICATION_ZIP) - .header("Content-Disposition", "attachment; filename=\"" + validationName + ".zip\"") - .build(); - } - if (APPLICATION_PDF_TYPE.equals(mediaType)) { - java.nio.file.Path report = validationHandler.writePdfReport(validatorReport); - return Response.ok(Files.readAllBytes(report)) - .type(APPLICATION_PDF) - .header("Content-Disposition", "attachment; filename=\"" + validationName + ".pdf\"") + return validate(event, mediaType); + } + + private Response validate(ValidationRequestedEvent validationRequestedEvent, MediaType mediaType) throws Exception { + + ValidationSettings settings = validationRequestedEvent.getSettings(); + validationHandler.validate(validationRequestedEvent); + + de.latlon.core.validator.events.ValidationRequestedEvent.MediaType requestedMediaType = validationRequestedEvent + .getRequestedMediaType(); + ValidationExecutionStorage.ReportType reportType = ValidationExecutionStorage.ReportType + .byFileExtension(requestedMediaType.getFileExtension()); + byte[] report = validationExecutionStorage.retrieveReport(validationRequestedEvent.getUuid(), reportType); + String filename = settings.getValidationName() + requestedMediaType.getFileExtension(); + // xml not directly supported anymore => parse and convert + if (APPLICATION_XML_TYPE.equals(mediaType) || TEXT_XML_TYPE.equals(mediaType)) { + ValidationReport validationReport = createReportFileFrom(report); + return Response.ok(validationReport) // + .type(mediaType) // + .header("Content-Disposition", "attachment; filename=\"" + filename + "\"") // .build(); } - URI wmsUrl = validationHandler.addToWms(archive); - ValidationReport validationReport = new ValidationReportBuilder().validatorReport(validatorReport) - .filename(xFileName) - .wmsUrl(wmsUrl) + + return Response.ok(report) // + .type(mediaType) // + .header("Content-Disposition", "attachment; filename=\"" + filename + "\"") // .build(); - return Response.ok(validationReport).build(); } private MediaType detectRequestedMediaType(Request request) { @@ -234,4 +231,17 @@ public class ValidateApi { return selectVariant.getMediaType(); } + private static ValidationRequestedEvent.MediaType detectValidationEventMediaType(MediaType mediaType) { + if (APPLICATION_ZIP_TYPE.equals(mediaType)) + return ValidationRequestedEvent.MediaType.ZIP; + if (APPLICATION_PDF_TYPE.equals(mediaType)) + return ValidationRequestedEvent.MediaType.PDF; + return ValidationRequestedEvent.MediaType.JSON; + } + + private ValidationReport createReportFileFrom(byte[] reportAsJsonInByte) throws IOException { + ObjectMapper mapper = new ObjectMapperContextResolver().getContext(ValidationReport.class); + return mapper.readValue(reportAsJsonInByte, ValidationReport.class); + } + } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2.java new file mode 100644 index 0000000000..b1961c26c8 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2.java @@ -0,0 +1,72 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static io.swagger.v3.oas.integration.api.OpenApiContext.OPENAPI_CONTEXT_ID_DEFAULT; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.ServletConfig; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; + +/** + * Controller class for handling access to the default resource returning the OpenAPI + * document. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +@Path("/") +public class DefaultApi2 extends BaseOpenApiResource { + + @Context + private ServletConfig config; + + @Context + private Application app; + + @GET + @Produces({ "application/json" }) + @Operation(summary = "OpenAPI document", description = "API documentation", + responses = { @ApiResponse(responseCode = "200", description = "successful operation"), + @ApiResponse(responseCode = "406", description = "Requested format is not available") }) + public Response openApi(@Context HttpHeaders headers, @Context UriInfo uriInfo) throws Exception { + return super.getOpenApi(headers, this.config, this.app, uriInfo, APPLICATION_JSON); + } + + @Override + protected String getContextId(ServletConfig config) { + String contextId = super.getContextId(config); + if (OPENAPI_CONTEXT_ID_DEFAULT.equals(contextId)) + return contextId + getClass().getSimpleName(); + return contextId; + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/InfoApi2.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/InfoApi2.java new file mode 100644 index 0000000000..07511f5bb3 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/InfoApi2.java @@ -0,0 +1,62 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import de.latlon.xplanbox.api.commons.v1.model.SystemConfig; +import de.latlon.xplanbox.api.validator.handler.ConfigHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.beans.factory.annotation.Autowired; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Response; +import java.io.IOException; + +/** + * Controller class for handling access to the application info resource. + * + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> + * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> + * @since 4.0 + */ +@Path("/info") +public class InfoApi2 { + + @Autowired + private ConfigHandler configHandler; + + @GET + @Produces({ "application/json" }) + @Operation(summary = "Show system and application configuration", + description = "Returns the system and application configuration", + responses = { + @ApiResponse(responseCode = "200", description = "successful operation", + content = @Content(schema = @Schema(implementation = SystemConfig.class))), + @ApiResponse(responseCode = "406", description = "Requested format is not available") }) + public Response showConfig() throws IOException { + return Response.ok().entity(configHandler.describeSystem()).build(); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/StatusApi.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/StatusApi.java new file mode 100644 index 0000000000..0c2a0065fd --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/StatusApi.java @@ -0,0 +1,83 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +import java.net.URISyntaxException; + +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.api.validator.exception.InvalidValidationUuid; +import de.latlon.xplanbox.api.validator.v2.model.StatusNotification; +import de.latlon.xplanbox.api.validator.v2.model.ValidationReceipt; +import de.latlon.xplanbox.validator.storage.Status; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Response; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> + * @since 8.0 + */ +@Path("/status") +public class StatusApi { + + @Autowired + private ValidationExecutionStorage validationExecutionStorage; + + @GET + @Produces({ "application/json" }) + @Path("/{uuid}") + @Operation(summary = "Status of a validation", description = "Returns the status of a validation", + tags = { "status" }, + responses = { + @ApiResponse(responseCode = "200", description = "ValidationReport", + content = { @Content(mediaType = APPLICATION_JSON, + schema = @Schema(implementation = ValidationReceipt.class)) }), + @ApiResponse(responseCode = "404", + description = "Invalid uuid, no validation with the passed uuid found"), + @ApiResponse(responseCode = "406", description = "Requested format is not available") }) + public Response status( + @PathParam("uuid") @Parameter(description = "UUID of the validation", + example = "0a0cedbc-bf3f-4f1f-bdaf-ea0e52075540") String uuid) + throws InvalidValidationUuid, StorageException, URISyntaxException { + try { + Status status = validationExecutionStorage.retrieveStatus(uuid); + return Response.ok(StatusNotification.fromStatus(status)).build(); + } + catch (StorageException e) { + if (e.getStatusCode() == 404) { + throw new InvalidValidationUuid(uuid); + } + throw e; + } + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2.java new file mode 100644 index 0000000000..0b60497c9e --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2.java @@ -0,0 +1,197 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static de.latlon.xplan.commons.util.ContentTypeChecker.checkContentTypesOfXPlanArchiveOrGml; +import static de.latlon.xplan.commons.util.TextPatternConstants.SIMPLE_NAME_PATTERN; +import static de.latlon.xplanbox.api.commons.ValidatorConverter.createValidationSettings; +import static de.latlon.xplanbox.api.commons.ValidatorConverter.detectOrCreateValidationName; +import static de.latlon.xplanbox.api.validator.v2.model.Link.RelEnum.STATUS; +import static io.swagger.v3.oas.annotations.enums.Explode.FALSE; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import de.latlon.core.validator.events.EventSender; +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplan.commons.util.UnsupportedContentTypeException; +import de.latlon.xplan.validator.ValidatorException; +import de.latlon.xplan.validator.web.shared.ValidationSettings; +import de.latlon.xplanbox.api.commons.exception.InvalidXPlanGmlOrArchive; +import de.latlon.xplanbox.api.commons.exception.UnsupportedHeaderValue; +import de.latlon.xplanbox.api.commons.exception.UnsupportedParameterValue; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import de.latlon.xplanbox.api.validator.v2.model.Link; +import de.latlon.xplanbox.api.validator.v2.model.ValidationReceipt; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.ServletContext; +import jakarta.validation.Valid; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Controller class for handling access to the validate resource. + * + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +@Path("/validate") +public class ValidateApi2 { + + @Autowired + private ValidationExecutionStorage validationExecutionStorage; + + @Autowired + private EventSender validationRequestNotifier; + + @Autowired + private ValidatorApiConfiguration validatorApiConfiguration; + + @Context + private ServletContext servletContext; + + @POST + @Consumes({ "text/xml", "application/gml+xml" }) + @Produces({ "application/json" }) + @Operation(summary = "Validate XPlanGML or XPlanArchive", description = "Validates XPlanGML or XPlanArchive file", + tags = { "validate" }, + responses = { + @ApiResponse(responseCode = "200", description = "ValidationReport", + content = { @Content(mediaType = APPLICATION_JSON, + schema = @Schema(implementation = ValidationReceipt.class)) }), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "406", description = "Requested format is not available"), + @ApiResponse(responseCode = "415", + description = "Unsupported media type or content - only xml/gml, zip are accepted; all zip files entries must also match the supported content types for XPlanArchives") }, + requestBody = @RequestBody(content = { + @Content(mediaType = "application/octet-stream", + schema = @Schema(type = "string", format = "binary", + description = "XPlanGML or XPlanArchive (application/zip) file to upload")), + @Content(mediaType = "application/zip", + schema = @Schema(type = "string", format = "binary", + description = "XPlanGML or XPlanArchive (application/zip) file to upload")), + @Content(mediaType = "application/x-zip", + schema = @Schema(type = "string", format = "binary", + description = "XPlanGML or XPlanArchive (application/zip) file to upload")), + @Content(mediaType = "application/x-zip-compressed", + schema = @Schema(type = "string", format = "binary", + description = "XPlanGML or XPlanArchive (application/zip) file to upload")), + @Content(mediaType = "text/xml", + schema = @Schema(type = "string", format = "binary", description = "XPlanGML to upload")), + @Content(mediaType = "application/gml+xml", schema = @Schema(type = "string", format = "binary", + description = "XPlanGML to upload")) })) + public Response validate(@Valid File body, + @HeaderParam("X-Filename") @Parameter(description = "Name of the file to be uploaded", + example = "File names such as xplan.gml, xplan.xml, xplan.zip", + schema = @Schema(pattern = SIMPLE_NAME_PATTERN)) String xFilename, + @QueryParam("name") @Parameter(description = "Name of the validation", + schema = @Schema(pattern = SIMPLE_NAME_PATTERN), + example = "xplan-1Pruefbericht_Torstrasse_10_report-4223") String name, + @QueryParam("skipSemantisch") @DefaultValue("false") @Parameter( + description = "skip semantische Validierung") Boolean skipSemantisch, + @QueryParam("skipGeometrisch") @DefaultValue("false") @Parameter( + description = "skip geometrische Validierung") Boolean skipGeometrisch, + @QueryParam("skipFlaechenschluss") @DefaultValue("false") @Parameter( + description = "skip Flaechenschluss Ueberpruefung") Boolean skipFlaechenschluss, + @QueryParam("skipGeltungsbereich") @DefaultValue("false") @Parameter( + description = "skip Geltungsbereich Ueberpruefung") Boolean skipGeltungsbereich, + @QueryParam("skipLaufrichtung") @DefaultValue("false") @Parameter( + description = "skip Laufrichtung Ueberpruefung") Boolean skipLaufrichtung, + @QueryParam("profiles") @Parameter( + description = "Names of profiles which shall be additionally used for validation", + explode = FALSE) List<String> profiles) + throws IOException, ValidatorException, URISyntaxException, InvalidXPlanGmlOrArchive, + UnsupportedContentTypeException, UnsupportedParameterValue, UnsupportedHeaderValue, StorageException { + return doValidate(body, xFilename, name, skipSemantisch, skipGeometrisch, skipFlaechenschluss, + skipGeltungsbereich, skipLaufrichtung, profiles, ValidationRequestedEvent.OriginFile.GML); + } + + @POST + @Consumes({ "application/octet-stream", "application/zip", "application/x-zip", "application/x-zip-compressed" }) + @Produces({ "application/json" }) + @Hidden + public Response validateZip(@Valid File body, + @HeaderParam("X-Filename") @Parameter(schema = @Schema(pattern = SIMPLE_NAME_PATTERN)) String xFilename, + @QueryParam("name") @Parameter(schema = @Schema(pattern = SIMPLE_NAME_PATTERN)) String name, + @QueryParam("skipSemantisch") @DefaultValue("false") Boolean skipSemantisch, + @QueryParam("skipGeometrisch") @DefaultValue("false") Boolean skipGeometrisch, + @QueryParam("skipFlaechenschluss") @DefaultValue("false") Boolean skipFlaechenschluss, + @QueryParam("skipGeltungsbereich") @DefaultValue("false") Boolean skipGeltungsbereich, + @QueryParam("skipLaufrichtung") @DefaultValue("false") Boolean skipLaufrichtung, + @QueryParam("profiles") @Parameter( + description = "Names of profiles which shall be additionally used for validation", + explode = FALSE) List<String> profiles) + throws IOException, UnsupportedContentTypeException, UnsupportedParameterValue, UnsupportedHeaderValue, + StorageException { + return doValidate(body, xFilename, name, skipSemantisch, skipGeometrisch, skipFlaechenschluss, + skipGeltungsbereich, skipLaufrichtung, profiles, ValidationRequestedEvent.OriginFile.ZIP); + } + + private Response doValidate(File body, String xFilename, String name, Boolean skipSemantisch, + Boolean skipGeometrisch, Boolean skipFlaechenschluss, Boolean skipGeltungsbereich, Boolean skipLaufrichtung, + List<String> profiles, ValidationRequestedEvent.OriginFile originFile) throws IOException, + UnsupportedContentTypeException, StorageException, UnsupportedParameterValue, UnsupportedHeaderValue { + checkContentTypesOfXPlanArchiveOrGml(body.toPath()); + + String uuid = validationExecutionStorage.addPlanToValidate(body.toPath()); + + String validationName = detectOrCreateValidationName(xFilename, name); + ValidationSettings settings = createValidationSettings(validationName, skipGeometrisch, skipSemantisch, + skipFlaechenschluss, skipGeltungsbereich, skipLaufrichtung, profiles); + + validationRequestNotifier.sendEvent(new ValidationRequestedEvent(uuid, settings, xFilename, + ValidationRequestedEvent.MediaType.JSON, originFile)); + + URI linkToStatus = createLinkToStatus(uuid); + Link statusLink = (Link) new Link().rel(STATUS).href(linkToStatus).type("application/json"); + ValidationReceipt receipt = new ValidationReceipt(uuid, statusLink); + + return Response.ok(receipt).build(); + } + + private URI createLinkToStatus(String uuid) { + URI apiUrl = validatorApiConfiguration.getApiUrl(); + if (apiUrl == null) + return null; + return apiUrl.resolve(servletContext.getContextPath() + "/api/v2/status/" + uuid); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/Link.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/Link.java new file mode 100644 index 0000000000..6cfb26d0aa --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/Link.java @@ -0,0 +1,144 @@ +/*- + * #%L + * xplan-manager-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2.model; + +import java.util.Date; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import de.latlon.xplanbox.api.commons.v1.model.AbstractLink; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; + +/** + * Datatype for Link. A Link to a resource related to the resource such as XPlanWerkWMS or + * the resource itself. + * + * @since 4.0 + */ +@Schema(description = "Link to a resource related to the resource such as XPlanWerkWMS or the resource itself") +@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", + date = "2020-08-28T13:42:47.160+02:00[Europe/Berlin]") +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class Link extends AbstractLink { + + public enum RelEnum { + + STATUS("status"), REPORT("report"); + + private String value; + + RelEnum(String v) { + value = v; + } + + public String value() { + return value; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static RelEnum fromValue(String value) { + for (RelEnum b : RelEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + + } + + private @Valid RelEnum rel; + + private Date expirationTime; + + public Link rel(RelEnum rel) { + this.rel = rel; + return this; + } + + @Schema(example = "self") + @JsonProperty("rel") + public RelEnum getRel() { + return rel; + } + + public void setRel(RelEnum rel) { + this.rel = rel; + } + + public Date getExpirationTime() { + return expirationTime; + } + + public void setExpirationTime(Date expirationTime) { + this.expirationTime = expirationTime; + } + + public Link expirationTime(Date expirationTime) { + this.expirationTime = expirationTime; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + Link link = (Link) o; + return rel == link.rel; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), rel); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class Link {\n"); + sb.append(" href: ").append(toIndentedString(getHref())).append("\n"); + sb.append(" rel: ").append(toIndentedString(rel)).append("\n"); + sb.append(" type: ").append(toIndentedString(getType())).append("\n"); + sb.append(" hreflang: ").append(toIndentedString(getHreflang())).append("\n"); + sb.append(" title: ").append(toIndentedString(getTitle())).append("\n"); + sb.append(" length: ").append(toIndentedString(getLength())).append("\n"); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusEnum.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusEnum.java new file mode 100644 index 0000000000..5783ac23d5 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusEnum.java @@ -0,0 +1,21 @@ +package de.latlon.xplanbox.api.validator.v2.model; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public enum StatusEnum { + + // Validation requested + REQUESTED, + + // Validation started + STARTED, + + // Validation failed + FAILED, + + // Validation finished + FINISHED + +} diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusNotification.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusNotification.java new file mode 100644 index 0000000000..3ed5fc155b --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/StatusNotification.java @@ -0,0 +1,94 @@ +package de.latlon.xplanbox.api.validator.v2.model; + +import static de.latlon.xplanbox.api.validator.v2.model.Link.RelEnum.REPORT; +import static de.latlon.xplanbox.api.validator.v2.model.StatusEnum.REQUESTED; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Date; +import java.util.List; + +import de.latlon.xplanbox.validator.storage.Status; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class StatusNotification { + + private StatusEnum status; + + private String errorMsg; + + private List<Link> links; + + private Date expirationTime; + + public StatusNotification() { + this.status = REQUESTED; + } + + public StatusEnum getStatus() { + return status; + } + + public void setStatus(StatusEnum status) { + this.status = status; + } + + public StatusNotification status(StatusEnum status) { + this.status = status; + return this; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + public StatusNotification errorMsg(String errorMsg) { + this.errorMsg = errorMsg; + return this; + } + + public List<Link> getLinks() { + return links; + } + + public void setLinks(List<Link> links) { + this.links = links; + } + + public StatusNotification links(List<Link> links) { + this.links = links; + return this; + } + + public StatusNotification links(Link... links) { + this.links = List.of(links); + return this; + } + + public static StatusNotification fromStatus(Status status) throws URISyntaxException { + StatusNotification statusNotification = new StatusNotification() + .status(StatusEnum.valueOf(status.getStatus().name())) + .errorMsg(status.getErrorMsg()); + if (status.getLinkToJsonReport() != null) + statusNotification.links( + (Link) new Link().rel(REPORT) + .expirationTime(status.getExpirationTime()) + .type("application/json") + .title("Validierungsreport") + .href(new URI(status.getLinkToJsonReport())), + (Link) new Link().rel(REPORT) + .expirationTime(status.getExpirationTime()) + .type("application/pdf") + .title("Validierungsreport") + .href(new URI(status.getLinkToPdfReport()))); + return statusNotification; + } + +} diff --git a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationMessageReceiver.java b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/ValidationReceipt.java similarity index 57% rename from xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationMessageReceiver.java rename to xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/ValidationReceipt.java index 156fd51805..e714f31277 100644 --- a/xplan-core/xplan-core-validator-events/src/main/java/de/latlon/core/validator/events/ValidationMessageReceiver.java +++ b/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/v2/model/ValidationReceipt.java @@ -1,6 +1,6 @@ /*- * #%L - * xplan-core-validator-events - Modul zur Gruppierung der Kernmodule + * xplan-core-validator - XPlan Validator Core Komponente * %% * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH * %% @@ -8,44 +8,41 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.core.validator.events; - -import java.util.function.Consumer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package de.latlon.xplanbox.api.validator.v2.model; /** + * A receipt for the submission of a validation request. + * * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> - * @since 7.2 + * @since 8.0 */ -public class ValidationMessageReceiver { +public class ValidationReceipt { - private static final Logger LOG = LoggerFactory.getLogger(ValidationMessageReceiver.class); + private final String uuid; - private Consumer<ValidationRequestedEvent> listener; + private final Link statusLink; + + public ValidationReceipt(String uuid, Link statusLink) { + this.uuid = uuid; + this.statusLink = statusLink; + } - void receiveMessage(ValidationRequestedEvent msg) { - if (listener != null) { - listener.accept(msg); - } - else { - LOG.warn("No listener registered"); - } + public String getUuid() { + return uuid; } - public void setListener(Consumer<ValidationRequestedEvent> listener) { - this.listener = listener; + public Link getStatusLink() { + return statusLink; } } diff --git a/xplan-validator/xplan-validator-api/src/main/resources/application-dev.properties b/xplan-validator/xplan-validator-api/src/main/resources/application-dev.properties new file mode 100644 index 0000000000..7675c93694 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/main/resources/application-dev.properties @@ -0,0 +1,23 @@ +### +# #%L +# xplan-api-validator - Modul zur Gruppierung der REST-API +# %% +# Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# #L% +### + +## for local development +server.port=8085 \ No newline at end of file diff --git a/xplan-validator/xplan-validator-api/src/main/resources/application.properties b/xplan-validator/xplan-validator-api/src/main/resources/application.properties index d22f627251..e7648d4e7c 100644 --- a/xplan-validator/xplan-validator-api/src/main/resources/application.properties +++ b/xplan-validator/xplan-validator-api/src/main/resources/application.properties @@ -33,3 +33,16 @@ spring.rabbitmq.port=${XPLAN_RABBIT_PORT:5672} spring.rabbitmq.username=${XPLAN_RABBIT_USER:guest} xplanbox.rabbitmq.internal.prefix=${XPLAN_RABBIT_INTERNAL_PREFIX:xplanbox} +xplanbox.rabbitmq.fanout.topics=none +xplanbox.rabbitmq.public.fanout=${XPLAN_RABBIT_PUBLIC_FANOUT:} +xplanbox.rabbitmq.task.topics=none + +xplanbox.s3.accessKeyId=${XPLAN_S3_ACCESS_KEY} +xplanbox.s3.endpoint.url=${XPLAN_S3_ENDPOINT} +xplanbox.s3.region=${XPLAN_S3_REGION} +xplanbox.s3.secretKey=${XPLAN_S3_SECRET_ACCESS_KEY} + +xplanbox.validation.fsdirectory=${XPLAN_FS_DIRECTORY:/tmp/validation} +xplanbox.validation.profiles=${XPLAN_VALIDATOR_PROFILES:} +xplanbox.validation.s3.bucketName=${XPLAN_VALIDATION_S3_BUCKET_NAME:validation} +xplanbox.validation.s3.bucketPublicUrl=${XPLAN_VALIDATION_S3_BUCKET_PUBLIC_URL} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/FakeAsyncValidationWrapper.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/FakeAsyncValidationWrapper.java new file mode 100644 index 0000000000..b374418c65 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/FakeAsyncValidationWrapper.java @@ -0,0 +1,51 @@ +package de.latlon.xplanbox.api.validator.config; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.xplanbox.api.validator.handler.AsyncValidationWrapper; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.ReportType; + +public class FakeAsyncValidationWrapper extends AsyncValidationWrapper { + + @Autowired + private ValidationExecutionStorage validationExecutionStorage; + + private Path reportForNextValidateRequest; + + private ValidationRequestedEvent lastValidatedEvent; + + FakeAsyncValidationWrapper() { + super(null); + } + + @Override + public boolean validate(ValidationRequestedEvent event) { + lastValidatedEvent = event; + + try { + Map<ReportType, Path> reports = new HashMap<>(); + reports.put(ReportType.JSON, reportForNextValidateRequest); + validationExecutionStorage.saveValidationResult(event.getUuid(), reports); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + return true; + } + + public void saveReportForNextValidateRequest(boolean successfull, Path validationResult) { + reportForNextValidateRequest = validationResult; + } + + public ValidationRequestedEvent getLastValidatedEvent() { + return lastValidatedEvent; + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/TestContext.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/TestContext.java index c5e52297cb..b06bb65e2b 100644 --- a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/TestContext.java +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/config/TestContext.java @@ -26,7 +26,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.slf4j.LoggerFactory.getLogger; -import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.util.Optional; import org.mockito.Mockito; import org.slf4j.Logger; @@ -35,12 +36,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import de.latlon.core.validator.events.ValidationRequestNotifier; +import de.latlon.core.validator.events.EventSender; import de.latlon.xplan.commons.archive.SemanticValidableXPlanArchive; import de.latlon.xplan.validator.semantic.configuration.metadata.RulesMetadata; import de.latlon.xplan.validator.semantic.profile.SemanticProfileValidator; import de.latlon.xplan.validator.semantic.profile.SemanticProfiles; import de.latlon.xplan.validator.semantic.report.SemanticValidatorResult; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.filesystem.FileSystemValidationExecutionStorage; +import jakarta.annotation.PostConstruct; /** * Indented to register the JAX-RS resources within Spring Application Context. TODO @@ -69,8 +73,18 @@ public class TestContext { } @Bean - public ValidationRequestNotifier validationRequestNotifier() { - return Mockito.mock(ValidationRequestNotifier.class); + public EventSender validationRequestNotifier() { + return Mockito.mock(EventSender.class); + } + + @Bean + public FakeAsyncValidationWrapper asyncValidationWrapper() { + return new FakeAsyncValidationWrapper(); + } + + @Bean + public ValidationExecutionStorage validationExecutionStorage() throws IOException { + return new FileSystemValidationExecutionStorage(Optional.empty()); } private static SemanticProfileValidator createValidator(RulesMetadata profile) { diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapperTest.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapperTest.java new file mode 100644 index 0000000000..4833ea1cb0 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/AsyncValidationWrapperTest.java @@ -0,0 +1,136 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.handler; + +import static de.latlon.core.validator.events.ValidationFinishedEvent.ValidationFinishedStatus.SUCCEEDED; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import de.latlon.core.validator.events.EventSender; +import de.latlon.core.validator.events.ValidationFinishedEvent; +import de.latlon.core.validator.events.ValidationRequestedEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class AsyncValidationWrapperTest { + + private List<String> logs = Collections.synchronizedList(new ArrayList<>()); + + @BeforeEach + private void cleanup() { + logs.clear(); + } + + @Test + void simpleCase() throws Exception { + EventSender eventSender = Mockito.mock(EventSender.class); + AsyncValidationWrapper wrapper = new AsyncValidationWrapper(eventSender); + + startAsyncValidation(wrapper, someEventWithUuid("uuid1"), "event1"); + verifyNewLog("event1: before validate"); + + // send validation finished event + wrapper.someValidationFinished(new ValidationFinishedEvent("uuid1", SUCCEEDED)); + verifyNewLog("event1: after validate"); + } + + @Test + void moreComplicated() throws Exception { + EventSender eventSender = Mockito.mock(EventSender.class); + AsyncValidationWrapper wrapper = new AsyncValidationWrapper(eventSender); + + startAsyncValidation(wrapper, someEventWithUuid("uuid1"), "event1"); + verifyNewLog("event1: before validate"); + + startAsyncValidation(wrapper, someEventWithUuid("uuid2"), "event2"); + verifyNewLog("event2: before validate"); + + startAsyncValidation(wrapper, someEventWithUuid("uuid3"), "event3"); + verifyNewLog("event3: before validate"); + + // send validation finished event for something else + wrapper.someValidationFinished(new ValidationFinishedEvent("uuidOther", SUCCEEDED)); + + // send validation finished event for event 2 + wrapper.someValidationFinished(new ValidationFinishedEvent("uuid2", SUCCEEDED)); + verifyNewLog("event2: after validate"); + + // send validation finished events for something else + wrapper.someValidationFinished(new ValidationFinishedEvent("uuidOther2", SUCCEEDED)); + wrapper.someValidationFinished(new ValidationFinishedEvent("uuid2", SUCCEEDED)); + + // send validation finished event for event 1 + wrapper.someValidationFinished(new ValidationFinishedEvent("uuid1", SUCCEEDED)); + verifyNewLog("event1: after validate"); + + // send validation finished event for event 1 + wrapper.someValidationFinished(new ValidationFinishedEvent("uuid3", SUCCEEDED)); + verifyNewLog("event3: after validate"); + } + + private void startAsyncValidation(AsyncValidationWrapper wrapper, ValidationRequestedEvent event, + String msgPrefix) { + new Thread("Thread " + msgPrefix) { + public void run() { + try { + logs.add(msgPrefix + ": before validate"); + wrapper.validate(event); + } + catch (Exception e) { + logs.add(msgPrefix + ": exception " + e.getMessage()); + } + logs.add(msgPrefix + ": after validate"); + }; + }.start(); + } + + private void verifyNewLog(String expectedNewMessage) throws Exception { + waitingIfNeeded(() -> assertThat(logs).containsExactly(expectedNewMessage)); + logs.clear(); + } + + private void waitingIfNeeded(Callable<?> callable) throws Exception { + long maxWaitTime = System.currentTimeMillis() + 5_000; + while (true) { + try { + callable.call(); + return; + } + catch (Throwable t) { + if (System.currentTimeMillis() > maxWaitTime) { + throw t; + } + Thread.sleep(1_000); + } + } + + } + + private ValidationRequestedEvent someEventWithUuid(String uuid) { + return new ValidationRequestedEvent(uuid, null, null, null, null); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ConfigHandlerTest.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ConfigHandlerTest.java index 961f69c7d7..d9700cee00 100644 --- a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ConfigHandlerTest.java +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ConfigHandlerTest.java @@ -33,12 +33,13 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.TestContext; /** * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> */ @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = { ApplicationContext.class }) +@ContextConfiguration(classes = { ApplicationContext.class, TestContext.class }) @ActiveProfiles("test") class ConfigHandlerTest { diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/DefaultApiTest.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/DefaultApiTest.java index 8b1df4bd5d..7212406d62 100644 --- a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/DefaultApiTest.java +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/DefaultApiTest.java @@ -27,10 +27,14 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import de.latlon.xplan.commons.configuration.DefaultPropertiesLoader; +import de.latlon.xplanbox.api.validator.api.v1.ApiV1Config; +import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.TestContext; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; import jakarta.servlet.ServletContext; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Response; - import org.apache.http.HttpHeaders; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; @@ -42,11 +46,6 @@ import org.mockito.Mockito; import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import de.latlon.xplan.commons.configuration.DefaultPropertiesLoader; -import de.latlon.xplanbox.api.validator.config.ApplicationContext; -import de.latlon.xplanbox.api.validator.config.JerseyConfig; -import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; - /** * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> */ @@ -73,12 +72,13 @@ class DefaultApiTest extends JerseyTest { try { ValidatorApiConfiguration validatorConfig = new ValidatorApiConfiguration( new DefaultPropertiesLoader(getClass())); - resourceConfig = new JerseyConfig(mockServletContext, validatorConfig); + resourceConfig = new ApiV1Config(mockServletContext, validatorConfig); } catch (Exception e) { throw new RuntimeException(e); } - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, + TestContext.class); resourceConfig.property("contextConfig", context); return resourceConfig; } diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiJerseyTest.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiJerseyTest.java new file mode 100644 index 0000000000..4af7b8b377 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiJerseyTest.java @@ -0,0 +1,236 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v1; + +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_X_ZIP; +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_X_ZIP_COMPRESSED; +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_ZIP; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM; +import static jakarta.ws.rs.core.MediaType.APPLICATION_XML; +import static jakarta.ws.rs.core.MediaType.TEXT_XML; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.http.HttpHeaders; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import de.latlon.core.validator.events.ValidationRequestedEvent.OriginFile; +import de.latlon.xplan.commons.configuration.DefaultPropertiesLoader; +import de.latlon.xplan.validator.semantic.profile.SemanticProfiles; +import de.latlon.xplanbox.api.validator.api.v1.ApiV1Config; +import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.FakeAsyncValidationWrapper; +import de.latlon.xplanbox.api.validator.config.TestContext; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import jakarta.servlet.ServletContext; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +/** + * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { TestContext.class }) +public class ValidateApiJerseyTest extends JerseyTest { + + @TempDir + public static Path tempFolder; + + @BeforeAll + static void setupFakedWorkspace() throws IOException { + Path workspace = tempFolder.resolve("xplan-webservices-validator-wms-memory-workspace"); + Files.createDirectories(workspace); + System.setProperty("DEEGREE_WORKSPACE_ROOT", workspace.getParent().toString()); + } + + @Autowired + private SemanticProfiles semanticProfiles; + + private FakeAsyncValidationWrapper fakeAsyncValidationWrapper; + + @Override + protected Application configure() { + ResourceConfig resourceConfig; + ServletContext mockServletContext = Mockito.mock(ServletContext.class); + Mockito.when(mockServletContext.getContextPath()).thenReturn(""); + + try { + ValidatorApiConfiguration validatorConfig = new ValidatorApiConfiguration( + new DefaultPropertiesLoader(getClass())); + resourceConfig = new ApiV1Config(mockServletContext, validatorConfig); + } + catch (Exception e) { + throw new RuntimeException(e); + } + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, + TestContext.class); + fakeAsyncValidationWrapper = context.getBean(FakeAsyncValidationWrapper.class); + resourceConfig.property("contextConfig", context); + return resourceConfig; + } + + @Test + void verifyThat_Response_ContainsCorrectStatusCodeAndMediaType() throws IOException, URISyntaxException { + final String data = Files.readString(Paths.get(ValidateApiJerseyTest.class.getResource("/xplan.gml").toURI())); + + fakeAsyncValidationWrapper.saveReportForNextValidateRequest(true, + Paths.get(getClass().getResource("validation-report1.json").toURI())); + + final Response response = target("/validate").request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, TEXT_XML)); + + assertThat(fakeAsyncValidationWrapper.getLastValidatedEvent().getOriginFile()).isEqualTo(OriginFile.GML); + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); + } + + @Test + void verifyThat_validationOctetStream_Response_ContainsXmlEncoding() throws URISyntaxException, IOException { + final byte[] data = Files.readAllBytes(Paths.get(getClass().getResource("/bplan_valid_41.zip").toURI())); + + fakeAsyncValidationWrapper.saveReportForNextValidateRequest(true, someJsonReport()); + + final Response response = target("/validate").request() + .accept(APPLICATION_XML) + .post(Entity.entity(data, APPLICATION_OCTET_STREAM)); + + assertThat(fakeAsyncValidationWrapper.getLastValidatedEvent().getOriginFile()).isEqualTo(OriginFile.ZIP); + + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); + assertThat(response.readEntity(String.class)).contains("valid"); + } + + @Test + void verifyThat_validationZip_Response_ContainsXmlEncoding() throws Exception { + final byte[] data = Files.readAllBytes(Paths.get(getClass().getResource("/bplan_valid_41.zip").toURI())); + + fakeAsyncValidationWrapper.saveReportForNextValidateRequest(true, someJsonReport()); + + final Response response = target("/validate").request() + .accept(APPLICATION_XML) + .post(Entity.entity(data, APPLICATION_ZIP)); + + assertThat(fakeAsyncValidationWrapper.getLastValidatedEvent().getOriginFile()).isEqualTo(OriginFile.ZIP); + + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); + assertThat(response.readEntity(String.class)).contains("valid"); + } + + @Test + void verifyThat_validationXZip_Response_ContainsXmlEncoding() throws Exception { + final byte[] data = Files.readAllBytes(Paths.get(getClass().getResource("/bplan_valid_41.zip").toURI())); + + fakeAsyncValidationWrapper.saveReportForNextValidateRequest(true, someJsonReport()); + + final Response response = target("/validate").request() + .accept(APPLICATION_XML) + .post(Entity.entity(data, APPLICATION_X_ZIP)); + + assertThat(fakeAsyncValidationWrapper.getLastValidatedEvent().getOriginFile()).isEqualTo(OriginFile.ZIP); + + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); + assertThat(response.readEntity(String.class)).contains("valid"); + } + + @Test + void verifyThat_validationXZipCompressed_Response_ContainsXmlEncoding() throws Exception { + final byte[] data = Files.readAllBytes(Paths.get(getClass().getResource("/bplan_valid_41.zip").toURI())); + + fakeAsyncValidationWrapper.saveReportForNextValidateRequest(true, someJsonReport()); + + final Response response = target("/validate").request() + .accept(APPLICATION_XML) + .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); + + assertThat(fakeAsyncValidationWrapper.getLastValidatedEvent().getOriginFile()).isEqualTo(OriginFile.ZIP); + + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); + assertThat(response.readEntity(String.class)).contains("valid"); + } + + @Test + void verifyThat_validationXZipCompressedWithProfilesCommaseparated_Response_ContainsJsonEncoding() + throws Exception { + + fakeAsyncValidationWrapper.saveReportForNextValidateRequest(true, someJsonReport()); + + final byte[] data = Files + .readAllBytes(Paths.get(ValidateApiJerseyTest.class.getResource("/bplan_valid_41.zip").toURI())); + String profileId0 = semanticProfiles.getProfileValidators().get(0).getId(); + String profileId1 = semanticProfiles.getProfileValidators().get(1).getId(); + final Response response = target("/validate").queryParam("profiles", profileId0 + "," + profileId1) + .request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); + + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); + assertThat(fakeAsyncValidationWrapper.getLastValidatedEvent().getSettings().getProfiles()) + .containsExactlyInAnyOrder(profileId0, profileId1); + } + + @Test + void verifyThat_validationWithInvalidXFileName_Response_IsStatusCode400() throws URISyntaxException, IOException { + final byte[] data = Files + .readAllBytes(Paths.get(ValidateApiJerseyTest.class.getResource("/bplan_valid_41.zip").toURI())); + final Response response = target("/validate").request() + .header("X-Filename", "invalid.filename with blanks") + .accept(APPLICATION_JSON) + .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); + + assertThat(response.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode()); + } + + @Test + void verifyThat_validationWithInvalidName_Response_IsStatusCode400() throws Exception { + final byte[] data = Files + .readAllBytes(Paths.get(ValidateApiJerseyTest.class.getResource("/bplan_valid_41.zip").toURI())); + final Response response = target("/validate").queryParam("name", "invalid.name with blanks") + .request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); + + assertThat(response.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode()); + } + + Path someJsonReport() throws URISyntaxException { + return Paths.get(getClass().getResource("validation-report1.json").toURI()); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiTest.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiTest.java index 72557f9ab8..39945640cd 100644 --- a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiTest.java +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v1/ValidateApiTest.java @@ -8,231 +8,82 @@ * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ package de.latlon.xplanbox.api.validator.v1; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_X_ZIP; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_X_ZIP_COMPRESSED; -import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_ZIP; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static jakarta.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM; -import static jakarta.ws.rs.core.MediaType.APPLICATION_XML; -import static jakarta.ws.rs.core.MediaType.TEXT_XML; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; -import jakarta.servlet.ServletContext; -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.core.Application; -import jakarta.ws.rs.core.Response; - -import org.apache.http.HttpHeaders; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.test.JerseyTest; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import de.latlon.xplan.commons.configuration.DefaultPropertiesLoader; -import de.latlon.xplan.validator.semantic.profile.SemanticProfiles; -import de.latlon.xplanbox.api.validator.config.ApplicationContext; -import de.latlon.xplanbox.api.validator.config.JerseyConfig; -import de.latlon.xplanbox.api.validator.config.TestContext; -import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; + +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.xplanbox.api.validator.handler.AsyncValidationWrapper; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import jakarta.ws.rs.core.Request; /** - * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = { TestContext.class }) -public class ValidateApiTest extends JerseyTest { +class ValidateApiTest { @TempDir - public static Path tempFolder; - - @BeforeAll - static void setupFakedWorkspace() throws IOException { - Path workspace = tempFolder.resolve("xplan-webservices-validator-wms-memory-workspace"); - Files.createDirectories(workspace); - System.setProperty("DEEGREE_WORKSPACE_ROOT", workspace.getParent().toString()); - } - - @Autowired - private SemanticProfiles semanticProfiles; - - @Override - protected Application configure() { - ResourceConfig resourceConfig; - ServletContext mockServletContext = Mockito.mock(ServletContext.class); - Mockito.when(mockServletContext.getContextPath()).thenReturn(""); - - try { - ValidatorApiConfiguration validatorConfig = new ValidatorApiConfiguration( - new DefaultPropertiesLoader(getClass())); - resourceConfig = new JerseyConfig(mockServletContext, validatorConfig); - } - catch (Exception e) { - throw new RuntimeException(e); - } - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, - TestContext.class); - resourceConfig.property("contextConfig", context); - return resourceConfig; - } + Path tmpDir; @Test - void verifyThat_Response_ContainsCorrectStatusCodeAndMediaType() throws IOException, URISyntaxException { - final String data = new String( - Files.readAllBytes(Paths.get(ValidateApiTest.class.getResource("/xplan.gml").toURI()))); - final Response response = target("/validate").request() - .accept(APPLICATION_JSON) - .post(Entity.entity(data, TEXT_XML)); - - assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); - } - - @Test - void verifyThat_validationOctetStream_Response_ContainsXmlEncoding() throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate").request() - .accept(APPLICATION_XML) - .post(Entity.entity(data, APPLICATION_OCTET_STREAM)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); - assertThat(response.readEntity(String.class)).contains("valid"); - } + void verifyThatProfilesAreSentToValidator_noProfile() throws Exception { + List<String> profilesInEvent = callValidateAndExtractProfilesInValidationRequestEvent(null); + assertThat(profilesInEvent).isEmpty(); - @Test - void verifyThat_validationZip_Response_ContainsXmlEncoding() throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate").request() - .accept(APPLICATION_XML) - .post(Entity.entity(data, APPLICATION_ZIP)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); - assertThat(response.readEntity(String.class)).contains("valid"); - } + profilesInEvent = callValidateAndExtractProfilesInValidationRequestEvent(Collections.emptyList()); + assertThat(profilesInEvent).isEmpty(); - @Test - void verifyThat_validationXZip_Response_ContainsXmlEncoding() throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate").request() - .accept(APPLICATION_XML) - .post(Entity.entity(data, APPLICATION_X_ZIP)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); - assertThat(response.readEntity(String.class)).contains("valid"); } @Test - void verifyThat_validationXZipCompressed_Response_ContainsXmlEncoding() throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate").request() - .accept(APPLICATION_XML) - .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_XML); - assertThat(response.readEntity(String.class)).contains("valid"); + void verifyThatProfilesAreSentToValidator_oneProfile() throws Exception { + List<String> profilesInEvent = callValidateAndExtractProfilesInValidationRequestEvent(List.of("profile1")); + assertThat(profilesInEvent).containsExactlyInAnyOrder("profile1"); } @Test - void verifyThat_validationXZipCompressedWithProfile_Response_ContainsJsonEncoding() - throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate") - .queryParam("profiles", semanticProfiles.getProfileValidators().get(0).getId()) - .request() - .accept(APPLICATION_JSON) - .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); - String actual = response.readEntity(String.class); - assertThat(actual).contains("profil"); + void verifyThatProfilesAreSentToValidator_twoProfiles() throws Exception { + List<String> profilesInEvent = callValidateAndExtractProfilesInValidationRequestEvent( + List.of("profile1", "profile2")); + assertThat(profilesInEvent).containsExactlyInAnyOrder("profile1", "profile2"); } - @Test - void verifyThat_validationXZipCompressedWithProfiles_Response_ContainsJsonEncoding() - throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate") - .queryParam("profiles", semanticProfiles.getProfileValidators().get(0).getId()) - .queryParam("profiles", semanticProfiles.getProfileValidators().get(1).getId()) - .request() - .accept(APPLICATION_JSON) - .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); - String actual = response.readEntity(String.class); - assertThat(actual).contains("profil"); - } + private List<String> callValidateAndExtractProfilesInValidationRequestEvent(List profiles) throws Exception { + ValidateApi validateApi = new ValidateApi(); + validateApi.validationExecutionStorage = Mockito.mock(ValidationExecutionStorage.class); + validateApi.validationHandler = Mockito.mock(AsyncValidationWrapper.class); - @Test - void verifyThat_validationXZipCompressedWithProfilesCommaseparated_Response_ContainsJsonEncoding() - throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate") - .queryParam("profiles", - semanticProfiles.getProfileValidators().get(0).getId() + "," - + semanticProfiles.getProfileValidators().get(1).getId()) - .request() - .accept(APPLICATION_JSON) - .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); - - assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); - String actual = response.readEntity(String.class); - assertThat(actual).contains("profil"); - } + Path tmpFile = tmpDir.resolve("my-file.xml"); + Files.writeString(tmpFile, ""); + Request request = Mockito.mock(Request.class); + validateApi.validate(request, tmpFile.toFile(), null, null, false, false, false, false, false, profiles); - @Test - void verifyThat_validationWithInvalidXFileName_Response_IsStatusCode400() throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate").request() - .header("X-Filename", "invalid.filename with blanks") - .accept(APPLICATION_JSON) - .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); - - assertThat(response.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode()); - } + ArgumentCaptor<ValidationRequestedEvent> argumentCaptor = ArgumentCaptor + .forClass(ValidationRequestedEvent.class); + Mockito.verify(validateApi.validationHandler).validate(argumentCaptor.capture()); - @Test - void verifyThat_validationWithInvalidName_Response_IsStatusCode400() throws URISyntaxException, IOException { - final byte[] data = Files - .readAllBytes(Paths.get(ValidateApiTest.class.getResource("/bplan_valid_41.zip").toURI())); - final Response response = target("/validate").queryParam("name", "invalid.name with blanks") - .request() - .accept(APPLICATION_JSON) - .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); - - assertThat(response.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode()); + ValidationRequestedEvent event = argumentCaptor.getValue(); + return event.getSettings().getProfiles(); } } diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2Test.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2Test.java new file mode 100644 index 0000000000..bdacc0f9d1 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/DefaultApi2Test.java @@ -0,0 +1,139 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import de.latlon.xplan.commons.configuration.DefaultPropertiesLoader; +import de.latlon.xplanbox.api.validator.api.v2.ApiV2Config; +import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.TestContext; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import de.latlon.xplanbox.api.validator.v1.DefaultApi; +import jakarta.servlet.ServletContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import org.apache.http.HttpHeaders; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.springframework.boot.test.json.BasicJsonTester; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> + * @since 8.0 + */ +class DefaultApi2Test extends JerseyTest { + + @TempDir + static Path tempFolder; + + @BeforeAll + static void setupFakedWorkspace() throws IOException { + Path workspace = tempFolder.resolve("xplan-webservices-validator-wms-memory-workspace"); + Files.createDirectories(workspace); + System.setProperty("DEEGREE_WORKSPACE_ROOT", workspace.getParent().toString()); + } + + @Override + protected Application configure() { + enable(TestProperties.LOG_TRAFFIC); + + ResourceConfig resourceConfig; + ServletContext mockServletContext = Mockito.mock(ServletContext.class); + Mockito.when(mockServletContext.getContextPath()).thenReturn(""); + + try { + ValidatorApiConfiguration validatorConfig = new ValidatorApiConfiguration( + new DefaultPropertiesLoader(DefaultApi.class)); + resourceConfig = new ApiV2Config(mockServletContext, validatorConfig); + } + catch (Exception e) { + throw new RuntimeException(e); + } + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, + TestContext.class); + resourceConfig.property("contextConfig", context); + return resourceConfig; + } + + @Test + void verifyThat_Response_ContainsCorrectStatusCodeAndMediaType() { + final Response response = target("/").request(APPLICATION_JSON).get(); + + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); + } + + @Test + void verifyThat_Response_ContainsOpenApiDocument() { + final String response = target("/").request(APPLICATION_JSON).get(String.class); + + BasicJsonTester json = new BasicJsonTester(getClass()); + assertThat(json.from(response)).extractingJsonPathStringValue("$.openapi").isEqualTo("3.0.1"); + } + + @Test + void verifyThat_Response_ContainsLicence() { + final String response = target("/").request(APPLICATION_JSON).get(String.class); + + BasicJsonTester json = new BasicJsonTester(getClass()); + assertThat(json.from(response)).extractingJsonPathStringValue("$.info.license.name").isEqualTo("Apache 2.0"); + } + + @Test + void verifyThat_Response_Paths() { + final String response = target("/").request(APPLICATION_JSON).get(String.class); + + BasicJsonTester json = new BasicJsonTester(getClass()); + assertThat(json.from(response)).extractingJsonPathMapValue("$.paths") + .containsOnlyKeys("/", "/info", "/status/{uuid}", "/validate"); + } + + @Test + void verifyThat_Response_ContainsTermsOfService() { + final String response = target("/").request(APPLICATION_JSON).get(String.class); + + BasicJsonTester json = new BasicJsonTester(getClass()); + assertThat(json.from(response)).extractingJsonPathStringValue("$.info.termsOfService") + .isEqualTo("http://xplanbox/api-validator/terms"); + } + + @Test + void verifyThat_Response_ContainsServersUrl() { + final String response = target("/").request(APPLICATION_JSON).get(String.class); + + BasicJsonTester json = new BasicJsonTester(getClass()); + assertThat(json.from(response)).extractingJsonPathStringValue("$.servers[0].url") + .isEqualTo("http://xplanbox-api-validator/api/v2"); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/InfoApi2Test.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/InfoApi2Test.java new file mode 100644 index 0000000000..e3443994b7 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/InfoApi2Test.java @@ -0,0 +1,92 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.TestContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import org.apache.http.HttpHeaders; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.test.json.BasicJsonTester; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> + * @since 8.0 + */ +class InfoApi2Test extends JerseyTest { + + @TempDir + public static Path tempFolder; + + @BeforeAll + static void setupFakedWorkspace() throws IOException { + Path workspace = tempFolder.resolve("xplan-validator-wms-memory-workspace"); + Files.createDirectories(workspace); + System.setProperty("DEEGREE_WORKSPACE_ROOT", workspace.getParent().toString()); + } + + @Override + protected Application configure() { + enable(TestProperties.LOG_TRAFFIC); + final ResourceConfig resourceConfig = new ResourceConfig(InfoApi2.class); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, + TestContext.class); + resourceConfig.property("contextConfig", context); + return resourceConfig; + } + + @Test + void verifyThat_Response_ContainsCorrectStatusCodeAndMediaType() { + final Response response = target("/info").request(APPLICATION_JSON).get(); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals(APPLICATION_JSON, response.getHeaderString(HttpHeaders.CONTENT_TYPE)); + + BasicJsonTester json = new BasicJsonTester(getClass()); + assertThat(json.from(response.readEntity(String.class))).extractingJsonPathStringValue("$.rulesMetadata.source") + .startsWith("https://gitlab.opencode.de/xleitstelle/xplanung/validierungsregeln/standard/-/tree/"); + + } + + @Test + void verifyThat_Response_ContainsSupportedXplanGmlVersionsAndProfiles() { + final String response = target("/info").request(APPLICATION_JSON).get(String.class); + + assertThat(response).contains("supportedXPlanGmlVersions"); + assertThat(response).contains("profiles"); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/StatusApi2Test.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/StatusApi2Test.java new file mode 100644 index 0000000000..02f0e28433 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/StatusApi2Test.java @@ -0,0 +1,80 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +import de.latlon.xplanbox.api.commons.exception.XPlanApiExceptionMapper; +import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.TestContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> + * @since 8.0 + */ +class StatusApi2Test extends JerseyTest { + + @TempDir + public static Path tempFolder; + + @BeforeAll + static void setupFakedWorkspace() throws IOException { + Path workspace = tempFolder.resolve("xplan-validator-wms-memory-workspace"); + Files.createDirectories(workspace); + System.setProperty("DEEGREE_WORKSPACE_ROOT", workspace.getParent().toString()); + } + + @Override + protected Application configure() { + enable(TestProperties.LOG_TRAFFIC); + final ResourceConfig resourceConfig = new ResourceConfig(StatusApi.class); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, + TestContext.class); + resourceConfig.property("contextConfig", context); + resourceConfig.register(XPlanApiExceptionMapper.class); + return resourceConfig; + } + + @Test + @Disabled("TODO: fix implementation") + void verify_404_forInvalidUuid() { + final Response response = target("/status/" + UUID.randomUUID()).request(APPLICATION_JSON).get(); + + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2Test.java b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2Test.java new file mode 100644 index 0000000000..c5b4250347 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/v2/ValidateApi2Test.java @@ -0,0 +1,147 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.api.validator.v2; + +import static de.latlon.xplanbox.api.commons.XPlanBoxMediaType.APPLICATION_X_ZIP_COMPRESSED; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.TEXT_XML; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import de.latlon.xplan.commons.configuration.DefaultPropertiesLoader; +import de.latlon.xplan.validator.semantic.profile.SemanticProfiles; +import de.latlon.xplanbox.api.validator.api.v2.ApiV2Config; +import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.api.validator.config.TestContext; +import de.latlon.xplanbox.api.validator.config.ValidatorApiConfiguration; +import jakarta.servlet.ServletContext; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import org.apache.http.HttpHeaders; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a> + * @since 8.0 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { TestContext.class }) +public class ValidateApi2Test extends JerseyTest { + + @TempDir + public static Path tempFolder; + + @BeforeAll + static void setupFakedWorkspace() throws IOException { + Path workspace = tempFolder.resolve("xplan-webservices-validator-wms-memory-workspace"); + Files.createDirectories(workspace); + System.setProperty("DEEGREE_WORKSPACE_ROOT", workspace.getParent().toString()); + } + + @Autowired + private SemanticProfiles semanticProfiles; + + @Override + protected Application configure() { + ResourceConfig resourceConfig; + ServletContext mockServletContext = Mockito.mock(ServletContext.class); + Mockito.when(mockServletContext.getContextPath()).thenReturn(""); + + try { + ValidatorApiConfiguration validatorConfig = new ValidatorApiConfiguration( + new DefaultPropertiesLoader(getClass())); + resourceConfig = new ApiV2Config(mockServletContext, validatorConfig); + } + catch (Exception e) { + throw new RuntimeException(e); + } + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationContext.class, + TestContext.class); + resourceConfig.property("contextConfig", context); + return resourceConfig; + } + + @Test + void verifyThat_Response_ContainsCorrectStatusCodeAndMediaType() throws IOException, URISyntaxException { + final String data = new String( + Files.readAllBytes(Paths.get(ValidateApi2Test.class.getResource("/xplan.gml").toURI()))); + final Response response = target("/validate").request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, TEXT_XML)); + + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); + assertThat(response.readEntity(String.class)).contains("uuid"); + } + + @Test + void verifyThat_validationXZipCompressed_Response_ContainsJsonEncoding() throws URISyntaxException, IOException { + final byte[] data = Files + .readAllBytes(Paths.get(ValidateApi2Test.class.getResource("/bplan_valid_41.zip").toURI())); + final Response response = target("/validate").request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); + + assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)).isEqualTo(APPLICATION_JSON); + assertThat(response.readEntity(String.class)).contains("uuid"); + } + + @Test + void verifyThat_validationWithInvalidName_Response_IsStatusCode400() throws URISyntaxException, IOException { + final byte[] data = Files.readAllBytes(Paths.get(ValidateApi2Test.class.getResource("/xplan.gml").toURI())); + final Response response = target("/validate").queryParam("name", "invalid.name with blanks") + .request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, TEXT_XML)); + + assertThat(response.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode()); + } + + @Test + void verifyThat_validationXZipCompressedWithInvalidName_Response_IsStatusCode400() + throws URISyntaxException, IOException { + final byte[] data = Files + .readAllBytes(Paths.get(ValidateApi2Test.class.getResource("/bplan_valid_41.zip").toURI())); + final Response response = target("/validate").queryParam("name", "invalid.name with blanks") + .request() + .accept(APPLICATION_JSON) + .post(Entity.entity(data, APPLICATION_X_ZIP_COMPRESSED)); + + assertThat(response.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode()); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report1.json b/xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report1.json new file mode 100644 index 0000000000..3a90816e2e --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report1.json @@ -0,0 +1 @@ +{"documentSummary":[{"name":"Nr. 108 Holstener Weg","type":"BP_Plan"}],"version":"XPLAN_41","filename":null,"name":"8147f2f6-5e53-4fc7-a795-75a6ea4b3190","bbox":{"minX":7.373668092967802,"minY":52.33234200586314,"maxX":7.377600099759094,"maxY":52.33376312995529,"crs":"EPSG:4326"},"date":1721376349423,"valid":false,"status":"Die Validierung wurde ausgeführt.","externalReferences":[],"externalReferencesResult":[],"rulesMetadata":{"version":"unbekannt","source":"unbekannt"},"validationResult":{"semantisch":{"valid":true,"rules":[]},"geometrisch":{"valid":false,"errors":["XPlanAuszug (Zeile 1, Spalte 1): 2.2.2.1: äußerer Ring verwendet falsche Laufrichtung (CW)."],"warnings":[]},"syntaktisch":{"valid":true,"messages":[]}}} \ No newline at end of file diff --git a/xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report2.xml b/xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report2.xml new file mode 100644 index 0000000000..073a1c07d8 --- /dev/null +++ b/xplan-validator/xplan-validator-api/src/test/resources/de/latlon/xplanbox/api/validator/v1/validation-report2.xml @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?><validationReport><version>XPLAN_41</version><name>858202ca-3d84-4174-be76-be04af280e36</name><documentSummary><name>Nr. 108 Holstener Weg</name><type>BP_Plan</type></documentSummary><bbox><minX>7.373668092967802</minX><minY>52.33234200586314</minY><maxX>7.377600099759094</maxX><maxY>52.33376312995529</maxY><crs>EPSG:4326</crs></bbox><date>2024-07-19T10:26:13.799+02:00</date><valid>false</valid><status>Die Validierung wurde ausgeführt.</status><wmsUrl>https://latlon.xplanbox.develop.diplanung.de/xplan-validator-wms/services/wms?PLANWERK_MANAGERID=4688&SERVICE=WMS&REQUEST=GetCapabilities</wmsUrl><rulesMetadata><version>1.1.9</version><source>https://gitlab.opencode.de/xleitstelle/xplanung/validierungsregeln/standard/-/tree/v1.1.9</source></rulesMetadata><validationResult><geometrisch><valid>false</valid><errors>XPlanAuszug (Zeile 1, Spalte 1): 2.2.2.1: äußerer Ring verwendet falsche Laufrichtung (CW).</errors></geometrisch><semantisch><valid>true</valid><rules><isValid>true</isValid><message>Verwendung vorgegebenen URNs für das uom-Attribut von GML-MeasureType</message><name>2.1.2.1</name></rules><rules><isValid>true</isValid><message>Angabe eines Standard CRS</message><name>2.1.3.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussbedingung</message><name>2.2.1.1</name></rules><rules><isValid>true</isValid><message>Relationen auf Text-Abschnitte</message><name>3.1.1.1</name></rules><rules><isValid>true</isValid><message>Relationen auf Begründungs-Abschnitte</message><name>3.1.1.2</name></rules><rules><isValid>true</isValid><message>Relation auf Präsentationsobjekte</message><name>3.1.2.1</name></rules><rules><isValid>true</isValid><message>Relation auf Fachobjekte</message><name>3.1.2.2</name></rules><rules><isValid>true</isValid><message>Relation auf Basis-Rasterplan</message><name>3.1.2.3</name></rules><rules><isValid>true</isValid><message>Relationen auf Text-Abschnitte</message><name>3.1.3.1</name></rules><rules><isValid>true</isValid><message>Relationen auf Begründungs-Abschnitte</message><name>3.1.3.2</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>3.1.3.3</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Präsentationsobjekte</message><name>3.1.3.4</name></rules><rules><isValid>true</isValid><message>Spezifikation des Textinhalts</message><name>3.2.1.1</name></rules><rules><isValid>true</isValid><message>Spezifikation des Textinhalts</message><name>3.2.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der verschiedenen Höhenangaben</message><name>3.2.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung von Höhenangaben, die sich auf eine auf Bezugshöhe beziehen, die auf Planebene definiert ist</message><name>3.2.3.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute hoehenbezug und abweichenderHoehenbezug</message><name>3.2.3.3</name></rules><rules><isValid>true</isValid><message>Verweis auf Dokumente.</message><name>3.2.4.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute ags (Amtlicher Gemeindeschlüssel) und rs (Regionalschlüssel).</message><name>3.2.5.1</name></rules><rules><isValid>true</isValid><message>Spezifikation des Fachobjekt-Attributs bei nicht-freien Präsentationsobjekten</message><name>3.3.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute art und index</message><name>3.3.1.2</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>3.3.1.3</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs auf Punktgeometrie.</message><name>3.3.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs auf Liniengeometrie.</message><name>3.3.3.1</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs auf Flächengeometrie.</message><name>3.3.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation bereich</message><name>4.1.1.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation inhaltBPlan</message><name>4.1.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation rasterAenderung</message><name>4.1.2.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation gehoertZuPlan</message><name>4.1.2.3</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchFlaeche</message><name>4.1.3.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchMassnahme</message><name>4.1.3.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchSPEMassnahme</message><name>4.1.3.3</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchSPEFlaeche</message><name>4.1.3.4</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>4.1.3.5</name></rules><rules><isValid>true</isValid><message>BPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.</message><name>4.1.3.6</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchABE</message><name>4.1.3.7</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>4.1.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Flächengeometrie</message><name>4.1.4.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>4.1.5.1</name></rules><rules><isValid>true</isValid><message>Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug</message><name>4.1.5.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekte gehören nie zum Flächenschluss.</message><name>4.1.6.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekte auf Ebene 0 gehören immer zum Flächenschluss</message><name>4.1.7.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Liniengeometrie</message><name>4.1.8.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Punktgeometrie</message><name>4.1.9.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GFZ</message><name>4.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GF</message><name>4.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GFZ und GF</message><name>4.2.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur BMZ</message><name>4.2.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur BM</message><name>4.2.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur BMZ und BM</message><name>4.2.6</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GRZ</message><name>4.2.7</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GR</message><name>4.2.8</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GRZ und GR</message><name>4.2.9</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zu Z</message><name>4.2.10</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zu ZU</message><name>4.2.11</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur Dachneigung</message><name>4.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute dachform und detaillierteDachform</message><name>4.3.2</name></rules><rules><isValid>true</isValid><message>Relation abweichungText</message><name>4.5.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung</message><name>4.5.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute besondereArtDerBaulNutzung und sondernutzung</message><name>4.5.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute bauweise und abweichendeBauweise</message><name>4.5.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sondernutzung</message><name>4.5.1.5</name></rules><rules><isValid>true</isValid><message>Relation baugrenze</message><name>4.5.2.1</name></rules><rules><isValid>true</isValid><message>Relation baulinie</message><name>4.5.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute typ und sonstTyp</message><name>4.5.10.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.5.13.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.5.13.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.5.13.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.5.14.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.5.14.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation eigentümer</message><name>4.5.14.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.5.14.4</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.5.15.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.6.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.7.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>4.7.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.7.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>4.7.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4)</message><name>4.7.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.7.1.6</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.7.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.7.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.7.2.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.8.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.8.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.8.1.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.8.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.8.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.8.2.3</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>4.8.2.4</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.8.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>4.8.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.8.3.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>4.8.3.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi</message><name>4.8.3.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.8.3.6</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme und weitereMassnahme1</message><name>4.9.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1</message><name>4.9.2.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>4.9.2.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme und weitereMassnahme1</message><name>4.9.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1</message><name>4.9.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>4.9.3.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme und weitereMassnahme1</message><name>4.9.5.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1</message><name>4.9.5.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>4.9.5.3</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.9.5.4</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.9.6.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.10.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.11.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>4.11.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.11.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>4.11.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi</message><name>4.11.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.11.1.6</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>4.11.1.7</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation begrenzungslinie</message><name>4.12.1.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation begrenzungslinie</message><name>4.12.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.12.2.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.12.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.13.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.13.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.14.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.14.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute startWinkel und endWinkel</message><name>4.14.3.1</name></rules><rules><isValid>true</isValid><message>Kein flächenhafter Raumbezug</message><name>4.14.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.14.4.1</name></rules><rules><isValid>true</isValid><message>Notwendige Spezifikation einer Textlichen Darstellung</message><name>4.14.6.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Relation hoehenangabe</message><name>4.14.7.1</name></rules><rules><isValid>true</isValid><message>Kein flächenhafter Raumbezug</message><name>4.14.7.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation bereich</message><name>5.1.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute planArt und sonstPlanArt</message><name>5.1.1.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation inhaltFPlan</message><name>5.1.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation rasterAenderung</message><name>5.1.2.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation gehoertZuPlan</message><name>5.1.2.3</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchFlaeche</message><name>5.1.3.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchSPE</message><name>5.1.3.2</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>5.1.3.3</name></rules><rules><isValid>true</isValid><message>FPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.</message><name>5.1.3.4</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>5.1.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Flächengeometrie</message><name>5.1.4.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>5.1.5.1</name></rules><rules><isValid>true</isValid><message>Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug</message><name>5.1.5.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekte gehören nie zum Flächenschluss</message><name>5.1.6.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekte der Ebene 0 gehören zum Flächenschluss</message><name>5.1.7.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Liniengeometrie</message><name>5.1.8.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Punktgeometrie</message><name>5.1.9.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung</message><name>5.3.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute besondereArtDerBaulNutzung und sonderNutzung</message><name>5.3.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sonderNutzung</message><name>5.3.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GFZ</message><name>5.3.1.4</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.4.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.4.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.4.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.4.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)</message><name>5.4.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.4.1.6</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs</message><name>5.4.1.7</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.4.1.8</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.4.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation einer einzigen detaillierter Zweckbestimmung</message><name>5.4.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.4.2.3</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs</message><name>5.4.2.4</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.4.2.5</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.5.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.5.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.5.1.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.5.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.5.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.5.2.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.5.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.5.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.5.3.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.5.3.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)</message><name>5.5.3.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.5.3.6</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.5.3.7</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2</message><name>5.6.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>5.6.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2</message><name>5.6.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>5.6.2.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.7.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.7.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.7.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.7.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2, 3)</message><name>5.7.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.7.1.6</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.7.1.7</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.8.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.8.1.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>5.8.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.9.1.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.9.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.9.2.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>5.9.2.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.10.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.10.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.10.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.10.2.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2)</message><name>5.10.2.4</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.10.3.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>5.10.4.1</name></rules><rules><isValid>true</isValid><message>Notwendige Spezifikation einer Textlichen Darstellung</message><name>5.10.6.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation bereich</message><name>6.1.1.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation inhaltSoPlan</message><name>6.1.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation rasterAenderung</message><name>6.1.2.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation gehoertZuPlan</message><name>6.1.2.3</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>6.1.3.1</name></rules><rules><isValid>true</isValid><message>SOPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.</message><name>6.1.3.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>6.1.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Flächengeometrie</message><name>6.1.4.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>6.1.5.1</name></rules><rules><isValid>true</isValid><message>Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug</message><name>6.1.5.2</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Liniengeometrie</message><name>6.1.6.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Punktgeometrie</message><name>6.1.7.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.2.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.2.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.2.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.4.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und besondereArtDerFestlegung</message><name>6.2.5.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung, besondereArtDerFestlegung und detailArtDerFestlegung</message><name>6.2.5.2</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>6.2.5.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.6.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.7.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>6.2.7.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.8.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.3.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.3.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.3.2.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.3.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.3.3.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.3.3.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute gebietsArt und sonstGebietsArt</message><name>6.4.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.4.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz von typ und sonstTyp</message><name>6.5.1.1</name></rules></semantisch><syntaktisch><valid>true</valid></syntaktisch></validationResult></validationReport> \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/Dockerfile b/xplan-validator/xplan-validator-executor/Dockerfile new file mode 100644 index 0000000000..8d209e5130 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/Dockerfile @@ -0,0 +1,44 @@ +ARG XPLANBOX_VERSION=latest +ARG XPLANBOX_IMAGE_NAME_PREFIX=xplanbox + +FROM ${XPLANBOX_IMAGE_NAME_PREFIX}/xplan-docker-tomcat:$XPLANBOX_VERSION as builder + +FROM eclipse-temurin:17.0.11_9-jre-alpine +ARG BUILD_DATE=? +ARG DOCKER_IMAGE_NAME=? +ARG GIT_REVISION=? +ARG JAR_FILE=target/*.jar +ARG XPLANBOX_VERSION=latest + +# see https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys +LABEL "org.opencontainers.image.created"="$BUILD_DATE" \ + "org.opencontainers.image.description"="ozgxplanung xPlanBox component" \ + "org.opencontainers.image.licenses"="GNU Affero General Public License & others" \ + "org.opencontainers.image.ref.name"="$DOCKER_IMAGE_NAME" \ + "org.opencontainers.image.revision"="$GIT_REVISION" \ + "org.opencontainers.image.title"="ozgxplanung - $DOCKER_IMAGE_NAME" \ + "org.opencontainers.image.url"="https://gitlab.opencode.de/diplanung/ozgxplanung" \ + "org.opencontainers.image.vendor"="lat/lon GmbH" \ + "org.opencontainers.image.version"="$XPLANBOX_VERSION" + +ENV TZ=Europe/Berlin + +# copy jmx exporter from xplan-docker-tomcat until we switch to actuator based prometheus publishing +ENV JMX_EXPORTER_DIR=/xplanbox/prometheus +RUN mkdir -p $JMX_EXPORTER_DIR +COPY --from=builder $JMX_EXPORTER_DIR $JMX_EXPORTER_DIR + +ENV JAVA_ADDITIONAL_ARG_JAVA17_EXPORTS="--add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED" \ + JAVA_ADDITIONAL_ARG_JMX_EXPORTER='-javaagent:$JMX_EXPORTER_DIR/jmx_prometheus_javaagent-1.0.1.jar=12345:$JMX_EXPORTER_DIR/jmx-exporter.config.yaml' + +# set environment variables +ENV DEEGREE_WORKSPACE_ROOT=/xplanbox/deegree \ + JAVA_ADDITIONAL_ARG_APP="-Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -Djts.overlay=ng -Duser.timezone=Europe/Berlin" \ + XPLANBOX_CONFIG="/xplanbox/xplan-validator-config/" + +COPY ${JAR_FILE} /xplanbox/app.jar +COPY run.sh /xplanbox/ + +USER 1001 + +ENTRYPOINT ["/bin/sh", "/xplanbox/run.sh"] diff --git a/xplan-validator/xplan-validator-executor/README.md b/xplan-validator/xplan-validator-executor/README.md new file mode 100644 index 0000000000..98c9a27570 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/README.md @@ -0,0 +1,11 @@ +# xPlanValidatorExecutor + +## Development + +The application can be started locally with + +``` +mvn spring-boot:run -Dspring-boot.run.profiles=dev +``` + +(RabbitMQ required on localhost:5672 with credentials guest/guest) diff --git a/xplan-validator/xplan-validator-executor/pom.xml b/xplan-validator/xplan-validator-executor/pom.xml new file mode 100644 index 0000000000..666341e8a3 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/pom.xml @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>xplan-validator-executor</artifactId> + + <parent> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-validator</artifactId> + <version>8.0-SNAPSHOT</version> + </parent> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> <!-- redeclare here to run *after* spring-boot repackage --> + <groupId>io.fabric8</groupId> + <artifactId>docker-maven-plugin</artifactId> + <executions> + <execution> + <id>context-sources</id> + <goals> + <goal>source</goal> + </goals> + <configuration> + <skip>${docker-image.skip-sources}</skip> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addDefaultImplementationEntries>true</addDefaultImplementationEntries> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <!-- xPlanBox --> + <dependency> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-core-validator-events</artifactId> + </dependency> + <dependency> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-validator-storage</artifactId> + </dependency> + <!-- XPlanung --> + <dependency> + <groupId>de.xleitstelle.xplanung</groupId> + <artifactId>regeln</artifactId> + </dependency> + <!-- spring --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <exclusions> + <exclusion> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-el</artifactId> + </exclusion> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>jul-to-slf4j</artifactId> + </exclusion> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>log4j-over-slf4j</artifactId> + </exclusion> + <exclusion> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-to-slf4j</artifactId> + </exclusion> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </exclusion> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-core</artifactId> + </exclusion> + <exclusion> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-amqp</artifactId> + <exclusions> + <exclusion> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-to-slf4j</artifactId> + </exclusion> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </exclusion> + <exclusion> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-core</artifactId> + </exclusion> + <exclusion> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jcl-over-slf4j</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jul-to-slf4j</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-layout-template-json</artifactId> + </dependency> + <dependency> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-core-validator</artifactId> + </dependency> + <dependency> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-core-api</artifactId> + </dependency> + <dependency> + <groupId>jakarta.inject</groupId> + <artifactId>jakarta.inject-api</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>docker</id> + <properties> + <docker-image.skip>false</docker-image.skip> + <docker-contextTarFile.expectedSizeInMat10pct>75</docker-contextTarFile.expectedSizeInMat10pct> + </properties> + <dependencies> + <dependency> <!-- to copy jmx exporter stuff from docker image --> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-docker-tomcat</artifactId> + <version>${project.version}</version> + <type>pom</type> + </dependency> + <!-- Profiles --> + <dependency> + <groupId>de.xleitstelle.xplanung</groupId> + <artifactId>regeln-berlin</artifactId> + </dependency> + <dependency> + <groupId>de.xleitstelle.xplanung</groupId> + <artifactId>regeln-brandenburg</artifactId> + </dependency> + </dependencies> + </profile> + </profiles> + +</project> \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/run.sh b/xplan-validator/xplan-validator-executor/run.sh new file mode 100755 index 0000000000..d12d67ccd8 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/run.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +ALL_ADDITIONAL_ARGS=$(env | grep ^JAVA_ADDITIONAL_ | sed 's/^JAVA_ADDITIONAL_[[:alnum:]_]*=//' | tr '\n' ' ') + +if [ -n "${ALL_ADDITIONAL_ARGS}" ]; then + JAVA_OPTS=$(eval "echo $ALL_ADDITIONAL_ARGS") + echo "xPlanBox JAVA_OPTS set: $JAVA_OPTS" +fi + +exec java $JAVA_OPTS -jar /xplanbox/app.jar diff --git a/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/PlanValidator.java b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/PlanValidator.java new file mode 100644 index 0000000000..3e676f94fc --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/PlanValidator.java @@ -0,0 +1,155 @@ +package de.latlon.xplanbox.validator.executor; + +import static de.latlon.xplanbox.validator.storage.ErrorType.INTERNAL_ERROR; +import static de.latlon.xplanbox.validator.storage.ErrorType.INVALID_REQUEST; +import static de.latlon.xplanbox.validator.storage.ErrorType.fromStatusCode; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.latlon.core.validator.events.EventSender; +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.core.validator.events.v1.XPlanboxPublicV1Event; +import de.latlon.core.validator.events.v1.XPlanboxPublicV1Event.EventType; +import de.latlon.xplan.commons.archive.XPlanArchive; +import de.latlon.xplan.validator.ValidatorException; +import de.latlon.xplan.validator.report.ValidatorReport; +import de.latlon.xplanbox.api.commons.ObjectMapperContextResolver; +import de.latlon.xplanbox.api.commons.ValidationReportBuilder; +import de.latlon.xplanbox.api.commons.exception.XPlanApiException; +import de.latlon.xplanbox.api.commons.v1.model.ValidationReport; +import de.latlon.xplanbox.validator.executor.handler.ValidationHandler; +import de.latlon.xplanbox.validator.storage.StatusType; +import de.latlon.xplanbox.validator.storage.StoredValidationReport; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.ReportType; + +/** + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +@Component +public class PlanValidator { + + private static final Logger LOG = LoggerFactory.getLogger(PlanValidator.class); + + private final ValidationExecutionStorage validationExecutionStorage; + + private final ValidationHandler validationHandler; + + private final EventSender eventSender; + + public PlanValidator(ValidationExecutionStorage validationExecutionStorage, ValidationHandler validationHandler, + EventSender eventSender) { + this.validationExecutionStorage = validationExecutionStorage; + this.validationHandler = validationHandler; + this.eventSender = eventSender; + } + + public void validate(ValidationRequestedEvent event) throws Exception { + validationExecutionStorage.changeStatus(event.getUuid(), StatusType.STARTED); + eventSender.sendPublicEvent( + new XPlanboxPublicV1Event(EventType.VALIDATION_START, event.getUuid(), "validation started")); + + try { + doValidate(event); + } + finally { + eventSender.sendPublicEvent( + new XPlanboxPublicV1Event(EventType.VALIDATION_FINISHED, event.getUuid(), "validation finished")); + } + } + + private void doValidate(ValidationRequestedEvent event) throws Exception { + Map<ReportType, Path> reports = new HashMap<>(); + try { + validationExecutionStorage.changeStatus(event.getUuid(), StatusType.STARTED); + Path tmpPath = Files.createTempFile(event.getUuid(), ".rcv"); + if (Files.exists(tmpPath)) + Files.delete(tmpPath); + validationExecutionStorage.writePlanToValidate(event.getUuid(), tmpPath); + + final XPlanArchive archive; + if (event.getOriginFile() == ValidationRequestedEvent.OriginFile.GML) { + archive = validationHandler.createArchiveFromGml(tmpPath.toFile(), + event.getSettings().getValidationName()); + } + else { + archive = validationHandler.createArchiveFromZip(tmpPath.toFile(), + event.getSettings().getValidationName()); + } + + LOG.info("Validating {}", tmpPath); + ValidatorReport validatorReport = validationHandler.validate(archive, event.getxFileName(), + event.getSettings()); + // TODO: delete tmpPath + + ValidationReport validationReport = createValidationReport(event, archive, validatorReport); + + reports.put(ReportType.JSON, createJsonReportFile(validationReport)); + reports.put(ReportType.PDF, createPdfReportFile(validatorReport)); + reports.put(ReportType.ZIP, createZipReportFile(validatorReport)); + + StoredValidationReport storedValidationReport = validationExecutionStorage + .saveValidationResult(event.getUuid(), reports); + LOG.info("Validation Report: " + validatorReport); + validationExecutionStorage.changeStatus(event.getUuid(), StatusType.FINISHED, storedValidationReport); + validationExecutionStorage.cleanupAfterValidation(event.getUuid()); + } + catch (XPlanApiException e) { + validationExecutionStorage.changeStatus(event.getUuid(), e.getMessage(), fromStatusCode(e.getStatusCode())); + LOG.info("Validation with uuid " + event.getUuid() + " failed: " + e.getMessage()); + throw e; + } + catch (ValidatorException e) { + validationExecutionStorage.changeStatus(event.getUuid(), e.getMessage(), INVALID_REQUEST); + LOG.info("Validation with uuid " + event.getUuid() + " failed: " + e.getMessage()); + throw e; + } + catch (Exception e) { + validationExecutionStorage.changeStatus(event.getUuid(), e.getMessage(), INTERNAL_ERROR); + LOG.info("Validation with uuid " + event.getUuid() + " failed: " + e.getMessage()); + throw e; + } + finally { + for (Path path : reports.values()) { + Files.deleteIfExists(path); + } + } + } + + private ValidationReport createValidationReport(ValidationRequestedEvent event, final XPlanArchive archive, + ValidatorReport validatorReport) { + URI wmsUrl = validationHandler.addToWms(archive); + return new ValidationReportBuilder().validatorReport(validatorReport) // + .filename(event.getxFileName()) // + .wmsUrl(wmsUrl) // + .build(); + } + + private Path createJsonReportFile(ValidationReport validationReport) throws IOException { + ObjectMapper mapper = new ObjectMapperContextResolver().getContext(ValidationReport.class); + Path reportFile = Files.createTempFile("", ".json"); + mapper.writeValue(reportFile.toFile(), validationReport); + return reportFile; + } + + private Path createPdfReportFile(ValidatorReport validatorReport) throws IOException { + return validationHandler.writePdfReport(validatorReport); + } + + private Path createZipReportFile(ValidatorReport validatorReport) throws IOException { + return validationHandler.zipReports(validatorReport); + } + +} diff --git a/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/SpringBootApp.java b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/SpringBootApp.java new file mode 100644 index 0000000000..57497839cd --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/SpringBootApp.java @@ -0,0 +1,54 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.validator.executor; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +@SpringBootApplication +@ComponentScan(basePackages = { "de.latlon.xplanbox.validator.executor" }) +@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class }) +public class SpringBootApp extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } + + /** + * Runs the application as deployable WAR. + * @param application Spring application builder + * @return Spring application builder with this application configured + */ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(SpringBootApp.class); + } + +} diff --git a/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ApplicationContext.java b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ApplicationContext.java new file mode 100644 index 0000000000..c223b5d3e5 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ApplicationContext.java @@ -0,0 +1,162 @@ +/*- + * #%L + * xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.validator.executor.config; + +import static java.nio.file.Files.createTempDirectory; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import de.latlon.xplan.commons.configuration.PropertiesLoader; +import de.latlon.xplan.commons.configuration.SystemPropertyPropertiesLoader; +import de.latlon.xplan.manager.web.shared.ConfigurationException; +import de.latlon.xplan.validator.XPlanValidator; +import de.latlon.xplan.validator.configuration.ValidatorConfiguration; +import de.latlon.xplan.validator.configuration.ValidatorConfigurationParser; +import de.latlon.xplan.validator.geometric.GeometricValidator; +import de.latlon.xplan.validator.geometric.GeometricValidatorImpl; +import de.latlon.xplan.validator.report.ReportArchiveGenerator; +import de.latlon.xplan.validator.report.ReportWriter; +import de.latlon.xplan.validator.semantic.SemanticValidator; +import de.latlon.xplan.validator.semantic.configuration.SemanticRulesConfiguration; +import de.latlon.xplan.validator.semantic.configuration.SemanticRulesMainConfiguration; +import de.latlon.xplan.validator.semantic.configuration.xquery.XQuerySemanticValidatorConfigurationRetriever; +import de.latlon.xplan.validator.semantic.profile.SemanticProfiles; +import de.latlon.xplan.validator.semantic.profile.SemanticProfilesCreator; +import de.latlon.xplan.validator.semantic.xquery.XQuerySemanticValidator; +import de.latlon.xplan.validator.syntactic.SyntacticValidator; +import de.latlon.xplan.validator.syntactic.SyntacticValidatorImpl; +import de.latlon.xplan.validator.wms.config.ValidatorWmsContext; +import de.latlon.xplanbox.api.commons.handler.SystemConfigHandler; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.config.AmazonS3Context; +import de.latlon.xplanbox.validator.storage.filesystem.FileSystemValidationExecutionStorage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.ResourceLoader; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +@Configuration +@Import({ ValidatorWmsContext.class, AmazonS3Context.class }) +public class ApplicationContext { + + @Autowired + private ResourceLoader resourceLoader; + + @Bean + public SystemConfigHandler systemConfigHandler(XQuerySemanticValidatorConfigurationRetriever configurationRetriever, + SemanticProfiles semanticProfiles) { + return new SystemConfigHandler(configurationRetriever, semanticProfiles.getProfileMetadata()); + } + + @Bean + public Path uploadFolder() throws IOException { + return createTempDirectory("xplan-validator"); + } + + @Bean + public SyntacticValidator syntacticValidator() { + return new SyntacticValidatorImpl(); + } + + @Bean + public GeometricValidator geometricValidator() { + return new GeometricValidatorImpl(); + } + + @Bean + public SemanticValidator semanticValidator(XQuerySemanticValidatorConfigurationRetriever configurationRetriever) + throws ConfigurationException { + return new XQuerySemanticValidator(configurationRetriever); + } + + @Bean + public XQuerySemanticValidatorConfigurationRetriever xQuerySemanticValidatorConfigurationRetriever( + SemanticRulesConfiguration semanticRulesConfiguration) { + return new XQuerySemanticValidatorConfigurationRetriever(semanticRulesConfiguration); + } + + @Bean + public SemanticRulesConfiguration semanticRulesConfiguration(ValidatorConfiguration validatorConfiguration) { + Path validationRulesDirectory = validatorConfiguration.getValidationRulesDirectory(); + return new SemanticRulesMainConfiguration(validationRulesDirectory); + } + + @Bean + public SemanticProfiles semanticProfiles(ValidatorConfiguration validatorConfiguration, + PropertiesLoader validatorPropertiesLoader, + @Value("${xplanbox.validation.profiles}") String activatedProfiles) throws ConfigurationException { + List<String> activatedProfilesList = activatedProfiles != null ? Arrays.asList(activatedProfiles.split(",")) + : Collections.emptyList(); + SemanticProfilesCreator semanticProfilesCreator = new SemanticProfilesCreator(validatorConfiguration, + validatorPropertiesLoader, resourceLoader); + return semanticProfilesCreator.createSemanticProfiles(activatedProfilesList); + } + + @Bean + public PropertiesLoader validatorPropertiesLoader() { + return new SystemPropertyPropertiesLoader(ValidatorConfiguration.class); + } + + @Bean + public ValidatorConfiguration validatorConfiguration(PropertiesLoader validatorPropertiesLoader) + throws IOException, ConfigurationException { + ValidatorConfigurationParser validatorConfigurationParser = new ValidatorConfigurationParser(); + return validatorConfigurationParser.parse(validatorPropertiesLoader); + } + + @Bean + public XPlanValidator xplanValidator(GeometricValidator geometricValidator, SyntacticValidator syntacticValidator, + SemanticValidator semanticValidator, SemanticProfiles semanticProfiles, + ReportArchiveGenerator reportArchiveGenerator) { + return new XPlanValidator(geometricValidator, syntacticValidator, semanticValidator, + semanticProfiles.getProfileValidators(), reportArchiveGenerator); + } + + @Bean + public ReportArchiveGenerator reportArchiveGenerator(ValidatorConfiguration validatorConfiguration) { + return new ReportArchiveGenerator(validatorConfiguration); + } + + @Bean + public ReportWriter reportWriter() { + return new ReportWriter(); + } + + @Bean + @Profile("!s3execution") + public ValidationExecutionStorage validationExecutionStorage( + @Value("${xplanbox.validation.fsdirectory}") Optional<Path> fsExecutionDir) throws IOException { + return new FileSystemValidationExecutionStorage(fsExecutionDir); + } + +} diff --git a/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/messagingrabbitmq/Receiver.java b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ExecutorRabbitConfiguration.java similarity index 56% rename from xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/messagingrabbitmq/Receiver.java rename to xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ExecutorRabbitConfiguration.java index 11f147b3f1..6741c53ed1 100644 --- a/xplan-manager/xplan-manager-api/src/main/java/de/latlon/xplanbox/api/manager/messagingrabbitmq/Receiver.java +++ b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/config/ExecutorRabbitConfiguration.java @@ -18,30 +18,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplanbox.api.manager.messagingrabbitmq; +package de.latlon.xplanbox.validator.executor.config; -import static org.slf4j.LoggerFactory.getLogger; +import de.latlon.core.validator.events.RabbitEmitterConfig; +import de.latlon.core.validator.events.RabbitReceiverConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; -import java.util.function.Consumer; - -import org.slf4j.Logger; -import org.springframework.stereotype.Component; - -import de.latlon.core.validator.events.ValidationMessageReceiver; -import de.latlon.core.validator.events.ValidationRequestedEvent; - -@Component -public class Receiver implements Consumer<ValidationRequestedEvent> { - - private static final Logger LOG = getLogger(Receiver.class); - - Receiver(ValidationMessageReceiver msgReceiver) { - msgReceiver.setListener(this); - } - - @Override - public void accept(ValidationRequestedEvent e) { - LOG.info("Received event: " + e); - } +@Configuration +@Import({ RabbitReceiverConfig.class, RabbitEmitterConfig.class }) +public class ExecutorRabbitConfiguration { } diff --git a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/ValidationHandler.java b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/handler/ValidationHandler.java similarity index 99% rename from xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/ValidationHandler.java rename to xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/handler/ValidationHandler.java index 2d01d0a471..f7b49c38b9 100644 --- a/xplan-validator/xplan-validator-api/src/main/java/de/latlon/xplanbox/api/validator/handler/ValidationHandler.java +++ b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/handler/ValidationHandler.java @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplanbox.api.validator.handler; +package de.latlon.xplanbox.validator.executor.handler; import de.latlon.xplan.commons.archive.XPlanArchive; import de.latlon.xplan.commons.archive.XPlanArchiveCreator; diff --git a/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/messagingrabbitmq/Receiver.java b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/messagingrabbitmq/Receiver.java new file mode 100644 index 0000000000..f1f00e2588 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/java/de/latlon/xplanbox/validator/executor/messagingrabbitmq/Receiver.java @@ -0,0 +1,71 @@ +/*- + * #%L + * xplan-manager-api - Software zur Verwaltung von XPlanGML Daten + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.validator.executor.messagingrabbitmq; + +import static de.latlon.core.validator.events.ValidationFinishedEvent.ValidationFinishedStatus.FAILED; +import static de.latlon.core.validator.events.ValidationFinishedEvent.ValidationFinishedStatus.SUCCEEDED; +import static org.slf4j.LoggerFactory.getLogger; + +import de.latlon.core.validator.events.EventSender; +import de.latlon.core.validator.events.ValidationFinishedEvent; +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.xplanbox.validator.executor.PlanValidator; +import org.slf4j.Logger; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +@Component +@RabbitListener(queues = "#{validationTaskQueue.name}", concurrency = "1") +public class Receiver { + + private static final Logger LOG = getLogger(Receiver.class); + + @Autowired + private PlanValidator validator; + + @Autowired + private EventSender eventSender; + + @RabbitHandler + public void accept(ValidationRequestedEvent event) { + LOG.info("Received event: " + event); + + try { + validator.validate(event); + + ValidationFinishedEvent finishedEvent = new ValidationFinishedEvent(event.getUuid(), SUCCEEDED); + eventSender.sendEvent(finishedEvent); + } + catch (Exception e) { + LOG.error("Failed to process event. Discarding it", e); + + ValidationFinishedEvent finishedEvent = new ValidationFinishedEvent(event.getUuid(), FAILED); + eventSender.sendEvent(finishedEvent); + } + } + +} diff --git a/xplan-validator/xplan-validator-executor/src/main/resources/application-dev.properties b/xplan-validator/xplan-validator-executor/src/main/resources/application-dev.properties new file mode 100644 index 0000000000..526d01eb7e --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/resources/application-dev.properties @@ -0,0 +1,23 @@ +### +# #%L +# xplan-api-validator-executor - Modul zur Gruppierung der REST-API +# %% +# Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# #L% +### + +## for local development +server.port=8094 \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/src/main/resources/application.properties b/xplan-validator/xplan-validator-executor/src/main/resources/application.properties new file mode 100644 index 0000000000..61cd9f4955 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/resources/application.properties @@ -0,0 +1,50 @@ +### +# #%L +# xplan-api-validator - Modul zur Gruppierung der REST-API +# %% +# Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# #L% +### +server.servlet.context-path=/xplan-validator-executor + +logging.level.root=warn +logging.level.de.latlon=debug +logging.level.org.springframework.web=debug +logging.level.org.hibernate=error + +management.endpoint.health.probes.enabled=true + +spring.rabbitmq.host=${XPLAN_RABBIT_HOST:localhost} +spring.rabbitmq.password=${XPLAN_RABBIT_PASSWORD:guest} +spring.rabbitmq.port=${XPLAN_RABBIT_PORT:5672} +spring.rabbitmq.username=${XPLAN_RABBIT_USER:guest} +spring.rabbitmq.listener.prefetch=1 + +xplanbox.rabbitmq.internal.prefix=${XPLAN_RABBIT_INTERNAL_PREFIX:xplanbox} +xplanbox.rabbitmq.public.fanout=${XPLAN_RABBIT_PUBLIC_FANOUT:} + +xplanbox.rabbitmq.fanout.topics=none +xplanbox.rabbitmq.task.topics=task.# + +xplanbox.s3.accessKeyId=${XPLAN_S3_ACCESS_KEY} +xplanbox.s3.endpoint.url=${XPLAN_S3_ENDPOINT} +xplanbox.s3.region=${XPLAN_S3_REGION} +xplanbox.s3.secretKey=${XPLAN_S3_SECRET_ACCESS_KEY} + +xplanbox.validation.fsdirectory=${XPLAN_FS_DIRECTORY:/tmp/validation} +xplanbox.validation.profiles=${XPLAN_VALIDATOR_PROFILES:} +xplanbox.validation.s3.bucketName=${XPLAN_VALIDATION_S3_BUCKET_NAME:validation} +xplanbox.validation.s3.bucketPublicUrl=${XPLAN_VALIDATION_S3_BUCKET_PUBLIC_URL} diff --git a/xplan-validator/xplan-validator-executor/src/main/resources/log4j2.yaml b/xplan-validator/xplan-validator-executor/src/main/resources/log4j2.yaml new file mode 100644 index 0000000000..fa1df7f8bf --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/main/resources/log4j2.yaml @@ -0,0 +1,82 @@ +### +# #%L +# xplan-validator-api - Software zur Verwaltung von XPlanGML Daten +# %% +# Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# #L% +### +Configuration: + name: XPlanValidatorExecutor + status: warn + Properties: + Property: + name: appenderToUse + value: stdout_${env:LOG4J_LAYOUT:-plain} + + Appenders: + Console: + - name: stdout_plain + target: SYSTEM_OUT + PatternLayout: + Pattern: "%d %p %C{1.} [%t] %m%n" + - name: stdout_json + target: SYSTEM_OUT + JsonTemplateLayout: + eventTemplateUri: classpath:de/latlon/xplan/commons/minimalist-format.json + + Loggers: + Root: + level: info + AppenderRef: + - ref: ${appenderToUse} + level: info + Logger: + - name: org.deegree + level: info + - name: de.latlon.xplan + level: info + - name: de.latlon.xplanbox.api + level: info + - name: de.latlon.xplanbox.api.validator.config + level: info + - name: org.springframework + level: warn + - name: org.glassfish + level: warn + - name: org.glassfish.jersey + level: warn + - name: org.apache + level: warn + - name: net.sf.jasperreports + level: warn + - name: com.ctc.wstx + level: warn + - name: com.sun.xml + level: warn + - name: org.reflections + level: fatal + - name: io.swagger.v3 + level: warn + - name: org.deegree.geometry.standard.AbstractDefaultGeometry + level: fatal + - name: de.latlon.xplan.validator.geometric.XPlanGeometryInspector + level: fatal + - name: de.latlon.xplan.validator.semantic.configuration.xquery.XQuerySemanticValidatorConfigurationRetriever + level: warn + - name: bind + level: error + - name: logger + level: error diff --git a/xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/PlanValidatorTest.java b/xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/PlanValidatorTest.java new file mode 100644 index 0000000000..45a735d2aa --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/PlanValidatorTest.java @@ -0,0 +1,196 @@ +/*- + * #%L + * xplan-core-api - Modul zur Gruppierung der Kernmodule + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.validator.executor; + +import static de.latlon.xplan.validator.web.shared.ValidationType.GEOMETRIC; +import static de.latlon.xplan.validator.web.shared.ValidationType.SEMANTIC; +import static de.latlon.xplan.validator.web.shared.ValidationType.SYNTACTIC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.latlon.core.validator.events.EventSender; +import de.latlon.core.validator.events.ValidationRequestedEvent; +import de.latlon.core.validator.events.ValidationRequestedEvent.MediaType; +import de.latlon.core.validator.events.ValidationRequestedEvent.OriginFile; +import de.latlon.core.validator.events.v1.XPlanboxPublicV1Event; +import de.latlon.core.validator.events.v1.XPlanboxPublicV1Event.EventType; +import de.latlon.xplan.commons.archive.SemanticValidableXPlanArchive; +import de.latlon.xplan.validator.semantic.configuration.metadata.RulesMetadata; +import de.latlon.xplan.validator.semantic.profile.SemanticProfileValidator; +import de.latlon.xplan.validator.semantic.profile.SemanticProfiles; +import de.latlon.xplan.validator.semantic.report.SemanticValidatorResult; +import de.latlon.xplan.validator.web.shared.ValidationSettings; +import de.latlon.xplan.validator.web.shared.ValidationType; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.ReportType; + +/** + * @author <a href="mailto:guillemot@lat-lon.de">Marc Guillemot</a> + * @since 8.0 + */ +@SpringBootTest(classes = { SpringBootApp.class }) +class PlanValidatorTest { + + @TestConfiguration + static class TestConfig { + + @Primary + @Bean + public SemanticProfiles fakeSemanticProfiles() { + RulesMetadata profile1 = new RulesMetadata("id1", "test1", "description1", "0.1", "unbekannt"); + RulesMetadata profile2 = new RulesMetadata("id2", "test2", "description2", "0.2", "lokal"); + return new SemanticProfiles().add(profile1, createValidator(profile1)) + .add(profile2, createValidator(profile2)); + } + + private static SemanticProfileValidator createValidator(RulesMetadata profile) { + SemanticProfileValidator semanticProfileValidator = mock(SemanticProfileValidator.class); + when(semanticProfileValidator.getId()).thenReturn(profile.getId()); + SemanticValidatorResult result = mock(SemanticValidatorResult.class); + when(result.getRulesMetadata()).thenReturn(profile); + when(semanticProfileValidator.validateSemantic(any(SemanticValidableXPlanArchive.class), anyList())) + .thenReturn(result); + return semanticProfileValidator; + } + + } + + @MockBean + EventSender eventSender; + + @Captor + ArgumentCaptor<XPlanboxPublicV1Event> publicEventCaptor; + + @Autowired + PlanValidator validator; + + @Autowired + ValidationExecutionStorage validationExecutionStorage; + + @Autowired + ObjectMapper jsonMapper; + + @Autowired + private SemanticProfiles semanticProfiles; + + @Test + // from old + // ValidateApiTest.verifyThat_Response_ContainsCorrectStatusCodeAndMediaType + void validateGmlJsonReport() throws Exception { + + Path pathToPlan = Paths.get(getClass().getResource("/xplan.gml").toURI()); + String uuid = validationExecutionStorage.addPlanToValidate(pathToPlan); + + List<ValidationType> validationTypes = List.of(SYNTACTIC, SEMANTIC, GEOMETRIC); + ValidationSettings settings = new ValidationSettings("edfd613e-b85d-4ea6-9b97-bb33712b1ba6", validationTypes, + null); + ValidationRequestedEvent event = new ValidationRequestedEvent(uuid, settings, + null /* filename */, MediaType.JSON, OriginFile.GML); + + validator.validate(event); + + verifyExpectedReport(uuid, ReportType.JSON, "report1.expected.json"); + + XPlanboxPublicV1Event expectedStartEvent = new XPlanboxPublicV1Event(EventType.VALIDATION_START, uuid, + "validation started"); + XPlanboxPublicV1Event expectedFinishedEvent = new XPlanboxPublicV1Event(EventType.VALIDATION_FINISHED, uuid, + "validation finished"); + Mockito.verify(eventSender, times(2)).sendPublicEvent(publicEventCaptor.capture()); + assertThat(publicEventCaptor.getAllValues()).containsExactly(expectedStartEvent, expectedFinishedEvent); + } + + @Test + // from old + // ValidateApiTest.verifyThat_validationXZipCompressedWithProfile_Response_ContainsJsonEncoding() + void valideZipWithOneProfile() throws Exception { + Path pathToPlan = Paths.get(getClass().getResource("/bplan_valid_41.zip").toURI()); + String uuid = validationExecutionStorage.addPlanToValidate(pathToPlan); + + List<ValidationType> validationTypes = List.of(SYNTACTIC, SEMANTIC, GEOMETRIC); + List<String> profiles = List.of(semanticProfiles.getProfileValidators().get(0).getId()); + ValidationSettings settings = new ValidationSettings("8dc3163c-a361-49b8-9c53-3733f7d61274", validationTypes, + profiles, null /* extendedOptions */); + ValidationRequestedEvent event = new ValidationRequestedEvent(uuid, settings, null, MediaType.JSON, + OriginFile.ZIP); + + validator.validate(event); + + verifyExpectedReport(uuid, ReportType.JSON, "report6.expected.json"); + } + + @Test + // from old + // ValidateApiTest.verifyThat_validationXZipCompressedWithProfiles_Response_ContainsJsonEncoding() + void valideZipWithTwoProfiles() throws Exception { + Path pathToPlan = Paths.get(getClass().getResource("/bplan_valid_41.zip").toURI()); + String uuid = validationExecutionStorage.addPlanToValidate(pathToPlan); + + List<ValidationType> validationTypes = List.of(SYNTACTIC, SEMANTIC, GEOMETRIC); + List<String> profiles = List.of(semanticProfiles.getProfileValidators().get(0).getId()); + ValidationSettings settings = new ValidationSettings("6463c72f-4a7d-40d0-a129-3de95fdd0eb8", validationTypes, + profiles, null /* extendedOptions */); + ValidationRequestedEvent event = new ValidationRequestedEvent(uuid, settings, null, MediaType.JSON, + OriginFile.ZIP); + + validator.validate(event); + + verifyExpectedReport(uuid, ReportType.JSON, "report7.expected.json"); + } + + private void verifyExpectedReport(String uuid, ReportType reportType, String expectedReportFile) throws Exception { + String jsonReport = new String(validationExecutionStorage.retrieveReport(uuid, reportType), + StandardCharsets.UTF_8); + + String expectedReport = Files.readString(Paths.get(getClass().getResource(expectedReportFile).toURI())); + assertThat(sanitizeDates(jsonReport)).isEqualTo(sanitizeDates(expectedReport)); + } + + private String sanitizeDates(String s) throws Exception { + s = s.replaceAll("\"date\":\"[^\"]+\"", "\"date\":\"-fixed-for-comparison-\""); + JSONObject json = new JSONObject(s); + return json.toString(4); + } + +} diff --git a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ValidationHandlerTest.java b/xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/handler/ValidationHandlerTest.java similarity index 91% rename from xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ValidationHandlerTest.java rename to xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/handler/ValidationHandlerTest.java index 3132d2cc73..5287428ec0 100644 --- a/xplan-validator/xplan-validator-api/src/test/java/de/latlon/xplanbox/api/validator/handler/ValidationHandlerTest.java +++ b/xplan-validator/xplan-validator-executor/src/test/java/de/latlon/xplanbox/validator/executor/handler/ValidationHandlerTest.java @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ -package de.latlon.xplanbox.api.validator.handler; +package de.latlon.xplanbox.validator.executor.handler; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -36,32 +36,31 @@ import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - +import de.latlon.core.validator.events.EventSender; import de.latlon.xplan.commons.archive.XPlanArchive; import de.latlon.xplan.validator.ValidatorException; import de.latlon.xplan.validator.report.ValidatorReport; import de.latlon.xplan.validator.web.shared.ValidationSettings; import de.latlon.xplanbox.api.commons.exception.InvalidXPlanGmlOrArchive; -import de.latlon.xplanbox.api.validator.config.ApplicationContext; +import de.latlon.xplanbox.validator.executor.SpringBootApp; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; /** * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = { ApplicationContext.class }) -@ActiveProfiles("test") +@SpringBootTest(classes = { SpringBootApp.class }) public class ValidationHandlerTest { + @MockBean + EventSender eventSender; + @TempDir public static Path tempFolder; diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/bplan_valid_41.zip b/xplan-validator/xplan-validator-executor/src/test/resources/bplan_valid_41.zip new file mode 100644 index 0000000000000000000000000000000000000000..3b382162672e420c133b227c2c5b8c6ebad7d6aa GIT binary patch literal 1652 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 literal 0 HcmV?d00001 diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/config/application.properties b/xplan-validator/xplan-validator-executor/src/test/resources/config/application.properties new file mode 100644 index 0000000000..bf0a025176 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/resources/config/application.properties @@ -0,0 +1,26 @@ +### +# #%L +# xplan-api-validator - Modul zur Gruppierung der REST-API +# %% +# Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# #L% +### + +# properties overwritten for unit/integration tests + +spring.profiles.active=test + +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report1.expected.json b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report1.expected.json new file mode 100644 index 0000000000..98e35f8add --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report1.expected.json @@ -0,0 +1,1697 @@ +{ + "date": "-fixed-for-comparison-", + "valid": false, + "externalReferences": [], + "externalReferencesResult": [], + "rulesMetadata": { + "source": "https:\/\/gitlab.opencode.de\/xleitstelle\/xplanung\/validierungsregeln\/standard\/-\/tree\/v1.1.9", + "version": "1.1.9" + }, + "filename": null, + "validationResult": { + "syntaktisch": { + "valid": true, + "messages": [] + }, + "semantisch": { + "valid": true, + "rules": [ + { + "isValid": true, + "name": "2.1.2.1", + "warnedFeatures": [], + "message": "Verwendung vorgegebenen URNs für das uom-Attribut von GML-MeasureType", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "2.1.3.1", + "warnedFeatures": [], + "message": "Angabe eines Standard CRS", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "2.2.1.1", + "warnedFeatures": [], + "message": "Flächenschlussbedingung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.1.1", + "warnedFeatures": [], + "message": "Relationen auf Text-Abschnitte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.1.2", + "warnedFeatures": [], + "message": "Relationen auf Begründungs-Abschnitte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.2.1", + "warnedFeatures": [], + "message": "Relation auf Präsentationsobjekte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.2.2", + "warnedFeatures": [], + "message": "Relation auf Fachobjekte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.2.3", + "warnedFeatures": [], + "message": "Relation auf Basis-Rasterplan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.3.1", + "warnedFeatures": [], + "message": "Relationen auf Text-Abschnitte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.3.2", + "warnedFeatures": [], + "message": "Relationen auf Begründungs-Abschnitte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.3.3", + "warnedFeatures": [], + "message": "Rückwärts-Referenzen auf Plan-Bereiche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.1.3.4", + "warnedFeatures": [], + "message": "Rückwärts-Referenzen auf Präsentationsobjekte", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.1.1", + "warnedFeatures": [], + "message": "Spezifikation des Textinhalts", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.2.1", + "warnedFeatures": [], + "message": "Spezifikation des Textinhalts", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.3.1", + "warnedFeatures": [], + "message": "Konsistenz der verschiedenen Höhenangaben", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.3.2", + "warnedFeatures": [], + "message": "Verwendung von Höhenangaben, die sich auf eine auf Bezugshöhe beziehen, die auf Planebene definiert ist", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.3.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute hoehenbezug und abweichenderHoehenbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.4.1", + "warnedFeatures": [], + "message": "Verweis auf Dokumente.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.2.5.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute ags (Amtlicher Gemeindeschlüssel) und rs (Regionalschlüssel).", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.3.1.1", + "warnedFeatures": [], + "message": "Spezifikation des Fachobjekt-Attributs bei nicht-freien Präsentationsobjekten", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.3.1.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute art und index", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.3.1.3", + "warnedFeatures": [], + "message": "Rückwärts-Referenzen auf Plan-Bereiche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.3.2.1", + "warnedFeatures": [], + "message": "Einschränkung des Raumbezugs auf Punktgeometrie.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.3.3.1", + "warnedFeatures": [], + "message": "Einschränkung des Raumbezugs auf Liniengeometrie.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "3.3.4.1", + "warnedFeatures": [], + "message": "Einschränkung des Raumbezugs auf Flächengeometrie.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.1.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation bereich", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.2.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation inhaltBPlan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.2.2", + "warnedFeatures": [], + "message": "Einschränkung der Relation rasterAenderung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.2.3", + "warnedFeatures": [], + "message": "Einschränkung der Relation gehoertZuPlan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchFlaeche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.2", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchMassnahme", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.3", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchSPEMassnahme", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.4", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchSPEFlaeche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.5", + "warnedFeatures": [], + "message": "Rückwärts-Referenzen auf Plan-Bereiche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.6", + "warnedFeatures": [], + "message": "BPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.3.7", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchABE", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.4.1", + "warnedFeatures": [], + "message": "Nur Flächenobjekte der Basisebene gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.4.2", + "warnedFeatures": [], + "message": "Einschränkung auf Flächengeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.5.1", + "warnedFeatures": [], + "message": "Nur Flächenobjekte der Basisebene gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.5.2", + "warnedFeatures": [], + "message": "Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.6.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekte gehören nie zum Flächenschluss.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.7.1", + "warnedFeatures": [], + "message": "Flächenschlussobjekte auf Ebene 0 gehören immer zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.8.1", + "warnedFeatures": [], + "message": "Einschränkung auf Liniengeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.1.9.1", + "warnedFeatures": [], + "message": "Einschränkung auf Punktgeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GFZ", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.2", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GF", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.3", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GFZ und GF", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.4", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur BMZ", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.5", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur BM", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.6", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur BMZ und BM", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.7", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GRZ", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.8", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GR", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.9", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GRZ und GR", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.10", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zu Z", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.2.11", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zu ZU", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.3.1", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur Dachneigung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.3.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute dachform und detaillierteDachform", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.1.1", + "warnedFeatures": [], + "message": "Relation abweichungText", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.1.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.1.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute besondereArtDerBaulNutzung und sondernutzung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.1.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute bauweise und abweichendeBauweise", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.1.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sondernutzung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.2.1", + "warnedFeatures": [], + "message": "Relation baugrenze", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.2.2", + "warnedFeatures": [], + "message": "Relation baulinie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.10.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute typ und sonstTyp", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.13.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.13.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.13.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.14.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.14.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.14.3", + "warnedFeatures": [], + "message": "Einschränkung der Relation eigentümer", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.14.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.5.15.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.6.3.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.1.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.1.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.1.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4)", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.1.6", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.2.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.2.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.7.2.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.1.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.2.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.2.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.2.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.2.4", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.3.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.3.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.3.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.3.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.3.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.8.3.6", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute massnahme und weitereMassnahme1", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.2.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.2.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.3.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute massnahme und weitereMassnahme1", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.3.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.3.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.5.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute massnahme und weitereMassnahme1", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.5.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.5.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.5.4", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.9.6.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.10.2.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.6", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.11.1.7", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.12.1.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation begrenzungslinie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.12.2.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation begrenzungslinie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.12.2.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.12.3.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.13.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.13.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.2.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.3.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute startWinkel und endWinkel", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.3.2", + "warnedFeatures": [], + "message": "Kein flächenhafter Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.4.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.6.1", + "warnedFeatures": [], + "message": "Notwendige Spezifikation einer Textlichen Darstellung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.7.1", + "warnedFeatures": [], + "message": "Verwendung der Relation hoehenangabe", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "4.14.7.2", + "warnedFeatures": [], + "message": "Kein flächenhafter Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.1.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation bereich", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.1.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute planArt und sonstPlanArt", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.2.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation inhaltFPlan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.2.2", + "warnedFeatures": [], + "message": "Einschränkung der Relation rasterAenderung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.2.3", + "warnedFeatures": [], + "message": "Einschränkung der Relation gehoertZuPlan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.3.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchFlaeche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.3.2", + "warnedFeatures": [], + "message": "Einschränkung der Relation wirdAusgeglichenDurchSPE", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.3.3", + "warnedFeatures": [], + "message": "Rückwärts-Referenzen auf Plan-Bereiche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.3.4", + "warnedFeatures": [], + "message": "FPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.4.1", + "warnedFeatures": [], + "message": "Nur Flächenobjekte der Basisebene gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.4.2", + "warnedFeatures": [], + "message": "Einschränkung auf Flächengeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.5.1", + "warnedFeatures": [], + "message": "Nur Flächenobjekte der Basisebene gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.5.2", + "warnedFeatures": [], + "message": "Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.6.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekte gehören nie zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.7.1", + "warnedFeatures": [], + "message": "Flächenschlussobjekte der Ebene 0 gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.8.1", + "warnedFeatures": [], + "message": "Einschränkung auf Liniengeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.1.9.1", + "warnedFeatures": [], + "message": "Einschränkung auf Punktgeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.3.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.3.1.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute besondereArtDerBaulNutzung und sonderNutzung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.3.1.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sonderNutzung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.3.1.4", + "warnedFeatures": [], + "message": "Konsistenz der Angaben zur GFZ", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.6", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.7", + "warnedFeatures": [], + "message": "Einschränkung des Raumbezugs", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.1.8", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.2.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.2.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation einer einzigen detaillierter Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.2.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.2.4", + "warnedFeatures": [], + "message": "Einschränkung des Raumbezugs", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.4.2.5", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.1.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.2.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.2.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.2.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.6", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.5.3.7", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.6.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.6.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.6.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.6.2.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.3", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.5", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2, 3)", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.6", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.7.1.7", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.8.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.8.1.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.8.1.3", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.9.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.9.1.2", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.9.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.9.2.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.1.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.2.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.2.2", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.2.3", + "warnedFeatures": [], + "message": "Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.2.4", + "warnedFeatures": [], + "message": "Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2)", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.3.1", + "warnedFeatures": [], + "message": "Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.4.1", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "5.10.6.1", + "warnedFeatures": [], + "message": "Notwendige Spezifikation einer Textlichen Darstellung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.1.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation bereich", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.2.1", + "warnedFeatures": [], + "message": "Einschränkung der Relation inhaltSoPlan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.2.2", + "warnedFeatures": [], + "message": "Einschränkung der Relation rasterAenderung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.2.3", + "warnedFeatures": [], + "message": "Einschränkung der Relation gehoertZuPlan", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.3.1", + "warnedFeatures": [], + "message": "Rückwärts-Referenzen auf Plan-Bereiche", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.3.2", + "warnedFeatures": [], + "message": "SOPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.4.1", + "warnedFeatures": [], + "message": "Nur Flächenobjekte der Basisebene gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.4.2", + "warnedFeatures": [], + "message": "Einschränkung auf Flächengeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.5.1", + "warnedFeatures": [], + "message": "Nur Flächenobjekte der Basisebene gehören zum Flächenschluss", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.5.2", + "warnedFeatures": [], + "message": "Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.6.1", + "warnedFeatures": [], + "message": "Einschränkung auf Liniengeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.1.7.1", + "warnedFeatures": [], + "message": "Einschränkung auf Punktgeometrie", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.1.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.2.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.3.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.4.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.5.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und besondereArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.5.2", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung, besondereArtDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.5.3", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.6.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.7.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.7.2", + "warnedFeatures": [], + "message": "Flächenschlussobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.2.8.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.3.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.3.1.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.3.2.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.3.2.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.3.3.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.3.3.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.4.1.1", + "warnedFeatures": [], + "message": "Konsistenz der Attribute gebietsArt und sonstGebietsArt", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.4.1.2", + "warnedFeatures": [], + "message": "Überlagerungsobjekt bei flächenhaftem Raumbezug", + "erroredFeatures": [] + }, + { + "isValid": true, + "name": "6.5.1.1", + "warnedFeatures": [], + "message": "Konsistenz von typ und sonstTyp", + "erroredFeatures": [] + } + ] + }, + "geometrisch": { + "valid": false, + "warnings": [], + "errors": [ + "XPlanAuszug (Zeile 1, Spalte 1): 2.2.2.1: äußerer Ring verwendet falsche Laufrichtung (CW)." + ] + } + }, + "bbox": { + "minY": 52.33234200586314, + "minX": 7.373668092967802, + "crs": "EPSG:4326", + "maxY": 52.33376312995529, + "maxX": 7.377600099759094 + }, + "name": "edfd613e-b85d-4ea6-9b97-bb33712b1ba6", + "version": "XPLAN_41", + "documentSummary": [ + { + "name": "Nr. 108 Holstener Weg", + "type": "BP_Plan" + } + ], + "status": "Die Validierung wurde ausgeführt." +} \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report2.expected.xml b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report2.expected.xml new file mode 100644 index 0000000000..ec4936d5d6 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report2.expected.xml @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?><validationReport><version>XPLAN_41</version><name>996c971b-d70d-439e-add8-3a17681022c8</name><documentSummary><name>Nr. 108 Holstener Weg</name><type>BP_Plan</type></documentSummary><bbox><minX>7.373668092967802</minX><minY>52.33234200586314</minY><maxX>7.377600099759094</maxX><maxY>52.33376312995529</maxY><crs>EPSG:4326</crs></bbox><date>2024-07-26T10:16:16.136+02:00</date><valid>false</valid><status>Die Validierung wurde ausgeführt.</status><rulesMetadata><version>1.1.9</version><source>https://gitlab.opencode.de/xleitstelle/xplanung/validierungsregeln/standard/-/tree/v1.1.9</source></rulesMetadata><validationResult><geometrisch><valid>false</valid><errors>XPlanAuszug (Zeile 1, Spalte 1): 2.2.2.1: äußerer Ring verwendet falsche Laufrichtung (CW).</errors></geometrisch><semantisch><valid>true</valid><rules><isValid>true</isValid><message>Verwendung vorgegebenen URNs für das uom-Attribut von GML-MeasureType</message><name>2.1.2.1</name></rules><rules><isValid>true</isValid><message>Angabe eines Standard CRS</message><name>2.1.3.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussbedingung</message><name>2.2.1.1</name></rules><rules><isValid>true</isValid><message>Relationen auf Text-Abschnitte</message><name>3.1.1.1</name></rules><rules><isValid>true</isValid><message>Relationen auf Begründungs-Abschnitte</message><name>3.1.1.2</name></rules><rules><isValid>true</isValid><message>Relation auf Präsentationsobjekte</message><name>3.1.2.1</name></rules><rules><isValid>true</isValid><message>Relation auf Fachobjekte</message><name>3.1.2.2</name></rules><rules><isValid>true</isValid><message>Relation auf Basis-Rasterplan</message><name>3.1.2.3</name></rules><rules><isValid>true</isValid><message>Relationen auf Text-Abschnitte</message><name>3.1.3.1</name></rules><rules><isValid>true</isValid><message>Relationen auf Begründungs-Abschnitte</message><name>3.1.3.2</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>3.1.3.3</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Präsentationsobjekte</message><name>3.1.3.4</name></rules><rules><isValid>true</isValid><message>Spezifikation des Textinhalts</message><name>3.2.1.1</name></rules><rules><isValid>true</isValid><message>Spezifikation des Textinhalts</message><name>3.2.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der verschiedenen Höhenangaben</message><name>3.2.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung von Höhenangaben, die sich auf eine auf Bezugshöhe beziehen, die auf Planebene definiert ist</message><name>3.2.3.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute hoehenbezug und abweichenderHoehenbezug</message><name>3.2.3.3</name></rules><rules><isValid>true</isValid><message>Verweis auf Dokumente.</message><name>3.2.4.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute ags (Amtlicher Gemeindeschlüssel) und rs (Regionalschlüssel).</message><name>3.2.5.1</name></rules><rules><isValid>true</isValid><message>Spezifikation des Fachobjekt-Attributs bei nicht-freien Präsentationsobjekten</message><name>3.3.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute art und index</message><name>3.3.1.2</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>3.3.1.3</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs auf Punktgeometrie.</message><name>3.3.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs auf Liniengeometrie.</message><name>3.3.3.1</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs auf Flächengeometrie.</message><name>3.3.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation bereich</message><name>4.1.1.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation inhaltBPlan</message><name>4.1.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation rasterAenderung</message><name>4.1.2.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation gehoertZuPlan</message><name>4.1.2.3</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchFlaeche</message><name>4.1.3.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchMassnahme</message><name>4.1.3.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchSPEMassnahme</message><name>4.1.3.3</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchSPEFlaeche</message><name>4.1.3.4</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>4.1.3.5</name></rules><rules><isValid>true</isValid><message>BPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.</message><name>4.1.3.6</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchABE</message><name>4.1.3.7</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>4.1.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Flächengeometrie</message><name>4.1.4.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>4.1.5.1</name></rules><rules><isValid>true</isValid><message>Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug</message><name>4.1.5.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekte gehören nie zum Flächenschluss.</message><name>4.1.6.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekte auf Ebene 0 gehören immer zum Flächenschluss</message><name>4.1.7.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Liniengeometrie</message><name>4.1.8.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Punktgeometrie</message><name>4.1.9.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GFZ</message><name>4.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GF</message><name>4.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GFZ und GF</message><name>4.2.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur BMZ</message><name>4.2.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur BM</message><name>4.2.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur BMZ und BM</message><name>4.2.6</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GRZ</message><name>4.2.7</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GR</message><name>4.2.8</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GRZ und GR</message><name>4.2.9</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zu Z</message><name>4.2.10</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zu ZU</message><name>4.2.11</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur Dachneigung</message><name>4.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute dachform und detaillierteDachform</message><name>4.3.2</name></rules><rules><isValid>true</isValid><message>Relation abweichungText</message><name>4.5.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung</message><name>4.5.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute besondereArtDerBaulNutzung und sondernutzung</message><name>4.5.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute bauweise und abweichendeBauweise</message><name>4.5.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sondernutzung</message><name>4.5.1.5</name></rules><rules><isValid>true</isValid><message>Relation baugrenze</message><name>4.5.2.1</name></rules><rules><isValid>true</isValid><message>Relation baulinie</message><name>4.5.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute typ und sonstTyp</message><name>4.5.10.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.5.13.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.5.13.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.5.13.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.5.14.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.5.14.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation eigentümer</message><name>4.5.14.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.5.14.4</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.5.15.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.6.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.7.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>4.7.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.7.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>4.7.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4)</message><name>4.7.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.7.1.6</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.7.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.7.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.7.2.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.8.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.8.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.8.1.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.8.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.8.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.8.2.3</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>4.8.2.4</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.8.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>4.8.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.8.3.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>4.8.3.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi</message><name>4.8.3.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.8.3.6</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme und weitereMassnahme1</message><name>4.9.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1</message><name>4.9.2.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>4.9.2.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme und weitereMassnahme1</message><name>4.9.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1</message><name>4.9.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>4.9.3.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme und weitereMassnahme1</message><name>4.9.5.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1</message><name>4.9.5.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>4.9.5.3</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.9.5.4</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.9.6.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.10.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.11.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>4.11.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>4.11.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>4.11.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi</message><name>4.11.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.11.1.6</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>4.11.1.7</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation begrenzungslinie</message><name>4.12.1.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation begrenzungslinie</message><name>4.12.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>4.12.2.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.12.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.13.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>4.13.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.14.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>4.14.2.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute startWinkel und endWinkel</message><name>4.14.3.1</name></rules><rules><isValid>true</isValid><message>Kein flächenhafter Raumbezug</message><name>4.14.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>4.14.4.1</name></rules><rules><isValid>true</isValid><message>Notwendige Spezifikation einer Textlichen Darstellung</message><name>4.14.6.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Relation hoehenangabe</message><name>4.14.7.1</name></rules><rules><isValid>true</isValid><message>Kein flächenhafter Raumbezug</message><name>4.14.7.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation bereich</message><name>5.1.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute planArt und sonstPlanArt</message><name>5.1.1.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation inhaltFPlan</message><name>5.1.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation rasterAenderung</message><name>5.1.2.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation gehoertZuPlan</message><name>5.1.2.3</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchFlaeche</message><name>5.1.3.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation wirdAusgeglichenDurchSPE</message><name>5.1.3.2</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>5.1.3.3</name></rules><rules><isValid>true</isValid><message>FPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.</message><name>5.1.3.4</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>5.1.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Flächengeometrie</message><name>5.1.4.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>5.1.5.1</name></rules><rules><isValid>true</isValid><message>Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug</message><name>5.1.5.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekte gehören nie zum Flächenschluss</message><name>5.1.6.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekte der Ebene 0 gehören zum Flächenschluss</message><name>5.1.7.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Liniengeometrie</message><name>5.1.8.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Punktgeometrie</message><name>5.1.9.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung</message><name>5.3.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute besondereArtDerBaulNutzung und sonderNutzung</message><name>5.3.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sonderNutzung</message><name>5.3.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Angaben zur GFZ</message><name>5.3.1.4</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.4.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.4.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.4.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.4.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)</message><name>5.4.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.4.1.6</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs</message><name>5.4.1.7</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.4.1.8</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.4.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation einer einzigen detaillierter Zweckbestimmung</message><name>5.4.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.4.2.3</name></rules><rules><isValid>true</isValid><message>Einschränkung des Raumbezugs</message><name>5.4.2.4</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.4.2.5</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.5.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.5.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.5.1.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.5.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.5.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.5.2.3</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.5.3.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.5.3.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.5.3.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.5.3.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)</message><name>5.5.3.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.5.3.6</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.5.3.7</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2</message><name>5.6.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>5.6.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2</message><name>5.6.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen</message><name>5.6.2.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.7.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.7.1.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen</message><name>5.7.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.7.1.4</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2, 3)</message><name>5.7.1.5</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.7.1.6</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.7.1.7</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.8.1.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung</message><name>5.8.1.2</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>5.8.1.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.9.1.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>5.9.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung</message><name>5.9.2.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>5.9.2.2</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.10.1.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.10.2.1</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen</message><name>5.10.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung</message><name>5.10.2.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2)</message><name>5.10.2.4</name></rules><rules><isValid>true</isValid><message>Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen</message><name>5.10.3.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>5.10.4.1</name></rules><rules><isValid>true</isValid><message>Notwendige Spezifikation einer Textlichen Darstellung</message><name>5.10.6.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation bereich</message><name>6.1.1.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation inhaltSoPlan</message><name>6.1.2.1</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation rasterAenderung</message><name>6.1.2.2</name></rules><rules><isValid>true</isValid><message>Einschränkung der Relation gehoertZuPlan</message><name>6.1.2.3</name></rules><rules><isValid>true</isValid><message>Rückwärts-Referenzen auf Plan-Bereiche</message><name>6.1.3.1</name></rules><rules><isValid>true</isValid><message>SOPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.</message><name>6.1.3.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>6.1.4.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Flächengeometrie</message><name>6.1.4.2</name></rules><rules><isValid>true</isValid><message>Nur Flächenobjekte der Basisebene gehören zum Flächenschluss</message><name>6.1.5.1</name></rules><rules><isValid>true</isValid><message>Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug</message><name>6.1.5.2</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Liniengeometrie</message><name>6.1.6.1</name></rules><rules><isValid>true</isValid><message>Einschränkung auf Punktgeometrie</message><name>6.1.7.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.2.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.2.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.2.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.3.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.4.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und besondereArtDerFestlegung</message><name>6.2.5.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung, besondereArtDerFestlegung und detailArtDerFestlegung</message><name>6.2.5.2</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>6.2.5.3</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.6.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.7.1</name></rules><rules><isValid>true</isValid><message>Flächenschlussobjekt bei flächenhaftem Raumbezug</message><name>6.2.7.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.2.8.1</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.3.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.3.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.3.2.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.3.2.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung</message><name>6.3.3.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.3.3.2</name></rules><rules><isValid>true</isValid><message>Konsistenz der Attribute gebietsArt und sonstGebietsArt</message><name>6.4.1.1</name></rules><rules><isValid>true</isValid><message>Überlagerungsobjekt bei flächenhaftem Raumbezug</message><name>6.4.1.2</name></rules><rules><isValid>true</isValid><message>Konsistenz von typ und sonstTyp</message><name>6.5.1.1</name></rules></semantisch><syntaktisch><valid>true</valid></syntaktisch></validationResult></validationReport> \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report6.expected.json b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report6.expected.json new file mode 100644 index 0000000000..847c3dbb47 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report6.expected.json @@ -0,0 +1 @@ +{"documentSummary":[{"name":"Nr. 108 Holstener Weg","type":"BP_Plan"}],"version":"XPLAN_41","filename":null,"name":"8dc3163c-a361-49b8-9c53-3733f7d61274","bbox":{"minX":7.373668092967802,"minY":52.33234200586314,"maxX":7.377600099759094,"maxY":52.33376312995529,"crs":"EPSG:4326"},"date":"2024-07-26T10:16:30.191+0200","valid":false,"status":"Die Validierung wurde ausgeführt.","externalReferences":[],"externalReferencesResult":[],"rulesMetadata":{"version":"1.1.9","source":"https://gitlab.opencode.de/xleitstelle/xplanung/validierungsregeln/standard/-/tree/v1.1.9"},"validationResult":{"semantisch":{"valid":true,"rules":[{"name":"2.1.2.1","isValid":true,"message":"Verwendung vorgegebenen URNs für das uom-Attribut von GML-MeasureType","warnedFeatures":[],"erroredFeatures":[]},{"name":"2.1.3.1","isValid":true,"message":"Angabe eines Standard CRS","warnedFeatures":[],"erroredFeatures":[]},{"name":"2.2.1.1","isValid":true,"message":"Flächenschlussbedingung","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.1.1","isValid":true,"message":"Relationen auf Text-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.1.2","isValid":true,"message":"Relationen auf Begründungs-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.2.1","isValid":true,"message":"Relation auf Präsentationsobjekte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.2.2","isValid":true,"message":"Relation auf Fachobjekte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.2.3","isValid":true,"message":"Relation auf Basis-Rasterplan","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.1","isValid":true,"message":"Relationen auf Text-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.2","isValid":true,"message":"Relationen auf Begründungs-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.3","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.4","isValid":true,"message":"Rückwärts-Referenzen auf Präsentationsobjekte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.1.1","isValid":true,"message":"Spezifikation des Textinhalts","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.2.1","isValid":true,"message":"Spezifikation des Textinhalts","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.3.1","isValid":true,"message":"Konsistenz der verschiedenen Höhenangaben","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.3.2","isValid":true,"message":"Verwendung von Höhenangaben, die sich auf eine auf Bezugshöhe beziehen, die auf Planebene definiert ist","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.3.3","isValid":true,"message":"Konsistenz der Attribute hoehenbezug und abweichenderHoehenbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.4.1","isValid":true,"message":"Verweis auf Dokumente.","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.5.1","isValid":true,"message":"Konsistenz der Attribute ags (Amtlicher Gemeindeschlüssel) und rs (Regionalschlüssel).","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.1.1","isValid":true,"message":"Spezifikation des Fachobjekt-Attributs bei nicht-freien Präsentationsobjekten","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.1.2","isValid":true,"message":"Konsistenz der Attribute art und index","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.1.3","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.2.1","isValid":true,"message":"Einschränkung des Raumbezugs auf Punktgeometrie.","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.3.1","isValid":true,"message":"Einschränkung des Raumbezugs auf Liniengeometrie.","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.4.1","isValid":true,"message":"Einschränkung des Raumbezugs auf Flächengeometrie.","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.1.1","isValid":true,"message":"Einschränkung der Relation bereich","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.2.1","isValid":true,"message":"Einschränkung der Relation inhaltBPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.2.2","isValid":true,"message":"Einschränkung der Relation rasterAenderung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.2.3","isValid":true,"message":"Einschränkung der Relation gehoertZuPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.1","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchFlaeche","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.2","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchMassnahme","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.3","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchSPEMassnahme","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.4","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchSPEFlaeche","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.5","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.6","isValid":true,"message":"BPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.7","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchABE","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.4.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.4.2","isValid":true,"message":"Einschränkung auf Flächengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.5.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.5.2","isValid":true,"message":"Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.6.1","isValid":true,"message":"Überlagerungsobjekte gehören nie zum Flächenschluss.","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.7.1","isValid":true,"message":"Flächenschlussobjekte auf Ebene 0 gehören immer zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.8.1","isValid":true,"message":"Einschränkung auf Liniengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.9.1","isValid":true,"message":"Einschränkung auf Punktgeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.1","isValid":true,"message":"Konsistenz der Angaben zur GFZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.2","isValid":true,"message":"Konsistenz der Angaben zur GF","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.3","isValid":true,"message":"Konsistenz der Angaben zur GFZ und GF","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.4","isValid":true,"message":"Konsistenz der Angaben zur BMZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.5","isValid":true,"message":"Konsistenz der Angaben zur BM","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.6","isValid":true,"message":"Konsistenz der Angaben zur BMZ und BM","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.7","isValid":true,"message":"Konsistenz der Angaben zur GRZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.8","isValid":true,"message":"Konsistenz der Angaben zur GR","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.9","isValid":true,"message":"Konsistenz der Angaben zur GRZ und GR","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.10","isValid":true,"message":"Konsistenz der Angaben zu Z","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.11","isValid":true,"message":"Konsistenz der Angaben zu ZU","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.3.1","isValid":true,"message":"Konsistenz der Angaben zur Dachneigung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.3.2","isValid":true,"message":"Konsistenz der Attribute dachform und detaillierteDachform","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.1","isValid":true,"message":"Relation abweichungText","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.2","isValid":true,"message":"Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.3","isValid":true,"message":"Konsistenz der Attribute besondereArtDerBaulNutzung und sondernutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.4","isValid":true,"message":"Konsistenz der Attribute bauweise und abweichendeBauweise","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.5","isValid":true,"message":"Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sondernutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.2.1","isValid":true,"message":"Relation baugrenze","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.2.2","isValid":true,"message":"Relation baulinie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.10.1","isValid":true,"message":"Konsistenz der Attribute typ und sonstTyp","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.13.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.13.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.13.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.3","isValid":true,"message":"Einschränkung der Relation eigentümer","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.4","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.15.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.6.3.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4)","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.1.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.4","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.2.1","isValid":true,"message":"Konsistenz der Attribute massnahme und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.2.2","isValid":true,"message":"Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.2.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.3.1","isValid":true,"message":"Konsistenz der Attribute massnahme und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.3.2","isValid":true,"message":"Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.3.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.1","isValid":true,"message":"Konsistenz der Attribute massnahme und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.2","isValid":true,"message":"Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.4","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.6.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.10.2.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.7","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.1.1","isValid":true,"message":"Einschränkung der Relation begrenzungslinie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.2.1","isValid":true,"message":"Einschränkung der Relation begrenzungslinie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.2.2","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.3.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.13.1.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.13.2.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.2.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.3.1","isValid":true,"message":"Konsistenz der Attribute startWinkel und endWinkel","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.3.2","isValid":true,"message":"Kein flächenhafter Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.4.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.6.1","isValid":true,"message":"Notwendige Spezifikation einer Textlichen Darstellung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.7.1","isValid":true,"message":"Verwendung der Relation hoehenangabe","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.7.2","isValid":true,"message":"Kein flächenhafter Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.1.1","isValid":true,"message":"Einschränkung der Relation bereich","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.1.2","isValid":true,"message":"Konsistenz der Attribute planArt und sonstPlanArt","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.2.1","isValid":true,"message":"Einschränkung der Relation inhaltFPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.2.2","isValid":true,"message":"Einschränkung der Relation rasterAenderung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.2.3","isValid":true,"message":"Einschränkung der Relation gehoertZuPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.1","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchFlaeche","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.2","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchSPE","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.3","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.4","isValid":true,"message":"FPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.4.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.4.2","isValid":true,"message":"Einschränkung auf Flächengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.5.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.5.2","isValid":true,"message":"Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.6.1","isValid":true,"message":"Überlagerungsobjekte gehören nie zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.7.1","isValid":true,"message":"Flächenschlussobjekte der Ebene 0 gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.8.1","isValid":true,"message":"Einschränkung auf Liniengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.9.1","isValid":true,"message":"Einschränkung auf Punktgeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.1","isValid":true,"message":"Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.2","isValid":true,"message":"Konsistenz der Attribute besondereArtDerBaulNutzung und sonderNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.3","isValid":true,"message":"Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sonderNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.4","isValid":true,"message":"Konsistenz der Angaben zur GFZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.7","isValid":true,"message":"Einschränkung des Raumbezugs","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.8","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation einer einzigen detaillierter Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.4","isValid":true,"message":"Einschränkung des Raumbezugs","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.5","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.1.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.7","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.1.1","isValid":true,"message":"Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.2.1","isValid":true,"message":"Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.5","isValid":true,"message":"Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2, 3)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.7","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.8.1.1","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.8.1.2","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.8.1.3","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.1.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.1.2","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.2.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.2.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.3","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.4","isValid":true,"message":"Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.3.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.4.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.6.1","isValid":true,"message":"Notwendige Spezifikation einer Textlichen Darstellung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.1.1","isValid":true,"message":"Einschränkung der Relation bereich","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.2.1","isValid":true,"message":"Einschränkung der Relation inhaltSoPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.2.2","isValid":true,"message":"Einschränkung der Relation rasterAenderung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.2.3","isValid":true,"message":"Einschränkung der Relation gehoertZuPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.3.1","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.3.2","isValid":true,"message":"SOPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.4.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.4.2","isValid":true,"message":"Einschränkung auf Flächengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.5.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.5.2","isValid":true,"message":"Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.6.1","isValid":true,"message":"Einschränkung auf Liniengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.7.1","isValid":true,"message":"Einschränkung auf Punktgeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.1.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.1.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.2.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.2.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.3.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.4.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.5.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und besondereArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.5.2","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung, besondereArtDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.5.3","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.6.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.7.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.7.2","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.8.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.1.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.1.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.2.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.2.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.3.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.3.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.4.1.1","isValid":true,"message":"Konsistenz der Attribute gebietsArt und sonstGebietsArt","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.4.1.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.5.1.1","isValid":true,"message":"Konsistenz von typ und sonstTyp","warnedFeatures":[],"erroredFeatures":[]}]},"geometrisch":{"valid":false,"errors":["XPlanAuszug (Zeile 1, Spalte 1): 2.2.2.1: äußerer Ring verwendet falsche Laufrichtung (CW)."],"warnings":[]},"syntaktisch":{"valid":true,"messages":[]},"profile":[{"name":"test1","description":"description1","ergebnis":{"valid":false,"rules":[]}}]}} \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report7.expected.json b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report7.expected.json new file mode 100644 index 0000000000..0e22c169b5 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/resources/de/latlon/xplanbox/validator/executor/report7.expected.json @@ -0,0 +1 @@ +{"documentSummary":[{"name":"Nr. 108 Holstener Weg","type":"BP_Plan"}],"version":"XPLAN_41","filename":null,"name":"6463c72f-4a7d-40d0-a129-3de95fdd0eb8","bbox":{"minX":7.373668092967802,"minY":52.33234200586314,"maxX":7.377600099759094,"maxY":52.33376312995529,"crs":"EPSG:4326"},"date":"2024-07-26T10:16:25.697+0200","valid":false,"status":"Die Validierung wurde ausgeführt.","externalReferences":[],"externalReferencesResult":[],"rulesMetadata":{"version":"1.1.9","source":"https://gitlab.opencode.de/xleitstelle/xplanung/validierungsregeln/standard/-/tree/v1.1.9"},"validationResult":{"semantisch":{"valid":true,"rules":[{"name":"2.1.2.1","isValid":true,"message":"Verwendung vorgegebenen URNs für das uom-Attribut von GML-MeasureType","warnedFeatures":[],"erroredFeatures":[]},{"name":"2.1.3.1","isValid":true,"message":"Angabe eines Standard CRS","warnedFeatures":[],"erroredFeatures":[]},{"name":"2.2.1.1","isValid":true,"message":"Flächenschlussbedingung","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.1.1","isValid":true,"message":"Relationen auf Text-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.1.2","isValid":true,"message":"Relationen auf Begründungs-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.2.1","isValid":true,"message":"Relation auf Präsentationsobjekte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.2.2","isValid":true,"message":"Relation auf Fachobjekte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.2.3","isValid":true,"message":"Relation auf Basis-Rasterplan","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.1","isValid":true,"message":"Relationen auf Text-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.2","isValid":true,"message":"Relationen auf Begründungs-Abschnitte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.3","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.1.3.4","isValid":true,"message":"Rückwärts-Referenzen auf Präsentationsobjekte","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.1.1","isValid":true,"message":"Spezifikation des Textinhalts","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.2.1","isValid":true,"message":"Spezifikation des Textinhalts","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.3.1","isValid":true,"message":"Konsistenz der verschiedenen Höhenangaben","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.3.2","isValid":true,"message":"Verwendung von Höhenangaben, die sich auf eine auf Bezugshöhe beziehen, die auf Planebene definiert ist","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.3.3","isValid":true,"message":"Konsistenz der Attribute hoehenbezug und abweichenderHoehenbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.4.1","isValid":true,"message":"Verweis auf Dokumente.","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.2.5.1","isValid":true,"message":"Konsistenz der Attribute ags (Amtlicher Gemeindeschlüssel) und rs (Regionalschlüssel).","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.1.1","isValid":true,"message":"Spezifikation des Fachobjekt-Attributs bei nicht-freien Präsentationsobjekten","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.1.2","isValid":true,"message":"Konsistenz der Attribute art und index","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.1.3","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.2.1","isValid":true,"message":"Einschränkung des Raumbezugs auf Punktgeometrie.","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.3.1","isValid":true,"message":"Einschränkung des Raumbezugs auf Liniengeometrie.","warnedFeatures":[],"erroredFeatures":[]},{"name":"3.3.4.1","isValid":true,"message":"Einschränkung des Raumbezugs auf Flächengeometrie.","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.1.1","isValid":true,"message":"Einschränkung der Relation bereich","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.2.1","isValid":true,"message":"Einschränkung der Relation inhaltBPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.2.2","isValid":true,"message":"Einschränkung der Relation rasterAenderung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.2.3","isValid":true,"message":"Einschränkung der Relation gehoertZuPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.1","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchFlaeche","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.2","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchMassnahme","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.3","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchSPEMassnahme","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.4","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchSPEFlaeche","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.5","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.6","isValid":true,"message":"BPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.3.7","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchABE","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.4.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.4.2","isValid":true,"message":"Einschränkung auf Flächengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.5.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.5.2","isValid":true,"message":"Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.6.1","isValid":true,"message":"Überlagerungsobjekte gehören nie zum Flächenschluss.","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.7.1","isValid":true,"message":"Flächenschlussobjekte auf Ebene 0 gehören immer zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.8.1","isValid":true,"message":"Einschränkung auf Liniengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.1.9.1","isValid":true,"message":"Einschränkung auf Punktgeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.1","isValid":true,"message":"Konsistenz der Angaben zur GFZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.2","isValid":true,"message":"Konsistenz der Angaben zur GF","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.3","isValid":true,"message":"Konsistenz der Angaben zur GFZ und GF","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.4","isValid":true,"message":"Konsistenz der Angaben zur BMZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.5","isValid":true,"message":"Konsistenz der Angaben zur BM","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.6","isValid":true,"message":"Konsistenz der Angaben zur BMZ und BM","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.7","isValid":true,"message":"Konsistenz der Angaben zur GRZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.8","isValid":true,"message":"Konsistenz der Angaben zur GR","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.9","isValid":true,"message":"Konsistenz der Angaben zur GRZ und GR","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.10","isValid":true,"message":"Konsistenz der Angaben zu Z","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.2.11","isValid":true,"message":"Konsistenz der Angaben zu ZU","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.3.1","isValid":true,"message":"Konsistenz der Angaben zur Dachneigung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.3.2","isValid":true,"message":"Konsistenz der Attribute dachform und detaillierteDachform","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.1","isValid":true,"message":"Relation abweichungText","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.2","isValid":true,"message":"Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.3","isValid":true,"message":"Konsistenz der Attribute besondereArtDerBaulNutzung und sondernutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.4","isValid":true,"message":"Konsistenz der Attribute bauweise und abweichendeBauweise","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.1.5","isValid":true,"message":"Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sondernutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.2.1","isValid":true,"message":"Relation baugrenze","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.2.2","isValid":true,"message":"Relation baulinie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.10.1","isValid":true,"message":"Konsistenz der Attribute typ und sonstTyp","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.13.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.13.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.13.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.3","isValid":true,"message":"Einschränkung der Relation eigentümer","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.14.4","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.5.15.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.6.3.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4)","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.7.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.1.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.2.4","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.8.3.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.2.1","isValid":true,"message":"Konsistenz der Attribute massnahme und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.2.2","isValid":true,"message":"Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.2.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.3.1","isValid":true,"message":"Konsistenz der Attribute massnahme und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.3.2","isValid":true,"message":"Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.3.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.1","isValid":true,"message":"Konsistenz der Attribute massnahme und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.2","isValid":true,"message":"Konsistenz der Attribute weitereMassnahme2und weitereMassnahme1","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.5.4","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.9.6.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.10.2.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.11.1.7","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.1.1","isValid":true,"message":"Einschränkung der Relation begrenzungslinie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.2.1","isValid":true,"message":"Einschränkung der Relation begrenzungslinie","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.2.2","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.12.3.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.13.1.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.13.2.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.2.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.3.1","isValid":true,"message":"Konsistenz der Attribute startWinkel und endWinkel","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.3.2","isValid":true,"message":"Kein flächenhafter Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.4.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.6.1","isValid":true,"message":"Notwendige Spezifikation einer Textlichen Darstellung","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.7.1","isValid":true,"message":"Verwendung der Relation hoehenangabe","warnedFeatures":[],"erroredFeatures":[]},{"name":"4.14.7.2","isValid":true,"message":"Kein flächenhafter Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.1.1","isValid":true,"message":"Einschränkung der Relation bereich","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.1.2","isValid":true,"message":"Konsistenz der Attribute planArt und sonstPlanArt","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.2.1","isValid":true,"message":"Einschränkung der Relation inhaltFPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.2.2","isValid":true,"message":"Einschränkung der Relation rasterAenderung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.2.3","isValid":true,"message":"Einschränkung der Relation gehoertZuPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.1","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchFlaeche","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.2","isValid":true,"message":"Einschränkung der Relation wirdAusgeglichenDurchSPE","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.3","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.3.4","isValid":true,"message":"FPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.4.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.4.2","isValid":true,"message":"Einschränkung auf Flächengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.5.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.5.2","isValid":true,"message":"Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.6.1","isValid":true,"message":"Überlagerungsobjekte gehören nie zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.7.1","isValid":true,"message":"Flächenschlussobjekte der Ebene 0 gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.8.1","isValid":true,"message":"Einschränkung auf Liniengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.1.9.1","isValid":true,"message":"Einschränkung auf Punktgeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.1","isValid":true,"message":"Konsistenz der Attribute allgArtDerBaulNutzung und besondereArtDerBaulNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.2","isValid":true,"message":"Konsistenz der Attribute besondereArtDerBaulNutzung und sonderNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.3","isValid":true,"message":"Konsistenz der Attribute detaillierteArtDer BaulNutzung, allgArtDerBaulNutzung, besondereArtDerBaulNutzung und sonderNutzung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.3.1.4","isValid":true,"message":"Konsistenz der Angaben zur GFZ","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.7","isValid":true,"message":"Einschränkung des Raumbezugs","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.1.8","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation einer einzigen detaillierter Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.4","isValid":true,"message":"Einschränkung des Raumbezugs","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.4.2.5","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.1.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.2.3","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.5","isValid":true,"message":"Konsistenz der Attribute weitereBesondZweckbestimmungi und weitereZweckbestimmungi (i = 1, 2, 3, 4, 5)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.5.3.7","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.1.1","isValid":true,"message":"Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.2.1","isValid":true,"message":"Konsistenz der Attribute massnahme, weitereMassnahme1 und weitereMassnahme2","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.6.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Maßnahmen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.3","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer detaillierter Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.4","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.5","isValid":true,"message":"Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2, 3)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.6","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.7.1.7","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.8.1.1","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.8.1.2","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung bzw. besondere Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.8.1.3","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.1.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.1.2","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.2.1","isValid":true,"message":"Konsistenz der Attribute für detaillierte Zweckbestimmung und Zweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.9.2.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.1.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.2","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer besonderer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.3","isValid":true,"message":"Konsistenz der Attribute zweckbestimmung und besondereZweckbestimmung","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.2.4","isValid":true,"message":"Konsistenz der Attribute weitereZweckbestimmungi und weitereBesondZweckbestimmungi (i = 1, 2)","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.3.1","isValid":true,"message":"Verwendung der Attribute zur Spezifikation mehrerer Zweckbestimmungen","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.4.1","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"5.10.6.1","isValid":true,"message":"Notwendige Spezifikation einer Textlichen Darstellung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.1.1","isValid":true,"message":"Einschränkung der Relation bereich","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.2.1","isValid":true,"message":"Einschränkung der Relation inhaltSoPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.2.2","isValid":true,"message":"Einschränkung der Relation rasterAenderung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.2.3","isValid":true,"message":"Einschränkung der Relation gehoertZuPlan","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.3.1","isValid":true,"message":"Rückwärts-Referenzen auf Plan-Bereiche","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.3.2","isValid":true,"message":"SOPlan-Inhalte dürfen nicht gleichzeitig als originärer Planinhalt und nachrichtliche Übernahme in den Plan integriert werden.","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.4.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.4.2","isValid":true,"message":"Einschränkung auf Flächengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.5.1","isValid":true,"message":"Nur Flächenobjekte der Basisebene gehören zum Flächenschluss","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.5.2","isValid":true,"message":"Angabe des Attributs flaechenschluss bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.6.1","isValid":true,"message":"Einschränkung auf Liniengeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.1.7.1","isValid":true,"message":"Einschränkung auf Punktgeometrie","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.1.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.1.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.2.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.2.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.3.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.4.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.5.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und besondereArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.5.2","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung, besondereArtDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.5.3","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.6.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.7.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.7.2","isValid":true,"message":"Flächenschlussobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.2.8.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.1.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.1.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.2.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.2.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.3.1","isValid":true,"message":"Konsistenz der Attribute artDerFestlegung und detailArtDerFestlegung","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.3.3.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.4.1.1","isValid":true,"message":"Konsistenz der Attribute gebietsArt und sonstGebietsArt","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.4.1.2","isValid":true,"message":"Überlagerungsobjekt bei flächenhaftem Raumbezug","warnedFeatures":[],"erroredFeatures":[]},{"name":"6.5.1.1","isValid":true,"message":"Konsistenz von typ und sonstTyp","warnedFeatures":[],"erroredFeatures":[]}]},"geometrisch":{"valid":false,"errors":["XPlanAuszug (Zeile 1, Spalte 1): 2.2.2.1: äußerer Ring verwendet falsche Laufrichtung (CW)."],"warnings":[]},"syntaktisch":{"valid":true,"messages":[]},"profile":[{"name":"test1","description":"description1","ergebnis":{"valid":false,"rules":[]}}]}} \ No newline at end of file diff --git a/xplan-validator/xplan-validator-executor/src/test/resources/xplan.gml b/xplan-validator/xplan-validator-executor/src/test/resources/xplan.gml new file mode 100644 index 0000000000..07d99bfb19 --- /dev/null +++ b/xplan-validator/xplan-validator-executor/src/test/resources/xplan.gml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- + #%L + xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + %% + Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> + +<xplan:XPlanAuszug xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xplan="http://www.xplanung.de/xplangml/4/1" + gml:id="GML_ad5adb8f-d05d-45d2-8197-29b26f4dd142"> + <gml:boundedBy> + <gml:Envelope srsName="EPSG:25832"> + <gml:lowerCorner>389187.29 5799248.293</gml:lowerCorner> + <gml:upperCorner>389451.908 5799400.487</gml:upperCorner> + </gml:Envelope> + </gml:boundedBy> + <gml:featureMember> + <xplan:BP_Bereich gml:id="GML_b3cca7aa-8908-4182-bf9f-1d96e623a1c7"> + <gml:boundedBy> + <gml:Envelope srsName="EPSG:25832"> + <gml:lowerCorner>389187.29 5799248.293</gml:lowerCorner> + <gml:upperCorner>389451.908 5799400.487</gml:upperCorner> + </gml:Envelope> + </gml:boundedBy> + <xplan:nummer>1683</xplan:nummer> + <xplan:name>610-531-01/37.1</xplan:name> + <xplan:gehoertZuPlan xlink:href="#GML_3a07cab4-ba41-43cf-a2b2-098a099ab0c3"/> + </xplan:BP_Bereich> + </gml:featureMember> + <gml:featureMember> + <xplan:BP_Plan gml:id="GML_3a07cab4-ba41-43cf-a2b2-098a099ab0c3"> + <gml:boundedBy> + <gml:Envelope srsName="EPSG:25832"> + <gml:lowerCorner>389187.29 5799248.293</gml:lowerCorner> + <gml:upperCorner>389451.908 5799400.487</gml:upperCorner> + </gml:Envelope> + </gml:boundedBy> + <xplan:name>Nr. 108 Holstener Weg</xplan:name> + <xplan:nummer>108</xplan:nummer> + <xplan:beschreibung>(§ 13 BauGB)</xplan:beschreibung> + <xplan:technHerstellDatum>2019-01-01</xplan:technHerstellDatum> + <xplan:genehmigungsDatum>2019-07-15</xplan:genehmigungsDatum> + <xplan:erstellungsMassstab>10000</xplan:erstellungsMassstab> + <xplan:xPlanGMLVersion>4.1</xplan:xPlanGMLVersion> + <xplan:raeumlicherGeltungsbereich> + <gml:Polygon srsName="EPSG:25832" gml:id="GML_8ddef72c-e5d0-4633-9514-fc27286aa5bd"> + <gml:exterior> + <gml:LinearRing> + <gml:posList srsDimension="2">389187.29 5799400.487 389192.226 5799400.102 389213.987 + 5799398.409 389228.148 5799397.301 389235.230180782 5799396.76051955 389267.224 + 5799394.319 389280.11 5799393.347 389313.709 5799390.721 389320.31 5799390.205 + 389322.384189298 5799390.04297999 389325.29 5799389.816 389335.943 5799388.983 + 389338.767 5799388.73 389359.381 5799386.879 389383.682 5799382.558 389399.504 + 5799378.591 389428.454 5799370.13900001 389431.729 5799369.183 389436.708 5799367.117 + 389442.462 5799364.731 389444.099 5799364.052 389444.497 5799363.887 389448.589 + 5799362.19 389450.365 5799361.454 389451.908 5799360.814 389435.775757406 + 5799350.39333417 389420.573 5799340.573 389415.206 5799337.114 389418.749 5799302.879 + 389419.677 5799293.894 389420.484 5799286.098 389422.311 5799268.414 389401.481518475 + 5799266.40521146 389396.274 5799265.903 389370.273 5799263.3 389356.361 5799262.039 + 389355.362 5799262.017 389341.745 5799262.042 389337.745 5799262.048 389323.108 + 5799262.075 389317.236 5799262.088 389312.912346494 5799261.28406564 389303.866 + 5799259.602 389281.835 5799255.493 389278.769 5799254.92 389271.16 5799253.497 + 389269.442 5799253.17600001 389256.587 5799250.788 389248.612 5799249.308 389247.613 + 5799249.119 389243.177 5799248.293 389187.29 5799400.487 + </gml:posList> + </gml:LinearRing> + </gml:exterior> + </gml:Polygon> + </xplan:raeumlicherGeltungsbereich> + <xplan:gemeinde> + <xplan:XP_Gemeinde> + <xplan:ags>03454045</xplan:ags> + <xplan:gemeindeName>Salzbergen</xplan:gemeindeName> + </xplan:XP_Gemeinde> + </xplan:gemeinde> + <xplan:planArt>9999</xplan:planArt> + <xplan:rechtsstand>4000</xplan:rechtsstand> + <xplan:auslegungsStartDatum>2019-04-01</xplan:auslegungsStartDatum> + <xplan:auslegungsEndDatum>2019-05-03</xplan:auslegungsEndDatum> + <xplan:satzungsbeschlussDatum>2019-08-25</xplan:satzungsbeschlussDatum> + <xplan:inkrafttretensDatum>2019-08-25</xplan:inkrafttretensDatum> + <xplan:bereich xlink:href="#GML_b3cca7aa-8908-4182-bf9f-1d96e623a1c7"/> + </xplan:BP_Plan> + </gml:featureMember> +</xplan:XPlanAuszug> diff --git a/xplan-validator/xplan-validator-storage/pom.xml b/xplan-validator/xplan-validator-storage/pom.xml new file mode 100644 index 0000000000..bd940607d2 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/pom.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>xplan-validator-storage</artifactId> + + <parent> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-validator</artifactId> + <version>8.0-SNAPSHOT</version> + </parent> + + <dependencies> + <!-- xPlanBox --> + <dependency> + <groupId>de.latlon.product.xplanbox</groupId> + <artifactId>xplan-core-commons</artifactId> + </dependency> + <!-- spring --> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-beans</artifactId> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-yaml</artifactId> + </dependency> + <!-- aws --> + <dependency> + <groupId>com.amazonaws</groupId> + <artifactId>aws-java-sdk-s3</artifactId> + </dependency> + <!-- test --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.findify</groupId> + <artifactId>s3mock_2.13</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>jakarta.annotation</groupId> + <artifactId>jakarta.annotation-api</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ErrorType.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ErrorType.java new file mode 100644 index 0000000000..cdc73c021c --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ErrorType.java @@ -0,0 +1,34 @@ +package de.latlon.xplanbox.validator.storage; + +import java.util.Arrays; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public enum ErrorType { + + INVALID_REQUEST(400), + + INVALID_CONTENT(422), + + INTERNAL_ERROR(500); + + private final int statusCode; + + ErrorType(int statusCode) { + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } + + public static ErrorType fromStatusCode(int statusCode) { + return Arrays.stream(values()) + .filter(errorType -> errorType.statusCode == statusCode) + .findFirst() + .orElse(INTERNAL_ERROR); + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/Status.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/Status.java new file mode 100644 index 0000000000..83fd1fcc45 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/Status.java @@ -0,0 +1,77 @@ +package de.latlon.xplanbox.validator.storage; + +import static de.latlon.xplanbox.validator.storage.StatusType.REQUESTED; + +import java.util.Date; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class Status { + + private StatusType status; + + private String errorMsg; + + private ErrorType errorType; + + private String linkToJsonReport; + + private String linkToPdfReport; + + private Date expirationTime; + + public Status() { + this.status = REQUESTED; + } + + public StatusType getStatus() { + return status; + } + + public void setStatus(StatusType status) { + this.status = status; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + public ErrorType getErrorType() { + return errorType; + } + + public void setErrorType(ErrorType errorType) { + this.errorType = errorType; + } + + public String getLinkToJsonReport() { + return linkToJsonReport; + } + + public void setLinkToJsonReport(String linkToJsonReport) { + this.linkToJsonReport = linkToJsonReport; + } + + public void setReportLinkExpirationTime(Date expirationTime) { + this.expirationTime = expirationTime; + } + + public Date getExpirationTime() { + return expirationTime; + } + + public void setReportLinkPdf(String linkToPdfReport) { + this.linkToPdfReport = linkToPdfReport; + } + + public String getLinkToPdfReport() { + return linkToPdfReport; + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StatusType.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StatusType.java new file mode 100644 index 0000000000..6f54a92f5a --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StatusType.java @@ -0,0 +1,21 @@ +package de.latlon.xplanbox.validator.storage; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public enum StatusType { + + // Validation requested + REQUESTED, + + // Validation started + STARTED, + + // Validation failed + FAILED, + + // Validation finished + FINISHED + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StoredValidationReport.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StoredValidationReport.java new file mode 100644 index 0000000000..866e4b58c9 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/StoredValidationReport.java @@ -0,0 +1,46 @@ +package de.latlon.xplanbox.validator.storage; + +import java.util.Date; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class StoredValidationReport { + + private final String linkToJsonReport; + + private final String linkToPdfReport; + + private final Date expirationTime; + + public StoredValidationReport(String linkToJsonReport, String linkToPdfReport) { + this(linkToJsonReport, linkToPdfReport, null); + } + + public StoredValidationReport(String linkToJsonReport, String linkToPdfReport, Date expirationTime) { + this.linkToJsonReport = linkToJsonReport; + this.linkToPdfReport = linkToPdfReport; + this.expirationTime = expirationTime; + } + + /** + * @return the url the file can be accessed, never <code>null</code> + */ + public String getLinkToJsonReport() { + return linkToJsonReport; + } + + public String getLinkToPdfReport() { + return linkToPdfReport; + } + + /** + * @return the date the objet expires, may be <code>null</code> if the file does not + * expire + */ + public Date getExpirationTime() { + return expirationTime; + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationDetails.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationDetails.java new file mode 100644 index 0000000000..467de70a37 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationDetails.java @@ -0,0 +1,29 @@ +package de.latlon.xplanbox.validator.storage; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class ValidationDetails { + + @JsonProperty("attachments") + private List<String> attachments = new ArrayList<>(); + + public List<String> getAttachments() { + return attachments; + } + + public void setAttachments(List<String> attachments) { + this.attachments = attachments; + } + + public void addAttachment(String attachments) { + getAttachments().add(attachments); + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionException.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionException.java new file mode 100644 index 0000000000..43d1560087 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionException.java @@ -0,0 +1,20 @@ +package de.latlon.xplanbox.validator.storage; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class ValidationExecutionException extends Exception { + + private final ErrorType errorType; + + public ValidationExecutionException(String msg, ErrorType errorType) { + super(msg); + this.errorType = errorType; + } + + public ErrorType getErrorType() { + return errorType; + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionStorage.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionStorage.java new file mode 100644 index 0000000000..3d771f09af --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/ValidationExecutionStorage.java @@ -0,0 +1,238 @@ +package de.latlon.xplanbox.validator.storage; + +import static de.latlon.xplanbox.validator.storage.StatusType.FAILED; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import de.latlon.xplan.commons.archive.XPlanArchiveCreator; +import de.latlon.xplan.commons.s3.StorageException; +import org.apache.tika.Tika; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public abstract class ValidationExecutionStorage { + + public static final String APPLICATION_YAML = "application/yaml"; + + public enum FileType { + + PLAN(""), DETAILS("_details.yaml"), STATUS("_status.yaml"); + + private final String suffix; + + FileType(String suffix) { + this.suffix = suffix; + } + + public String getSuffix() { + return suffix; + } + + } + + public enum ReportType { + + JSON(".json"), PDF(".pdf"), ZIP(".zip"); + + private final String fileExtension; + + ReportType(String fileExtension) { + this.fileExtension = fileExtension; + } + + public static ReportType byFileExtension(String fileExtension) { + Optional<ReportType> reportType = Arrays.stream(values()) + .filter(rt -> rt.fileExtension.equals(fileExtension)) + .findFirst(); + return reportType.orElseThrow( + () -> new IllegalArgumentException("Could not find ReportType with fileExtension" + fileExtension)); + } + + public String getFileExtension() { + return fileExtension; + } + + } + + static public class Key { + + private String fileName; + + Key(String uuid, FileType fileType) { + this.fileName = uuid + fileType.getSuffix(); + } + + public Key(String uuid, ReportType reportType) { + this.fileName = uuid + "_report" + reportType.getFileExtension(); + } + + public String toFileName() { + return fileName; + } + + public static Key details(String uuid) { + return new Key(uuid, FileType.DETAILS); + } + + public static Key status(String uuid) { + return new Key(uuid, FileType.STATUS); + } + + public static Key plan(String uuid) { + return new Key(uuid, FileType.PLAN); + } + + public static Key report(String uuid, ReportType reportType) { + return new Key(uuid, reportType); + } + + } + + public String addPlanToValidate(Path plan) throws IOException, StorageException { + String uuid = UUID.randomUUID().toString(); + ValidationDetails validationDetails = createValidationDetails(plan); + byte[] serializedValidationDetails = serialize(validationDetails); + addToStore(Key.details(uuid), APPLICATION_YAML, serializedValidationDetails); + addToStore(Key.status(uuid), APPLICATION_YAML, serialize(new Status())); + addToStore(Key.plan(uuid), plan); + return uuid; + } + + public void cleanupAfterValidation(String uuid) throws StorageException { + removeFromStore(Key.details(uuid)); + removeFromStore(Key.plan(uuid)); + } + + public Status retrieveStatus(String uuid) throws StorageException { + byte[] content = retrieveContent(Key.status(uuid)); + return deserialize(content); + } + + public void changeStatus(String uuid, StatusType statusType) throws StorageException { + changeStatus(uuid, statusType, null); + } + + public void changeStatus(String uuid, String errorMsg, ErrorType errorType) throws StorageException { + Status status = retrieveStatus(uuid); + status.setStatus(FAILED); + status.setErrorMsg(errorMsg); + status.setErrorType(errorType); + addToStore(Key.status(uuid), APPLICATION_YAML, serialize(status)); + } + + public void changeStatus(String uuid, StatusType statusType, StoredValidationReport storedValidationReport) + throws StorageException { + Status status = retrieveStatus(uuid); + status.setStatus(statusType); + if (storedValidationReport != null) { + status.setLinkToJsonReport(storedValidationReport.getLinkToJsonReport()); + status.setReportLinkPdf(storedValidationReport.getLinkToPdfReport()); + status.setReportLinkExpirationTime(storedValidationReport.getExpirationTime()); + } + addToStore(Key.status(uuid), APPLICATION_YAML, serialize(status)); + } + + public byte[] retrieveReport(String uuid, ReportType reportType) + throws StorageException, ValidationExecutionException { + Status status = retrieveStatus(uuid); + if (status.getStatus() == FAILED) + throw new ValidationExecutionException(status.getErrorMsg(), status.getErrorType()); + return retrieveContent(Key.report(uuid, reportType)); + } + + /** + * @param key of the file to return, never <code>null</code> + * @return the content of the file, never <code>null</code> + * @throws StorageException if an error occured retrieving the file or the file does + * not exist + */ + protected abstract byte[] retrieveContent(Key key) throws StorageException; + + protected abstract void addToStore(Key key, String contentType, byte[] file) throws StorageException; + + protected abstract void addToStore(Key key, Path file) throws IOException, StorageException; + + protected abstract void removeFromStore(Key key) throws StorageException; + + /** + * ugly name to improve + * @throws IOException + */ + public abstract void writePlanToValidate(String uuid, Path toPath) throws IOException, StorageException; + + /** + * ugly name to improve + * @return + * @throws IOException + */ + public abstract StoredValidationReport saveValidationResult(String uuid, Map<ReportType, Path> reports) + throws IOException, StorageException; + + private ValidationDetails createValidationDetails(Path plan) throws IOException { + ValidationDetails validationDetails = new ValidationDetails(); + if (isZipFile(plan)) { + try (ZipFile zipFile = new ZipFile(plan.toFile())) { + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + validationDetails.addAttachment(entry.getName()); + } + } + } + else { + validationDetails.addAttachment(XPlanArchiveCreator.MAIN_FILE); + } + return validationDetails; + } + + private boolean isZipFile(Path path) throws IOException { + String contentType = new Tika().detect(path); + if ("application/zip".equals(contentType)) + return true; + return false; + } + + private byte[] serialize(ValidationDetails validationDetails) throws StorageException { + try { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + return mapper.writeValueAsBytes(validationDetails); + } + catch (JsonProcessingException e) { + throw new StorageException("Could not serialize ValidationDetails " + validationDetails, e); + } + } + + private byte[] serialize(Status statusNotification) throws StorageException { + try { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + return mapper.writeValueAsBytes(statusNotification); + } + catch (JsonProcessingException e) { + throw new StorageException("Could not serialize Status " + statusNotification, e); + } + } + + private Status deserialize(byte[] content) throws StorageException { + try { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + return mapper.readValue(content, Status.class); + } + catch (IOException e) { + throw new StorageException("Could not deserialize Status", e); + } + } + +} \ No newline at end of file diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/config/AmazonS3Context.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/config/AmazonS3Context.java new file mode 100644 index 0000000000..e8880b26da --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/config/AmazonS3Context.java @@ -0,0 +1,92 @@ +/*- + * #%L + * xplan-core-manager - XPlan Manager Core Komponente + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.validator.storage.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import de.latlon.xplan.commons.s3.S3Storage; +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.s3.S3PlanValidationExecutionStorage; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +/** + * Spring configuration for using AWS S3 as a storage. + * + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> + * @since 7.0 + */ +@Configuration +@Profile("s3execution") +public class AmazonS3Context { + + @Bean(destroyMethod = "shutdown") + public AmazonS3 s3Client(AWSCredentials credentials, @Value("${xplanbox.s3.region}") String signingRegion, + @Value("${xplanbox.s3.endpoint.url}") String endpointUrl) { + AmazonS3 client; + // TODO refactoring if/else to @ConditionalOnExpression with SpringBoot into 2 + // SpringBeans + if (endpointUrl == null || endpointUrl.isEmpty()) { + client = AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(signingRegion) + .build(); + } + else { + AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(endpointUrl, + signingRegion); + client = AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withEndpointConfiguration(endpoint) + .build(); + } + return client; + } + + @Bean + public AWSCredentials credentials(@Value("${xplanbox.s3.accessKeyId}") String accessKeyId, + @Value("${xplanbox.s3.secretKey}") String secretKey) { + return new BasicAWSCredentials(accessKeyId, secretKey); + } + + @Bean + public ValidationExecutionStorage validationExecutionStorage(S3Storage s3Storage, + @Value("${xplanbox.validation.s3.bucketPublicUrl}") String s3PublicUrl) { + return new S3PlanValidationExecutionStorage(s3Storage, s3PublicUrl); + } + + @Bean + public S3Storage s3Storage(AmazonS3 s3Client, @Value("${xplanbox.validation.s3.bucketName}") String bucketName) + throws StorageException { + S3Storage s3Storage = new S3Storage(s3Client, bucketName); + s3Storage.setBucketExpirationDate("deleteAfter30d", 30); + return s3Storage; + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorage.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorage.java new file mode 100644 index 0000000000..54517fa677 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorage.java @@ -0,0 +1,89 @@ +package de.latlon.xplanbox.validator.storage.filesystem; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.validator.storage.StoredValidationReport; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class FileSystemValidationExecutionStorage extends ValidationExecutionStorage { + + private final Path storageDir; + + public FileSystemValidationExecutionStorage(Optional<Path> storageDir) throws IOException { + if (!storageDir.isPresent()) + this.storageDir = Files.createTempDirectory("FileSystemPlanStorage"); + else + this.storageDir = storageDir.get(); + if (!Files.exists(this.storageDir)) + Files.createDirectories(this.storageDir); + } + + @Override + protected byte[] retrieveContent(Key key) throws StorageException { + try { + Path source = storageDir.resolve(key.toFileName()); + if (!Files.exists(source)) + throw new StorageException("File " + source + " does not exist.", 404); + return Files.readAllBytes(source); + } + catch (IOException e) { + throw new StorageException("Could not find file " + key.toFileName() + " from " + storageDir, e); + } + } + + @Override + protected void addToStore(Key key, String contentType, byte[] content) throws StorageException { + Path target = storageDir.resolve(key.toFileName()); + try { + Files.write(target, content); + } + catch (IOException e) { + throw new StorageException("Could not write file " + key.toFileName() + " to " + storageDir, e); + } + } + + @Override + protected void addToStore(Key key, Path file) throws IOException { + Path target = storageDir.resolve(key.toFileName()); + Files.copy(file, target); + } + + @Override + protected void removeFromStore(Key key) throws StorageException { + try { + Path fileToDelete = storageDir.resolve(key.toFileName()); + Files.delete(fileToDelete); + } + catch (IOException e) { + throw new StorageException("Could not delete file " + key.toFileName(), e); + } + } + + @Override + public void writePlanToValidate(String uuid, Path toPath) throws IOException { + Path origin = storageDir.resolve(Key.plan(uuid).toFileName()); + Files.copy(origin, toPath); + } + + @Override + public StoredValidationReport saveValidationResult(String uuid, Map<ReportType, Path> reports) throws IOException { + + for (Entry<ReportType, Path> entry : reports.entrySet()) { + Key key = Key.report(uuid, entry.getKey()); + addToStore(key, entry.getValue()); + } + return new StoredValidationReport(Key.report(uuid, ReportType.JSON).toFileName(), + Key.report(uuid, ReportType.PDF).toFileName()); + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/s3/S3PlanValidationExecutionStorage.java b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/s3/S3PlanValidationExecutionStorage.java new file mode 100644 index 0000000000..897e2458e1 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/main/java/de/latlon/xplanbox/validator/storage/s3/S3PlanValidationExecutionStorage.java @@ -0,0 +1,84 @@ +package de.latlon.xplanbox.validator.storage.s3; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Map.Entry; + +import com.amazonaws.services.s3.model.PutObjectResult; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import de.latlon.xplan.commons.s3.S3Metadata; +import de.latlon.xplan.commons.s3.S3Object; +import de.latlon.xplan.commons.s3.S3Storage; +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.validator.storage.StoredValidationReport; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class S3PlanValidationExecutionStorage extends ValidationExecutionStorage { + + private final S3Storage s3Storage; + + private final String s3PublicUrl; + + public S3PlanValidationExecutionStorage(S3Storage s3Storage, String s3PublicUrl) { + this.s3Storage = s3Storage; + this.s3PublicUrl = s3PublicUrl; + } + + @Override + protected byte[] retrieveContent(Key key) throws StorageException { + S3Object object = s3Storage.getObject(key.toFileName()); + return object.getContent(); + } + + @Override + protected void addToStore(Key key, String contentType, byte[] content) throws StorageException { + S3Metadata s3Metadata = new S3Metadata(key.toFileName(), contentType, content.length); + S3Object s3Object = new S3Object(s3Metadata, content); + s3Storage.insertObject(s3Object); + } + + @Override + protected void addToStore(Key key, Path file) throws StorageException { + s3Storage.insertObject(key.toFileName(), file); + } + + @Override + protected void removeFromStore(Key key) { + S3ObjectSummary object = new S3ObjectSummary(); + object.setKey(key.toFileName()); + s3Storage.deleteObject(object); + } + + @Override + public void writePlanToValidate(String uuid, Path toPath) throws StorageException { + Key key = Key.plan(uuid); + S3Object object = s3Storage.getObject(key.toFileName()); + try { + Files.copy(new ByteArrayInputStream(object.getContent()), toPath); + } + catch (IOException e) { + throw new StorageException("Could not write plan to file" + toPath, e); + } + } + + @Override + public StoredValidationReport saveValidationResult(String uuid, Map<ReportType, Path> reports) + throws StorageException { + PutObjectResult lastPutObjectResult = null; + for (Entry<ReportType, Path> entry : reports.entrySet()) { + String fileName = Key.report(uuid, entry.getKey()).toFileName(); + lastPutObjectResult = s3Storage.insertObject(fileName, entry.getValue()); + } + String reportLinkToJson = s3PublicUrl + "/" + Key.report(uuid, ReportType.JSON).toFileName(); + String reportLinkToPdf = s3PublicUrl + "/" + Key.report(uuid, ReportType.PDF).toFileName(); + return new StoredValidationReport(reportLinkToJson, reportLinkToPdf, lastPutObjectResult.getExpirationTime()); + } + +} \ No newline at end of file diff --git a/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorageStorageTest.java b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorageStorageTest.java new file mode 100644 index 0000000000..44c27343f1 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/filesystem/FileSystemValidationExecutionStorageStorageTest.java @@ -0,0 +1,75 @@ +package de.latlon.xplanbox.validator.storage.filesystem; + +import static de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.FileType.DETAILS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.validator.storage.ValidationDetails; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +public class FileSystemValidationExecutionStorageStorageTest { + + @TempDir + static Path tempFolder; + + @Test + void testAddPlan_gml() throws IOException, URISyntaxException, StorageException { + Path targetFolder = tempFolder.resolve("testGml"); + Files.createDirectory(targetFolder); + FileSystemValidationExecutionStorage fileSystemPlanStorage = new FileSystemValidationExecutionStorage( + Optional.of(targetFolder)); + URL xplanGml = getClass().getResource("/xplan.gml"); + String key = fileSystemPlanStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + + assertThat(Files.exists(targetFolder.resolve(key))); + assertThat(Files.exists(targetFolder.resolve(key + DETAILS.getSuffix()))); + + ValidationDetails validationDetails = deserializeDetails(targetFolder, key); + assertThat(validationDetails.getAttachments()).containsExactlyInAnyOrder("xplan.gml"); + } + + @Test + void testAddPlan_zip() throws IOException, URISyntaxException, StorageException { + Path targetFolder = tempFolder.resolve("testZip"); + Files.createDirectory(targetFolder); + FileSystemValidationExecutionStorage fileSystemPlanStorage = new FileSystemValidationExecutionStorage( + Optional.of(targetFolder)); + URL xplanGml = getClass().getResource("/BPlan002_5-3.zip"); + String key = fileSystemPlanStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + + assertTrue(Files.exists(targetFolder.resolve(key))); + assertTrue(Files.exists(targetFolder.resolve(key + DETAILS.getSuffix()))); + + ValidationDetails validationDetails = deserializeDetails(targetFolder, key); + assertThat(validationDetails.getAttachments()).containsExactlyInAnyOrder("BPlan002_5-3.pgw", "BPlan002_5-3.png", + "xplan.gml"); + } + + private ValidationDetails deserializeDetails(Path targetFolder, String key) throws IOException { + Path details = targetFolder.resolve(key + DETAILS.getSuffix()); + byte[] detailsContent = Files.readAllBytes(details); + return deserialize(detailsContent); + } + + private ValidationDetails deserialize(byte[] detailsContent) throws IOException { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + return mapper.readValue(detailsContent, ValidationDetails.class); + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/AmazonS3TestContext.java b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/AmazonS3TestContext.java new file mode 100644 index 0000000000..5e224f92b5 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/AmazonS3TestContext.java @@ -0,0 +1,88 @@ +/*- + * #%L + * xplan-core-manager - XPlan Manager Core Komponente + * %% + * Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +package de.latlon.xplanbox.validator.storage.s3; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.AnonymousAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import io.findify.s3mock.S3Mock; +import jakarta.annotation.PreDestroy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.test.util.TestSocketUtils; + +/** + * Spring Configuration to enable usage of mock objects for integration tests. + * + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @author <a href="mailto:friebe@lat-lon.de">Torsten Friebe</a> + */ +@Configuration +public class AmazonS3TestContext { + + @Autowired(required = false) + private S3Mock s3Mock; + + @Autowired + private AmazonS3 s3TestClient; + + private final int port = TestSocketUtils.findAvailableTcpPort(); + + @Bean + @Profile("mock") + public S3Mock s3Mock() { + S3Mock s3Mock = new S3Mock.Builder().withPort(port).withInMemoryBackend().build(); + s3Mock.start(); + return s3Mock; + } + + @Bean + @Primary + @Profile("mock") + public AmazonS3 s3TestClient(@Value("${xplanbox.s3.region}") String signingRegion, + @Value("${xplanbox.validation.s3.bucketName}") String bucketName, + @Value("${xplanbox.s3.endpoint.url}") String url) { + AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(url + ":" + port, + signingRegion); + AmazonS3 client = AmazonS3ClientBuilder.standard() + .withPathStyleAccessEnabled(true) + .withEndpointConfiguration(endpoint) + .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())) + .build(); + client.createBucket(bucketName); + return client; + } + + @PreDestroy + public void shutdown() { + s3TestClient.shutdown(); + if (s3Mock != null) { + s3Mock.stop(); + } + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageIT.java b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageIT.java new file mode 100644 index 0000000000..d9ea4d9c86 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageIT.java @@ -0,0 +1,87 @@ +package de.latlon.xplanbox.validator.storage.s3; + +import static de.latlon.xplanbox.validator.storage.ErrorType.INTERNAL_ERROR; +import static de.latlon.xplanbox.validator.storage.StatusType.FINISHED; +import static de.latlon.xplanbox.validator.storage.StatusType.REQUESTED; +import static de.latlon.xplanbox.validator.storage.StatusType.STARTED; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; + +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.validator.storage.ErrorType; +import de.latlon.xplanbox.validator.storage.Status; +import de.latlon.xplanbox.validator.storage.StoredValidationReport; +import de.latlon.xplanbox.validator.storage.ValidationExecutionException; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage; +import de.latlon.xplanbox.validator.storage.config.AmazonS3Context; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { AmazonS3Context.class, AmazonS3TestContext.class }) +@ActiveProfiles({ "s3execution", "mock" }) +@TestPropertySource("classpath:s3Mock.properties") +public class S3ValidationExecutionStorageIT { + + @Autowired + private S3PlanValidationExecutionStorage validationExecutionStorage; + + @Test + void testStatus() throws StorageException, URISyntaxException, IOException { + URL xplanGml = getClass().getResource("/xplan.gml"); + + String uuid = validationExecutionStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + Status statusRequested = validationExecutionStorage.retrieveStatus(uuid); + assertThat(statusRequested.getStatus()).isEqualTo(REQUESTED); + + validationExecutionStorage.changeStatus(uuid, STARTED); + Status statusStarted = validationExecutionStorage.retrieveStatus(uuid); + assertThat(statusStarted.getStatus()).isEqualTo(STARTED); + + StoredValidationReport validationReportDetails = new StoredValidationReport("http://test.org/test.json", + "http://test.org/test.pdf"); + validationExecutionStorage.changeStatus(uuid, FINISHED, validationReportDetails); + Status statusFinished = validationExecutionStorage.retrieveStatus(uuid); + assertThat(statusFinished.getStatus()).isEqualTo(FINISHED); + assertThat(statusFinished.getLinkToJsonReport()).isEqualTo(validationReportDetails.getLinkToJsonReport()); + assertThat(statusFinished.getLinkToPdfReport()).isEqualTo(validationReportDetails.getLinkToPdfReport()); + assertThat(statusFinished.getExpirationTime()).isEqualTo(validationReportDetails.getExpirationTime()); + } + + @Test + void testAddAndCleanup() throws StorageException, URISyntaxException, IOException { + URL xplanGml = getClass().getResource("/xplan.gml"); + + String uuid = validationExecutionStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + validationExecutionStorage.cleanupAfterValidation(uuid); + } + + @Test + void testChangeStatus_toError() throws URISyntaxException, StorageException, IOException { + URL xplanGml = getClass().getResource("/xplan.gml"); + String uuid = validationExecutionStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + + String errorMsg = "Error test"; + validationExecutionStorage.changeStatus(uuid, errorMsg, INTERNAL_ERROR); + + ValidationExecutionException exception = Assertions.assertThrows(ValidationExecutionException.class, + () -> validationExecutionStorage.retrieveReport(uuid, ValidationExecutionStorage.ReportType.JSON)); + assertThat(exception.getErrorType()).isEqualTo(ErrorType.fromStatusCode(INTERNAL_ERROR.getStatusCode())); + assertThat(exception.getMessage()).isEqualTo(errorMsg); + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageTest.java b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageTest.java new file mode 100644 index 0000000000..f56ca231d4 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/test/java/de/latlon/xplanbox/validator/storage/s3/S3ValidationExecutionStorageTest.java @@ -0,0 +1,111 @@ +package de.latlon.xplanbox.validator.storage.s3; + +import static de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.FileType.DETAILS; +import static de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.FileType.STATUS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectResult; +import de.latlon.xplan.commons.s3.S3Storage; +import de.latlon.xplan.commons.s3.StorageException; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.Key; +import de.latlon.xplanbox.validator.storage.ValidationExecutionStorage.ReportType; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +/** + * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a> + * @since 8.0 + */ +class S3ValidationExecutionStorageTest { + + private static final String BUCKET_NAME = "xplanbox"; + + private static final String S3_PUBLIC_URL = "http://xplanbox.s3.eu-central-1.amazonaws.com"; + + @Test + void testAddPlanToValidate_gml() throws IOException, URISyntaxException, StorageException { + AmazonS3 client = spy(AmazonS3.class); + S3Storage s3Storage = new S3Storage(client, BUCKET_NAME); + S3PlanValidationExecutionStorage validationExecutionStorage = new S3PlanValidationExecutionStorage(s3Storage, + S3_PUBLIC_URL); + URL xplanGml = getClass().getResource("/xplan.gml"); + String key = validationExecutionStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + + ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class); + + verify(client, times(3)).doesBucketExistV2(eq(BUCKET_NAME)); + verify(client, times(2)).putObject(eq(BUCKET_NAME), keyCaptor.capture(), any(InputStream.class), + any(ObjectMetadata.class)); + verify(client).putObject(eq(BUCKET_NAME), keyCaptor.capture(), any(File.class)); + + assertThat(keyCaptor.getAllValues()).containsExactlyInAnyOrder(key, key + DETAILS.getSuffix(), + key + STATUS.getSuffix()); + } + + @Test + void testAddPlanToValidate_zip() throws IOException, URISyntaxException, StorageException { + AmazonS3 client = spy(AmazonS3.class); + S3Storage s3Storage = new S3Storage(client, BUCKET_NAME); + S3PlanValidationExecutionStorage validationExecutionStorage = new S3PlanValidationExecutionStorage(s3Storage, + S3_PUBLIC_URL); + URL xplanGml = getClass().getResource("/BPlan002_5-3.zip"); + String key = validationExecutionStorage.addPlanToValidate(Paths.get(xplanGml.toURI())); + + ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class); + + verify(client, times(3)).doesBucketExistV2(eq(BUCKET_NAME)); + verify(client, times(2)).putObject(eq(BUCKET_NAME), keyCaptor.capture(), any(InputStream.class), + any(ObjectMetadata.class)); + verify(client).putObject(eq(BUCKET_NAME), keyCaptor.capture(), any(File.class)); + + assertThat(keyCaptor.getAllValues()).containsExactlyInAnyOrder(key, key + DETAILS.getSuffix(), + key + STATUS.getSuffix()); + } + + @Test + void testSaveValidationResult() throws URISyntaxException, StorageException { + AmazonS3 client = spy(AmazonS3.class); + when(client.putObject(anyString(), anyString(), any(File.class))).thenReturn(mock(PutObjectResult.class)); + S3Storage s3Storage = new S3Storage(client, BUCKET_NAME); + S3PlanValidationExecutionStorage validationExecutionStorage = new S3PlanValidationExecutionStorage(s3Storage, + S3_PUBLIC_URL); + URL xplanGml = getClass().getResource("/BPlan002_5-3.zip"); + String uuid = UUID.randomUUID().toString(); + + Map<ReportType, Path> reports = new HashMap<>(); + reports.put(ReportType.JSON, Paths.get(xplanGml.toURI())); + reports.put(ReportType.PDF, Paths.get(xplanGml.toURI())); + validationExecutionStorage.saveValidationResult(uuid, reports); + + ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class); + + verify(client, times(2)).doesBucketExistV2(eq(BUCKET_NAME)); + verify(client, times(2)).putObject(eq(BUCKET_NAME), keyCaptor.capture(), any(File.class)); + + assertThat(keyCaptor.getAllValues()).containsExactlyInAnyOrder(Key.report(uuid, ReportType.PDF).toFileName(), + Key.report(uuid, ReportType.JSON).toFileName()); + } + +} diff --git a/xplan-validator/xplan-validator-storage/src/test/resources/BPlan002_5-3.zip b/xplan-validator/xplan-validator-storage/src/test/resources/BPlan002_5-3.zip new file mode 100644 index 0000000000000000000000000000000000000000..e68a0a9a42a7f9247e54484a768f2e49ec2bf261 GIT binary patch literal 60474 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%0D9rbsrOT@ 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 D8bwE4 literal 0 HcmV?d00001 diff --git a/xplan-validator/xplan-validator-storage/src/test/resources/s3Mock.properties b/xplan-validator/xplan-validator-storage/src/test/resources/s3Mock.properties new file mode 100644 index 0000000000..d858dfb28a --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/test/resources/s3Mock.properties @@ -0,0 +1,25 @@ +### +# #%L +# xplan-manager-core - XPlan Manager Core Komponente +# %% +# Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH +# %% +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# #L% +### +xplanbox.s3.accessKeyId= +xplanbox.s3.secretKey= +xplanbox.s3.region=eu-central-1 +xplanbox.s3.endpoint.url=http://localhost +xplanbox.validation.s3.bucketName=latlonxpbexecution diff --git a/xplan-validator/xplan-validator-storage/src/test/resources/xplan.gml b/xplan-validator/xplan-validator-storage/src/test/resources/xplan.gml new file mode 100644 index 0000000000..07d99bfb19 --- /dev/null +++ b/xplan-validator/xplan-validator-storage/src/test/resources/xplan.gml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- + #%L + xplan-validator-api - Software zur Verwaltung von XPlanGML Daten + %% + Copyright (C) 2008 - 2024 Freie und Hansestadt Hamburg, developed by lat/lon gesellschaft für raumbezogene Informationssysteme mbH + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> + +<xplan:XPlanAuszug xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xplan="http://www.xplanung.de/xplangml/4/1" + gml:id="GML_ad5adb8f-d05d-45d2-8197-29b26f4dd142"> + <gml:boundedBy> + <gml:Envelope srsName="EPSG:25832"> + <gml:lowerCorner>389187.29 5799248.293</gml:lowerCorner> + <gml:upperCorner>389451.908 5799400.487</gml:upperCorner> + </gml:Envelope> + </gml:boundedBy> + <gml:featureMember> + <xplan:BP_Bereich gml:id="GML_b3cca7aa-8908-4182-bf9f-1d96e623a1c7"> + <gml:boundedBy> + <gml:Envelope srsName="EPSG:25832"> + <gml:lowerCorner>389187.29 5799248.293</gml:lowerCorner> + <gml:upperCorner>389451.908 5799400.487</gml:upperCorner> + </gml:Envelope> + </gml:boundedBy> + <xplan:nummer>1683</xplan:nummer> + <xplan:name>610-531-01/37.1</xplan:name> + <xplan:gehoertZuPlan xlink:href="#GML_3a07cab4-ba41-43cf-a2b2-098a099ab0c3"/> + </xplan:BP_Bereich> + </gml:featureMember> + <gml:featureMember> + <xplan:BP_Plan gml:id="GML_3a07cab4-ba41-43cf-a2b2-098a099ab0c3"> + <gml:boundedBy> + <gml:Envelope srsName="EPSG:25832"> + <gml:lowerCorner>389187.29 5799248.293</gml:lowerCorner> + <gml:upperCorner>389451.908 5799400.487</gml:upperCorner> + </gml:Envelope> + </gml:boundedBy> + <xplan:name>Nr. 108 Holstener Weg</xplan:name> + <xplan:nummer>108</xplan:nummer> + <xplan:beschreibung>(§ 13 BauGB)</xplan:beschreibung> + <xplan:technHerstellDatum>2019-01-01</xplan:technHerstellDatum> + <xplan:genehmigungsDatum>2019-07-15</xplan:genehmigungsDatum> + <xplan:erstellungsMassstab>10000</xplan:erstellungsMassstab> + <xplan:xPlanGMLVersion>4.1</xplan:xPlanGMLVersion> + <xplan:raeumlicherGeltungsbereich> + <gml:Polygon srsName="EPSG:25832" gml:id="GML_8ddef72c-e5d0-4633-9514-fc27286aa5bd"> + <gml:exterior> + <gml:LinearRing> + <gml:posList srsDimension="2">389187.29 5799400.487 389192.226 5799400.102 389213.987 + 5799398.409 389228.148 5799397.301 389235.230180782 5799396.76051955 389267.224 + 5799394.319 389280.11 5799393.347 389313.709 5799390.721 389320.31 5799390.205 + 389322.384189298 5799390.04297999 389325.29 5799389.816 389335.943 5799388.983 + 389338.767 5799388.73 389359.381 5799386.879 389383.682 5799382.558 389399.504 + 5799378.591 389428.454 5799370.13900001 389431.729 5799369.183 389436.708 5799367.117 + 389442.462 5799364.731 389444.099 5799364.052 389444.497 5799363.887 389448.589 + 5799362.19 389450.365 5799361.454 389451.908 5799360.814 389435.775757406 + 5799350.39333417 389420.573 5799340.573 389415.206 5799337.114 389418.749 5799302.879 + 389419.677 5799293.894 389420.484 5799286.098 389422.311 5799268.414 389401.481518475 + 5799266.40521146 389396.274 5799265.903 389370.273 5799263.3 389356.361 5799262.039 + 389355.362 5799262.017 389341.745 5799262.042 389337.745 5799262.048 389323.108 + 5799262.075 389317.236 5799262.088 389312.912346494 5799261.28406564 389303.866 + 5799259.602 389281.835 5799255.493 389278.769 5799254.92 389271.16 5799253.497 + 389269.442 5799253.17600001 389256.587 5799250.788 389248.612 5799249.308 389247.613 + 5799249.119 389243.177 5799248.293 389187.29 5799400.487 + </gml:posList> + </gml:LinearRing> + </gml:exterior> + </gml:Polygon> + </xplan:raeumlicherGeltungsbereich> + <xplan:gemeinde> + <xplan:XP_Gemeinde> + <xplan:ags>03454045</xplan:ags> + <xplan:gemeindeName>Salzbergen</xplan:gemeindeName> + </xplan:XP_Gemeinde> + </xplan:gemeinde> + <xplan:planArt>9999</xplan:planArt> + <xplan:rechtsstand>4000</xplan:rechtsstand> + <xplan:auslegungsStartDatum>2019-04-01</xplan:auslegungsStartDatum> + <xplan:auslegungsEndDatum>2019-05-03</xplan:auslegungsEndDatum> + <xplan:satzungsbeschlussDatum>2019-08-25</xplan:satzungsbeschlussDatum> + <xplan:inkrafttretensDatum>2019-08-25</xplan:inkrafttretensDatum> + <xplan:bereich xlink:href="#GML_b3cca7aa-8908-4182-bf9f-1d96e623a1c7"/> + </xplan:BP_Plan> + </gml:featureMember> +</xplan:XPlanAuszug> diff --git a/xplan-webservices/xplan-webservices-validator-wms/src/test/resources/de/latlon/xplan/validator/wms/libs.expected.txt b/xplan-webservices/xplan-webservices-validator-wms/src/test/resources/de/latlon/xplan/validator/wms/libs.expected.txt index 33c42d2c13..1341834ed6 100644 --- a/xplan-webservices/xplan-webservices-validator-wms/src/test/resources/de/latlon/xplan/validator/wms/libs.expected.txt +++ b/xplan-webservices/xplan-webservices-validator-wms/src/test/resources/de/latlon/xplan/validator/wms/libs.expected.txt @@ -3,6 +3,9 @@ angus-activation-2.0.2.jar antlr-runtime-3.5.3.jar apache-mime4j-core-0.8.6.jar asm-9.6.jar +aws-java-sdk-core-1.12.765.jar +aws-java-sdk-kms-1.12.765.jar +aws-java-sdk-s3-1.12.765.jar axiom-api-1.4.0.jar axiom-impl-1.4.0.jar batik-anim-1.17.jar @@ -36,6 +39,7 @@ commons-fileupload2-core-2.0.0-M2.jar commons-fileupload2-jakarta-servlet6-2.0.0-M2.jar commons-io-2.16.1.jar commons-lang-2.6.jar +commons-logging-1.1.3.jar commons-math-2.2.jar commons-pool2-2.12.0.jar com.sun.xml.bind-jaxb-core-4.0.5.jar @@ -117,6 +121,7 @@ istack-commons-runtime-4.1.2.jar jackson-annotations-2.17.2.jar jackson-core-2.17.2.jar jackson-databind-2.17.2.jar +jackson-dataformat-cbor-2.17.2.jar jackson-dataformat-yaml-2.17.2.jar jai-codec-1.1.3.jar jai-core-1.1.3.jar @@ -140,6 +145,8 @@ jaxb-runtime-4.0.5.jar jaxen-2.0.0.jar jcl-over-slf4j-2.0.13.jar jgridshift-core-1.3.1.jar +jmespath-java-1.12.765.jar +joda-time-2.12.7.jar jogl-1.1.2.jar json-path-2.9.0.jar json-smart-2.5.1.jar -- GitLab