From 128e116285831750de51fe7b67202165c9c779e2 Mon Sep 17 00:00:00 2001 From: slaurenz <82034561+slaurenz@users.noreply.github.com> Date: Fri, 25 Jun 2021 15:42:34 +0200 Subject: [PATCH 1/2] Feat/download data (#14) * Added functionality for value sets * Added local storage functionality for business rules * Updated Value Set handling and api description * Updated country list handling and api description * Updated business rule handling and api description * Added `X-VERSION` Header as optional * Added Licence Text * Added download of business rules, value set and country list using the dgc-lib * Add simple sanity check for downloaded data. * Update DGC-Lib to 1.1.1 Co-authored-by: Felix Dittrich <31076102+f11h@users.noreply.github.com> --- pom.xml | 8 +- .../DgcBusinessRuleServiceApplication.java | 2 +- .../config/DgcConfigProperties.java | 12 +- .../dgc/businessrule/config/ErrorHandler.java | 2 +- .../businessrule/config/OpenApiConfig.java | 20 + .../businessrule/config/SchedulerConfig.java | 2 +- .../businessrule/config/ShedLockConfig.java | 2 +- .../entity/BusinessRuleEntity.java | 22 +- .../entity/CountryListEntity.java | 20 + .../businessrule/entity/ShedlockEntity.java | 2 +- .../businessrule/entity/ValueSetEntity.java | 20 + .../DgcaBusinessRulesResponseException.java | 20 + .../businessrule/model/BusinessRuleItem.java | 19 + .../dgc/businessrule/model/ValueSetItem.java | 15 + .../repository/BusinessRuleRepository.java | 22 ++ .../repository/CountryListRepository.java | 20 + .../repository/ValueSetRepository.java | 22 ++ .../controller/BusinessRuleController.java | 47 ++- .../controller/CountryListController.java | 16 +- .../controller/ValueSetController.java | 29 +- .../restapi/dto/BusinessRuleListItemDto.java | 20 + .../restapi/dto/ProblemReportDto.java | 2 +- .../restapi/dto/ValueSetListItemDto.java | 20 + .../service/BusinessRuleService.java | 124 +++++-- .../service/CountryListService.java | 42 ++- .../service/GatewayDataDownloadService.java | 40 ++ .../GatewayDataDownloadServiceImpl.java | 131 +++++++ .../businessrule/service/ValueSetService.java | 111 ++++-- .../utils/BusinessRulesUtils.java | 20 + src/main/resources/application-btp.yml | 6 + src/main/resources/application.yml | 33 +- .../static/valuesets/country-2-codes.json | 2 +- .../ec/dgc/businessrule/OpenApiTest.java | 31 ++ ...BusinessRuleControllerIntegrationTest.java | 351 ++++++++++++++++++ .../CountryListControllerIntegrationTest.java | 33 ++ .../ValueSetControllerIntegrationTest.java | 161 ++++++++ .../service/BusinessRuleServiceTest.java | 176 +++++++++ .../service/CountryListServiceTest.java | 77 ++++ .../service/ValueSetServiceTest.java | 146 ++++++++ .../testdata/BusinessRulesTestHelper.java | 187 ++++++++++ src/test/resources/application.yml | 14 +- templates/file-header.txt | 2 +- 42 files changed, 1948 insertions(+), 103 deletions(-) create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/model/BusinessRuleItem.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/model/ValueSetItem.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadService.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadServiceImpl.java create mode 100644 src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleControllerIntegrationTest.java create mode 100644 src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetControllerIntegrationTest.java create mode 100644 src/test/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleServiceTest.java create mode 100644 src/test/java/eu/europa/ec/dgc/businessrule/service/CountryListServiceTest.java create mode 100644 src/test/java/eu/europa/ec/dgc/businessrule/service/ValueSetServiceTest.java create mode 100644 src/test/java/eu/europa/ec/dgc/businessrule/testdata/BusinessRulesTestHelper.java diff --git a/pom.xml b/pom.xml index c60ff56..8ea59dd 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ 1.68 4.9.1 4.23.0 - 0.5.0 + 1.1.1 3.1.2 3.6.1.1688 @@ -133,7 +133,11 @@ - + + eu.europa.ec.dgc + dgc-lib + ${dgc.lib.version} + org.springframework.boot spring-boot-starter diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/DgcBusinessRuleServiceApplication.java b/src/main/java/eu/europa/ec/dgc/businessrule/DgcBusinessRuleServiceApplication.java index a2e4103..3f32e62 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/DgcBusinessRuleServiceApplication.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/DgcBusinessRuleServiceApplication.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/config/DgcConfigProperties.java b/src/main/java/eu/europa/ec/dgc/businessrule/config/DgcConfigProperties.java index 9a8b12e..962da8a 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/config/DgcConfigProperties.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/config/DgcConfigProperties.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- @@ -29,12 +29,18 @@ @ConfigurationProperties("dgc") public class DgcConfigProperties { - private final CertificatesDownloader certificatesDownloader = new CertificatesDownloader(); + private final GatewayDownload businessRulesDownload = new GatewayDownload(); + + private final GatewayDownload valueSetsDownload = new GatewayDownload(); + + private final GatewayDownload countryListDownload = new GatewayDownload(); @Getter @Setter - public static class CertificatesDownloader { + public static class GatewayDownload { private Integer timeInterval; private Integer lockLimit; } + + } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/config/ErrorHandler.java b/src/main/java/eu/europa/ec/dgc/businessrule/config/ErrorHandler.java index f3e0e78..0fd7dab 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/config/ErrorHandler.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/config/ErrorHandler.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/config/OpenApiConfig.java b/src/main/java/eu/europa/ec/dgc/businessrule/config/OpenApiConfig.java index 2252e59..98fe27e 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/config/OpenApiConfig.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/config/OpenApiConfig.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.config; import io.swagger.v3.oas.models.OpenAPI; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/config/SchedulerConfig.java b/src/main/java/eu/europa/ec/dgc/businessrule/config/SchedulerConfig.java index c1ddb8f..c475149 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/config/SchedulerConfig.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/config/SchedulerConfig.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/config/ShedLockConfig.java b/src/main/java/eu/europa/ec/dgc/businessrule/config/ShedLockConfig.java index 765295f..8e7415f 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/config/ShedLockConfig.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/config/ShedLockConfig.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/entity/BusinessRuleEntity.java b/src/main/java/eu/europa/ec/dgc/businessrule/entity/BusinessRuleEntity.java index bf931d2..b6cc511 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/entity/BusinessRuleEntity.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/entity/BusinessRuleEntity.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.entity; import javax.persistence.Column; @@ -31,7 +51,7 @@ public class BusinessRuleEntity { private String identifier; @Column(name = "version", nullable = false) - String version = "1.0.0"; + String version; @Column(name = "country_code", nullable = false, length = 2) String country; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/entity/CountryListEntity.java b/src/main/java/eu/europa/ec/dgc/businessrule/entity/CountryListEntity.java index d4b3b97..62e1c3a 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/entity/CountryListEntity.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/entity/CountryListEntity.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.entity; import javax.persistence.Column; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/entity/ShedlockEntity.java b/src/main/java/eu/europa/ec/dgc/businessrule/entity/ShedlockEntity.java index 926548f..750af02 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/entity/ShedlockEntity.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/entity/ShedlockEntity.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/entity/ValueSetEntity.java b/src/main/java/eu/europa/ec/dgc/businessrule/entity/ValueSetEntity.java index c137094..409f727 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/entity/ValueSetEntity.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/entity/ValueSetEntity.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.entity; import javax.persistence.Column; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/exception/DgcaBusinessRulesResponseException.java b/src/main/java/eu/europa/ec/dgc/businessrule/exception/DgcaBusinessRulesResponseException.java index 05e1242..af583aa 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/exception/DgcaBusinessRulesResponseException.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/exception/DgcaBusinessRulesResponseException.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.exception; import lombok.Getter; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/model/BusinessRuleItem.java b/src/main/java/eu/europa/ec/dgc/businessrule/model/BusinessRuleItem.java new file mode 100644 index 0000000..4ba9f75 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/model/BusinessRuleItem.java @@ -0,0 +1,19 @@ +package eu.europa.ec.dgc.businessrule.model; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BusinessRuleItem { + + private String hash; + + private String identifier; + + private String version; + + private String country; + + private String rawData; +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/model/ValueSetItem.java b/src/main/java/eu/europa/ec/dgc/businessrule/model/ValueSetItem.java new file mode 100644 index 0000000..a665c3a --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/model/ValueSetItem.java @@ -0,0 +1,15 @@ +package eu.europa.ec.dgc.businessrule.model; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ValueSetItem { + + private String hash; + + private String id; + + private String rawData; +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/repository/BusinessRuleRepository.java b/src/main/java/eu/europa/ec/dgc/businessrule/repository/BusinessRuleRepository.java index f39828d..98bbd05 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/repository/BusinessRuleRepository.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/repository/BusinessRuleRepository.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.repository; import eu.europa.ec.dgc.businessrule.entity.BusinessRuleEntity; @@ -14,4 +34,6 @@ public interface BusinessRuleRepository extends JpaRepository findAllByCountryOrderByIdentifierAsc(String country); BusinessRuleEntity findOneByCountryAndHash(String country, String hash); + + void deleteByHashNotIn(List hashes); } \ No newline at end of file diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/repository/CountryListRepository.java b/src/main/java/eu/europa/ec/dgc/businessrule/repository/CountryListRepository.java index 9b3d14e..bcaba7e 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/repository/CountryListRepository.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/repository/CountryListRepository.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.repository; import eu.europa.ec.dgc.businessrule.entity.CountryListEntity; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/repository/ValueSetRepository.java b/src/main/java/eu/europa/ec/dgc/businessrule/repository/ValueSetRepository.java index 65e9f3b..0c358e2 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/repository/ValueSetRepository.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/repository/ValueSetRepository.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.repository; import eu.europa.ec.dgc.businessrule.entity.ValueSetEntity; @@ -10,4 +30,6 @@ public interface ValueSetRepository extends JpaRepository findAllByOrderByIdAsc(); ValueSetEntity findOneByHash(String hash); + + void deleteByHashNotIn(List hashes); } \ No newline at end of file diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleController.java b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleController.java index 254570f..5d9789d 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleController.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleController.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- @@ -22,9 +22,11 @@ import eu.europa.ec.dgc.businessrule.entity.BusinessRuleEntity; import eu.europa.ec.dgc.businessrule.exception.DgcaBusinessRulesResponseException; +import eu.europa.ec.dgc.businessrule.model.BusinessRuleItem; import eu.europa.ec.dgc.businessrule.restapi.dto.BusinessRuleListItemDto; import eu.europa.ec.dgc.businessrule.restapi.dto.ProblemReportDto; import eu.europa.ec.dgc.businessrule.service.BusinessRuleService; +import eu.europa.ec.dgc.businessrule.utils.BusinessRulesUtils; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -35,6 +37,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Locale; import javax.validation.Valid; @@ -58,10 +61,14 @@ @RequiredArgsConstructor public class BusinessRuleController { - private final BusinessRuleService businessRuleService; + private static final String API_VERSION_HEADER = "X-VERSION"; private static final String X_SIGNATURE_HEADER = "X-SIGNATURE"; + private final BusinessRuleService businessRuleService; + + private final BusinessRulesUtils businessRulesUtils; + /** * Http Method for getting the business rules list. */ @@ -84,17 +91,14 @@ public class BusinessRuleController { @ApiResponse( responseCode = "200", description = "Returns a list of all business rule ids country codes and hash values.", - headers = { - @Header( - name = "X-SIGNATURE", - description = "ECDSA signature of the returned value, if configured.") - }, content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = BusinessRuleListItemDto.class)))) } ) - public ResponseEntity> getRules() { + public ResponseEntity> getRules( + @RequestHeader(value = API_VERSION_HEADER, required = false) String apiVersion + ) { return ResponseEntity.ok(businessRuleService.getBusinessRulesList()); } @@ -123,11 +127,6 @@ public ResponseEntity> getRules() { @ApiResponse( responseCode = "200", description = "Returns a list of all business rule ids country codes and hash values for a country.", - headers = { - @Header( - name = "X-SIGNATURE", - description = "ECDSA signature of the returned value, if configured.") - }, content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = BusinessRuleListItemDto.class)))), @@ -140,6 +139,7 @@ public ResponseEntity> getRules() { } ) public ResponseEntity> getRulesForCountry( + @RequestHeader(value = API_VERSION_HEADER, required = false) String apiVersion, @Valid @PathVariable("country") String country ) { validateCountryParameter(country); @@ -220,7 +220,8 @@ public ResponseEntity> getRulesForCountry( mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemReportDto.class))) }) - public ResponseEntity getRuleByHash( + public ResponseEntity getRuleByCountryAndHash( + @RequestHeader(value = API_VERSION_HEADER, required = false) String apiVersion, @Valid @PathVariable("country") String country, @Valid @PathVariable("hash") String hash ) { @@ -247,15 +248,31 @@ public ResponseEntity getRuleByHash( public ResponseEntity createRule( @RequestHeader(value = "X_COUNTRY") String country, @RequestHeader(value = "X_ID") String id, + @RequestHeader(value = "X_VER") String version, @RequestBody String ruleData) { + String hash; validateCountryParameter(country); if (id == null || id.isBlank()) { throw new DgcaBusinessRulesResponseException(HttpStatus.BAD_REQUEST, "0x003", "Possible reasons: " + "The id of the rule is not set.", id,""); } - businessRuleService.saveBusinessRule(id, country.toUpperCase(Locale.ROOT), ruleData); + + try { + hash = businessRulesUtils.calculateHash(ruleData); + } catch (NoSuchAlgorithmException e) { + log.error("Calculation of hash failed:", e); + throw new DgcaBusinessRulesResponseException(HttpStatus.INTERNAL_SERVER_ERROR, "0x500", + "Internal Server Error","",""); + } + BusinessRuleItem bri = new BusinessRuleItem(); + bri.setHash(hash); + bri.setIdentifier(id); + bri. setCountry(country); + bri.setVersion(version); + bri.setRawData(ruleData); + businessRuleService.saveBusinessRule(bri); return ResponseEntity.ok("Upload: OK"); } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListController.java b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListController.java index a654c54..0361dc1 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListController.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListController.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- @@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -48,6 +49,8 @@ @RequiredArgsConstructor public class CountryListController { + private static final String API_VERSION_HEADER = "X-VERSION"; + private final CountryListService countryListService; /** @@ -70,11 +73,6 @@ public class CountryListController { @ApiResponse( responseCode = "200", description = "Returns a JSON list, with all onboarded member states as country code.", - headers = { - @Header( - name = "X-SIGNATURE", - description = "ECDSA signature of the returned value, if configured.") - }, content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = String.class)), @@ -86,7 +84,9 @@ public class CountryListController { })) } ) - public ResponseEntity getCountryList() { + public ResponseEntity getCountryList( + @RequestHeader(value = API_VERSION_HEADER, required = false) String apiVersion + ) { return ResponseEntity.ok(countryListService.getCountryList()); } @@ -95,7 +95,7 @@ public ResponseEntity getCountryList() { */ @PostMapping(path = "", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @Hidden - public ResponseEntity createRule( + public ResponseEntity createCountryList( @RequestBody String countryListData) { countryListService.saveCountryList(countryListData); diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetController.java b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetController.java index 3f7d585..8d0f962 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetController.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetController.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- @@ -53,6 +53,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -62,6 +63,8 @@ @RequiredArgsConstructor public class ValueSetController { + private static final String API_VERSION_HEADER = "X-VERSION"; + private final BusinessRulesUtils businessRulesUtils; private final ValueSetService valueSetService; @@ -89,11 +92,6 @@ public class ValueSetController { @ApiResponse( responseCode = "200", description = "Returns a list of all value set ids and there hash values.", - headers = { - @Header( - name = "X-SIGNATURE", - description = "ECDSA signature of the returned value, if configured.") - }, content = @Content( mediaType = MediaType.APPLICATION_JSON_VALUE, array = @ArraySchema(schema = @Schema(implementation = ValueSetListItemDto.class)), @@ -135,8 +133,9 @@ public class ValueSetController { })) } ) - public ResponseEntity> getValueSetList() { - + public ResponseEntity> getValueSetList( + @RequestHeader(value = API_VERSION_HEADER, required = false) String apiVersion + ) { return ResponseEntity.ok(valueSetService.getValueSetsList()); } @@ -194,6 +193,7 @@ public ResponseEntity> getValueSetList() { )) }) public ResponseEntity getValueSet( + @RequestHeader(value = API_VERSION_HEADER, required = false) String apiVersion, @Valid @PathVariable("hash") String hash ) { ValueSetEntity vse = valueSetService.getValueSetByHash(hash); @@ -226,12 +226,21 @@ public ResponseEntity loadDummyData() { private void loadValuesetFile(String filename, String valueSetName) { Resource resource = new ClassPathResource(filename); + String rawData; + String hash; try { - valueSetService.saveValueSet(valueSetName, - IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8)); + rawData = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); + hash = businessRulesUtils.calculateHash(rawData); + + } catch (NoSuchAlgorithmException e) { + log.error("Calculation of hash failed:", e); + return; } catch (IOException e) { log.error("Could not read file: " + valueSetName); + return; } + + valueSetService.saveValueSet(hash, valueSetName, rawData); } } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/BusinessRuleListItemDto.java b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/BusinessRuleListItemDto.java index 6ab6f2c..6d38830 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/BusinessRuleListItemDto.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/BusinessRuleListItemDto.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.restapi.dto; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ProblemReportDto.java b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ProblemReportDto.java index 836c219..b0db429 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ProblemReportDto.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ProblemReportDto.java @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ValueSetListItemDto.java b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ValueSetListItemDto.java index 705356a..e9cc7d5 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ValueSetListItemDto.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/restapi/dto/ValueSetListItemDto.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.restapi.dto; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleService.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleService.java index 23fdf5d..9319c82 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleService.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleService.java @@ -1,12 +1,36 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.service; import eu.europa.ec.dgc.businessrule.entity.BusinessRuleEntity; +import eu.europa.ec.dgc.businessrule.model.BusinessRuleItem; import eu.europa.ec.dgc.businessrule.repository.BusinessRuleRepository; import eu.europa.ec.dgc.businessrule.restapi.dto.BusinessRuleListItemDto; import eu.europa.ec.dgc.businessrule.utils.BusinessRulesUtils; +import eu.europa.ec.dgc.gateway.connector.model.ValidationRule; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -18,35 +42,13 @@ @Service public class BusinessRuleService { - private final BusinessRulesUtils businessRulesUtils; - private final BusinessRuleRepository businessRuleRepository; - /** - * Saves a Business rule. - * - */ - @Transactional - public void saveBusinessRule(String ruleId, String ruleCountry, String ruleData) { - String hash; - try { - hash = businessRulesUtils.calculateHash(ruleData); - } catch (NoSuchAlgorithmException e) { - log.error("Calculation of hash failed:", e); - return; - } - - BusinessRuleEntity bre = new BusinessRuleEntity(); - bre.setIdentifier(ruleId); - bre.setCountry(ruleCountry); - bre.setRawData(ruleData.toUpperCase(Locale.ROOT)); - bre.setHash(hash); - - businessRuleRepository.save(bre); - } + private final BusinessRulesUtils businessRulesUtils; /** * Gets list of all business rules ids and hashes. + * */ public List getBusinessRulesList() { @@ -64,7 +66,6 @@ public List getBusinessRulesListForCountry(String count return rulesItems; } - /**f * Gets a business rule by hash. */ @@ -73,4 +74,77 @@ public BusinessRuleEntity getBusinessRuleByCountryAndHash(String country, String return businessRuleRepository.findOneByCountryAndHash(country, hash); } + + /** + * Updates the list of business rules. + * @param businessRules list of actual value sets + */ + @Transactional + public void updateBusinesRules(List businessRules) { + List ruleHashes = + businessRules.stream().map(BusinessRuleItem::getHash).collect(Collectors.toList()); + List alreadyStoredRules = getBusinessRulesHashList(); + + if (ruleHashes.isEmpty()) { + businessRuleRepository.deleteAll(); + } else { + businessRuleRepository.deleteByHashNotIn(ruleHashes); + } + + for (BusinessRuleItem rule : businessRules) { + if (!alreadyStoredRules.contains(rule.getHash())) { + saveBusinessRule(rule); + } + } + + } + + /** + * Saves a Business rule. + * @param rule The rule to be saved. + */ + @Transactional + public void saveBusinessRule(BusinessRuleItem rule) { + BusinessRuleEntity bre = new BusinessRuleEntity(); + bre.setHash(rule.getHash()); + bre.setIdentifier(rule.getIdentifier()); + bre.setCountry(rule.getCountry().toUpperCase(Locale.ROOT)); + bre.setVersion(rule.getVersion()); + bre.setRawData(rule.getRawData()); + + businessRuleRepository.save(bre); + } + + /** + * Creates a List of business rule items from a list of validation rules. + * @param validationRules the list containing the validation rules. + * @return List of BusinessRuleItems. + */ + public List createBusinessRuleItemList(List validationRules) + throws NoSuchAlgorithmException { + List businessRuleItems = new ArrayList<>(); + + for (ValidationRule validationRule: validationRules) { + BusinessRuleItem businessRuleItem = new BusinessRuleItem(); + + businessRuleItem.setHash(businessRulesUtils.calculateHash(validationRule.getRawJson())); + businessRuleItem.setIdentifier(validationRule.getIdentifier()); + businessRuleItem.setCountry(validationRule.getCountry()); + businessRuleItem.setVersion(validationRule.getVersion()); + businessRuleItem.setRawData(validationRule.getRawJson()); + + businessRuleItems.add(businessRuleItem); + } + + return businessRuleItems; + } + + + /** + * Gets a list of hash values of all stored business rules. + * @return List of hash values + */ + private List getBusinessRulesHashList() { + return getBusinessRulesList().stream().map(BusinessRuleListItemDto::getHash).collect(Collectors.toList()); + } } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/CountryListService.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/CountryListService.java index 94d3ed6..2b99a83 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/service/CountryListService.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/CountryListService.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.service; import eu.europa.ec.dgc.businessrule.entity.CountryListEntity; @@ -17,7 +37,8 @@ public class CountryListService { private final CountryListRepository countryListRepository; /** - * returns the country list. + * Gets the actual country list. + * @return the country list. */ @Transactional public String getCountryList() { @@ -29,8 +50,23 @@ public String getCountryList() { } } + /** - * Saves a country list by replacing an old one. + * Updates a country List, if it is different from the old one. + * @param newCountryListData new country list data + */ + @Transactional + public void updateCountryList(String newCountryListData) { + String oldList = getCountryList(); + if (!newCountryListData.equals(oldList)) { + saveCountryList(newCountryListData); + } + } + + + /** + * Saves a country list by replacing an old one. + * @param listData the country list to be saved. */ @Transactional @@ -40,4 +76,6 @@ public void saveCountryList(String listData) { } + + } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadService.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadService.java new file mode 100644 index 0000000..ad5c868 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadService.java @@ -0,0 +1,40 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package eu.europa.ec.dgc.businessrule.service; + +public interface GatewayDataDownloadService { + + /** + * Synchronises the business rules with the gateway. + */ + void downloadBusinessRules(); + + /** + * Synchronises the value sets with the gateway. + */ + void downloadValueSets(); + + /** + * Synchronises the country list with the gateway. + */ + void downloadCountryList(); + +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadServiceImpl.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadServiceImpl.java new file mode 100644 index 0000000..af62673 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadServiceImpl.java @@ -0,0 +1,131 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package eu.europa.ec.dgc.businessrule.service; + +import eu.europa.ec.dgc.businessrule.model.BusinessRuleItem; +import eu.europa.ec.dgc.businessrule.model.ValueSetItem; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import net.minidev.json.JSONArray; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + + +/** + * A service to download the valuesets, business rules and country list from the digital covid certificate gateway. + */ +@Slf4j +@RequiredArgsConstructor +@Component +@Profile("!btp") +public class GatewayDataDownloadServiceImpl implements GatewayDataDownloadService { + + private final DgcGatewayValidationRuleDownloadConnector dgcRuleConnector; + + private final DgcGatewayValueSetDownloadConnector dgcValueSetConnector; + + private final DgcGatewayCountryListDownloadConnector dgcCountryListConnector; + + private final BusinessRuleService businessRuleService; + + private final ValueSetService valueSetService; + + private final CountryListService countryListService; + + @Override + @Scheduled(fixedDelayString = "${dgc.businessRulesDownload.timeInterval}") + @SchedulerLock(name = "GatewayDataDownloadService_downloadBusinessRules", lockAtLeastFor = "PT0S", + lockAtMostFor = "${dgc.businessRulesDownload.lockLimit}") + public void downloadBusinessRules() { + List ruleItems; + + log.info("Business rules download started"); + + try { + ruleItems = businessRuleService.createBusinessRuleItemList(dgcRuleConnector.getValidationRules().flat()); + } catch (NoSuchAlgorithmException e) { + log.error("Failed to hash business rules on download.",e); + return; + } + + if (!ruleItems.isEmpty()) { + businessRuleService.updateBusinesRules(ruleItems); + } else { + log.warn("The download of the business rules seems to fail, as the download connector " + + "returns an empty business rules list.-> No data was changed."); + } + + log.info("Business rules finished"); + } + + @Override + @Scheduled(fixedDelayString = "${dgc.valueSetsDownload.timeInterval}") + @SchedulerLock(name = "GatewayDataDownloadService_downloadValueSets", lockAtLeastFor = "PT0S", + lockAtMostFor = "${dgc.valueSetsDownload.lockLimit}") + public void downloadValueSets() { + List valueSetItems; + log.info("Valuesets download started"); + + try { + valueSetItems = valueSetService.createValueSetItemListFromMap(dgcValueSetConnector.getValueSets()); + } catch (NoSuchAlgorithmException e) { + log.error("Failed to hash business rules on download.",e); + return; + } + + if (!valueSetItems.isEmpty()) { + valueSetService.updateValueSets(valueSetItems); + } else { + log.warn("The download of the value sets seems to fail, as the download connector " + + "returns an empty value sets list.-> No data was changed."); + } + + log.info("Valuesets download finished"); + } + + @Override + @Scheduled(fixedDelayString = "${dgc.countryListDownload.timeInterval}") + @SchedulerLock(name = "GatewayDataDownloadService_downloadCountryList", lockAtLeastFor = "PT0S", + lockAtMostFor = "${dgc.countryListDownload.lockLimit}") + public void downloadCountryList() { + log.info("Country list download started"); + + List countryList = dgcCountryListConnector.getCountryList(); + + if (!countryList.isEmpty()) { + String countryListJsonStr = JSONArray.toJSONString(countryList); + countryListService.updateCountryList(countryListJsonStr); + } else { + log.warn("The download of the country list seems to fail, as the download connector " + + "returns an empty country list.-> No data was changed."); + } + + log.info("Country list download finished"); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java index e996287..ceace91 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java @@ -1,15 +1,37 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.service; import eu.europa.ec.dgc.businessrule.entity.ValueSetEntity; +import eu.europa.ec.dgc.businessrule.model.ValueSetItem; import eu.europa.ec.dgc.businessrule.repository.ValueSetRepository; import eu.europa.ec.dgc.businessrule.restapi.dto.ValueSetListItemDto; import eu.europa.ec.dgc.businessrule.utils.BusinessRulesUtils; -import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,45 +44,92 @@ public class ValueSetService { private final ValueSetRepository valueSetRepository; + + /** + * Gets list of all value set ids and hashes. + */ + public List getValueSetsList() { + + List valueSetItems = valueSetRepository.findAllByOrderByIdAsc(); + return valueSetItems; + } + + + /** + * Gets a value set by its hash value. + */ + @Transactional + public ValueSetEntity getValueSetByHash(String hash) { + + return valueSetRepository.findOneByHash(hash); + } + /** - * Saves a valueset. - * + * Updates the list of value sets. + * @param valueSets list of actual value sets */ @Transactional - public void saveValueSet(String valueSetName, String valueSetData) { - String hash; - try { - hash = businessRulesUtils.calculateHash(valueSetData); - } catch (NoSuchAlgorithmException e) { - log.error("Calculation of hash failed:", e); - return; + public void updateValueSets(List valueSets) { + List valueSetsHashes = valueSets.stream().map(ValueSetItem::getHash).collect(Collectors.toList()); + List alreadyStoredValueSets = getValueSetsHashList(); + + if (valueSetsHashes.isEmpty()) { + valueSetRepository.deleteAll(); + } else { + valueSetRepository.deleteByHashNotIn(valueSetsHashes); } + for (ValueSetItem valueSet : valueSets) { + if (!alreadyStoredValueSets.contains(valueSet.getHash())) { + saveValueSet(valueSet.getHash(), valueSet.getId(), valueSet.getRawData()); + } + } + + } + + /** + * Saves a value set. + * @param hash The hash value of the value set data. + * @param valueSetName The name of the value set. + * @param valueSetData The raw value set data. + */ + @Transactional + public void saveValueSet(String hash, String valueSetName, String valueSetData) { + ValueSetEntity vse = new ValueSetEntity(); + vse.setHash(hash); vse.setId(valueSetName); vse.setRawData(valueSetData); - vse.setHash(hash); valueSetRepository.save(vse); } /** - * gets list of all valueset ids and hashes. + * Creates a List of value set items from a map of value sets without hashes. + * @param valueSetMap the map containing the row value sets. + * @return List of ValueSetItems */ - public List getValueSetsList() { + public List createValueSetItemListFromMap(Map valueSetMap) + throws NoSuchAlgorithmException { + List valueSetItems = new ArrayList<>(); + + for (Map.Entry vse: valueSetMap.entrySet()) { + ValueSetItem valueSetItem = new ValueSetItem(); + valueSetItem.setHash(businessRulesUtils.calculateHash(vse.getValue())); + valueSetItem.setId(vse.getKey()); + valueSetItem.setRawData(vse.getValue()); + valueSetItems.add(valueSetItem); + } - List valueSetItems = valueSetRepository.findAllByOrderByIdAsc(); return valueSetItems; } - /** - * Gets valueset by hash. + * Gets a list of hash values of all stored value sets. + * @return List of hash values */ - @Transactional - public ValueSetEntity getValueSetByHash(String hash) { - - return valueSetRepository.findOneByHash(hash); + private List getValueSetsHashList() { + return getValueSetsList().stream().map(ValueSetListItemDto::getHash).collect(Collectors.toList()); } } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/utils/BusinessRulesUtils.java b/src/main/java/eu/europa/ec/dgc/businessrule/utils/BusinessRulesUtils.java index fb4f367..fa00da5 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/utils/BusinessRulesUtils.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/utils/BusinessRulesUtils.java @@ -1,3 +1,23 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.utils; import java.math.BigInteger; diff --git a/src/main/resources/application-btp.yml b/src/main/resources/application-btp.yml index aadec68..133216a 100644 --- a/src/main/resources/application-btp.yml +++ b/src/main/resources/application-btp.yml @@ -2,3 +2,9 @@ dgc: gateway: connector: enabled: false + tls-trust-store: + path: false + tls-key-store: + path: false + trust-anchor: + path: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a3c5cce..a9e0409 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,7 @@ server: port: 8080 spring: application: - name: dgca-verifier-service + name: dgca-businessrule-service datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:dgc;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1; @@ -46,7 +46,32 @@ springdoc: swagger-ui: path: /swagger dgc: - certificatesDownloader: - timeInterval: 60000 - lockLimit: 1800000 + businessRulesDownload: + timeInterval: 1800000 + lockLimit: 3600000 + valueSetsDownload: + timeInterval: 1800000 + lockLimit: 3600000 + countryListDownload: + timeInterval: 1800000 + lockLimit: 3600000 + gateway: + connector: + enabled: true + endpoint: https://dgc-gateway.example.com + proxy: + enabled: false + max-cache-age: 300 + tls-trust-store: + password: dgcg-p4ssw0rd + path: classpath:tls_trust_store.p12 + tls-key-store: + alias: 1 + password: dgcg-p4ssw0rd + path: classpath:tls_key_store.p12 + trust-anchor: + alias: ta + password: dgcg-p4ssw0rd + path: classpath:trust_anchor.jks + diff --git a/src/main/resources/static/valuesets/country-2-codes.json b/src/main/resources/static/valuesets/country-2-codes.json index 7fd5aa6..50edfcd 100644 --- a/src/main/resources/static/valuesets/country-2-codes.json +++ b/src/main/resources/static/valuesets/country-2-codes.json @@ -1746,4 +1746,4 @@ "version": "" } } -} +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/OpenApiTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/OpenApiTest.java index 43f845d..11b9538 100644 --- a/src/test/java/eu/europa/ec/dgc/businessrule/OpenApiTest.java +++ b/src/test/java/eu/europa/ec/dgc/businessrule/OpenApiTest.java @@ -1,6 +1,29 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.net.URL; @@ -21,6 +44,14 @@ ) class OpenApiTest { + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; @Test void apiDocs() { diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleControllerIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleControllerIntegrationTest.java new file mode 100644 index 0000000..5b6bf54 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/BusinessRuleControllerIntegrationTest.java @@ -0,0 +1,351 @@ +package eu.europa.ec.dgc.businessrule.restapi.controller; + +import eu.europa.ec.dgc.businessrule.repository.BusinessRuleRepository; +import eu.europa.ec.dgc.businessrule.testdata.BusinessRulesTestHelper; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class BusinessRuleControllerIntegrationTest { + + private static final String API_VERSION_HEADER = "X-VERSION"; + + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; + + @Autowired + BusinessRuleRepository businessRuleRepository; + + @Autowired + BusinessRulesTestHelper businessRulesTestHelper; + + @Autowired + private MockMvc mockMvc; + + + @BeforeEach + void clearRepositoryData() { + businessRuleRepository.deleteAll(); + } + + @Test + void getEmptyRulesList() throws Exception { + mockMvc.perform(get("/rules").header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + } + + @Test + void getRulesList() throws Exception { + String expectedJson = "[{\"identifier\":\"VR-DE-1\",\"version\":\"1.0.0\",\"country\":\"DE\",\"hash\":" + + "\"ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\"},{\"identifier\":\"VR-DE-2\"," + + "\"version\":\"1.0.0\",\"country\":\"DE\",\"hash\":" + + "\"edd69d42d52a7b52059cfbea379e647039fc16117b75bf3dfec68c965552a2fd\"},{\"identifier\":\"VR-EU-1\"," + + "\"version\":\"1.0.0\",\"country\":\"EU\",\"hash\":" + + "\"7bbffe1ac60dc201cf4a1303de4b8ba25ffa5ab714d882a7e4e80dfbb2c08fe7\"}]"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + mockMvc.perform(get("/rules").header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getRulesListSameRuleWithDiffrentVersions() throws Exception { + String expectedJson = "[{\"identifier\":\"VR-DE-1\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\"}," + + "{\"identifier\":\"VR-DE-1\",\"version\":\"2.0.0\",\"country\":\"DE\"," + + "\"hash\":\"1706b888b9abc095e78ab1ebf32f2445a36c6a263b72634ae56476ecac5c89de\"}," + + "{\"identifier\":\"VR-DE-2\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"edd69d42d52a7b52059cfbea379e647039fc16117b75bf3dfec68c965552a2fd\"}," + + "{\"identifier\":\"VR-EU-1\",\"version\":\"1.0.0\",\"country\":\"EU\"," + + "\"hash\":\"7bbffe1ac60dc201cf4a1303de4b8ba25ffa5ab714d882a7e4e80dfbb2c08fe7\"}]"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_4, + BusinessRulesTestHelper.BR_IDENTIFIER_4, BusinessRulesTestHelper.BR_COUNTRY_4, + BusinessRulesTestHelper.BR_VERSION_4, BusinessRulesTestHelper.BR_DATA_4); + + + mockMvc.perform(get("/rules").header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getEmptyRulesListForCountry() throws Exception { + mockMvc.perform(get("/rules/" + BusinessRulesTestHelper.BR_COUNTRY_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + } + + @Test + void getRulesListForCountry() throws Exception { + String expectedJson = "[{\"identifier\":\"VR-DE-1\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\"}," + + "{\"identifier\":\"VR-DE-2\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"edd69d42d52a7b52059cfbea379e647039fc16117b75bf3dfec68c965552a2fd\"}]"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + mockMvc.perform(get("/rules/" + BusinessRulesTestHelper.BR_COUNTRY_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getRulesListForCountryRuleWithDiffrentVersions() throws Exception { + String expectedJson = "[{\"identifier\":\"VR-DE-1\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\"}," + + "{\"identifier\":\"VR-DE-1\",\"version\":\"2.0.0\",\"country\":\"DE\"," + + "\"hash\":\"1706b888b9abc095e78ab1ebf32f2445a36c6a263b72634ae56476ecac5c89de\"}," + + "{\"identifier\":\"VR-DE-2\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"edd69d42d52a7b52059cfbea379e647039fc16117b75bf3dfec68c965552a2fd\"}]"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_4, + BusinessRulesTestHelper.BR_IDENTIFIER_4, BusinessRulesTestHelper.BR_COUNTRY_4, + BusinessRulesTestHelper.BR_VERSION_4, BusinessRulesTestHelper.BR_DATA_4); + + mockMvc.perform(get("/rules/" + BusinessRulesTestHelper.BR_COUNTRY_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getRulesListForCountryWrongCountryFormat() throws Exception { + String expectedJson = "{\"code\":\"0x004\",\"problem\":\"Possible reasons: The Country Code has a wrong format." + + " Should be 2 char format.\",\"sendValue\":\"EUR\",\"details\":\"\"}"; + + mockMvc.perform(get("/rules/EUR") + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + expectedJson = "{\"code\":\"0x004\",\"problem\":\"Possible reasons: The Country Code has a wrong format." + + " Should be 2 char format.\",\"sendValue\":\"E\",\"details\":\"\"}"; + + mockMvc.perform(get("/rules/E") + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + expectedJson = "{\"code\":\"0x004\",\"problem\":\"Possible reasons: The Country Code has a wrong format." + + " Should be 2 char format.\",\"sendValue\":\"22\",\"details\":\"\"}"; + + mockMvc.perform(get("/rules/22") + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getRuleByCountryAndHash() throws Exception { + String expectedJson = "[{\"identifier\":\"VR-DE-1\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\"}," + + "{\"identifier\":\"VR-DE-1\",\"version\":\"2.0.0\",\"country\":\"DE\"," + + "\"hash\":\"1706b888b9abc095e78ab1ebf32f2445a36c6a263b72634ae56476ecac5c89de\"}," + + "{\"identifier\":\"VR-DE-2\",\"version\":\"1.0.0\",\"country\":\"DE\"," + + "\"hash\":\"edd69d42d52a7b52059cfbea379e647039fc16117b75bf3dfec68c965552a2fd\"}]"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_4, + BusinessRulesTestHelper.BR_IDENTIFIER_4, BusinessRulesTestHelper.BR_COUNTRY_4, + BusinessRulesTestHelper.BR_VERSION_4, BusinessRulesTestHelper.BR_DATA_4); + + mockMvc.perform(get("/rules/" + BusinessRulesTestHelper.BR_COUNTRY_1 + "/" + + BusinessRulesTestHelper.BR_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(BusinessRulesTestHelper.BR_DATA_1)); + } + + @Test + void getRuleByCountryAndHashWrongCountryFormat() throws Exception { + String expectedJson = "{\"code\":\"0x004\",\"problem\":\"Possible reasons: The Country Code has a wrong format." + + " Should be 2 char format.\",\"sendValue\":\"EUR\",\"details\":\"\"}"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_4, + BusinessRulesTestHelper.BR_IDENTIFIER_4, BusinessRulesTestHelper.BR_COUNTRY_4, + BusinessRulesTestHelper.BR_VERSION_4, BusinessRulesTestHelper.BR_DATA_4); + + mockMvc.perform(get("/rules/EUR/" + BusinessRulesTestHelper.BR_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + expectedJson = "{\"code\":\"0x004\",\"problem\":\"Possible reasons: The Country Code has a wrong format." + + " Should be 2 char format.\",\"sendValue\":\"E\",\"details\":\"\"}"; + + mockMvc.perform(get("/rules/E/" + BusinessRulesTestHelper.BR_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + expectedJson = "{\"code\":\"0x004\",\"problem\":\"Possible reasons: The Country Code has a wrong format." + + " Should be 2 char format.\",\"sendValue\":\"23\",\"details\":\"\"}"; + + mockMvc.perform(get("/rules/23/" + BusinessRulesTestHelper.BR_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getRuleByCountryAndHashWrongCountryHashCombination() throws Exception { + String expectedJson = "{\"code\":\"0x006\",\"problem\":\"Possible reasons: The provided hash or country may " + + "not be correct.\",\"sendValue\":\"country: EU, " + + "hash: ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\",\"details\":\"\"}"; + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_4, + BusinessRulesTestHelper.BR_IDENTIFIER_4, BusinessRulesTestHelper.BR_COUNTRY_4, + BusinessRulesTestHelper.BR_VERSION_4, BusinessRulesTestHelper.BR_DATA_4); + + mockMvc.perform(get("/rules/" + BusinessRulesTestHelper.BR_COUNTRY_3 + + "/" + BusinessRulesTestHelper.BR_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + @Test + void getRuleByCountryAndHashNotExist() throws Exception { + String expectedJson = "{\"code\":\"0x006\",\"problem\":\"Possible reasons: The provided hash or country may " + + "not be correct.\",\"sendValue\":\"country: DE, " + + "hash: ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c\",\"details\":\"\"}"; + + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_2, + BusinessRulesTestHelper.BR_IDENTIFIER_2, BusinessRulesTestHelper.BR_COUNTRY_2, + BusinessRulesTestHelper.BR_VERSION_2, BusinessRulesTestHelper.BR_DATA_2); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_3, + BusinessRulesTestHelper.BR_IDENTIFIER_3, BusinessRulesTestHelper.BR_COUNTRY_3, + BusinessRulesTestHelper.BR_VERSION_3, BusinessRulesTestHelper.BR_DATA_3); + + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_4, + BusinessRulesTestHelper.BR_IDENTIFIER_4, BusinessRulesTestHelper.BR_COUNTRY_4, + BusinessRulesTestHelper.BR_VERSION_4, BusinessRulesTestHelper.BR_DATA_4); + + mockMvc.perform(get("/rules/" + BusinessRulesTestHelper.BR_COUNTRY_1 + + "/" + BusinessRulesTestHelper.BR_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListControllerIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListControllerIntegrationTest.java index d1aa0f8..b169ada 100644 --- a/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListControllerIntegrationTest.java +++ b/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/CountryListControllerIntegrationTest.java @@ -1,12 +1,36 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + package eu.europa.ec.dgc.businessrule.restapi.controller; import eu.europa.ec.dgc.businessrule.entity.CountryListEntity; import eu.europa.ec.dgc.businessrule.repository.CountryListRepository; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -23,6 +47,15 @@ class CountryListControllerIntegrationTest { + "\"CZ\", \"FR\", \"HU\", \"SI\", \"DK\", \"HR\", \"MT\", \"SK\", \"DE\", \"IT\", \"NL\", \"FI\", \"EE\", " + "\"CY\", \"AT\", \"SE\", \"IE\", \"LV\", \"PL\"]"; + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; + @Autowired CountryListRepository countryListRepository; diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetControllerIntegrationTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetControllerIntegrationTest.java new file mode 100644 index 0000000..c134c59 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/businessrule/restapi/controller/ValueSetControllerIntegrationTest.java @@ -0,0 +1,161 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-businessrule-service + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ + +package eu.europa.ec.dgc.businessrule.restapi.controller; + +import eu.europa.ec.dgc.businessrule.repository.ValueSetRepository; +import eu.europa.ec.dgc.businessrule.testdata.BusinessRulesTestHelper; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class ValueSetControllerIntegrationTest { + + private static final String API_VERSION_HEADER = "X-VERSION"; + + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; + + + @Autowired + ValueSetRepository valueSetRepository; + + @Autowired + BusinessRulesTestHelper businessRulesTestHelper; + + @Autowired + private MockMvc mockMvc; + + + + @BeforeEach + void clearRepositoryData() { + valueSetRepository.deleteAll(); + } + + @Test + void getEmptyValueSetList() throws Exception { + mockMvc.perform(get("/valuesets").header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + } + + + + @Test + void getValueSetList() throws Exception { + + String expectedJson = "[{\"id\":\""+BusinessRulesTestHelper.VALUESET_IDENTIFIER_1 +"\"," + + "\"hash\":\""+BusinessRulesTestHelper.VALUESET_HASH_1+"\"}," + + "{\"id\":\""+BusinessRulesTestHelper.VALUESET_IDENTIFIER_2 +"\"," + + "\"hash\":\""+BusinessRulesTestHelper.VALUESET_HASH_2+"\"}]"; + + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_1, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, + BusinessRulesTestHelper.VALUESET_DATA_1); + + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_2, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_2, + BusinessRulesTestHelper.VALUESET_DATA_2); + + mockMvc.perform(get("/valuesets").header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + } + + + @Test + void getValueSet() throws Exception { + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_1, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, + BusinessRulesTestHelper.VALUESET_DATA_1); + + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_2, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_2, + BusinessRulesTestHelper.VALUESET_DATA_2); + + mockMvc.perform(get("/valuesets/" + BusinessRulesTestHelper.VALUESET_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(BusinessRulesTestHelper.VALUESET_DATA_1)); + + mockMvc.perform(get("/valuesets/" + BusinessRulesTestHelper.VALUESET_HASH_2) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(BusinessRulesTestHelper.VALUESET_DATA_2)); + + } + + @Test + void getValueSetNotExist() throws Exception { + + String expectedJson = "{\"code\":\"0x001\",\"problem\":\"Possible reasons: The provided hash value is " + + "not correct\",\"sendValue\":\""+BusinessRulesTestHelper.VALUESET_HASH_1+"\",\"details\":\"\"}"; + + + mockMvc.perform(get("/valuesets/" + BusinessRulesTestHelper.VALUESET_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_2, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_2, + BusinessRulesTestHelper.VALUESET_DATA_2); + + mockMvc.perform(get("/valuesets/" + BusinessRulesTestHelper.VALUESET_HASH_2) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(BusinessRulesTestHelper.VALUESET_DATA_2)); + + mockMvc.perform(get("/valuesets/" + BusinessRulesTestHelper.VALUESET_HASH_1) + .header(API_VERSION_HEADER, "1.0")) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + } + + +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleServiceTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleServiceTest.java new file mode 100644 index 0000000..22fcde8 --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/businessrule/service/BusinessRuleServiceTest.java @@ -0,0 +1,176 @@ +package eu.europa.ec.dgc.businessrule.service; + +import eu.europa.ec.dgc.businessrule.entity.BusinessRuleEntity; +import eu.europa.ec.dgc.businessrule.model.BusinessRuleItem; +import eu.europa.ec.dgc.businessrule.repository.BusinessRuleRepository; +import eu.europa.ec.dgc.businessrule.testdata.BusinessRulesTestHelper; +import eu.europa.ec.dgc.businessrule.utils.BusinessRulesUtils; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.model.ValidationRule; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + + + +@SpringBootTest +@AutoConfigureMockMvc +class BusinessRuleServiceTest { + + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; + + @Autowired + BusinessRuleService businessRuleService; + + @Autowired + BusinessRuleRepository businessRuleRepository; + + @Autowired + BusinessRulesTestHelper businessRulesTestHelper; + + @Autowired + BusinessRulesUtils businessRulesUtils; + + @BeforeEach + void clearRepositoryData() { + businessRuleRepository.deleteAll(); + } + + @Test + void updateBusinessRulesWithExisting() throws Exception { + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + List businessRuleItems = new ArrayList<>(); + + BusinessRuleItem businessRuleItem = new BusinessRuleItem(); + + businessRuleItem.setHash(businessRulesUtils.calculateHash(BusinessRulesTestHelper.BR_DATA_1)); + businessRuleItem.setIdentifier(BusinessRulesTestHelper.BR_IDENTIFIER_1); + businessRuleItem.setCountry(BusinessRulesTestHelper.BR_COUNTRY_1); + businessRuleItem.setVersion(BusinessRulesTestHelper.BR_VERSION_1); + businessRuleItem.setRawData(BusinessRulesTestHelper.BR_DATA_1); + businessRuleItems.add(businessRuleItem); + + businessRuleService.updateBusinesRules(businessRuleItems); + + Assertions.assertEquals(1, businessRuleRepository.count()); + } + + @Test + void updateBusinessRulesWithEmptyList() { + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + List businessRuleItems = new ArrayList<>(); + + businessRuleService.updateBusinesRules(businessRuleItems); + + Assertions.assertEquals(0, businessRuleRepository.count()); + } + + @Test + void updateBusinessRule() throws Exception { + businessRulesTestHelper.insertBusinessRule(BusinessRulesTestHelper.BR_HASH_1, + BusinessRulesTestHelper.BR_IDENTIFIER_1, BusinessRulesTestHelper.BR_COUNTRY_1, + BusinessRulesTestHelper.BR_VERSION_1, BusinessRulesTestHelper.BR_DATA_1); + + List businessRuleItems = new ArrayList<>(); + + BusinessRuleItem businessRuleItem = new BusinessRuleItem(); + + businessRuleItem.setHash(businessRulesUtils.calculateHash(BusinessRulesTestHelper.BR_DATA_1)); + businessRuleItem.setIdentifier(BusinessRulesTestHelper.BR_IDENTIFIER_1); + businessRuleItem.setCountry(BusinessRulesTestHelper.BR_COUNTRY_1); + businessRuleItem.setVersion(BusinessRulesTestHelper.BR_VERSION_1); + businessRuleItem.setRawData(BusinessRulesTestHelper.BR_DATA_1); + businessRuleItems.add(businessRuleItem); + + BusinessRuleItem Item2 = new BusinessRuleItem(); + + Item2.setHash(businessRulesUtils.calculateHash(BusinessRulesTestHelper.BR_DATA_2)); + Item2.setIdentifier(BusinessRulesTestHelper.BR_IDENTIFIER_2); + Item2.setCountry(BusinessRulesTestHelper.BR_COUNTRY_2); + Item2.setVersion(BusinessRulesTestHelper.BR_VERSION_2); + Item2.setRawData(BusinessRulesTestHelper.BR_DATA_2); + businessRuleItems.add(Item2); + + businessRuleService.updateBusinesRules(businessRuleItems); + + Assertions.assertEquals(2, businessRuleRepository.count()); + + businessRuleItems.remove(0); + + businessRuleService.updateBusinesRules(businessRuleItems); + + List result = businessRuleRepository.findAll(); + Assertions.assertEquals(1, result.size()); + + BusinessRuleEntity resultEntity = result.get(0); + Assertions.assertEquals(businessRulesUtils.calculateHash(BusinessRulesTestHelper.BR_DATA_2), + resultEntity.getHash()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_IDENTIFIER_2, resultEntity.getIdentifier()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_COUNTRY_2, resultEntity.getCountry()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_VERSION_2, resultEntity.getVersion()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_DATA_2, resultEntity.getRawData()); + } + + + @Test + void createBusinessRuleItemList() throws Exception{ + List validationRules = new ArrayList<>(); + List businessRuleItems; + + ValidationRule item1 = new ValidationRule(); + item1.setIdentifier(BusinessRulesTestHelper.BR_IDENTIFIER_1); + item1.setVersion(BusinessRulesTestHelper.BR_VERSION_1); + item1.setCountry(BusinessRulesTestHelper.BR_COUNTRY_1); + item1.setRawJson(BusinessRulesTestHelper.BR_DATA_1); + + validationRules.add(item1); + + ValidationRule item2 = new ValidationRule(); + item2.setIdentifier(BusinessRulesTestHelper.BR_IDENTIFIER_3); + item2.setVersion(BusinessRulesTestHelper.BR_VERSION_3); + item2.setCountry(BusinessRulesTestHelper.BR_COUNTRY_3); + item2.setRawJson(BusinessRulesTestHelper.BR_DATA_3); + + validationRules.add(item2); + + businessRuleItems = businessRuleService.createBusinessRuleItemList(validationRules); + + Assertions.assertEquals(2, businessRuleItems.size()); + + BusinessRuleItem resultItem1 = businessRuleItems.get(0); + Assertions.assertEquals(BusinessRulesTestHelper.BR_HASH_1, resultItem1.getHash()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_IDENTIFIER_1, resultItem1.getIdentifier()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_VERSION_1, resultItem1.getVersion()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_COUNTRY_1, resultItem1.getCountry()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_DATA_1, resultItem1.getRawData()); + + BusinessRuleItem resultItem2 = businessRuleItems.get(1); + Assertions.assertEquals(BusinessRulesTestHelper.BR_HASH_3, resultItem2.getHash()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_IDENTIFIER_3, resultItem2.getIdentifier()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_VERSION_3, resultItem2.getVersion()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_COUNTRY_3, resultItem2.getCountry()); + Assertions.assertEquals(BusinessRulesTestHelper.BR_DATA_3, resultItem2.getRawData()); + + } +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/service/CountryListServiceTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/service/CountryListServiceTest.java new file mode 100644 index 0000000..92c035d --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/businessrule/service/CountryListServiceTest.java @@ -0,0 +1,77 @@ +package eu.europa.ec.dgc.businessrule.service; + +import eu.europa.ec.dgc.businessrule.entity.CountryListEntity; +import eu.europa.ec.dgc.businessrule.repository.CountryListRepository; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + + +@SpringBootTest +@AutoConfigureMockMvc +class CountryListServiceTest { + + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; + + @Autowired + CountryListService countryListService; + + @Autowired + CountryListRepository countryListRepository; + + @BeforeEach + void clearRepositoryData() { + countryListRepository.deleteAll(); + } + + + @Test + void updateCountryList() { + String countryList_1 = "[\"BE\", \"EL\", \"LT\", \"PT\", \"BG\"]"; + String countryList_2 = "[\"SE\", \"DE\", \"EU\", \"CZ\", \"DK\"]"; + + countryListService.updateCountryList(countryList_1); + + List cl = countryListRepository.findAll(); + Assertions.assertEquals(1, cl.size()); + CountryListEntity cle = cl.get(0); + Assertions.assertEquals(countryList_1, cle.getRawData()); + + countryListService.updateCountryList(countryList_2); + + cl = countryListRepository.findAll(); + Assertions.assertEquals(1, cl.size()); + cle = cl.get(0); + Assertions.assertEquals(countryList_2, cle.getRawData()); + + countryListService.updateCountryList(countryList_2); + + cl = countryListRepository.findAll(); + Assertions.assertEquals(1, cl.size()); + cle = cl.get(0); + Assertions.assertEquals(countryList_2, cle.getRawData()); + + countryListService.updateCountryList(countryList_1); + + cl = countryListRepository.findAll(); + Assertions.assertEquals(1, cl.size()); + cle = cl.get(0); + Assertions.assertEquals(countryList_1, cle.getRawData()); + } + +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/service/ValueSetServiceTest.java b/src/test/java/eu/europa/ec/dgc/businessrule/service/ValueSetServiceTest.java new file mode 100644 index 0000000..71929fb --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/businessrule/service/ValueSetServiceTest.java @@ -0,0 +1,146 @@ +package eu.europa.ec.dgc.businessrule.service; + +import eu.europa.ec.dgc.businessrule.entity.ValueSetEntity; +import eu.europa.ec.dgc.businessrule.model.ValueSetItem; +import eu.europa.ec.dgc.businessrule.repository.ValueSetRepository; +import eu.europa.ec.dgc.businessrule.testdata.BusinessRulesTestHelper; +import eu.europa.ec.dgc.businessrule.utils.BusinessRulesUtils; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayCountryListDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValidationRuleDownloadConnector; +import eu.europa.ec.dgc.gateway.connector.DgcGatewayValueSetDownloadConnector; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + + +@SpringBootTest +@AutoConfigureMockMvc +class ValueSetServiceTest { + + @MockBean + DgcGatewayValidationRuleDownloadConnector dgcGatewayValidationRuleDownloadConnector; + + @MockBean + DgcGatewayValueSetDownloadConnector dgcGatewayValueSetDownloadConnector; + + @MockBean + DgcGatewayCountryListDownloadConnector dgcGatewayCountryListDownloadConnector; + + @Autowired + ValueSetService valueSetService; + + @Autowired + ValueSetRepository valueSetRepository; + + @Autowired + BusinessRulesTestHelper businessRulesTestHelper; + + @Autowired + BusinessRulesUtils businessRulesUtils; + + @BeforeEach + void clearRepositoryData() { + valueSetRepository.deleteAll(); + } + + @Test + void updateValueSetsWithExisting() { + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_1, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, BusinessRulesTestHelper.VALUESET_DATA_1); + + List items = new ArrayList<>(); + + ValueSetItem item = new ValueSetItem(); + item.setHash(BusinessRulesTestHelper.VALUESET_HASH_1); + item.setId(BusinessRulesTestHelper.VALUESET_IDENTIFIER_1); + item.setRawData(BusinessRulesTestHelper.VALUESET_DATA_1); + + items.add(item); + + valueSetService.updateValueSets(items); + + Assertions.assertEquals(1, valueSetRepository.count()); + } + + @Test + void updateValueSetsWithEmptyList() { + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_1, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, BusinessRulesTestHelper.VALUESET_DATA_1); + + List items = new ArrayList<>(); + + valueSetService.updateValueSets(items); + + Assertions.assertEquals(0, valueSetRepository.count()); + } + + @Test + void updateValueSets() { + businessRulesTestHelper.insertValueSet(BusinessRulesTestHelper.VALUESET_HASH_1, + BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, BusinessRulesTestHelper.VALUESET_DATA_1); + + List items = new ArrayList<>(); + + ValueSetItem item = new ValueSetItem(); + item.setHash(BusinessRulesTestHelper.VALUESET_HASH_1); + item.setId(BusinessRulesTestHelper.VALUESET_IDENTIFIER_1); + item.setRawData(BusinessRulesTestHelper.VALUESET_DATA_1); + + items.add(item); + + ValueSetItem item2 = new ValueSetItem(); + item2.setHash(BusinessRulesTestHelper.VALUESET_HASH_2); + item2.setId(BusinessRulesTestHelper.VALUESET_IDENTIFIER_2); + item2.setRawData(BusinessRulesTestHelper.VALUESET_DATA_2); + + items.add(item2); + + valueSetService.updateValueSets(items); + + Assertions.assertEquals(2, valueSetRepository.count()); + + items.remove(0); + + valueSetService.updateValueSets(items); + + List result = valueSetRepository.findAll(); + Assertions.assertEquals(1, result.size()); + + ValueSetEntity resultEntity = result.get(0); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_HASH_2, resultEntity.getHash()); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_IDENTIFIER_2, resultEntity.getId()); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_DATA_2, resultEntity.getRawData()); + } + + + @Test + void createValueSetItemListFromMap() throws Exception{ + Map map = new HashMap<>(); + map.put(BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, BusinessRulesTestHelper.VALUESET_DATA_1); + map.put(BusinessRulesTestHelper.VALUESET_IDENTIFIER_2, BusinessRulesTestHelper.VALUESET_DATA_2); + + List list = valueSetService.createValueSetItemListFromMap(map); + + + Assertions.assertEquals(2, list.size()); + + ValueSetItem resultItem1 = list.get(0); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_HASH_1, resultItem1.getHash()); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_IDENTIFIER_1, resultItem1.getId()); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_DATA_1, resultItem1.getRawData()); + + ValueSetItem resultItem2 = list.get(1); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_HASH_2, resultItem2.getHash()); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_IDENTIFIER_2, resultItem2.getId()); + Assertions.assertEquals(BusinessRulesTestHelper.VALUESET_DATA_2, resultItem2.getRawData()); + + } +} \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/dgc/businessrule/testdata/BusinessRulesTestHelper.java b/src/test/java/eu/europa/ec/dgc/businessrule/testdata/BusinessRulesTestHelper.java new file mode 100644 index 0000000..f041d8e --- /dev/null +++ b/src/test/java/eu/europa/ec/dgc/businessrule/testdata/BusinessRulesTestHelper.java @@ -0,0 +1,187 @@ +package eu.europa.ec.dgc.businessrule.testdata; + +import eu.europa.ec.dgc.businessrule.entity.BusinessRuleEntity; +import eu.europa.ec.dgc.businessrule.entity.ValueSetEntity; +import eu.europa.ec.dgc.businessrule.repository.BusinessRuleRepository; +import eu.europa.ec.dgc.businessrule.repository.ValueSetRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class BusinessRulesTestHelper { + + public static final String BR_IDENTIFIER_1 = "VR-DE-1"; + public static final String BR_HASH_1 = "ce50e623fd57e482ad9edf63eae7c898d639056e716aeb7f9975a3471bf3e59c"; + public static final String BR_COUNTRY_1 = "DE"; + public static final String BR_VERSION_1 = "1.0.0"; + public static final String BR_DATA_1 = "{\n" + + " \"Identifier\": \"VR-DE-1\",\n" + + " \"Version\": \"1.0.0\",\n" + + " \"SchemaVersion\":\"1.0.0\",\n" + + " \"Engine\":\"CERTLOGIC\",\n" + + " \"EngineVersion\":\"1.0.0\",\n" + + " \"Type\":\"Acceptance\",\n" + + " \"Country\":\"DE\",\n" + + " \"CertificateType\":\"Vaccination\",\n" + + " \"Description\":[{\"lang\":\"en\",\"desc\":\"Vaccination must be from June and doses must be 2\"}],\n" + + " \"ValidFrom\":\"2021-06-27T07:46:40Z\",\n" + + " \"ValidTo\":\"2021-08-01T07:46:40Z\",\n" + + " \"AffectedFields\":[\"dt\",\"dn\"],\n" + + " \"Logic\":{\n" + + " \"and\": [\n" + + " {\">=\":[ {\"var\":\"dt\"}, \"2021-06-01T00:00:00Z\" ]},\n" + + " {\">=\":[ {\"var\":\"dn\"}, 2 ]}\n" + + " ]\n" + + " }\n" + + "}"; + + public static final String BR_IDENTIFIER_2 = "VR-DE-2"; + public static final String BR_HASH_2 = "edd69d42d52a7b52059cfbea379e647039fc16117b75bf3dfec68c965552a2fd"; + public static final String BR_COUNTRY_2 = "DE"; + public static final String BR_VERSION_2 = "1.0.0"; + public static final String BR_DATA_2 = "{\n" + + " \"Identifier\":\"VR-DE-2\",\n" + + " \"Type\":\"Acceptance\",\n" + + " \"Country\":\"DE\",\n" + + " \"Version\":\"1.0.0\",\n" + + " \"SchemaVersion\":\"1.0.0\",\n" + + " \"Engine\":\"CERTLOGIC\",\n" + + " \"EngineVersion\":\"1.0.0\",\n" + + " \"CertificateType\":\"Vaccination\",\n" + + " \"Description\":[\n" + + " {\n" + + " \"lang\":\"en\",\n" + + " \"desc\":\"Just the following vaccines are valid: Moderna,AstraZeneca,Biontech, J&J\"\n" + + " }\n" + + " ],\n" + + " \"ValidFrom\":\"2021-05-27T07:46:40Z\",\n" + + " \"ValidTo\":\"2030-06-01T07:46:40Z\",\n" + + " \"AffectedFields\":[\n" + + " \"v.0.mp\"\n" + + " ],\n" + + " \"Logic\":{\n" + + " \"in\":[\n" + + " {\n" + + " \"var\":\"payload.v.0.mp\"\n" + + " },\n" + + " [\n" + + " \"EU/1/20/1528\",\n" + + " \"EU/1/20/1507\",\n" + + " \"EU/1/21/1529\",\n" + + " \"EU/1/20/1525\"\n" + + " ]\n" + + " ]\n" + + " }\n" + + "}"; + + public static final String BR_IDENTIFIER_3 = "VR-EU-1"; + public static final String BR_HASH_3 = "7bbffe1ac60dc201cf4a1303de4b8ba25ffa5ab714d882a7e4e80dfbb2c08fe7"; + public static final String BR_COUNTRY_3 = "EU"; + public static final String BR_VERSION_3 = "1.0.0"; + public static final String BR_DATA_3 = "{\n" + + " \"Identifier\": \"VR-EU-1\",\n" + + " \"Version\": \"1.0.0\",\n" + + " \"SchemaVersion\":\"1.0.0\",\n" + + " \"Engine\":\"CERTLOGIC\",\n" + + " \"EngineVersion\":\"1.0.0\",\n" + + " \"Type\":\"Acceptance\",\n" + + " \"Country\":\"DE\",\n" + + " \"CertificateType\":\"Vaccination\",\n" + + " \"Description\":[{\"lang\":\"en\",\"desc\":\"Vaccination must be from June and doses must be 2\"}],\n" + + " \"ValidFrom\":\"2021-06-27T07:46:40Z\",\n" + + " \"ValidTo\":\"2021-08-01T07:46:40Z\",\n" + + " \"AffectedFields\":[\"dt\",\"dn\"],\n" + + " \"Logic\":{\n" + + " \"and\": [\n" + + " {\">=\":[ {\"var\":\"dt\"}, \"2021-06-01T00:00:00Z\" ]},\n" + + " {\">=\":[ {\"var\":\"dn\"}, 2 ]}\n" + + " ]\n" + + " }\n" + + "}"; + + public static final String BR_IDENTIFIER_4 = "VR-DE-1"; + public static final String BR_HASH_4 = "1706b888b9abc095e78ab1ebf32f2445a36c6a263b72634ae56476ecac5c89de"; + public static final String BR_COUNTRY_4 = "DE"; + public static final String BR_VERSION_4 = "2.0.0"; + public static final String BR_DATA_4 = "{\n" + + " \"Identifier\": \"VR-DE-1\",\n" + + " \"Version\": \"2.0.0\",\n" + + " \"SchemaVersion\":\"1.0.0\",\n" + + " \"Engine\":\"CERTLOGIC\",\n" + + " \"EngineVersion\":\"1.0.0\",\n" + + " \"Type\":\"Acceptance\",\n" + + " \"Country\":\"DE\",\n" + + " \"CertificateType\":\"Vaccination\",\n" + + " \"Description\":[{\"lang\":\"en\",\"desc\":\"Vaccination must be from June and doses must be 2\"}],\n" + + " \"ValidFrom\":\"2021-06-27T07:46:40Z\",\n" + + " \"ValidTo\":\"2021-09-01T07:46:40Z\",\n" + + " \"AffectedFields\":[\"dt\",\"dn\"],\n" + + " \"Logic\":{\n" + + " \"and\": [\n" + + " {\">=\":[ {\"var\":\"dt\"}, \"2021-06-01T00:00:00Z\" ]},\n" + + " {\">=\":[ {\"var\":\"dn\"}, 2 ]}\n" + + " ]\n" + + " }\n" + + "}"; + + + public static final String VALUESET_DATA_1 = "{\n" + + " \"valueSetId\": \"sct-vaccines-covid-19\",\n" + + " \"valueSetDate\": \"2021-04-27\",\n" + + " \"valueSetValues\": {\n" + + " \"1119349007\": {\n" + + " \"display\": \"SARS-CoV-2 mRNA vaccine\",\n" + + " \"lang\": \"en\",\n" + + " \"active\": true,\n" + + " \"version\": \"http://snomed.info/sct/900000000000207008/version/20210131\",\n" + + " \"system\": \"http://snomed.info/sct\"\n" + + " }}}"; + + public static final String VALUESET_HASH_1 = "7d8a9a79caa9ccc5373209d85eb91c3f6beec6762fa06ddacf0172ec819cd058"; + + public static final String VALUESET_IDENTIFIER_1 = "sct-vaccines-covid-19"; + + public static final String VALUESET_DATA_2 = "{\n" + + " \"valueSetId\": \"vaccines-covid-19-names\",\n" + + " \"valueSetDate\": \"2021-04-27\",\n" + + " \"valueSetValues\": {\n" + + " \"EU/1/20/1528\": {\n" + + " \"display\": \"Comirnaty\",\n" + + " \"lang\": \"en\",\n" + + " \"active\": true,\n" + + " \"system\": \"https://ec.europa.eu/health/documents/community-register/html/\",\n" + + " \"version\": \"\"\n" + + " }}}"; + + public static final String VALUESET_HASH_2 = "d2c03840b0e771b02967170bfc7b633702e0932b09f643a6edcd079df1ea096d"; + + public static final String VALUESET_IDENTIFIER_2 = "vaccines-covid-19-names"; + + + private final ValueSetRepository valueSetRepository; + + private final BusinessRuleRepository businessRuleRepository; + + public void insertBusinessRule(String hash, String identifier, String country, String version, String data) { + BusinessRuleEntity bre = new BusinessRuleEntity(); + bre.setHash(hash); + bre.setIdentifier(identifier); + bre.setVersion(version); + bre.setCountry(country); + bre.setRawData(data); + + businessRuleRepository.save(bre); + } + + + public void insertValueSet(String hash, String identifier, String data) { + ValueSetEntity vse = new ValueSetEntity(); + vse.setHash(hash); + vse.setId(identifier); + vse.setRawData(data); + + valueSetRepository.save(vse); + + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 04d168c..abd53f6 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -5,15 +5,21 @@ spring: active: - test application: - name: dgca-verifier-service + name: dgca-businessrule-service liquibase: change-log: classpath:db/changelog.xml main: allow-bean-definition-overriding: true dgc: - synchroniseCertificates: - timeInterval: 60000 - lockLimit: 1800000 + businessRulesDownload: + timeInterval: 1800000 + lockLimit: 3600000 + valueSetsDownload: + timeInterval: 1800000 + lockLimit: 3600000 + countryListDownload: + timeInterval: 1800000 + lockLimit: 3600000 gateway: connector: enabled: false diff --git a/templates/file-header.txt b/templates/file-header.txt index 2e2579b..288fbd6 100644 --- a/templates/file-header.txt +++ b/templates/file-header.txt @@ -1,6 +1,6 @@ /*- * ---license-start - * eu-digital-green-certificates / dgca-verifier-service + * eu-digital-green-certificates / dgca-businessrule-service * --- * Copyright (C) 2021 T-Systems International GmbH and all other contributors * --- From 6e87451612437324c03287cb6aabd31d7d501612 Mon Sep 17 00:00:00 2001 From: Julian <82032362+jurosens@users.noreply.github.com> Date: Sat, 26 Jun 2021 00:13:55 +0200 Subject: [PATCH 2/2] feat: gateway integration on BTP (#15) --- pom.xml | 5 + .../btp/SapCredentialStoreCfEnvProcessor.java | 68 ++++ .../GatewayDataDownloadBtpServiceImpl.java | 364 ++++++++++++++++++ .../businessrule/service/ValueSetService.java | 8 + .../utils/btp/CredentialStore.java | 45 +++ .../utils/btp/CredentialStoreConfig.java | 42 ++ .../utils/btp/CredentialStoreCryptoUtil.java | 80 ++++ .../utils/btp/JsonNodeDeserializer.java | 22 ++ .../businessrule/utils/btp/SapCredential.java | 31 ++ src/main/resources/META-INF/spring.factories | 1 + src/main/resources/application-btp.yml | 16 +- 11 files changed, 676 insertions(+), 6 deletions(-) create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/config/btp/SapCredentialStoreCfEnvProcessor.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadBtpServiceImpl.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStore.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreConfig.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreCryptoUtil.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/JsonNodeDeserializer.java create mode 100644 src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/SapCredential.java create mode 100644 src/main/resources/META-INF/spring.factories diff --git a/pom.xml b/pom.xml index 8ea59dd..e10220a 100644 --- a/pom.xml +++ b/pom.xml @@ -260,6 +260,11 @@ com.sap.hcp.cf.logging cf-java-logging-support-logback + + com.nimbusds + nimbus-jose-jwt + 9.9.2 + diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/config/btp/SapCredentialStoreCfEnvProcessor.java b/src/main/java/eu/europa/ec/dgc/businessrule/config/btp/SapCredentialStoreCfEnvProcessor.java new file mode 100644 index 0000000..d4a5231 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/config/btp/SapCredentialStoreCfEnvProcessor.java @@ -0,0 +1,68 @@ +package eu.europa.ec.dgc.businessrule.config.btp; + +import io.pivotal.cfenv.core.CfCredentials; +import io.pivotal.cfenv.core.CfService; +import io.pivotal.cfenv.spring.boot.CfEnvProcessor; +import io.pivotal.cfenv.spring.boot.CfEnvProcessorProperties; +import java.util.Map; + +/** + * Custom implementation of {@link CfEnvProcessor} for reading the SAP credential store parameters from the + * VCAP_SERVICES environment variable and making them available as properties in the spring context. + *

+ * The following properties are available in the context after the processor is done: + * + * + * + * + * @see CfEnvProcessor + */ +public class SapCredentialStoreCfEnvProcessor implements CfEnvProcessor { + + private static final String CRED_STORE_SCHEME = "credstore"; + private static final String CRED_STORE_PROPERTY_PREFIX = "sap.btp.credstore"; + + @Override + public boolean accept(CfService service) { + return service.existsByTagIgnoreCase(CRED_STORE_SCHEME, "securestore", "keystore", "credentials") + || service.existsByLabelStartsWith(CRED_STORE_SCHEME) + || service.existsByUriSchemeStartsWith(CRED_STORE_SCHEME); + } + + @Override + public void process(CfCredentials cfCredentials, Map properties) { + properties.put(CRED_STORE_PROPERTY_PREFIX + ".url", cfCredentials.getString("url")); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".password", cfCredentials.getString("password")); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".username", cfCredentials.getString("username")); + + @SuppressWarnings("unchecked") + Map encryption = (Map) cfCredentials.getMap().get("encryption"); + if (encryption == null) { + // Encryption features have been disabled on this BTP instance. + properties.put(CRED_STORE_PROPERTY_PREFIX + ".clientPrivateKey", "encryption-disabled"); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".serverPublicKey", "encryption-disabled"); + return; + } + + String clientPrivateKey = encryption.get("client_private_key").toString(); + String serverPublicKey = encryption.get("server_public_key").toString(); + + properties.put(CRED_STORE_PROPERTY_PREFIX + ".clientPrivateKey", clientPrivateKey); + properties.put(CRED_STORE_PROPERTY_PREFIX + ".serverPublicKey", serverPublicKey); + } + + @Override + public CfEnvProcessorProperties getProperties() { + return CfEnvProcessorProperties.builder() + .propertyPrefixes(CRED_STORE_PROPERTY_PREFIX) + .serviceName("CredentialStore") + .build(); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadBtpServiceImpl.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadBtpServiceImpl.java new file mode 100644 index 0000000..240e7b2 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/GatewayDataDownloadBtpServiceImpl.java @@ -0,0 +1,364 @@ +package eu.europa.ec.dgc.businessrule.service; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.FieldNamingStrategy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import eu.europa.ec.dgc.businessrule.model.BusinessRuleItem; +import eu.europa.ec.dgc.businessrule.model.ValueSetItem; +import eu.europa.ec.dgc.businessrule.utils.btp.JsonNodeDeserializer; +import eu.europa.ec.dgc.gateway.connector.dto.TrustListItemDto; +import eu.europa.ec.dgc.gateway.connector.dto.ValidationRuleDto; +import eu.europa.ec.dgc.gateway.connector.model.ValidationRule; +import eu.europa.ec.dgc.signing.SignedStringMessageParser; +import java.io.IOException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.util.EntityUtils; +import org.bouncycastle.cert.X509CertificateHolder; +import org.slf4j.MDC; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +@Profile("btp") +public class GatewayDataDownloadBtpServiceImpl implements GatewayDataDownloadService { + + private static final String DGCG_DESTINATION = "dgcg-destination"; + private static final String DCCG_UPLOAD_CERTS_ENDPOINT = "/trustList/UPLOAD"; + private static final String DCCG_BUSINESS_RULES_ENDPOINT = "/rules"; + private static final String DCCG_VALUE_SETS_ENDPOINT = "/valuesets"; + private static final String DCCG_COUNTRY_LIST_ENDPOINT = "/countrylist"; + + private final BusinessRuleService businessRuleService; + private final ValueSetService valueSetService; + private final CountryListService countryListService; + + @Override + @Scheduled(fixedDelayString = "${dgc.businessRulesDownload.timeInterval}") + @SchedulerLock(name = "GatewayDataDownloadService_downloadBusinessRules", lockAtLeastFor = "PT0S", + lockAtMostFor = "${dgc.businessRulesDownload.lockLimit}") + public void downloadBusinessRules() { + try { + initializeLogging(); + log.debug("Business rules download started."); + + //List uploadCerts = fetchUploadCerts(httpClient); + List countryCodes = fetchCountryList(); + + List ruleItems = new ArrayList<>(); + try { + ruleItems = businessRuleService.createBusinessRuleItemList(fetchValidationRulesAndVerify(countryCodes)); + } catch (NoSuchAlgorithmException e) { + log.error("Could not create business rule item list: {}", e.getMessage(), e); + } + + if (!ruleItems.isEmpty()) { + businessRuleService.updateBusinesRules(ruleItems); + } else { + log.warn("The download of the business rules seems to fail, as the download connector " + + "returns an empty list. No data will be changed."); + } + + log.info("Business rules download finished."); + } finally { + cleanLogging(); + } + + } + + @Override + @Scheduled(fixedDelayString = "${dgc.valueSetsDownload.timeInterval}") + @SchedulerLock(name = "GatewayDataDownloadService_downloadValueSets", lockAtLeastFor = "PT0S", + lockAtMostFor = "${dgc.valueSetsDownload.lockLimit}") + public void downloadValueSets() { + try { + initializeLogging(); + log.debug("Value sets download started."); + List valueSetItems; + List valueSetIds = fetchValueSetIds(); + + try { + valueSetItems = valueSetService.createValueSetItemListFromMap(fetchValueSets(valueSetIds)); + log.debug("Downloaded {} value set items.", valueSetItems.size()); + } catch (NoSuchAlgorithmException e) { + log.error("Failed to hash value set on download.",e); + return; + } + + if (!valueSetItems.isEmpty()) { + valueSetService.updateValueSets(valueSetItems); + } else { + log.warn("The download of the value sets seems to fail, as the download connector " + + "returns an empty list. No data will be changed."); + } + + log.debug("Value sets download finished."); + } finally { + cleanLogging(); + } + } + + @Override + @Scheduled(fixedDelayString = "${dgc.countryListDownload.timeInterval}") + @SchedulerLock(name = "GatewayDataDownloadService_downloadCountryList", lockAtLeastFor = "PT0S", + lockAtMostFor = "${dgc.countryListDownload.lockLimit}") + public void downloadCountryList() { + try { + initializeLogging(); + log.debug("Country list download started."); + + List countryList = fetchCountryList(); + log.debug("Downloaded {} country codes.", countryList.size()); + + if (!countryList.isEmpty()) { + countryListService.updateCountryList(gson().toJson(countryList)); + } else { + log.warn("The download of the country list seems to fail as the gateway " + + "returns an empty list. No data will be changed."); + } + + log.debug("Country list download finished."); + } finally { + cleanLogging(); + } + } + + private List fetchUploadCerts(HttpClient httpClient) { + List listOfUploadCerts = new ArrayList<>(); + + try { + HttpResponse response = httpClient.execute(RequestBuilder.get(DCCG_UPLOAD_CERTS_ENDPOINT).build()); + List trustListItems = gson().fromJson(toJsonString(response.getEntity()), + new TypeToken>() {}.getType()); + + listOfUploadCerts = trustListItems.stream() + .filter(this::checkThumbprintIntegrity) + .filter(this::checkTrustAnchorSignature) + .map(this::getCertificateFromTrustListItem) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } catch (IOException e) { + log.error("Fetching upload verts from gateway failed: {}", e.getMessage(), e); + } + + return listOfUploadCerts; + } + + private List fetchCountryList() { + HttpDestination httpDestination = DestinationAccessor.getDestination(DGCG_DESTINATION).asHttp(); + HttpClient httpClient = HttpClientAccessor.getHttpClient(httpDestination); + List countryList = new ArrayList<>(); + + try { + HttpResponse response = httpClient.execute(RequestBuilder.get(DCCG_COUNTRY_LIST_ENDPOINT).build()); + countryList = new ArrayList<>(gson().fromJson(toJsonString(response.getEntity()), + new TypeToken>() {}.getType())); + } catch (IOException e) { + log.error("Could not fetch country list from gateway: {}", e.getMessage(), e); + } + + return countryList; + } + + private List fetchValueSetIds() { + HttpDestination httpDestination = DestinationAccessor.getDestination(DGCG_DESTINATION).asHttp(); + HttpClient httpClient = HttpClientAccessor.getHttpClient(httpDestination); + List valueSetIds = new ArrayList<>(); + + try { + HttpResponse response = httpClient.execute(RequestBuilder.get(DCCG_VALUE_SETS_ENDPOINT).build()); + valueSetIds = new ArrayList<>(gson().fromJson(toJsonString(response.getEntity()), + new TypeToken>() {}.getType())); + } catch (IOException e) { + log.error("Could not fetch value set IDs from gateway: {}", e.getMessage()); + } + + return valueSetIds; + } + + private Gson gson() { + return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new TypeAdapter() { + @Override + public void write(JsonWriter out, ZonedDateTime value) throws IOException { + out.value(value.toString()); + } + + @Override + public ZonedDateTime read(JsonReader in) throws IOException { + return ZonedDateTime.parse(in.nextString()); + } + }) + .enableComplexMapKeySerialization() + .create(); + } + + private Gson gsonForValidationRule() { + return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new TypeAdapter() { + @Override + public void write(JsonWriter out, ZonedDateTime value) throws IOException { + out.value(value.toString()); + } + + @Override + public ZonedDateTime read(JsonReader in) throws IOException { + return ZonedDateTime.parse(in.nextString()); + } + }) + .registerTypeAdapter(JsonNode.class, new JsonNodeDeserializer()) + .setFieldNamingStrategy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .enableComplexMapKeySerialization() + .create(); + } + + private String toJsonString(HttpEntity entity) throws IOException { + return EntityUtils.toString(entity); + } + + private boolean checkThumbprintIntegrity(TrustListItemDto trustListItem) { + byte[] certificateRawData = Base64.getDecoder().decode(trustListItem.getRawData()); + + try { + if (trustListItem.getThumbprint() + .equals(this.getCertThumbprint(new X509CertificateHolder(certificateRawData)))) { + return true; + } else { + log.debug("Thumbprint of trust list item '{}' did not match.", trustListItem.getKid()); + return false; + } + } catch (IOException e) { + log.error("Could not parse certificate raw data: {}", e.getMessage()); + return false; + } + } + + private String getCertThumbprint(X509CertificateHolder x509CertificateHolder) { + try { + byte[] data = x509CertificateHolder.getEncoded(); + byte[] certHashBytes = MessageDigest.getInstance("SHA-256").digest(data); + String hexString = (new BigInteger(1, certHashBytes)).toString(16); + if (hexString.length() == 63) { + hexString = "0" + hexString; + } + return hexString; + } catch (NoSuchAlgorithmException | IOException e) { + log.error("Could not calculate thumbprint of certificate '{}': {}.", + x509CertificateHolder.getSubject(), e.getMessage()); + return null; + } + } + + private X509CertificateHolder getCertificateFromTrustListItem(TrustListItemDto trustListItem) { + byte[] decodedBytes = Base64.getDecoder().decode(trustListItem.getRawData()); + try { + return new X509CertificateHolder(decodedBytes); + } catch (IOException e) { + log.error("Failed to parse Certificate Raw Data. KID: {}, Country: {}", trustListItem.getKid(), + trustListItem.getCountry()); + return null; + } + } + + private boolean checkTrustAnchorSignature(TrustListItemDto trustListItemDto) { + // Implement me... + return true; + } + + private List fetchValidationRulesAndVerify(List countryCodes) { + HttpDestination httpDestination = DestinationAccessor.getDestination(DGCG_DESTINATION).asHttp(); + HttpClient httpClient = HttpClientAccessor.getHttpClient(httpDestination); + List allRules = new ArrayList<>(); + + for (String countryCode : countryCodes) { + log.debug("Fetching rules for country '{}'...", countryCode); + try { + HttpResponse response = httpClient + .execute(RequestBuilder.get(DCCG_BUSINESS_RULES_ENDPOINT + "/" + countryCode).build()); + Map fetchedForCountry = gson().fromJson(toJsonString(response.getEntity()), + new TypeToken>() {}.getType()); + + log.debug("Fetched {} rule(s) for country '{}'. Parsing now...", fetchedForCountry.values().size(), + countryCode); + allRules.addAll(fetchedForCountry.values().stream().flatMap(Arrays::stream).map(this::mapRule) + .filter(Objects::nonNull).collect(Collectors.toList())); + } catch (IOException | JsonSyntaxException e) { + log.warn("Could not fetch rules for country '{}': {}", countryCode, e.getMessage(), e); + } + } + + return allRules; + } + + private ValidationRule mapRule(ValidationRuleDto dto) { + try { + SignedStringMessageParser parser = new SignedStringMessageParser(dto.getCms()); + ValidationRule validationRule = gsonForValidationRule().fromJson(parser.getPayload(), ValidationRule.class); + validationRule.setRawJson(parser.getPayload()); + return validationRule; + } catch (JsonSyntaxException e) { + log.warn("Could not parse validation rule: {}", e.getMessage(), e); + } + return null; + } + + private Map fetchValueSets(List valueSetIds) { + HttpDestination httpDestination = DestinationAccessor.getDestination(DGCG_DESTINATION).asHttp(); + HttpClient httpClient = HttpClientAccessor.getHttpClient(httpDestination); + Map valueSets = new HashMap<>(); + + for (String valueSetId : valueSetIds) { + try { + HttpResponse response = httpClient + .execute(RequestBuilder.get(DCCG_VALUE_SETS_ENDPOINT + "/" + valueSetId).build()); + valueSets.put(valueSetId, toJsonString(response.getEntity())); + } catch (IOException e) { + log.warn("Could not fetch value set with ID '{}': {}", valueSetId, e.getMessage(), e); + } + } + + return valueSets; + } + + private static final String CORRELATION_ID_LOG_VAR_NAME = "correlation_id"; + + private void initializeLogging() { + MDC.put(CORRELATION_ID_LOG_VAR_NAME, UUID.randomUUID().toString()); + } + + private void cleanLogging() { + MDC.remove(CORRELATION_ID_LOG_VAR_NAME); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java b/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java index ceace91..962c7f7 100644 --- a/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java +++ b/src/main/java/eu/europa/ec/dgc/businessrule/service/ValueSetService.java @@ -72,16 +72,24 @@ public ValueSetEntity getValueSetByHash(String hash) { public void updateValueSets(List valueSets) { List valueSetsHashes = valueSets.stream().map(ValueSetItem::getHash).collect(Collectors.toList()); List alreadyStoredValueSets = getValueSetsHashList(); + log.debug("Got {} value sets from gateway and {} already stored in the database. Processing update now...", + valueSetsHashes.size(), alreadyStoredValueSets.size()); if (valueSetsHashes.isEmpty()) { + log.info("Got no value sets from gateway. Deleting all stored value sets."); valueSetRepository.deleteAll(); } else { + log.info("Deleting value sets not contained in latest response from gateway."); valueSetRepository.deleteByHashNotIn(valueSetsHashes); } for (ValueSetItem valueSet : valueSets) { + log.debug("Processing value set with hash '{}'.", valueSet.getHash()); if (!alreadyStoredValueSets.contains(valueSet.getHash())) { saveValueSet(valueSet.getHash(), valueSet.getId(), valueSet.getRawData()); + log.debug("Saved value set '{}'.", valueSet.getHash()); + } else { + log.debug("Value set already exists in database. Persisting skipped."); } } diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStore.java b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStore.java new file mode 100644 index 0000000..9a75e72 --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStore.java @@ -0,0 +1,45 @@ +package eu.europa.ec.dgc.businessrule.utils.btp; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@Profile("btp") +public class CredentialStore { + + private static final Logger log = LoggerFactory.getLogger(CredentialStore.class); + + @Value("${sap.btp.credstore.url}") + private String url; + + private final CredentialStoreCryptoUtil cryptoUtil; + + private final RestTemplate restTemplate; + + @Autowired + public CredentialStore(CredentialStoreCryptoUtil cryptoUtil, RestTemplate restTemplate) { + this.cryptoUtil = cryptoUtil; + this.restTemplate = restTemplate; + } + + /** + * Return the key located under the given name in the credential store. + * + * @param name the name of the key + * @return the key from the credential store + */ + public SapCredential getKeyByName(String name) { + log.debug("Querying key with name '{}'.", name); + String response = restTemplate.getForEntity(url + "/key?name=" + URLEncoder.encode(name, + StandardCharsets.UTF_8), String.class).getBody(); + return SapCredential.fromJson(cryptoUtil.decrypt(response)); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreConfig.java b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreConfig.java new file mode 100644 index 0000000..3cbf0ed --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreConfig.java @@ -0,0 +1,42 @@ +package eu.europa.ec.dgc.businessrule.utils.btp; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.client.RestTemplate; + +@Configuration +@Profile("btp") +public class CredentialStoreConfig { + + @Value("${sap.btp.credstore.username}") + private String username; + + @Value("${sap.btp.credstore.password}") + private String password; + + @Value("${sap.btp.credstore.namespace}") + private String namespace; + + @Bean + RestTemplate restTemplate(RestTemplateBuilder builder) { + RestTemplate restTemplate = builder.build(); + restTemplate.getInterceptors().add((request, body, execution) -> { + request.getHeaders().set("Authorization", "Basic " + getAuthToken()); + request.getHeaders().set("sapcp-credstore-namespace", namespace); + return execution.execute(request, body); + }); + + return restTemplate; + } + + private String getAuthToken() { + String authHeader = username + ":" + password; + return Base64.getEncoder().encodeToString(authHeader.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreCryptoUtil.java b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreCryptoUtil.java new file mode 100644 index 0000000..047f40d --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/CredentialStoreCryptoUtil.java @@ -0,0 +1,80 @@ +package eu.europa.ec.dgc.businessrule.utils.btp; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWEObject; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.crypto.RSADecrypter; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.text.ParseException; +import java.util.Base64; +import javax.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.NotImplementedException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@Profile("btp") +public class CredentialStoreCryptoUtil { + + @Value("${sap.btp.credstore.clientPrivateKey}") + private String clientPrivateKeyBase64; + + @Value("${sap.btp.credstore.serverPublicKey}") + private String serverPublicKeyBase64; + + @Value("${sap.btp.credstore.encrypted}") + private boolean encryptionEnabled; + + private PrivateKey ownPrivateKey; + + private PublicKey serverPublicKey; + + @PostConstruct + private void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { + if (!encryptionEnabled) { + return; + } + + KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder() + .decode(clientPrivateKeyBase64)); + this.ownPrivateKey = rsaKeyFactory.generatePrivate(pkcs8EncodedKeySpec); + + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder() + .decode(serverPublicKeyBase64)); + this.serverPublicKey = rsaKeyFactory.generatePublic(x509EncodedKeySpec); + } + + protected void encrypt() { + throw new NotImplementedException("Encryption is still to be implemented yet."); + } + + protected String decrypt(String jweResponse) { + if (!encryptionEnabled) { + return jweResponse; + } + + JWEObject jweObject; + + try { + RSADecrypter rsaDecrypter = new RSADecrypter(ownPrivateKey); + jweObject = JWEObject.parse(jweResponse); + jweObject.decrypt(rsaDecrypter); + + Payload payload = jweObject.getPayload(); + return payload.toString(); + } catch (ParseException | JOSEException e) { + log.error("Failed to parse JWE response: {}.", e.getMessage()); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/JsonNodeDeserializer.java b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/JsonNodeDeserializer.java new file mode 100644 index 0000000..128699f --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/JsonNodeDeserializer.java @@ -0,0 +1,22 @@ +package eu.europa.ec.dgc.businessrule.utils.btp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import java.lang.reflect.Type; + +public class JsonNodeDeserializer implements JsonDeserializer { + @Override + public JsonNode deserialize(JsonElement jsonElement, Type type, + JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + try { + return new ObjectMapper().readTree(jsonElement.getAsJsonObject().toString()); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + } +} diff --git a/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/SapCredential.java b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/SapCredential.java new file mode 100644 index 0000000..6c2cc3a --- /dev/null +++ b/src/main/java/eu/europa/ec/dgc/businessrule/utils/btp/SapCredential.java @@ -0,0 +1,31 @@ +package eu.europa.ec.dgc.businessrule.utils.btp; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.util.Date; +import lombok.Data; + +@Data +public class SapCredential { + + private String id; + private String name; + private Date modifiedAt; + private String value; + private String status; + private String username; + private String format; + private String category; + private String type; + + public static SapCredential fromJson(String rawJson) { + return gson().fromJson(rawJson, SapCredential.class); + } + + private static Gson gson() { + return new GsonBuilder() + .enableComplexMapKeySerialization() + .create(); + } + +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..294d9aa --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +io.pivotal.cfenv.spring.boot.CfEnvProcessor=eu.europa.ec.dgc.businessrule.config.btp.SapCredentialStoreCfEnvProcessor diff --git a/src/main/resources/application-btp.yml b/src/main/resources/application-btp.yml index 133216a..aa7fc1b 100644 --- a/src/main/resources/application-btp.yml +++ b/src/main/resources/application-btp.yml @@ -2,9 +2,13 @@ dgc: gateway: connector: enabled: false - tls-trust-store: - path: false - tls-key-store: - path: false - trust-anchor: - path: false +sap: + btp: + credstore: + namespace: DgcaIssuerServiceCredentialStore + encrypted: false + username: + password: + url: + clientPrivateKey: + serverPublicKey: