From d1aa5a3b70a87dce8f8cb738c3b7e7cefa010218 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 08:48:42 +0530 Subject: [PATCH 01/53] Initial application structure --- .gitignore | 3 + docs/README.md | 1 + g2pc-core-lib/README.md | 1 + g2pc-dc-core-lib/README.md | 1 + g2pc-dp-core-lib/README.md | 1 + g2pc-reference-apps/README.md | 92 +++++++++++++++++++ .../g2pc-ref-dc-client/README.md | 1 + .../g2pc-ref-farmer-regsvc/README.md | 1 + .../g2pc-ref-mno-regsvc/README.md | 1 + 9 files changed, 102 insertions(+) create mode 100644 docs/README.md create mode 100644 g2pc-core-lib/README.md create mode 100644 g2pc-dc-core-lib/README.md create mode 100644 g2pc-dp-core-lib/README.md create mode 100644 g2pc-reference-apps/README.md create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/README.md create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md diff --git a/.gitignore b/.gitignore index 524f096..f0acbb7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +.vscode/ +.idea/ \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..79fe32c --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +# G2pc Core Lib \ No newline at end of file diff --git a/g2pc-core-lib/README.md b/g2pc-core-lib/README.md new file mode 100644 index 0000000..79fe32c --- /dev/null +++ b/g2pc-core-lib/README.md @@ -0,0 +1 @@ +# G2pc Core Lib \ No newline at end of file diff --git a/g2pc-dc-core-lib/README.md b/g2pc-dc-core-lib/README.md new file mode 100644 index 0000000..54ca6f2 --- /dev/null +++ b/g2pc-dc-core-lib/README.md @@ -0,0 +1 @@ +# G2pc DC Core Lib \ No newline at end of file diff --git a/g2pc-dp-core-lib/README.md b/g2pc-dp-core-lib/README.md new file mode 100644 index 0000000..9e22c68 --- /dev/null +++ b/g2pc-dp-core-lib/README.md @@ -0,0 +1 @@ +# G2pc DP Core Lib \ No newline at end of file diff --git a/g2pc-reference-apps/README.md b/g2pc-reference-apps/README.md new file mode 100644 index 0000000..4fbf326 --- /dev/null +++ b/g2pc-reference-apps/README.md @@ -0,0 +1,92 @@ +# g2pc-reference-apps + + + +## Getting started + +To make it easy for you to get started with GitLab, here's a list of recommended next steps. + +Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! + +## Add your files + +- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files +- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: + +``` +cd existing_repo +git remote add origin https://git.tekdi.net/cdpi-farmer-input-support-program/g2pc-reference-apps.git +git branch -M main +git push -uf origin main +``` + +## Integrate with your tools + +- [ ] [Set up project integrations](https://git.tekdi.net/cdpi-farmer-input-support-program/g2pc-reference-apps/-/settings/integrations) + +## Collaborate with your team + +- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) +- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) +- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) +- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) +- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) + +## Test and Deploy + +Use the built-in continuous integration in GitLab. + +- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) +- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) +- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) +- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) +- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) + +*** + +# Editing this README + +When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. + +## Suggestions for a good README +Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. + +## Name +Choose a self-explaining name for your project. + +## Description +Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. + +## Badges +On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. + +## Visuals +Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. + +## Installation +Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. + +## Usage +Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + +## Support +Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. + +## Roadmap +If you have ideas for releases in the future, it is a good idea to list them in the README. + +## Contributing +State if you are open to contributions and what your requirements are for accepting them. + +For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. + +You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. + +## Authors and acknowledgment +Show your appreciation to those who have contributed to the project. + +## License +For open source projects, say how it is licensed. + +## Project status +If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/README.md b/g2pc-reference-apps/g2pc-ref-dc-client/README.md new file mode 100644 index 0000000..2191629 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/README.md @@ -0,0 +1 @@ +# G2pc Ref DC Client \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md new file mode 100644 index 0000000..7ef31ba --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md @@ -0,0 +1 @@ +# G2pc Ref Farmer Reg Svc \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md b/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md new file mode 100644 index 0000000..d01317f --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md @@ -0,0 +1 @@ +# G2pc Ref Mno Reg Svc \ No newline at end of file From 89cd999edfc7944b4433436b47129bf0f11131a1 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 11:49:19 +0530 Subject: [PATCH 02/53] g2pc-core-lib sub repo added. --- g2pc-core-lib/.gitignore | 36 ++++++ g2pc-core-lib/README.md | 2 +- g2pc-core-lib/pom.xml | 47 ++++++++ .../g2pc/core/lib/constants/Constants.java | 6 + .../lib/dto/common/AcknowledgementDTO.java | 18 +++ .../core/lib/dto/common/cache/CacheDTO.java | 24 ++++ .../core/lib/dto/common/header/HeaderDTO.java | 53 +++++++++ .../core/lib/dto/common/header/MetaDTO.java | 11 ++ .../dto/common/header/RequestHeaderDTO.java | 31 +++++ .../dto/common/header/ResponseHeaderDTO.java | 45 +++++++ .../common/message/request/AuthorizeDTO.java | 9 ++ .../common/message/request/ConsentDTO.java | 9 ++ .../common/message/request/MessageDTO.java | 18 +++ .../common/message/request/PaginationDTO.java | 18 +++ .../dto/common/message/request/QueryDTO.java | 16 +++ .../common/message/request/RequestDTO.java | 23 ++++ .../message/request/SearchCriteriaDTO.java | 39 +++++++ .../message/request/SearchRequestDTO.java | 26 +++++ .../dto/common/message/request/SortDTO.java | 19 +++ .../dto/common/message/response/DataDTO.java | 25 ++++ .../common/message/response/MessageDTO.java | 25 ++++ .../message/response/PaginationDTO.java | 21 ++++ .../common/message/response/ResponseDTO.java | 23 ++++ .../message/response/SearchResponseDTO.java | 37 ++++++ .../g2pc/core/lib/enums/AlgorithmENUM.java | 28 +++++ .../g2pc/core/lib/enums/HeaderStatusENUM.java | 43 +++++++ .../lib/enums/HeaderStatusReasonCodeENUM.java | 63 ++++++++++ .../g2pc/core/lib/enums/QueryTypeEnum.java | 30 +++++ .../g2pc/core/lib/enums/SortOrderEnum.java | 28 +++++ .../lib/exceptionhandler/ErrorResponse.java | 27 +++++ .../GlobalExceptionHandler.java | 30 +++++ .../g2pc/core/lib/exceptions/G2pcError.java | 19 +++ .../exceptions/G2pcValidationException.java | 31 +++++ .../java/g2pc/core/lib/utils/CommonUtils.java | 85 ++++++++++++++ .../schema/ResponseHeaderschema.json | 78 +++++++++++++ .../schema/ResponseMessageschema.json | 103 ++++++++++++++++ .../main/resources/schema/headerschema.json | 55 +++++++++ .../main/resources/schema/messageschema.json | 110 ++++++++++++++++++ 38 files changed, 1310 insertions(+), 1 deletion(-) create mode 100644 g2pc-core-lib/.gitignore create mode 100644 g2pc-core-lib/pom.xml create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/HeaderDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/RequestHeaderDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/ResponseHeaderDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java create mode 100644 g2pc-core-lib/src/main/resources/schema/ResponseHeaderschema.json create mode 100644 g2pc-core-lib/src/main/resources/schema/ResponseMessageschema.json create mode 100644 g2pc-core-lib/src/main/resources/schema/headerschema.json create mode 100644 g2pc-core-lib/src/main/resources/schema/messageschema.json diff --git a/g2pc-core-lib/.gitignore b/g2pc-core-lib/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-core-lib/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-core-lib/README.md b/g2pc-core-lib/README.md index 79fe32c..c5c8dee 100644 --- a/g2pc-core-lib/README.md +++ b/g2pc-core-lib/README.md @@ -1 +1 @@ -# G2pc Core Lib \ No newline at end of file +# G2pc Core Lib \ No newline at end of file diff --git a/g2pc-core-lib/pom.xml b/g2pc-core-lib/pom.xml new file mode 100644 index 0000000..cbea19e --- /dev/null +++ b/g2pc-core-lib/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.16 + + + g2pc.core.lib + g2pc-core-library + 0.0.1-SNAPSHOT + g2p-core + Common g2pc specifications + + 11 + + + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + + org.springframework.boot + spring-boot-starter-web + + + com.konghq + unirest-java + 3.0.00 + + + com.networknt + json-schema-validator + 1.0.57 + + + diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java new file mode 100644 index 0000000..ac9ba64 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java @@ -0,0 +1,6 @@ +package g2pc.core.lib.constants; + +public class Constants { + + public static final String CANNOT_DESERIALIZE_TYPE= "Cannot deserialize Type"; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java new file mode 100644 index 0000000..f8ebc6e --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java @@ -0,0 +1,18 @@ +package g2pc.core.lib.dto.common; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AcknowledgementDTO { + + @JsonProperty("message") + private String message; + + @JsonProperty("status") + private String status; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java new file mode 100644 index 0000000..2542fd4 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java @@ -0,0 +1,24 @@ +package g2pc.core.lib.dto.common.cache; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CacheDTO { + + @JsonProperty("data") + private String data; + + @JsonProperty("status") + private String status; + + @JsonProperty("created_date") + private String createdDate; + + @JsonProperty("last_updated_date") + private String lastUpdatedDate; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/HeaderDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/HeaderDTO.java new file mode 100644 index 0000000..b474fbe --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/HeaderDTO.java @@ -0,0 +1,53 @@ +package g2pc.core.lib.dto.common.header; + +import com.fasterxml.jackson.annotation.*; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, + property = "type") +@JsonSubTypes({ +}) +public abstract class HeaderDTO { + + private String version = "1.0.0"; + + @JsonProperty("message_id") + private String messageId; + + @JsonProperty("message_ts") + private String messageTs; + + private String action; + + @JsonProperty("total_count") + private Integer totalCount; + + @JsonProperty("sender_id") + private String senderId; + + @JsonProperty("receiver_id") + private String receiverId; + + @JsonProperty("is_msg_encrypted") + private Boolean isMsgEncrypted; + + private MetaDTO meta; + + public HeaderDTO() { + } + + public HeaderDTO(String messageId, + String messageTs, + String action, + Integer totalCount, + String senderId, + String receiverId, + Boolean isMsgEncrypted, + MetaDTO meta) { + + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java new file mode 100644 index 0000000..ba1446d --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java @@ -0,0 +1,11 @@ +package g2pc.core.lib.dto.common.header; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaDTO { + + +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/RequestHeaderDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/RequestHeaderDTO.java new file mode 100644 index 0000000..0b6b451 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/RequestHeaderDTO.java @@ -0,0 +1,31 @@ +package g2pc.core.lib.dto.common.header; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@JsonTypeName("requestHeader") +public class RequestHeaderDTO extends HeaderDTO { + + @JsonProperty("sender_uri") + private String senderUri; + + public RequestHeaderDTO(String messageId, + String messageTs, + String action, + Integer totalCount, + String senderId, + String receiverId, + Boolean isMsgEncrypted, + MetaDTO meta, + String senderUri) { + + super(messageId, messageTs, action, totalCount, senderId, receiverId, isMsgEncrypted, meta); + this.senderUri = senderUri; + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/ResponseHeaderDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/ResponseHeaderDTO.java new file mode 100644 index 0000000..f29bb2d --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/ResponseHeaderDTO.java @@ -0,0 +1,45 @@ +package g2pc.core.lib.dto.common.header; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@JsonTypeName("responseHeader") +public class ResponseHeaderDTO extends HeaderDTO { + + private String status; + + @JsonProperty("status_reason_code") + private String statusReasonCode; + + @JsonProperty("status_reason_message") + private String statusReasonMessage; + + @JsonProperty("completed_count") + private Integer completedCount; + + public ResponseHeaderDTO(String messageId, + String messageTs, + String action, + Integer totalCount, + String senderId, + String receiverId, + Boolean isMsgEncrypted, + MetaDTO meta, + String status, + String statusReasonCode, + String statusReasonMessage, + Integer completedCount) { + + super(messageId, messageTs, action, totalCount, senderId, receiverId, isMsgEncrypted, meta); + this.status = status; + this.statusReasonCode=statusReasonCode; + this.statusReasonMessage=statusReasonMessage; + this.completedCount=completedCount; + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java new file mode 100644 index 0000000..66b17ad --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java @@ -0,0 +1,9 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AuthorizeDTO { +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java new file mode 100644 index 0000000..b8a6ef5 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java @@ -0,0 +1,9 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ConsentDTO { +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java new file mode 100644 index 0000000..01bf646 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java @@ -0,0 +1,18 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MessageDTO { + + @JsonProperty("transaction_id") + private String transactionId; + + @JsonProperty("search_request") + private SearchRequestDTO searchRequest; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java new file mode 100644 index 0000000..d023a96 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java @@ -0,0 +1,18 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PaginationDTO { + + @JsonProperty("page_size") + private int pageSize; + + @JsonProperty("page_number") + private int pageNumber; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java new file mode 100644 index 0000000..2accfaf --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java @@ -0,0 +1,16 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class QueryDTO { + + @JsonProperty("query_name") + private String queryName; + + @JsonProperty("query_params") + private Object queryParams; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java new file mode 100644 index 0000000..99c2330 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java @@ -0,0 +1,23 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RequestDTO { + + @JsonProperty("signature") + private String signature; + + @JsonProperty("header") + private HeaderDTO header; + + @JsonProperty("message") + private MessageDTO message; + +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java new file mode 100644 index 0000000..6cb615f --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java @@ -0,0 +1,39 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SearchCriteriaDTO { + + @JsonProperty("version") + private String version; + + @JsonProperty("reg_type") + private String regType; + + @JsonProperty("reg_sub_type") + private String regSubType; + + @JsonProperty("query_type") + private String queryType; + + @JsonProperty("query") + private QueryDTO query; + + @JsonProperty("sort") + private List sort; + + @JsonProperty("pagination") + private PaginationDTO pagination; + + @JsonProperty("consent") + private ConsentDTO consent; + + @JsonProperty("authorize") + private AuthorizeDTO authorize; +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java new file mode 100644 index 0000000..4902837 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java @@ -0,0 +1,26 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SearchRequestDTO { + + @JsonProperty("reference_id") + private String referenceId; + + @JsonProperty("timestamp") + private String timestamp; + + @JsonProperty("search_criteria") + private SearchCriteriaDTO searchCriteria; + + @JsonProperty("locale") + private String locale; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java new file mode 100644 index 0000000..e47626a --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java @@ -0,0 +1,19 @@ +package g2pc.core.lib.dto.common.message.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SortDTO { + + @JsonProperty("attribute_name") + private String attributeName; + + @JsonProperty("sort_order") + private String sortOrder; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java new file mode 100644 index 0000000..d55f271 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java @@ -0,0 +1,25 @@ +package g2pc.core.lib.dto.common.message.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DataDTO { + + @JsonProperty("version") + private String version; + + @JsonProperty("reg_type") + private String regType; + + @JsonProperty("reg_sub_type") + private String regSubType; + + @JsonProperty("reg_record_type") + private String regRecordType; + + @JsonProperty("reg_records") + private Object regRecords; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java new file mode 100644 index 0000000..ae058ad --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java @@ -0,0 +1,25 @@ +package g2pc.core.lib.dto.common.message.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MessageDTO { + + @JsonProperty("transaction_id") + private String transactionId; + + @JsonProperty("correlation_id") + private String correlationId; + + @JsonProperty("search_response") + private SearchResponseDTO searchResponse; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java new file mode 100644 index 0000000..e419780 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java @@ -0,0 +1,21 @@ +package g2pc.core.lib.dto.common.message.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PaginationDTO { + + @JsonProperty("page_size") + private Integer pageSize; + + @JsonProperty("page_number") + private Integer pageNumber; + + @JsonProperty("total_count") + private Integer totalCount; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java new file mode 100644 index 0000000..6d04c17 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java @@ -0,0 +1,23 @@ +package g2pc.core.lib.dto.common.message.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ResponseDTO { + + @JsonProperty("signature") + private String signature; + + @JsonProperty("header") + private HeaderDTO header; + + @JsonProperty("message") + private MessageDTO message; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java new file mode 100644 index 0000000..cc59979 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java @@ -0,0 +1,37 @@ +package g2pc.core.lib.dto.common.message.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SearchResponseDTO { + + @JsonProperty("reference_id") + private String referenceId; + + @JsonProperty("timestamp") + private String timestamp; + + @JsonProperty("status") + private String status; + + @JsonProperty("status_reason_code") + private String statusReasonCode; + + @JsonProperty("status_reason_message") + private String statusReasonMessage; + + @JsonProperty("data") + private DataDTO data; + + @JsonProperty("pagination") + private PaginationDTO pagination; + + @JsonProperty("locale") + private String locale; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java new file mode 100644 index 0000000..a9ff021 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java @@ -0,0 +1,28 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.Constants; + +import java.io.IOException; + +public enum AlgorithmENUM { + + ED25519, RSA; + + public String toValue() { + switch (this) { + case ED25519: return "Ed25519"; + case RSA: return "rsa"; + } + return null; + } + + public static AlgorithmENUM forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "ed25519": return ED25519; + case "rsa": return RSA; + } + } + throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java new file mode 100644 index 0000000..87815c5 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java @@ -0,0 +1,43 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.Constants; + +import java.io.IOException; + +public enum HeaderStatusENUM { + + RCVD, + PDNG, + SUCC, + RJCT; + + public String toValue() { + switch (this) { + case RCVD: + return "rcvd"; + case PDNG: + return "pdng"; + case SUCC: + return "succ"; + case RJCT: + return "rjct"; + } + return null; + } + + public static HeaderStatusENUM forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "rcvd": + return RCVD; + case "pdng": + return PDNG; + case "succ": + return SUCC; + case "rjct": + return RJCT; + } + } + throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java new file mode 100644 index 0000000..39710a6 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java @@ -0,0 +1,63 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.Constants; + +import java.io.IOException; + +public enum HeaderStatusReasonCodeENUM { + + RJCT_VERSION_INVALID, + RJCT_MESSAGE_ID_DUPLICATE, + RJCT_MESSAGE_TS_INVALID, + RJCT_ACTION_INVALID, + RJCT_ACTION_NOT_SUPPORTED, + RJCT_TOTAL_COUNT_INVALID, + RJCT_TOTAL_COUNT_LIMIT_EXCEEDED, + RJCT_ERRORS_TOO_MANY; + + public String toValue() { + switch (this) { + case RJCT_VERSION_INVALID: + return "rjct.version.invalid"; + case RJCT_MESSAGE_ID_DUPLICATE: + return "rjct.message_id.duplicate"; + case RJCT_MESSAGE_TS_INVALID: + return "rjct.message_ts.invalid"; + case RJCT_ACTION_INVALID: + return "rjct.action.invalid"; + case RJCT_ACTION_NOT_SUPPORTED: + return "rjct.action.not_supported"; + case RJCT_TOTAL_COUNT_INVALID: + return "rjct.total_count.invalid"; + case RJCT_TOTAL_COUNT_LIMIT_EXCEEDED: + return "rjct.total_count.limit_exceeded"; + case RJCT_ERRORS_TOO_MANY: + return "rjct.errors.too_many"; + } + return null; + } + + public static HeaderStatusReasonCodeENUM forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "rjct.version.invalid": + return RJCT_VERSION_INVALID; + case "rjct.message_id.duplicate": + return RJCT_MESSAGE_ID_DUPLICATE; + case "rjct.message_ts.invalid": + return RJCT_MESSAGE_TS_INVALID; + case "rjct.action.invalid": + return RJCT_ACTION_INVALID; + case "rjct.action.not_supported": + return RJCT_ACTION_NOT_SUPPORTED; + case "rjct.total_count.invalid": + return RJCT_TOTAL_COUNT_INVALID; + case "rjct.total_count.limit_exceeded": + return RJCT_TOTAL_COUNT_LIMIT_EXCEEDED; + case "rjct.errors.too_many": + return RJCT_ERRORS_TOO_MANY; + } + } + throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java new file mode 100644 index 0000000..befcc3e --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java @@ -0,0 +1,30 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.Constants; + +import java.io.IOException; + +public enum QueryTypeEnum { + + NAMEDQUERY , IDTYPE , PREDICATE ; + + public String toValue() { + switch (this) { + case NAMEDQUERY: return "namedQuery"; + case IDTYPE: return "idtype"; + case PREDICATE:return "predicate"; + } + return null; + } + + public static QueryTypeEnum forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "namedQuery": return NAMEDQUERY; + case "idtype": return IDTYPE; + case "predicate":return PREDICATE; + } + } + throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java new file mode 100644 index 0000000..8f60f59 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java @@ -0,0 +1,28 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.Constants; + +import java.io.IOException; + +public enum SortOrderEnum { + + ASC , DESC ; + + public String toValue() { + switch (this) { + case ASC: return "asc"; + case DESC: return "desc"; + } + return null; + } + + public static SortOrderEnum forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "asc": return ASC; + case "desc": return DESC; + } + } + throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java new file mode 100644 index 0000000..25d7214 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java @@ -0,0 +1,27 @@ +package g2pc.core.lib.exceptionhandler; + +import g2pc.core.lib.exceptions.G2pcError; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * The type Error response. + */ +@Data +@NoArgsConstructor +public class ErrorResponse { + private List g2PcErrors; + + /** + * Instantiates a new Error response. + * + * @param g2PcErrorList the error list + */ + public ErrorResponse(List g2PcErrorList) + { + super(); + this.g2PcErrors = g2PcErrorList; + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java new file mode 100644 index 0000000..479e939 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java @@ -0,0 +1,30 @@ +package g2pc.core.lib.exceptionhandler; + +import g2pc.core.lib.exceptions.G2pcValidationException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * The type Global exception handler. + */ +@ControllerAdvice +public class GlobalExceptionHandler { + + /** + * Handle exception error response. + * + * @param ex the ValidationExeption + * @return the error response + */ + @ExceptionHandler(value + = G2pcValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public @ResponseBody ErrorResponse + handleException(G2pcValidationException ex) + { + return new ErrorResponse(ex.getG2PcErrorList()); + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java new file mode 100644 index 0000000..bfa9f32 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java @@ -0,0 +1,19 @@ +package g2pc.core.lib.exceptions; + + +import lombok.*; + +/** + * The type Error. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class G2pcError { + + private String code ; + + private String message; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java new file mode 100644 index 0000000..5dd02d2 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java @@ -0,0 +1,31 @@ +package g2pc.core.lib.exceptions; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + + +/** + * The type Validation exception. + */ +@Data +@Getter +@Setter +public class G2pcValidationException extends Exception{ + + + + private List g2PcErrorList; + + /** + * Instantiates a new Validation exception. + * + * @param g2PcErrorList the error list + */ + public G2pcValidationException(List g2PcErrorList){ + this.g2PcErrorList = g2PcErrorList; + + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java new file mode 100644 index 0000000..e59929e --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java @@ -0,0 +1,85 @@ +package g2pc.core.lib.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.UUID; + +/** + * The type Common utils. + */ +@Service +@Slf4j +public class CommonUtils { + + /** + * Gets current time stamp. + * + * @return the current time stamp + */ + public String getCurrentTimeStamp() + { + return new SimpleDateFormat("yyyy-MM-dd HH.mm.ss").format(new java.util.Date()); + } + + /** + * Gets uuid. + * + * @return the uuid + */ + public String getUUID() + { + return UUID.randomUUID().toString(); + } + + + /** + * Get request header string input stream. + * + * @return the input stream + */ + public InputStream getRequestHeaderString(){ + InputStream schemaStream = CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/headerschema.json"); + return schemaStream; + } + + /** + * Get request message string input stream. + * + * @return the input stream + */ + public InputStream getRequestMessageString(){ + InputStream schemaStream = CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/messageschema.json"); + return schemaStream; + } + + /** + * Get response header string input stream. + * + * @return the input stream + */ + public InputStream getResponseHeaderString(){ + InputStream schemaStream = CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/ResponseHeaderschema.json"); + return schemaStream; + } + + /** + * Get response message string input stream. + * + * @return the input stream + */ + public InputStream getResponseMessageString(){ + InputStream schemaStream = CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/ResponseMessageschema.json"); + return schemaStream; + } + + + + +} diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseHeaderschema.json b/g2pc-core-lib/src/main/resources/schema/ResponseHeaderschema.json new file mode 100644 index 0000000..7e4ad75 --- /dev/null +++ b/g2pc-core-lib/src/main/resources/schema/ResponseHeaderschema.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "header response schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "version" : { + "type": "string" + }, + "message_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "message_ts": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "action": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "status" : { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "status_reason_code" : { + "type" : [ "string", "null" ], + "enum": ["rjct.reference_id.invalid", "rjct.reference_id.duplicate", + "rjct.timestamp.invalid" ,"rjct.search_criteria.invalid" , + "rjct.filter.invalid" ,"rjct.sort.invalid" , + "rjct.pagination.invalid" ,"rjct.search.too_many_records_found"] + }, + "status_reason_message": { + "anyOf": [ + { "type": "string" }, + { "type": "null" } + ], + "minLength": 0, + "maxLength": 99 + }, + "total_count": { + "type": "number" + }, + "completed_count": { + "type": "number" + }, + "sender_id": { + "type": "string" + }, + "receiver_id": { + "type": "string" + }, + "is_msg_encrypted": { + "type": "boolean" + }, + "meta": { + "anyOf": [ + { "type": "string" }, + { "type": "null" } + ], + "properties": { + + } + } + }, + "required": ["message_id","message_ts","action","total_count"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseMessageschema.json b/g2pc-core-lib/src/main/resources/schema/ResponseMessageschema.json new file mode 100644 index 0000000..c27cbb8 --- /dev/null +++ b/g2pc-core-lib/src/main/resources/schema/ResponseMessageschema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties" : { + "type": { + "type": "string" + }, + "transaction_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "correlation_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "search_response" : { + "type" : "object", + "properties" : { + "reference_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "timestamp": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "status": { + "type": "string", + "enum": ["rcvd","pdng","succ","rjct"], + "$ref": "#/definitions/nonEmptyString" + }, + "status_reason_code" : { + "type": "string", + "enum": ["rjct.reference_id.invalid", "rjct.reference_id.duplicate", + "rjct.timestamp.invalid" ,"rjct.search_criteria.invalid" , + "rjct.filter.invalid" ,"rjct.sort.invalid" , + "rjct.pagination.invalid" ,"rjct.search.too_many_records_found"] + }, + "status_reason_message": { + "type": "string", + "minLength": 0, + "maxLength": 99 + }, + "data" : { + "type": "object", + "properties": { + "version" : { + "type": "string" + }, + "reg_type": { + "type": "string" + }, + "reg_sub_type": { + "type": "string" + }, + "reg_record_type": { + "type": "string" + } + }, + "required": ["reg_type","reg_record_type"] + }, + "pagination" : { + "type": "object", + "properties": { + "page_size": { + "type": "number" + }, + "page_number": { + "type": "number" + } + }, + "required": ["page_size"] + }, + "locale" : { + "type": "string" + } + }, + "required": [ + "reference_id" ,"timestamp" , "status" + ] + + + } + } , + "required": [ + "transaction_id","correlation_id" , "search_response" + ], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + diff --git a/g2pc-core-lib/src/main/resources/schema/headerschema.json b/g2pc-core-lib/src/main/resources/schema/headerschema.json new file mode 100644 index 0000000..188edfb --- /dev/null +++ b/g2pc-core-lib/src/main/resources/schema/headerschema.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "header schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "version" : { + "type": [ "string", "null" ] + }, + "message_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "message_ts": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "action": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "sender_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "sender_uri": { + "type": ["string","null"] + }, + "receiver_id": { + "type": ["string","null"] + }, + "total_count": { + "type": "number" + }, + "is_msg_encrypted": { + "type": ["boolean","null"], + "default": "false" + }, + "meta": { + "type": [ "object", "null" ] + } + }, + "required": ["message_id","message_ts","action","sender_id","total_count"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} diff --git a/g2pc-core-lib/src/main/resources/schema/messageschema.json b/g2pc-core-lib/src/main/resources/schema/messageschema.json new file mode 100644 index 0000000..26e8971 --- /dev/null +++ b/g2pc-core-lib/src/main/resources/schema/messageschema.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties" : { + "type": { + "type": "string" + }, + "transaction_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "search_request" : { + "type" : "object", + "properties" : { + "reference_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "timestamp": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "search_criteria" : { + "type": "object" , + "properties": { + "version": { + "type": "string" + }, + "reg_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "reg_sub_type" : { + "type": "string" + }, + "query_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString", + "enum": ["namedQuery","idtype","predicate"] + }, + + "sort" : { + "type": "array", + "items": [ + { + "type": "object" , + "properties": { + "attribute_name" : { + "type": "string" + }, + "sort_order" : { + "type": "string", + "enum": ["asc" , "desc"] + } + } + } + ] + }, + "pagination" : { + "type": "object", + "properties": { + "page_size": { + "type": "number" + }, + "page_number": { + "type": "number" + } + }, + "required": ["page_size"] + }, + "consent": { + "type": "object" + }, + "authorize": { + "type": "object" + } + }, + "required": [ + "reg_type" ,"query_type" + ] + }, + "locale" : { + "type": "string" + + } + }, + "required": [ + "reference_id" ,"timestamp" , "search_criteria" + ] + + + } + } , + "required": [ + "transaction_id" , "search_request" + ], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + From efd4ca881728b31884ff21f83d9cf12cde3f9f30 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 11:53:33 +0530 Subject: [PATCH 03/53] g2pc-dc-core-lib sub repo added. --- g2pc-dc-core-lib/.gitignore | 36 ++++++ g2pc-dc-core-lib/pom.xml | 65 +++++++++++ .../lib/G2pcDcCoreLibraryApplication.java | 15 +++ .../g2pc/dc/core/lib/config/RedisConfig.java | 67 +++++++++++ .../dc/core/lib/constants/DcConstants.java | 8 ++ .../lib/service/RequestBuilderService.java | 22 ++++ .../lib/service/ResponseHandlerService.java | 39 +++++++ .../RequestBuilderServiceImpl.java | 108 ++++++++++++++++++ .../ResponseHandlerServiceImpl.java | 108 ++++++++++++++++++ .../src/main/resources/application.yml | 47 ++++++++ .../G2pcDcCoreLibraryApplicationTests.java | 13 +++ 11 files changed, 528 insertions(+) create mode 100644 g2pc-dc-core-lib/.gitignore create mode 100644 g2pc-dc-core-lib/pom.xml create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java create mode 100644 g2pc-dc-core-lib/src/main/resources/application.yml create mode 100644 g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java diff --git a/g2pc-dc-core-lib/.gitignore b/g2pc-dc-core-lib/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-dc-core-lib/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-dc-core-lib/pom.xml b/g2pc-dc-core-lib/pom.xml new file mode 100644 index 0000000..1fc99c8 --- /dev/null +++ b/g2pc-dc-core-lib/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + + + g2pc.dc.core.lib + g2pc-dc-core-library + 0.0.1-SNAPSHOT + g2pc-dc-core-library + g2pc-dc-core-library + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + true + + + g2pc.core.lib + g2pc-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.3.5 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + redis.clients + jedis + 3.7.1 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java new file mode 100644 index 0000000..aacdefe --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java @@ -0,0 +1,15 @@ +package g2pc.dc.core.lib; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({"g2pc.core.lib"}) +public class G2pcDcCoreLibraryApplication { + + public static void main(String[] args) { + SpringApplication.run(G2pcDcCoreLibraryApplication.class, args); + } + +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java new file mode 100644 index 0000000..6282b0a --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java @@ -0,0 +1,67 @@ +package g2pc.dc.core.lib.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.JedisPoolConfig; + + +@Configuration +@Slf4j +public class RedisConfig { + + @Value("${spring.data.redis.host}") + String host; + + @Value("${spring.data.redis.port}") + int port; + + @Value("${spring.data.redis.password:}") + String password; + + public RedisConnectionFactory redisConnectionFactory() { + + JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(10); + poolConfig.setTestOnBorrow(true); + poolConfig.setTestOnReturn(true); + + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port); + + log.info("Redis Connection Details Host: {}, Port: {}", host, port); + + configuration.setHostName(host); + if (password != null && !password.trim().isEmpty()) { + log.info("Redis Password Set: {}", password); + configuration.setPassword(password); + } + configuration.setPort(port); + + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(configuration); + connectionFactory.setPoolConfig(poolConfig); + + connectionFactory.afterPropertiesSet(); + + return connectionFactory; + } + + @Bean + RedisTemplate redisTemplate() { + final RedisTemplate template = new RedisTemplate<>(); + log.info("Redis Template for String..."); + template.setConnectionFactory(redisConnectionFactory()); + template.setKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new GenericToStringSerializer<>(String.class)); + template.setValueSerializer(new GenericToStringSerializer<>(String.class)); + return template; + } + + +} \ No newline at end of file diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java new file mode 100644 index 0000000..57abd83 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java @@ -0,0 +1,8 @@ +package g2pc.dc.core.lib.constants; + +public class DcConstants { + + public static final String PENDING = "PENDING"; + + public static final String COMPLETED = "COMPLETED"; +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java new file mode 100644 index 0000000..ca6febe --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java @@ -0,0 +1,22 @@ +package g2pc.dc.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; + +public interface RequestBuilderService { + + MessageDTO buildMessage(SearchCriteriaDTO searchCriteriaDTO) ; + + HeaderDTO buildHeader(); + + String buildRequest(SearchCriteriaDTO searchCriteriaDTO,String transactionId) throws JsonProcessingException; + + void sendRequest(String requestString, String uri); + + CacheDTO createCache(String data, String status); + + void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java new file mode 100644 index 0000000..ca8590a --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java @@ -0,0 +1,39 @@ +package g2pc.dc.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; + +/** + * The interface Response handler service. + */ +public interface ResponseHandlerService { + + /** + * Update cache. + * + * @param cacheKey the cache key + * @throws JsonProcessingException the json processing exception + */ + void updateCache(String cacheKey) throws JsonProcessingException; + + /** + * Validate response header. + * + * @param headerDTO the header dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ + public void validateResponseHeader(ResponseHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException; + + /** + * Validate response message. + * + * @param messageDTO the message dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ + public void validateResponseMessage( MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; + +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java new file mode 100644 index 0000000..dd00421 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java @@ -0,0 +1,108 @@ +package g2pc.dc.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.*; +import g2pc.core.lib.enums.SortOrderEnum; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.constants.DcConstants; +import g2pc.dc.core.lib.service.RequestBuilderService; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +public class RequestBuilderServiceImpl implements RequestBuilderService { + + @Autowired + private CommonUtils commonUtils; + + @Autowired + private RedisTemplate redisTemplate; + + @Override + public MessageDTO buildMessage(SearchCriteriaDTO searchCriteriaDTO) { + SearchRequestDTO searchRequestDTO = new SearchRequestDTO(); + searchRequestDTO.setReferenceId("txn98765"); + searchRequestDTO.setTimestamp("2022-12-04T17:20:07-04:00"); + searchRequestDTO.setLocale("en"); + searchRequestDTO.setSearchCriteria(searchCriteriaDTO); + + MessageDTO messageDTO = new MessageDTO(); + messageDTO.setTransactionId("123456789"); + messageDTO.setSearchRequest(searchRequestDTO); + + return messageDTO; + } + + @Override + public HeaderDTO buildHeader() { + RequestHeaderDTO requestHeaderDTO = new RequestHeaderDTO(); + requestHeaderDTO.setMessageId("123"); + requestHeaderDTO.setMessageTs("2022-12-04T17:20:07-04:00"); + requestHeaderDTO.setAction("search"); + requestHeaderDTO.setSenderId("spp.example.org"); + requestHeaderDTO.setReceiverId("pymts.example.org"); + requestHeaderDTO.setTotalCount(21800); + requestHeaderDTO.setIsMsgEncrypted(false); + requestHeaderDTO.setMeta(null); + requestHeaderDTO.setSenderUri("https://spp.example.org/{namespace}/callback/on-search"); + return requestHeaderDTO; + } + + @Override + public String buildRequest(SearchCriteriaDTO searchCriteriaDTO, String transactionId) throws JsonProcessingException { + + MessageDTO messageDTO = buildMessage(searchCriteriaDTO); + messageDTO.setTransactionId(transactionId); + + HeaderDTO headerDTO = buildHeader(); + + RequestDTO requestDTO = new RequestDTO(); + requestDTO.setSignature("new signature to be generated for request"); + requestDTO.setHeader(headerDTO); + requestDTO.setMessage(messageDTO); + + return new ObjectMapper().writeValueAsString(requestDTO); + } + + @Override + public void sendRequest(String requestString, String uri) { + log.info("Save requests to DPs"); + HttpResponse response = Unirest.post(uri) + .header("Content-Type", "application/json") + .body(requestString) + .asString(); + log.info("request send response status = {}", response.getStatus()); + } + + @Override + public CacheDTO createCache(String data, String status) { + log.info("Save requests in cache with status pending"); + CacheDTO cacheDTO = new CacheDTO(); + cacheDTO.setData(data); + cacheDTO.setStatus(status); + cacheDTO.setCreatedDate(commonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + return cacheDTO; + } + + @Override + public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + } +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java new file mode 100644 index 0000000..46d664b --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java @@ -0,0 +1,108 @@ +package g2pc.dc.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.constants.DcConstants; +import g2pc.dc.core.lib.service.ResponseHandlerService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * The type Response handler service. + */ +@Service +@Slf4j +public class ResponseHandlerServiceImpl implements ResponseHandlerService { + + @Autowired + private CommonUtils commonUtils; + + @Autowired + private RedisTemplate redisTemplate; + + @Override + public void updateCache(String cacheKey) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(redisTemplate.opsForValue().get(cacheKey)); + + cacheDTO.setStatus(DcConstants.COMPLETED); + cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + } + + @Override + public void validateResponseHeader(ResponseHeaderDTO responseHeaderDTO) throws G2pcValidationException, JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + String headerInfoString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(responseHeaderDTO); + InputStream schemaStream = commonUtils.getResponseHeaderString(); + JsonNode jsonNodeMessage = objectMapper.readTree(headerInfoString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorCombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorCombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorCombinedMessage); + } + + } + + @Override + public void validateResponseMessage(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(messageDTO); + log.info("MessageString -> " + messageString); + InputStream schemaStream = commonUtils.getResponseMessageString(); + JsonNode jsonNodeMessage = objectMapper.readTree(messageString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorCombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorCombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorCombinedMessage); + } + + } +} diff --git a/g2pc-dc-core-lib/src/main/resources/application.yml b/g2pc-dc-core-lib/src/main/resources/application.yml new file mode 100644 index 0000000..f6479aa --- /dev/null +++ b/g2pc-dc-core-lib/src/main/resources/application.yml @@ -0,0 +1,47 @@ + +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/g2p?currentSchema=common + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +client: + api_urls: + mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + + diff --git a/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java b/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java new file mode 100644 index 0000000..6849175 --- /dev/null +++ b/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java @@ -0,0 +1,13 @@ +package g2pc.dc.core.lib; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class G2pcDcCoreLibraryApplicationTests { + + @Test + void contextLoads() { + } + +} From 2d4ff49465ce6f4b02a6e67f6c1c1779fe626eff Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 11:57:37 +0530 Subject: [PATCH 04/53] g2pc-dp-core-lib sub repo added. --- g2pc-dp-core-lib/.gitignore | 36 ++++ g2pc-dp-core-lib/pom.xml | 70 ++++++++ .../lib/G2pcDpCoreLibraryApplication.java | 15 ++ .../core/lib/config/DcObjectMapperConfig.java | 21 +++ .../g2pc/dp/core/lib/config/RedisConfig.java | 67 ++++++++ .../dp/core/lib/constants/DpConstants.java | 13 ++ .../lib/service/RequestHandlerService.java | 25 +++ .../lib/service/ResponseBuilderService.java | 20 +++ .../RequestHandlerServiceImpl.java | 155 ++++++++++++++++++ .../ResponseBuilderServiceImpl.java | 153 +++++++++++++++++ .../src/main/resources/application.yml | 47 ++++++ .../main/resources/schemas/headerschema.json | 55 +++++++ .../main/resources/schemas/messageschema.json | 110 +++++++++++++ .../G2pcDpCoreLibraryApplicationTests.java | 13 ++ 14 files changed, 800 insertions(+) create mode 100644 g2pc-dp-core-lib/.gitignore create mode 100644 g2pc-dp-core-lib/pom.xml create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/resources/application.yml create mode 100644 g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json create mode 100644 g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json create mode 100644 g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java diff --git a/g2pc-dp-core-lib/.gitignore b/g2pc-dp-core-lib/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-dp-core-lib/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-dp-core-lib/pom.xml b/g2pc-dp-core-lib/pom.xml new file mode 100644 index 0000000..c4ef9ab --- /dev/null +++ b/g2pc-dp-core-lib/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + + + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + g2pc-dp-core-library + g2pc-dp-core-library + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + true + + + g2pc.core.lib + g2pc-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.3.5 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + redis.clients + jedis + 3.7.1 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.networknt + json-schema-validator + 1.0.57 + + + diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java new file mode 100644 index 0000000..4163301 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java @@ -0,0 +1,15 @@ +package g2pc.dp.core.lib; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib"}) +public class G2pcDpCoreLibraryApplication { + + public static void main(String[] args) { + SpringApplication.run(G2pcDpCoreLibraryApplication.class, args); + } + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java new file mode 100644 index 0000000..7f6074e --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java @@ -0,0 +1,21 @@ +package g2pc.dp.core.lib.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DcObjectMapperConfig { + + @Bean + public ObjectMapper dcObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class); + + return objectMapper; + } +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java new file mode 100644 index 0000000..11a57f0 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java @@ -0,0 +1,67 @@ +package g2pc.dp.core.lib.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.JedisPoolConfig; + + +@Configuration +@Slf4j +public class RedisConfig { + + @Value("${spring.data.redis.host}") + String host; + + @Value("${spring.data.redis.port}") + int port; + + @Value("${spring.data.redis.password:}") + String password; + + public RedisConnectionFactory redisConnectionFactory() { + + JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(10); + poolConfig.setTestOnBorrow(true); + poolConfig.setTestOnReturn(true); + + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port); + + log.info("Redis Connection Details Host: {}, Port: {}", host, port); + + configuration.setHostName(host); + if (password != null && !password.trim().isEmpty()) { + log.info("Redis Password Set: {}", password); + configuration.setPassword(password); + } + configuration.setPort(port); + + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(configuration); + connectionFactory.setPoolConfig(poolConfig); + + connectionFactory.afterPropertiesSet(); + + return connectionFactory; + } + + @Bean + RedisTemplate redisTemplate() { + final RedisTemplate template = new RedisTemplate<>(); + log.info("Redis Template for String..."); + template.setConnectionFactory(redisConnectionFactory()); + template.setKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new GenericToStringSerializer<>(String.class)); + template.setValueSerializer(new GenericToStringSerializer<>(String.class)); + return template; + } + + +} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java new file mode 100644 index 0000000..f0e72d7 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java @@ -0,0 +1,13 @@ +package g2pc.dp.core.lib.constants; + +public class DpConstants { + + private DpConstants() { + } + + public static final String PENDING = "PENDING"; + + public static final String COMPLETED = "COMPLETED"; + + public static final String SEARCH_REQUEST_RECEIVED = "Search request received"; +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java new file mode 100644 index 0000000..7970636 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java @@ -0,0 +1,25 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; + +import java.util.List; + +public interface RequestHandlerService { + + AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) throws Exception; + + void saveCacheRequest(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; + + List getCacheKeys(String cacheKeySearchString); + + String getRequestData(String cacheKey) throws JsonProcessingException; + + public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException; + + public void validateRequestMessage(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java new file mode 100644 index 0000000..3eb88b0 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java @@ -0,0 +1,20 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; + +public interface ResponseBuilderService { + + String buildResponseMessage(DataDTO dataDTO) throws JsonProcessingException; + + String buildResponseHeader(String headerInfoString, String messageString) throws JsonProcessingException; + + String getResponse(String headerInfoString, String messageString, String algorithm) throws JsonProcessingException; + + String buildResponseString(String cacheKeySearchString, DataDTO dataDTO) throws JsonProcessingException; + + void sendOnSearchResponse(String responseString, String uri); + + void updateRequestStatus(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException; +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java new file mode 100644 index 0000000..178fe07 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java @@ -0,0 +1,155 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.service.RequestHandlerService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; +import g2pc.core.lib.exceptions.G2pcError; + +import java.io.InputStream; +import java.util.*; + +/** + * The type Request handler service. + */ +@Service +@Slf4j +public class RequestHandlerServiceImpl implements RequestHandlerService { + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private CommonUtils commonUtils; + + /** + * Build a request to save in Redis cache + * + * @param requestData required + * @param cacheKey required + * @return Acknowledgement + */ + @Override + public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) throws JsonProcessingException { + log.info("Request saved in cache with status pending"); + CacheDTO cacheDTO = new CacheDTO(); + cacheDTO.setData(requestData); + cacheDTO.setStatus(DpConstants.PENDING); + cacheDTO.setCreatedDate(commonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + + saveCacheRequest(cacheDTO, cacheKey); + + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + + return acknowledgementDTO; + } + + /** + * Save a request in Redis cache + * + * @param cacheDTO required + */ + @Override + public void saveCacheRequest(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, (new ObjectMapper()).writeValueAsString(cacheDTO)); + } + + /** + * Get all cache keys from Redis + * + * @param cacheKeySearchString required unique to DP + * @return List of cache keys + */ + @Override + public List getCacheKeys(String cacheKeySearchString) { + Set redisKeys = redisTemplate.keys(cacheKeySearchString); + return new ArrayList<>(Objects.requireNonNull(redisKeys)); + } + + /** + * Geta a single request with its cache key + * + * @param cacheKey required unique to DP + * @return Request data + */ + @Override + public String getRequestData(String cacheKey) { + return redisTemplate.opsForValue().get(cacheKey); + } + + /** + * The Object mapper. + */ + ObjectMapper objectMapper = new ObjectMapper(); + @Override + public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException { + + String headerInfoString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(headerDTO); + InputStream schemaStream = commonUtils.getRequestHeaderString(); + JsonNode jsonNodeMessage = objectMapper.readTree(headerInfoString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorcombinedMessage); + } + } + + @Override + public void validateRequestMessage(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(messageDTO); + log.info("MessageString -> " + messageString); + InputStream schemaStream = commonUtils.getRequestMessageString(); + JsonNode jsonNodeMessage = objectMapper.readTree(messageString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorcombinedMessage); + } + } + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java new file mode 100644 index 0000000..773042f --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java @@ -0,0 +1,153 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.response.*; +import g2pc.core.lib.enums.AlgorithmENUM; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.service.ResponseBuilderService; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Slf4j +public class ResponseBuilderServiceImpl implements ResponseBuilderService { + + @Autowired + private RequestHandlerService requestService; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private CommonUtils commonUtils; + + @Override + public String buildResponseMessage(DataDTO dataDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + + PaginationDTO paginationDTO = new PaginationDTO(); + paginationDTO.setPageSize(10); + paginationDTO.setPageNumber(1); + paginationDTO.setTotalCount(100); + + SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); + searchResponseDTO.setReferenceId("12345678901234567890"); + searchResponseDTO.setTimestamp("2022-12-04T17:20:07-04:00"); + searchResponseDTO.setStatus(HeaderStatusENUM.SUCC.toValue()); + searchResponseDTO.setStatusReasonCode(""); + searchResponseDTO.setStatusReasonMessage(""); + searchResponseDTO.setData(dataDTO); + searchResponseDTO.setPagination(paginationDTO); + searchResponseDTO.setLocale("en"); + + MessageDTO messageDTO = new MessageDTO(); + messageDTO.setTransactionId("123456789"); + messageDTO.setCorrelationId("9876543210"); + messageDTO.setSearchResponse(searchResponseDTO); + + return objectMapper.writerWithDefaultPrettyPrinter() + .writeValueAsString(messageDTO); + } + + @Override + public String buildResponseHeader(String headerInfoString, String messageString) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). + readValue(headerInfoString); + return new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(headerDTO); + } + + @Override + public String getResponse(String headerInfoString, String messageString, String algorithm) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + + ResponseHeaderDTO responseHeaderDTO = objectMapper.readerFor(ResponseHeaderDTO.class). + readValue(headerInfoString); + + MessageDTO messageDTO = objectMapper.readerFor(MessageDTO.class). + readValue(messageString); + + ResponseDTO responseDTO = new ResponseDTO(); + responseDTO.setSignature("new signature to be generated for response"); + responseDTO.setHeader(responseHeaderDTO); + responseDTO.setMessage(messageDTO); + + return objectMapper + .writerWithDefaultPrettyPrinter() + .writeValueAsString(responseDTO); + } + + @Override + public String buildResponseString(String cacheKeySearchString,DataDTO dataDTO) throws JsonProcessingException { + String responseString = ""; + + String message = buildResponseMessage(dataDTO); + + ResponseHeaderDTO responseHeaderDTO = getResponseHeaderDTO(); + + String headerInfoString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(responseHeaderDTO); + + String header = buildResponseHeader(headerInfoString, message); + responseString = getResponse(header, message, AlgorithmENUM.ED25519.toValue()); + return responseString; + } + + /** + * Get response header + * + * @return ResponseHeaderDTO currently using static header data + */ + private static ResponseHeaderDTO getResponseHeaderDTO() { + ResponseHeaderDTO responseHeaderDTO = new ResponseHeaderDTO(); + responseHeaderDTO.setMessageId("123"); + responseHeaderDTO.setMessageTs("2022-12-04T17:20:07-04:00"); + responseHeaderDTO.setAction("on-search"); + responseHeaderDTO.setSenderId("spp.example.org"); + responseHeaderDTO.setReceiverId("pymts.example.org"); + responseHeaderDTO.setTotalCount(21800); + responseHeaderDTO.setIsMsgEncrypted(false); + responseHeaderDTO.setMeta(null); + responseHeaderDTO.setStatus(HeaderStatusENUM.SUCC.toValue()); + responseHeaderDTO.setStatusReasonCode(""); + responseHeaderDTO.setStatusReasonMessage(""); + responseHeaderDTO.setCompletedCount(50); + return responseHeaderDTO; + } + + @Override + public void sendOnSearchResponse(String responseString, String uri) { + log.info("Send on-search response"); + HttpResponse response = Unirest.post(uri) + .header("Content-Type", "application/json") + .body(responseString) + .asString(); + log.info("on-search response status = {}", response.getStatus()); + } + + @Override + public void updateRequestStatus(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException { + log.info("Updated cache status"); + cacheDTO.setStatus(status); + cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + } +} diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml new file mode 100644 index 0000000..f6479aa --- /dev/null +++ b/g2pc-dp-core-lib/src/main/resources/application.yml @@ -0,0 +1,47 @@ + +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/g2p?currentSchema=common + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +client: + api_urls: + mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + + diff --git a/g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json b/g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json new file mode 100644 index 0000000..188edfb --- /dev/null +++ b/g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "header schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "version" : { + "type": [ "string", "null" ] + }, + "message_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "message_ts": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "action": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "sender_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "sender_uri": { + "type": ["string","null"] + }, + "receiver_id": { + "type": ["string","null"] + }, + "total_count": { + "type": "number" + }, + "is_msg_encrypted": { + "type": ["boolean","null"], + "default": "false" + }, + "meta": { + "type": [ "object", "null" ] + } + }, + "required": ["message_id","message_ts","action","sender_id","total_count"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} diff --git a/g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json b/g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json new file mode 100644 index 0000000..26e8971 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties" : { + "type": { + "type": "string" + }, + "transaction_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "search_request" : { + "type" : "object", + "properties" : { + "reference_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "timestamp": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "search_criteria" : { + "type": "object" , + "properties": { + "version": { + "type": "string" + }, + "reg_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "reg_sub_type" : { + "type": "string" + }, + "query_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString", + "enum": ["namedQuery","idtype","predicate"] + }, + + "sort" : { + "type": "array", + "items": [ + { + "type": "object" , + "properties": { + "attribute_name" : { + "type": "string" + }, + "sort_order" : { + "type": "string", + "enum": ["asc" , "desc"] + } + } + } + ] + }, + "pagination" : { + "type": "object", + "properties": { + "page_size": { + "type": "number" + }, + "page_number": { + "type": "number" + } + }, + "required": ["page_size"] + }, + "consent": { + "type": "object" + }, + "authorize": { + "type": "object" + } + }, + "required": [ + "reg_type" ,"query_type" + ] + }, + "locale" : { + "type": "string" + + } + }, + "required": [ + "reference_id" ,"timestamp" , "search_criteria" + ] + + + } + } , + "required": [ + "transaction_id" , "search_request" + ], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + diff --git a/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java b/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java new file mode 100644 index 0000000..4178ad7 --- /dev/null +++ b/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java @@ -0,0 +1,13 @@ +package g2pc.dp.core.lib; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class G2pcDpCoreLibraryApplicationTests { + + @Test + void contextLoads() { + } + +} From 7fe95fab2ae4fc24237a6d3be32a1ed4e6178a6e Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 12:01:08 +0530 Subject: [PATCH 05/53] reference app g2pc-ref-dc-client sub repo added. --- .../g2pc-ref-dc-client/.gitignore | 36 ++++ .../g2pc-ref-dc-client/pom.xml | 85 ++++++++++ .../dc/client/G2pcRefDcClientApplication.java | 15 ++ .../dc/client/config/ObjectMapperConfig.java | 21 +++ .../ref/dc/client/constants/Constants.java | 31 ++++ .../client/controller/rest/DcController.java | 114 +++++++++++++ .../dto/farmer/request/QueryFarmerDTO.java | 17 ++ .../farmer/request/QueryParamsFarmerDTO.java | 24 +++ .../farmer/response/RegRecordFarmerDTO.java | 30 ++++ .../dto/mobile/request/QueryMobileDTO.java | 17 ++ .../mobile/request/QueryParamsMobileDTO.java | 22 +++ .../dto/mobile/response/DataMobileDTO.java | 19 +++ .../mobile/response/RegRecordMobileDTO.java | 30 ++++ .../ref/dc/client/dto/payload/PayloadDTO.java | 15 ++ .../dc/client/dto/payload/PayloadDataDTO.java | 26 +++ .../entity/RegistryTransactionsEntity.java | 47 ++++++ .../RegistryTransactionsRepository.java | 17 ++ .../service/DcRequestBuilderService.java | 20 +++ .../service/DcResponseHandlerService.java | 13 ++ .../client/service/DcValidationService.java | 33 ++++ .../DcRequestBuilderServiceImpl.java | 155 ++++++++++++++++++ .../DcResponseHandlerServiceImpl.java | 112 +++++++++++++ .../serviceimpl/DcValidationServiceImpl.java | 84 ++++++++++ .../src/main/resources/application.yml | 53 ++++++ .../schema/RegRecordFarmerSchema.json | 39 +++++ .../G2pcRefDcClientApplicationTests.java | 21 +++ 26 files changed, 1096 insertions(+) create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/.gitignore create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/pom.xml create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/response/RegRecordFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/DataMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/RegRecordMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/.gitignore b/g2pc-reference-apps/g2pc-ref-dc-client/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml new file mode 100644 index 0000000..7219d54 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + + + g2pc.ref.dc.client + g2pc-ref-dc-client + 0.0.1-SNAPSHOT + g2pc-ref-dc-client + g2pc-ref-dc-client + + 11 + + + + org.springframework.boot + spring-boot-starter + + + org.projectlombok + lombok + true + + + org.postgresql + postgresql + 42.3.5 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + g2pc.dc.core.lib + g2pc-dc-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.springdoc + springdoc-openapi-ui + 1.6.8 + + + redis.clients + jedis + 3.7.1 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + + com.networknt + json-schema-validator + 1.0.57 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java new file mode 100644 index 0000000..e16d517 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java @@ -0,0 +1,15 @@ +package g2pc.ref.dc.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({"g2pc.core.lib","g2pc.dc.core.lib","g2pc.ref.dc.client"}) +public class G2pcRefDcClientApplication { + + public static void main(String[] args) { + SpringApplication.run(G2pcRefDcClientApplication.class, args); + } + +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java new file mode 100644 index 0000000..bf3ed88 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java @@ -0,0 +1,21 @@ +package g2pc.ref.dc.client.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.ref.dc.client.dto.mobile.response.DataMobileDTO; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObjectMapperConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class); + return objectMapper; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java new file mode 100644 index 0000000..b9fbf2c --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java @@ -0,0 +1,31 @@ +package g2pc.ref.dc.client.constants; + +public class Constants { + + private Constants() { + } + + public static final String SEARCH_REQUEST_RECEIVED = "Search request received successfully"; + + public static final String INVALID_RESPONSE = "Invalid Response received from server"; + + public static final String CONFLICT = "CONFLICT"; + + public static final String INVALID_AUTHORIZATION = "Invalid Authorization"; + + public static final String PENDING = "PENDING"; + + public static final String COMPLETED = "COMPLETED"; + + public static final String CACHE_KEY_STRING = "req*"; + + public static final String ON_SEARCH_RESPONSE_RECEIVED = "On-Search response received successfully"; + + public static final String PAID_FARMER = "paid_farmer"; + + public static final String IS_REGISTERED = "is_registered"; + + public static final String FARMER_CACHE_STRING = "farmer-"; + + public static final String MOBILE_CACHE_STRING = "mno-"; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java new file mode 100644 index 0000000..11d01d7 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java @@ -0,0 +1,114 @@ +package g2pc.ref.dc.client.controller.rest; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.ref.dc.client.constants.Constants; +import g2pc.ref.dc.client.dto.payload.PayloadDTO; +import g2pc.ref.dc.client.service.DcRequestBuilderService; +import g2pc.ref.dc.client.service.DcResponseHandlerService; +import g2pc.ref.dc.client.service.DcValidationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.io.IOException; + +@RestController +@Slf4j +@RequestMapping(produces = "application/json") +@Tag(name = "Data Consumer", description = "DC APIs") +public class DcController { + + @Autowired + private DcRequestBuilderService dcRequestBuilderService; + + @Autowired + private DcResponseHandlerService dcResponseHandlerService; + + @Autowired + DcValidationService dcValidationService; + + /** + * Get consumer search request + * + * @param payloadDTO required + * @return Search request received acknowledgement + */ + @Operation(summary = "Receive consumer search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/consumer/search") + public AcknowledgementDTO createSearchRequests(@RequestBody PayloadDTO payloadDTO) throws JsonProcessingException { + log.info("Payload received"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(payloadDTO.getData())) { + acknowledgementDTO = dcRequestBuilderService.generateRequest(new ObjectMapper().writeValueAsString(payloadDTO)); + } + return acknowledgementDTO; + } + + /** + * Listen to registry response + * + * @param responseDTO required + * @return On-Search response received acknowledgement + */ + @Operation(summary = "Listen to registry response") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.ON_SEARCH_RESPONSE_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/on-search") + public AcknowledgementDTO createSearchRequests(@RequestBody ResponseDTO responseDTO) throws G2pcValidationException{ + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + try { + dcValidationService.validateResponseDto(responseDTO); + if (ObjectUtils.isNotEmpty(responseDTO)) { + acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO); + } + } catch (G2pcValidationException e) { + + throw new G2pcValidationException(e.getG2PcErrorList()); + } + catch (JsonProcessingException e){ + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); + } + catch (Exception e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST , e.getMessage()); + } + return acknowledgementDTO; + } + + + /** + * Handle validation exception error response. + * + * @param ex the ValidationException + * @return the error response + */ + @ExceptionHandler(value + = G2pcValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse + handleValidationException( + G2pcValidationException ex) + { + return new ErrorResponse( + ex.getG2PcErrorList()); + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java new file mode 100644 index 0000000..cb1e751 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java @@ -0,0 +1,17 @@ +package g2pc.ref.dc.client.dto.farmer.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryFarmerDTO { + + @JsonProperty("query_params") + private QueryParamsFarmerDTO queryParams; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java new file mode 100644 index 0000000..21a2544 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java @@ -0,0 +1,24 @@ +package g2pc.ref.dc.client.dto.farmer.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryParamsFarmerDTO { + + @JsonProperty("farmer_id") + private List farmerId; + + @JsonProperty("season") + private String season; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/response/RegRecordFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/response/RegRecordFarmerDTO.java new file mode 100644 index 0000000..b0937ea --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/response/RegRecordFarmerDTO.java @@ -0,0 +1,30 @@ +package g2pc.ref.dc.client.dto.farmer.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class RegRecordFarmerDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("season") + private String season; + + @JsonProperty("payment_status") + private String paymentStatus; + + @JsonProperty("payment_date") + private String paymentDate; + + @JsonProperty("payment_amount") + private Double paymentAmount; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java new file mode 100644 index 0000000..0e35e2d --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java @@ -0,0 +1,17 @@ +package g2pc.ref.dc.client.dto.mobile.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class QueryMobileDTO{ + + @JsonProperty("query_params") + private QueryParamsMobileDTO queryParams; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java new file mode 100644 index 0000000..6dc40d2 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java @@ -0,0 +1,22 @@ +package g2pc.ref.dc.client.dto.mobile.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class QueryParamsMobileDTO { + + @JsonProperty("mobile_number") + private List mobileNumber; + + @JsonProperty("season") + private String season; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/DataMobileDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/DataMobileDTO.java new file mode 100644 index 0000000..e2c84bb --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/DataMobileDTO.java @@ -0,0 +1,19 @@ +package g2pc.ref.dc.client.dto.mobile.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class DataMobileDTO{ + + @JsonProperty("reg_records") + private List regRecords; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/RegRecordMobileDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/RegRecordMobileDTO.java new file mode 100644 index 0000000..cb20ede --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/response/RegRecordMobileDTO.java @@ -0,0 +1,30 @@ +package g2pc.ref.dc.client.dto.mobile.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class RegRecordMobileDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("season") + private String season; + + @JsonProperty("mobile_number") + private String mobileNumber; + + @JsonProperty("mobile_status") + private String mobileStatus; + + @JsonProperty("created_date") + private String createdDate; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java new file mode 100644 index 0000000..739e497 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java @@ -0,0 +1,15 @@ +package g2pc.ref.dc.client.dto.payload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PayloadDTO { + + PayloadDataDTO data; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java new file mode 100644 index 0000000..18e2a41 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java @@ -0,0 +1,26 @@ +package g2pc.ref.dc.client.dto.payload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PayloadDataDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("mobile_number") + private String mobileNumber; + + @JsonProperty("season") + private String season; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java new file mode 100644 index 0000000..1d8a068 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java @@ -0,0 +1,47 @@ +package g2pc.ref.dc.client.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.sql.Timestamp; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "registry_transactions") +public class RegistryTransactionsEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + private String transactionId; + + private String farmerId; + + private String farmerName; + + private String mobileNumber; + + private String season; + + private String paymentStatus; + + private String paymentDate; + + private Double paymentAmount; + + private String mobileStatus; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java new file mode 100644 index 0000000..e325097 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java @@ -0,0 +1,17 @@ +package g2pc.ref.dc.client.repository; + +import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RegistryTransactionsRepository extends JpaRepository { + + Optional getByTransactionId(String transactionId); + + Optional getByTransactionIdAndFarmerId(String transactionId,String farmerId); + + Optional getByTransactionIdAndMobileNumber(String transactionId,String mobileNumber); +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java new file mode 100644 index 0000000..c3edda0 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java @@ -0,0 +1,20 @@ +package g2pc.ref.dc.client.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; +import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; + +import java.util.List; + +public interface DcRequestBuilderService { + + List createQuery(String payloadString) throws JsonProcessingException; + + SearchCriteriaDTO getSearchCriteriaDTO(QueryDTO queryDTO, String regType); + + AcknowledgementDTO generateRequest(String payloadString) throws JsonProcessingException; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java new file mode 100644 index 0000000..3fb9766 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java @@ -0,0 +1,13 @@ +package g2pc.ref.dc.client.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; + +public interface DcResponseHandlerService { + + void updateTransactionDbAndCache(String transactionId, SearchResponseDTO searchResponseDTO) throws JsonProcessingException; + + AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java new file mode 100644 index 0000000..042e1e5 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java @@ -0,0 +1,33 @@ +package g2pc.ref.dc.client.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import org.springframework.stereotype.Service; + +/** + * The interface Dc validation service. + */ +@Service +public interface DcValidationService { + + + /** + * Validate response dto. + * + * @param responseDTO the response dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ + public void validateResponseDto(ResponseDTO responseDTO) throws G2pcValidationException, JsonProcessingException; + + /** + * Validate reg records. + * + * @param messageDTO the message dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ + public void validateRegRecords(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java new file mode 100644 index 0000000..7c5cead --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java @@ -0,0 +1,155 @@ +package g2pc.ref.dc.client.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.message.request.*; +import g2pc.core.lib.enums.SortOrderEnum; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.service.RequestBuilderService; +import g2pc.ref.dc.client.constants.Constants; +import g2pc.ref.dc.client.dto.farmer.request.QueryParamsFarmerDTO; +import g2pc.ref.dc.client.dto.mobile.request.QueryParamsMobileDTO; +import g2pc.ref.dc.client.dto.payload.PayloadDTO; +import g2pc.ref.dc.client.dto.payload.PayloadDataDTO; +import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; +import g2pc.ref.dc.client.repository.RegistryTransactionsRepository; +import g2pc.ref.dc.client.service.DcRequestBuilderService; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { + + @Value("${registry.api_urls.farmer_search_api}") + private String farmerSearchURL; + + @Value("${registry.api_urls.mobile_search_api}") + private String mobileSearchURL; + + @Autowired + private CommonUtils commonUtils; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private RegistryTransactionsRepository registryTransactionsRepository; + + @Autowired + private RequestBuilderService requestBuilderService; + + @Override + public List createQuery(String payloadString) throws JsonProcessingException { + PayloadDTO payloadDTO = new ObjectMapper().readValue(payloadString, PayloadDTO.class); + List queryDTOList = new ArrayList<>(); + PayloadDataDTO payloadDataDTO = payloadDTO.getData(); + if (StringUtils.isNotEmpty(payloadDataDTO.getFarmerId())) { + QueryParamsFarmerDTO queryParamsDTO = new QueryParamsFarmerDTO(); + queryParamsDTO.setFarmerId(Collections.singletonList(payloadDataDTO.getFarmerId())); + queryParamsDTO.setSeason(payloadDataDTO.getSeason()); + + QueryDTO queryDTO = new QueryDTO(); + queryDTO.setQueryName(Constants.PAID_FARMER); + queryDTO.setQueryParams(queryParamsDTO); + queryDTOList.add(queryDTO); + } + if (StringUtils.isNotEmpty(payloadDataDTO.getMobileNumber())) { + QueryParamsMobileDTO queryParamsDTO = new QueryParamsMobileDTO(); + queryParamsDTO.setMobileNumber(Collections.singletonList(payloadDataDTO.getMobileNumber())); + queryParamsDTO.setSeason(payloadDataDTO.getSeason()); + + QueryDTO queryDTO = new QueryDTO(); + queryDTO.setQueryName(Constants.IS_REGISTERED); + queryDTO.setQueryParams(queryParamsDTO); + queryDTOList.add(queryDTO); + } + return queryDTOList; + } + + @Override + public AcknowledgementDTO generateRequest(String payloadString) throws JsonProcessingException { + String requestString = ""; + PayloadDTO payloadDTO = new ObjectMapper().readValue(payloadString, PayloadDTO.class); + ObjectMapper objectMapper = new ObjectMapper(); + + String transactionId = "123456789"; + CacheDTO cacheDTO = requestBuilderService.createCache(objectMapper.writeValueAsString(payloadDTO.getData()), "RECEIVED"); + requestBuilderService.saveCache(cacheDTO, "initial-" + transactionId); + + Optional entityOptional = registryTransactionsRepository.getByTransactionId(transactionId); + if (entityOptional.isEmpty()) { + RegistryTransactionsEntity entity = new RegistryTransactionsEntity(); + entity.setTransactionId(transactionId); + registryTransactionsRepository.save(entity); + } + + List queryDTOList = createQuery(objectMapper.writeValueAsString(payloadDTO)); + for (QueryDTO queryDTO : queryDTOList) { + SearchCriteriaDTO searchCriteriaDTO = getSearchCriteriaDTO(queryDTO, queryDTO.getQueryName()); + + requestString = requestBuilderService.buildRequest(searchCriteriaDTO, transactionId); + if (queryDTO.getQueryName().equals(Constants.PAID_FARMER)) { + requestBuilderService.sendRequest(requestString, farmerSearchURL); + + cacheDTO = requestBuilderService.createCache(requestString, Constants.PENDING); + requestBuilderService.saveCache(cacheDTO, Constants.FARMER_CACHE_STRING + transactionId); + } else if (queryDTO.getQueryName().equals(Constants.IS_REGISTERED)) { + requestBuilderService.sendRequest(requestString, mobileSearchURL); + + cacheDTO = requestBuilderService.createCache(requestString, Constants.PENDING); + requestBuilderService.saveCache(cacheDTO, Constants.MOBILE_CACHE_STRING + transactionId); + } + } + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(Constants.SEARCH_REQUEST_RECEIVED); + acknowledgementDTO.setStatus("RECEIVED"); + return acknowledgementDTO; + } + + @Override + public SearchCriteriaDTO getSearchCriteriaDTO(QueryDTO queryDTO, String regType) { + PaginationDTO paginationDTO = new PaginationDTO(10, 1); + ConsentDTO consentDTO = new ConsentDTO(); + AuthorizeDTO authorizeDTO = new AuthorizeDTO(); + List sortDTOList = new ArrayList<>(); + SortDTO sortDTO = new SortDTO(); + if (queryDTO.getQueryName().equals("paid_farmer")) { + sortDTO.setAttributeName("farmer_id"); + sortDTO.setSortOrder(SortOrderEnum.ASC.toValue()); + } else if (queryDTO.getQueryName().equals("is_registered")) { + sortDTO.setAttributeName("mobile_number"); + sortDTO.setSortOrder(SortOrderEnum.ASC.toValue()); + } + sortDTOList.add(sortDTO); + + SearchCriteriaDTO searchCriteriaDTO = new SearchCriteriaDTO(); + searchCriteriaDTO.setVersion("1.0.0"); + if (queryDTO.getQueryName().equals("paid_farmer")) { + searchCriteriaDTO.setRegType("ns:FARMER_REGISTRY"); + } else if (queryDTO.getQueryName().equals("is_registered")) { + searchCriteriaDTO.setRegType("ns:MOBILE_REGISTRY"); + } + searchCriteriaDTO.setRegSubType(""); + searchCriteriaDTO.setQueryType("namedQuery"); + searchCriteriaDTO.setQuery(queryDTO); + searchCriteriaDTO.setSort(sortDTOList); + searchCriteriaDTO.setPagination(paginationDTO); + searchCriteriaDTO.setConsent(consentDTO); + searchCriteriaDTO.setAuthorize(authorizeDTO); + return searchCriteriaDTO; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java new file mode 100644 index 0000000..eadd1fc --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java @@ -0,0 +1,112 @@ +package g2pc.ref.dc.client.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.service.ResponseHandlerService; +import g2pc.ref.dc.client.constants.Constants; +import g2pc.ref.dc.client.dto.farmer.response.RegRecordFarmerDTO; +import g2pc.ref.dc.client.dto.mobile.response.RegRecordMobileDTO; +import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; +import g2pc.ref.dc.client.repository.RegistryTransactionsRepository; +import g2pc.ref.dc.client.service.DcResponseHandlerService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + + +@Service +@Slf4j +public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { + + @Autowired + private CommonUtils commonUtils; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private RegistryTransactionsRepository registryTransactionsRepository; + + @Autowired + private ResponseHandlerService responseHandlerService; + + @Override + public void updateTransactionDbAndCache(String transactionId, SearchResponseDTO searchResponseDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + + String regType = searchResponseDTO.getData().getRegType(); + String cacheKey = ""; + Optional entityOptional = registryTransactionsRepository.getByTransactionId(transactionId); + RegistryTransactionsEntity entity = new RegistryTransactionsEntity(); + if (entityOptional.isPresent()) { + entity = entityOptional.get(); + } + if (regType.equals("ns:FARMER_REGISTRY")) { + log.info("on-search response received from farmer registry"); + cacheKey = "farmer-" + transactionId; + + DataDTO dataDTO = searchResponseDTO.getData(); + List recordFarmerDTOList = objectMapper.convertValue(dataDTO.getRegRecords(), new TypeReference<>() { + }); + for (RegRecordFarmerDTO regRecordFarmerDTO : recordFarmerDTOList) { + Optional optional = registryTransactionsRepository.getByTransactionIdAndFarmerId(transactionId, regRecordFarmerDTO.getFarmerId()); + if (optional.isEmpty() && (ObjectUtils.isNotEmpty(entity))) { + entity.setFarmerId(regRecordFarmerDTO.getFarmerId()); + entity.setFarmerName(regRecordFarmerDTO.getFarmerName()); + entity.setSeason(regRecordFarmerDTO.getSeason()); + entity.setPaymentStatus(regRecordFarmerDTO.getPaymentStatus()); + entity.setPaymentDate(regRecordFarmerDTO.getPaymentDate()); + entity.setPaymentAmount(regRecordFarmerDTO.getPaymentAmount()); + registryTransactionsRepository.save(entity); + } + } + } else if (regType.equals("ns:MOBILE_REGISTRY")) { + log.info("on-search response received from mobile registry"); + cacheKey = "mno-" + transactionId; + + DataDTO dataDTO = searchResponseDTO.getData(); + List recordMobileDTOList = objectMapper.convertValue(dataDTO.getRegRecords(), new TypeReference<>() { + }); + for (RegRecordMobileDTO regRecordMobileDTO : recordMobileDTOList) { + Optional optional = registryTransactionsRepository.getByTransactionIdAndMobileNumber(transactionId, regRecordMobileDTO.getMobileNumber()); + if (optional.isEmpty() && (ObjectUtils.isNotEmpty(entity))) { + entity.setFarmerId(regRecordMobileDTO.getFarmerId()); + entity.setFarmerName(regRecordMobileDTO.getFarmerName()); + entity.setSeason(regRecordMobileDTO.getSeason()); + entity.setMobileNumber(regRecordMobileDTO.getMobileNumber()); + entity.setMobileStatus(regRecordMobileDTO.getMobileStatus()); + registryTransactionsRepository.save(entity); + } + } + } + log.info("transaction table updated with combined data"); + responseHandlerService.updateCache(cacheKey); + } + + @Override + public AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + + MessageDTO messageDTO = responseDTO.getMessage(); + String transactionId = messageDTO.getTransactionId(); + SearchResponseDTO searchResponseDTO = messageDTO.getSearchResponse(); + + updateTransactionDbAndCache(transactionId, searchResponseDTO); + + acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED); + acknowledgementDTO.setStatus(Constants.COMPLETED); + return acknowledgementDTO; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java new file mode 100644 index 0000000..d804f84 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java @@ -0,0 +1,84 @@ +package g2pc.ref.dc.client.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dc.core.lib.service.ResponseHandlerService; +import g2pc.ref.dc.client.service.DcValidationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +@Service +@Slf4j +public class DcValidationServiceImpl implements DcValidationService { + + @Autowired + ResponseHandlerService responseHandlerService; + + @Override + public void validateResponseDto(ResponseDTO responseDTO) throws G2pcValidationException, JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(responseDTO.getHeader()); + ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). + readValue(headerString); + responseHandlerService.validateResponseHeader(headerDTO); + + validateRegRecords(responseDTO.getMessage()); + + responseHandlerService.validateResponseMessage(responseDTO.getMessage()); + + + + } + + @Override + public void validateRegRecords(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + DataDTO dataDTO = messageDTO.getSearchResponse().getData(); + + String regRecordString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(dataDTO.getRegRecords()); + log.info("MessageString -> " + regRecordString); + InputStream schemaStream = DcValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/RegRecordFarmerSchema.json"); + JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); + JsonSchema schemaRegRecord = null; + if(schemaStream !=null){ + schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); + List errorCombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorCombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorCombinedMessage); + } + } + } + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml new file mode 100644 index 0000000..0ce0a34 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -0,0 +1,53 @@ +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/g2p?currentSchema=dc + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + +server: + port: 8000 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +registry: + api_urls: + #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" + #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json new file mode 100644 index 0000000..94475f2 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "farmer_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "farmer_name": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "season": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "payment_status": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "payment_date": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "payment_amount": { + "type": "number" + } + }, + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java new file mode 100644 index 0000000..b412b79 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java @@ -0,0 +1,21 @@ +package g2pc.ref.dc.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@Slf4j +class G2pcRefDcClientApplicationTests { + + @Test + void contextLoads() { + } + + @Test + void testResponse() throws JsonProcessingException { + + + } +} From 46a7db0b2ffb66bb8af16a9b7ca3b1675204bb34 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 12:03:41 +0530 Subject: [PATCH 06/53] reference app g2pc-ref-farmer-regsvc sub repo added. --- .../g2pc-ref-farmer-regsvc/.gitignore | 36 +++++++ .../g2pc-ref-farmer-regsvc/pom.xml | 80 +++++++++++++++ .../G2pcRefFarmerRegsvcApplication.java | 17 ++++ .../regsvc/config/ObjectMapperConfig.java | 19 ++++ .../farmer/regsvc/constants/Constants.java | 19 ++++ .../controller/rest/RegistryController.java | 98 +++++++++++++++++++ .../regsvc/dto/request/QueryFarmerDTO.java | 16 +++ .../dto/request/QueryParamsFarmerDTO.java | 21 ++++ .../regsvc/dto/response/DataFarmerDTO.java | 19 ++++ .../dto/response/RegRecordFarmerDTO.java | 30 ++++++ .../regsvc/entity/FarmerInfoEntity.java | 34 +++++++ .../repository/FarmerInfoRepository.java | 13 +++ .../farmer/regsvc/scheduler/Scheduler.java | 71 ++++++++++++++ .../service/FarmerResponseBuilderService.java | 17 ++++ .../service/FarmerValidationService.java | 30 ++++++ .../FarmerResponseBuilderServiceImpl.java | 95 ++++++++++++++++++ .../FarmerValidationServiceImpl.java | 90 +++++++++++++++++ .../src/main/resources/application.yml | 50 ++++++++++ .../resources/schema/farmerQuerySchema.json | 42 ++++++++ .../G2pcRefFarmerRegsvcApplicationTests.java | 25 +++++ 20 files changed, 822 insertions(+) create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/.gitignore create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.gitignore b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml new file mode 100644 index 0000000..557136d --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + + + g2pc.ref.farmer.regsvc + g2pc-ref-farmer-regsvc + 0.0.1-SNAPSHOT + g2pc-ref-farmer-regsvc + g2pc-ref-farmer-regsvc + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + true + + + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.3.5 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + redis.clients + jedis + 3.7.1 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springdoc + springdoc-openapi-ui + 1.6.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java new file mode 100644 index 0000000..99aacfd --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java @@ -0,0 +1,17 @@ +package g2pc.ref.farmer.regsvc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib","g2pc.ref.farmer.regsvc","g2pc.dp.core.lib.service"}) +@EnableScheduling +public class G2pcRefFarmerRegsvcApplication { + + public static void main(String[] args) { + SpringApplication.run(G2pcRefFarmerRegsvcApplication.class, args); + } + +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java new file mode 100644 index 0000000..c4de97d --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java @@ -0,0 +1,19 @@ +package g2pc.ref.farmer.regsvc.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObjectMapperConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class); + return objectMapper; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java new file mode 100644 index 0000000..3fb299b --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java @@ -0,0 +1,19 @@ +package g2pc.ref.farmer.regsvc.constants; + +public class Constants { + + private Constants() { + } + + public static final String SEARCH_REQUEST_RECEIVED = "Search request received successfully"; + + public static final String INVALID_RESPONSE = "Invalid Response received from server"; + + public static final String CONFLICT = "CONFLICT"; + + public static final String INVALID_AUTHORIZATION = "Invalid Authorization"; + + public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; + + public static final String CACHE_KEY_STRING = "request-farmer-"; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java new file mode 100644 index 0000000..6489789 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java @@ -0,0 +1,98 @@ +package g2pc.ref.farmer.regsvc.controller.rest; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; +import g2pc.ref.farmer.regsvc.service.FarmerValidationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; +import g2pc.core.lib.exceptionhandler.ErrorResponse; + +/** + * The type Registry controller. + */ +@RestController +@Slf4j +@RequestMapping(produces = "application/json") +@Tag(name = "Provider", description = "Provider APIs") +public class RegistryController { + + @Autowired + private RequestHandlerService requestHandlerService; + + /** + * The Farmer validation service. + */ + @Autowired + FarmerValidationService farmerValidationService; + + + /** + * Get search request from DC + * + * @param requestDTO required + * @return Search request received acknowledgement + * @throws JsonProcessingException the json processing exception + * @throws ResponseStatusException the response status exception + * @throws G2pcValidationException the validation exception + */ + @Operation(summary = "Receive search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/search") + public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO requestDTO) throws JsonProcessingException, ResponseStatusException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + String cacheKey = Constants.CACHE_KEY_STRING + requestDTO.getMessage().getTransactionId(); + try { + farmerValidationService.validateRequestDTO(requestDTO); + return requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey); + } catch (G2pcValidationException e) { + + throw new G2pcValidationException(e.getG2PcErrorList()); + } + catch (JsonProcessingException e){ + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); + } + catch (Exception e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST , e.getMessage()); + } + + + } + + + /** + * Handle validation exception error response. + * + * @param ex the ValidationException + * @return the error response + */ + @ExceptionHandler(value + = G2pcValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse + handleValidationException( + G2pcValidationException ex) + { + return new ErrorResponse( + ex.getG2PcErrorList()); + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryFarmerDTO.java new file mode 100644 index 0000000..c5d6faf --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryFarmerDTO.java @@ -0,0 +1,16 @@ +package g2pc.ref.farmer.regsvc.dto.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryFarmerDTO { + + @JsonProperty("query_params") + private QueryParamsFarmerDTO queryParams; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java new file mode 100644 index 0000000..bea2396 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java @@ -0,0 +1,21 @@ +package g2pc.ref.farmer.regsvc.dto.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryParamsFarmerDTO { + + @JsonProperty("farmer_id") + private List farmerId; + + @JsonProperty("season") + private String season; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java new file mode 100644 index 0000000..85d2059 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java @@ -0,0 +1,19 @@ +package g2pc.ref.farmer.regsvc.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class DataFarmerDTO { + + @JsonProperty("reg_records") + private List regRecords; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java new file mode 100644 index 0000000..0f3242d --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java @@ -0,0 +1,30 @@ +package g2pc.ref.farmer.regsvc.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class RegRecordFarmerDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("season") + private String season; + + @JsonProperty("payment_status") + private String paymentStatus; + + @JsonProperty("payment_date") + private String paymentDate; + + @JsonProperty("payment_amount") + private Double paymentAmount; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java new file mode 100644 index 0000000..aff84f7 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java @@ -0,0 +1,34 @@ +package g2pc.ref.farmer.regsvc.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "farmer_info") +public class FarmerInfoEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + private String farmerId; + + private String farmerName; + + private String season; + + private String paymentStatus; + + private String paymentDate; + + private Double paymentAmount; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java new file mode 100644 index 0000000..fcea09a --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java @@ -0,0 +1,13 @@ +package g2pc.ref.farmer.regsvc.repository; + +import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface FarmerInfoRepository extends JpaRepository { + Optional> findBySeasonAndFarmerIdIn(String season,List farmerId); +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java new file mode 100644 index 0000000..ff72cea --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java @@ -0,0 +1,71 @@ +package g2pc.ref.farmer.regsvc.scheduler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.service.ResponseBuilderService; +import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +@Slf4j +@Service +public class Scheduler { + + @Value("${client.api_urls.client_search_api}") + String onSearchURL; + + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + private ResponseBuilderService responseBuilderService; + + @Autowired + private FarmerResponseBuilderService farmerResponseBuilderService; + + + /** + * Response scheduler + */ + @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. + public void responseScheduler() throws IOException { + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class); + + List cacheKeysList = requestHandlerService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); + // TODO: this logic has to be handled to get single cacheKey + String cacheKey = cacheKeysList.get(0); + String requestData = requestHandlerService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + + String refRecordsString = farmerResponseBuilderService.getRegFarmerRecords( + objectMapper.writeValueAsString(requestDTO.getMessage())); + + DataDTO dataDTO = farmerResponseBuilderService.buildData(refRecordsString); + + String responseString = responseBuilderService.buildResponseString(Constants.CACHE_KEY_SEARCH_STRING, dataDTO); + log.info("Scheduler responseString : {}", responseString); + + responseBuilderService.sendOnSearchResponse(responseString, onSearchURL); + + responseBuilderService.updateRequestStatus(cacheKey, DpConstants.COMPLETED, cacheDTO); + } catch (Exception ex) { + log.error("Scheduler error : {}", ex.getMessage()); + } + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java new file mode 100644 index 0000000..4987a24 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java @@ -0,0 +1,17 @@ +package g2pc.ref.farmer.regsvc.service; + +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.ref.farmer.regsvc.dto.response.RegRecordFarmerDTO; +import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; + +import java.io.IOException; +import java.util.List; + +public interface FarmerResponseBuilderService { + + List getRegRecordFarmerDTO(List farmerInfoList); + + String getRegFarmerRecords(String messageString) throws IOException; + + DataDTO buildData(String regRecordsString) throws IOException; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java new file mode 100644 index 0000000..e6cecfe --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java @@ -0,0 +1,30 @@ +package g2pc.ref.farmer.regsvc.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; + +/** + * The interface Farmer validation service. + */ +public interface FarmerValidationService { + + /** + * Validate request dto. + * + * @param requestDTO the request dto + * @throws G2pcValidationException the validation exception + * @throws JsonProcessingException the json processing exception + */ + void validateRequestDTO (RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException; + + /** + * Validate query dto. + * + * @param queryFarmerDTO the query farmer dto + * @throws G2pcValidationException the validation exception + * @throws JsonProcessingException the json processing exception + */ + void validateQueryDto (QueryFarmerDTO queryFarmerDTO) throws G2pcValidationException, JsonProcessingException; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java new file mode 100644 index 0000000..b1eaeb7 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java @@ -0,0 +1,95 @@ +package g2pc.ref.farmer.regsvc.serviceimpl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.ref.farmer.regsvc.dto.request.QueryParamsFarmerDTO; +import g2pc.ref.farmer.regsvc.dto.response.RegRecordFarmerDTO; +import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; +import g2pc.ref.farmer.regsvc.repository.FarmerInfoRepository; +import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderService { + + @Autowired + private FarmerInfoRepository farmerInfoRepository; + + /** + * Get farmer records information from DB + * + * @param farmerInfoList required + * @return List of farmer records + */ + @Override + public List getRegRecordFarmerDTO(List farmerInfoList) { + List regRecordFarmerDTOList = new ArrayList<>(); + for (FarmerInfoEntity farmerInfoEntity : farmerInfoList) { + RegRecordFarmerDTO dto = new RegRecordFarmerDTO(); + dto.setFarmerId(farmerInfoEntity.getFarmerId()); + dto.setFarmerName(farmerInfoEntity.getFarmerName()); + dto.setSeason(farmerInfoEntity.getSeason()); + dto.setPaymentStatus(farmerInfoEntity.getPaymentStatus()); + dto.setPaymentDate(farmerInfoEntity.getPaymentDate()); + dto.setPaymentAmount(farmerInfoEntity.getPaymentAmount()); + regRecordFarmerDTOList.add(dto); + } + return regRecordFarmerDTOList; + } + + /** + * Get farmer records information string + * + * @param messageString required + * @return String of farmer records + */ + @Override + public String getRegFarmerRecords(String messageString) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + //remove only use while testing + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class); + + MessageDTO messageDTO = objectMapper.readerFor(MessageDTO.class).readValue(messageString); + String queryParams = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery().getQueryParams()); + QueryParamsFarmerDTO queryParamsFarmerDTO = objectMapper.readValue(queryParams, QueryParamsFarmerDTO.class); + + List farmerIds = queryParamsFarmerDTO.getFarmerId(); + String season = queryParamsFarmerDTO.getSeason(); + + List regRecordFarmerDTOList = new ArrayList<>(); + Optional> optionalList = farmerInfoRepository. + findBySeasonAndFarmerIdIn(season, farmerIds); + if (optionalList.isPresent()) { + regRecordFarmerDTOList = getRegRecordFarmerDTO(optionalList.get()); + } + + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(regRecordFarmerDTOList); + } + + @Override + public DataDTO buildData(String regRecordsString) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + List regRecordFarmerDTOList = objectMapper.readerFor(List.class). + readValue(regRecordsString); + + DataDTO dataDTO = new DataDTO(); + dataDTO.setVersion("1.0.0"); + dataDTO.setRegType("ns:FARMER_REGISTRY"); + dataDTO.setRegSubType(""); + dataDTO.setRegRecordType("ns:FARMER_REGISTRY:FARMER"); + dataDTO.setRegRecords(regRecordFarmerDTOList); + return dataDTO; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java new file mode 100644 index 0000000..fec317a --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java @@ -0,0 +1,90 @@ +package g2pc.ref.farmer.regsvc.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; +import g2pc.ref.farmer.regsvc.service.FarmerValidationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +/** + * The type Farmer validation service. + */ +@Service +@Slf4j +public class FarmerValidationServiceImpl implements FarmerValidationService { + + /** + * The Request handler service. + */ + @Autowired + RequestHandlerService requestHandlerService; + + @Override + public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + String queryString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(requestDTO.getMessage().getSearchRequest().getSearchCriteria().getQuery()); + QueryFarmerDTO queryFarmerDTO = objectMapper.readerFor(QueryFarmerDTO.class). + readValue(queryString); + validateQueryDto(queryFarmerDTO); + + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(requestDTO.getHeader()); + RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). + readValue(headerString); + requestHandlerService.validateRequestHeader(headerDTO); + requestHandlerService.validateRequestMessage(requestDTO.getMessage()); + + + } + + @Override + public void validateQueryDto(QueryFarmerDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { + + ObjectMapper objectMapper = new ObjectMapper(); + log.info("Query object -> "+queryFarmerDTO); + String queryString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(queryFarmerDTO); + log.info("Query String" + queryString); + InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/farmerQuerySchema.json"); + JsonNode jsonNode = objectMapper.readTree(queryString); + JsonSchema schema = null; + if(schemaStreamQuery !=null){ + schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStreamQuery); + } + Set errorMessage = schema.validate(jsonNode); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorcombinedMessage); + } + } + } + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml new file mode 100644 index 0000000..669abd2 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml @@ -0,0 +1,50 @@ +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/g2p?currentSchema=farmer + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + +server: + port: 9001 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +client: + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json new file mode 100644 index 0000000..7a0fb85 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Query schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "query_name" : { + "type": "string" + }, + "query_params": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + }, + "farmer_id": { + "type": "array", + "items": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "season": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "required": ["farmer_id","season"] + } + }, + "required": ["query_params"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java new file mode 100644 index 0000000..150e82e --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java @@ -0,0 +1,25 @@ +package g2pc.ref.farmer.regsvc; + +import g2pc.ref.farmer.regsvc.scheduler.Scheduler; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.IOException; + +@SpringBootTest +class G2pcRefFarmerRegsvcApplicationTests { + + @Autowired + private Scheduler scheduler; + + @Test + void contextLoads() { + } + + @Test + void testResponseScheduler() throws IOException { + scheduler.responseScheduler(); + } + +} From 61d0324e4623de15b9b9e6ba2e1979eb11323ee9 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 12:08:24 +0530 Subject: [PATCH 07/53] reference app g2pc-ref-mno-regsvc sub repo added. --- .../g2pc-ref-mno-regsvc/.gitignore | 36 +++++++ .../g2pc-ref-mno-regsvc/pom.xml | 85 +++++++++++++++++ .../regsvc/G2pcRefMnoRegsvcApplication.java | 18 ++++ .../mno/regsvc/config/ObjectMapperConfig.java | 23 +++++ .../ref/mno/regsvc/constants/Constants.java | 19 ++++ .../controller/rest/RegistryController.java | 93 +++++++++++++++++++ .../regsvc/dto/request/QueryMobileDTO.java | 21 +++++ .../dto/request/QueryParamsMobileDTO.java | 25 +++++ .../regsvc/dto/response/DataMobileDTO.java | 21 +++++ .../dto/response/RegRecordMobileDTO.java | 30 ++++++ .../ref/mno/regsvc/scheduler/Scheduler.java | 71 ++++++++++++++ .../service/MobileResponseBuilderService.java | 13 +++ .../service/MobileValidationService.java | 31 +++++++ .../MobileResponseBuilderServiceImpl.java | 75 +++++++++++++++ .../MobileValidationServiceImpl.java | 85 +++++++++++++++++ .../src/main/resources/application.yml | 21 +++++ .../resources/schema/mobileQuerySchema.json | 42 +++++++++ .../G2pcRefMnoRegsvcApplicationTests.java | 26 ++++++ 18 files changed, 735 insertions(+) create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/.gitignore create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/ObjectMapperConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/RegRecordMobileDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/.gitignore b/g2pc-reference-apps/g2pc-ref-mno-regsvc/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml new file mode 100644 index 0000000..6c8649a --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + + + g2pc.ref.mno.regsvc + g2pc-ref-mno-regsvc + 0.0.1-SNAPSHOT + g2pc-ref-mno-regsvc + g2pc-ref-mno-regsvc + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + true + + + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.3.5 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + redis.clients + jedis + 3.7.1 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springdoc + springdoc-openapi-ui + 1.6.8 + + + org.springdoc + springdoc-openapi-ui + 1.6.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java new file mode 100644 index 0000000..e927eb3 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java @@ -0,0 +1,18 @@ +package g2pc.ref.mno.regsvc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +@ComponentScan({"g2pc.core.lib", "g2pc.dp.core.lib", "g2pc.ref.mno.regsvc","g2pc.dp.core.lib.service"}) +@EnableScheduling +public class G2pcRefMnoRegsvcApplication { + + public static void main(String[] args) { + SpringApplication.run(G2pcRefMnoRegsvcApplication.class, args); + } + +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/ObjectMapperConfig.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/ObjectMapperConfig.java new file mode 100644 index 0000000..29e2fd7 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/ObjectMapperConfig.java @@ -0,0 +1,23 @@ +package g2pc.ref.mno.regsvc.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; +import g2pc.ref.mno.regsvc.dto.request.QueryParamsMobileDTO; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObjectMapperConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, + QueryMobileDTO.class, + QueryParamsMobileDTO.class); + return objectMapper; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java new file mode 100644 index 0000000..2792b53 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java @@ -0,0 +1,19 @@ +package g2pc.ref.mno.regsvc.constants; + +public class Constants { + + private Constants() { + } + + public static final String SEARCH_REQUEST_RECEIVED = "Search request received successfully"; + + public static final String INVALID_RESPONSE = "Invalid Response received from server"; + + public static final String CONFLICT = "CONFLICT"; + + public static final String INVALID_AUTHORIZATION = "Invalid Authorization"; + + public static final String CACHE_KEY_SEARCH_STRING = "request-mno*"; + + public static final String CACHE_KEY_STRING = "request-mno-"; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java new file mode 100644 index 0000000..61c1268 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java @@ -0,0 +1,93 @@ +package g2pc.ref.mno.regsvc.controller.rest; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.mno.regsvc.constants.Constants; +import g2pc.ref.mno.regsvc.service.MobileValidationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +/** + * The type Registry controller. + */ +@RestController +@Slf4j +@RequestMapping(produces = "application/json") +@Tag(name = "Provider", description = "Provider APIs") +public class RegistryController { + + @Autowired + private RequestHandlerService requestHandlerService; + + /** + * The Mobile validation service. + */ + @Autowired + MobileValidationService mobileValidationService; + + /** + * Get search request from DC + * + * @param requestDTO required + * @return Search request received acknowledgement + * @throws JsonProcessingException the json processing exception + * @throws ResponseStatusException the response status exception + * @throws G2pcValidationException the validation exception + */ + @Operation(summary = "Receive search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/search") + public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO requestDTO) throws JsonProcessingException, ResponseStatusException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + String cacheKey = Constants.CACHE_KEY_STRING + requestDTO.getMessage().getTransactionId(); + + + try { + mobileValidationService.validateRequestDTO(requestDTO); + return requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey); + } catch (G2pcValidationException e) { + + throw new G2pcValidationException(e.getG2PcErrorList()); + } + catch (JsonProcessingException e){ + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); + } + catch (Exception e){ + throw new ResponseStatusException(HttpStatus.MULTI_STATUS , e.getMessage()); + } + } + + /** + * Handle validation exception error response. + * + * @param ex the ValidationException + * @return the error response + */ + @ExceptionHandler(value + = G2pcValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse + handleValidationException( + G2pcValidationException ex) + { + return new ErrorResponse( + ex.getG2PcErrorList()); + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java new file mode 100644 index 0000000..b06fb99 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java @@ -0,0 +1,21 @@ +package g2pc.ref.mno.regsvc.dto.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import g2pc.core.lib.dto.common.message.request.QueryDTO; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryMobileDTO{ + + @JsonProperty("query_params") + private QueryParamsMobileDTO queryParams; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java new file mode 100644 index 0000000..f13d592 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java @@ -0,0 +1,25 @@ +package g2pc.ref.mno.regsvc.dto.request; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryParamsMobileDTO { + + @JsonProperty("mobile_number") + private List mobileNumber; + + @JsonProperty("season") + private String season; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java new file mode 100644 index 0000000..b99adc6 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java @@ -0,0 +1,21 @@ +package g2pc.ref.mno.regsvc.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class DataMobileDTO{ + + @JsonProperty("reg_records") + private List regRecords; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/RegRecordMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/RegRecordMobileDTO.java new file mode 100644 index 0000000..65c9f5d --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/RegRecordMobileDTO.java @@ -0,0 +1,30 @@ +package g2pc.ref.mno.regsvc.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class RegRecordMobileDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("season") + private String season; + + @JsonProperty("mobile_number") + private String mobileNumber; + + @JsonProperty("mobile_status") + private String mobileStatus; + + @JsonProperty("created_date") + private String createdDate; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java new file mode 100644 index 0000000..5df3e74 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java @@ -0,0 +1,71 @@ +package g2pc.ref.mno.regsvc.scheduler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.service.ResponseBuilderService; +import g2pc.ref.mno.regsvc.constants.Constants; +import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +@Slf4j +@Service +public class Scheduler { + + @Value("${client.api_urls.client_search_api}") + String onSearchURL; + + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + private ResponseBuilderService responseBuilderService; + + @Autowired + private MobileResponseBuilderService mobileResponseBuilderService; + + + /** + * Response scheduler + */ + @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. + public void responseScheduler() throws IOException { + try { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class); + + List cacheKeysList = requestHandlerService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); + // TODO: this logic has to be handled to get single cacheKey + String cacheKey = cacheKeysList.get(0); + String requestData = requestHandlerService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + + String refRecordsString = mobileResponseBuilderService.getRegMobileRecords( + objectMapper.writeValueAsString(requestDTO.getMessage())); + + DataDTO dataDTO = mobileResponseBuilderService.buildData(refRecordsString); + + String responseString = responseBuilderService.buildResponseString(Constants.CACHE_KEY_SEARCH_STRING, dataDTO); + log.info("Scheduler responseString : {}", responseString); + + responseBuilderService.sendOnSearchResponse(responseString, onSearchURL); + + responseBuilderService.updateRequestStatus(cacheKey, DpConstants.COMPLETED, cacheDTO); + } catch (Exception ex) { + log.error("Scheduler error : {}", ex.getMessage()); + } + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java new file mode 100644 index 0000000..71b0d14 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java @@ -0,0 +1,13 @@ +package g2pc.ref.mno.regsvc.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.response.DataDTO; + +import java.io.IOException; + +public interface MobileResponseBuilderService { + + String getRegMobileRecords(String messageString) throws JsonProcessingException; + + DataDTO buildData(String regRecordsString) throws IOException; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java new file mode 100644 index 0000000..f34485a --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java @@ -0,0 +1,31 @@ +package g2pc.ref.mno.regsvc.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; + +/** + * The interface Mobile validation service. + */ +public interface MobileValidationService { + + /** + * Validate request dto. + * + * @param requestDTO the request dto + * @throws G2pcValidationException the validation exception + * @throws JsonProcessingException the json processing exception + */ + void validateRequestDTO (RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException; + + /** + * Validate query dto. + * + * @param queryMobileDTO the query mobile dto + * @throws G2pcValidationException the validation exception + * @throws JsonProcessingException the json processing exception + */ + void validateQueryDto (QueryMobileDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException; + +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java new file mode 100644 index 0000000..f13b823 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java @@ -0,0 +1,75 @@ +package g2pc.ref.mno.regsvc.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; +import g2pc.ref.mno.regsvc.dto.request.QueryParamsMobileDTO; +import g2pc.ref.mno.regsvc.dto.response.RegRecordMobileDTO; +import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +@Service +@Slf4j +public class MobileResponseBuilderServiceImpl implements MobileResponseBuilderService { + + @Value("${client.api_urls.mobile_info_api}") + String mobileInfoURL; + + /** + * Get farmer records information string from API + * + * @param messageString required + * @return String of mobile records + */ + @Override + public String getRegMobileRecords(String messageString) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + //remove only use for testing + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, + QueryMobileDTO.class, + QueryParamsMobileDTO.class); + MessageDTO messageDTO = objectMapper.readerFor(MessageDTO.class).readValue(messageString); + String queryParams = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery().getQueryParams()); + QueryParamsMobileDTO queryParamsMobileDTO = objectMapper.readValue(queryParams, QueryParamsMobileDTO.class); + + List mobileNumbers = queryParamsMobileDTO.getMobileNumber(); + String season = queryParamsMobileDTO.getSeason(); + + String uri = mobileInfoURL; + HttpResponse response = Unirest.post(uri) + .body(objectMapper.writeValueAsString(queryParamsMobileDTO)) + .asString(); + + List regRecordMobileDTOList = objectMapper.readerFor(List.class). + readValue(response.getBody()); + + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(regRecordMobileDTOList); + } + + @Override + public DataDTO buildData(String regRecordsString) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + List regRecordFarmerDTOList = objectMapper.readerFor(List.class). + readValue(regRecordsString); + + DataDTO dataDTO = new DataDTO(); + dataDTO.setVersion("1.0.0"); + dataDTO.setRegType("ns:MOBILE_REGISTRY"); + dataDTO.setRegSubType(""); + dataDTO.setRegRecordType("ns:MOBILE_REGISTRY:MOBILE"); + dataDTO.setRegRecords(regRecordFarmerDTOList); + return dataDTO; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java new file mode 100644 index 0000000..dd79fab --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java @@ -0,0 +1,85 @@ +package g2pc.ref.mno.regsvc.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; +import g2pc.ref.mno.regsvc.service.MobileValidationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +/** + * The type Mobile validation service. + */ +@Service +@Slf4j +public class MobileValidationServiceImpl implements MobileValidationService { + + /** + * The Request handler service. + */ + @Autowired + RequestHandlerService requestHandlerService; + @Override + public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + String queryString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(requestDTO.getMessage().getSearchRequest().getSearchCriteria().getQuery()); + QueryMobileDTO queryMobileDTO = objectMapper.readerFor(QueryMobileDTO.class). + readValue(queryString); + validateQueryDto(queryMobileDTO); + + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(requestDTO.getHeader()); + RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). + readValue(headerString); + requestHandlerService.validateRequestHeader(headerDTO); + requestHandlerService.validateRequestMessage(requestDTO.getMessage()); + } + + @Override + public void validateQueryDto(QueryMobileDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + log.info("Query object -> "+queryMobileDTO); + String queryString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(queryMobileDTO); + log.info("Query String" + queryString); + InputStream schemaStreamQuery = MobileValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/mobileQuerySchema.json"); + JsonNode jsonNode = objectMapper.readTree(queryString); + JsonSchema schema = null; + if(schemaStreamQuery !=null){ + schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStreamQuery); + } + Set errorMessage = schema.validate(jsonNode); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } + if (errorMessage.size()>0){ + throw new G2pcValidationException(errorcombinedMessage); + } + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml new file mode 100644 index 0000000..5b47634 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml @@ -0,0 +1,21 @@ +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + +server: + port: 9002 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +client: + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json new file mode 100644 index 0000000..fc5a883 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Query schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "query_name" : { + "type": "string" + }, + "query_params": { + "type": "object", + "properties": { + "type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "mobile_number": { + "type": "array", + "items": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "season": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "required": ["mobile_number","season"] + } + }, + "required": ["query_params"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java new file mode 100644 index 0000000..dd69f06 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java @@ -0,0 +1,26 @@ +package g2pc.ref.mno.regsvc; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.ref.mno.regsvc.scheduler.Scheduler; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.IOException; + +@SpringBootTest +class G2pcRefMnoRegsvcApplicationTests { + + @Autowired + private Scheduler scheduler; + + @Test + void contextLoads() { + } + + @Test + void testResponseScheduler() throws IOException { + scheduler.responseScheduler(); + } + +} From dc13157502441ad9728831178a0793ebcbd69e91 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Thu, 9 Nov 2023 12:14:52 +0530 Subject: [PATCH 08/53] documents structure added. --- docs/README.md | 2 +- g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md | 1 + g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md diff --git a/docs/README.md b/docs/README.md index 79fe32c..3a7c4ad 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1 +1 @@ -# G2pc Core Lib \ No newline at end of file +# docs to be added here \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md new file mode 100644 index 0000000..cc2ca81 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md @@ -0,0 +1 @@ +# well known file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md b/g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md new file mode 100644 index 0000000..cc2ca81 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md @@ -0,0 +1 @@ +# well known file From e16f8b9d095aacf3e92fe22d0ce7bd376e5ef8fa Mon Sep 17 00:00:00 2001 From: Vijay Vujjini Date: Thu, 16 Nov 2023 16:54:53 +0530 Subject: [PATCH 09/53] registry draw.io file registry access low level design file --- docs/g2pc-registry_design.drawio | 350 +++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 docs/g2pc-registry_design.drawio diff --git a/docs/g2pc-registry_design.drawio b/docs/g2pc-registry_design.drawio new file mode 100644 index 0000000..9b3233b --- /dev/null +++ b/docs/g2pc-registry_design.drawio @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0c03d707e70de82a5b23da148f2f1d18e82f01a5 Mon Sep 17 00:00:00 2001 From: Vijay Vujjini Date: Thu, 16 Nov 2023 17:51:52 +0530 Subject: [PATCH 10/53] Update color index --- docs/g2pc-registry_design.drawio | 391 +++++++++++++++---------------- 1 file changed, 191 insertions(+), 200 deletions(-) diff --git a/docs/g2pc-registry_design.drawio b/docs/g2pc-registry_design.drawio index 9b3233b..b4d7a9f 100644 --- a/docs/g2pc-registry_design.drawio +++ b/docs/g2pc-registry_design.drawio @@ -1,332 +1,323 @@ - + - + - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + - - + + - + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - + + - + - + - - + + - + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - - + + - - + + - - + + - + - - + + - - + + - - + + - - + + - + - + - + - - + + - - - - + + + + - - + + - + - - + + - - + + - + - - + + - - + + - + - - + + - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + From fda209afd393979d298a54bf793c681a8aba3e0f Mon Sep 17 00:00:00 2001 From: vvujjini Date: Tue, 28 Nov 2023 15:15:09 +0530 Subject: [PATCH 11/53] Simplified index color codes --- docs/g2pc-registry_design.drawio | 209 +++++++++++++++---------------- 1 file changed, 103 insertions(+), 106 deletions(-) diff --git a/docs/g2pc-registry_design.drawio b/docs/g2pc-registry_design.drawio index b4d7a9f..8998c6f 100644 --- a/docs/g2pc-registry_design.drawio +++ b/docs/g2pc-registry_design.drawio @@ -1,34 +1,31 @@ - + - + - + - - + + - - + + - - + + - - - - + - + - - + + - + @@ -37,70 +34,70 @@ - - + + - - + + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -108,49 +105,49 @@ - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -159,7 +156,7 @@ - + @@ -168,59 +165,59 @@ - - + + - + - + - + - + - + - - + + - + - - + + - + - - + + - + - + - + - + - + - + @@ -230,18 +227,18 @@ - + - + - + - + @@ -253,70 +250,70 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - - + + - - + + - + From fe6561fa81b02585e2ec1da7dee61a1aa9169c3a Mon Sep 17 00:00:00 2001 From: vvujjini Date: Tue, 28 Nov 2023 21:45:54 +0530 Subject: [PATCH 12/53] index colour update --- docs/g2pc-registry_design.drawio | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/g2pc-registry_design.drawio b/docs/g2pc-registry_design.drawio index 8998c6f..cf76415 100644 --- a/docs/g2pc-registry_design.drawio +++ b/docs/g2pc-registry_design.drawio @@ -1,6 +1,6 @@ - + - + From 72dab634a6d3ba7502818af0d4c880366b7a3aed Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 15:59:49 +0530 Subject: [PATCH 13/53] Refactored g2pc-core-lib changes. --- g2pc-core-lib/README.md | 74 ++++++- g2pc-core-lib/pom.xml | 66 +++++- .../java/g2pc/core/lib/config/G2pUnirest.java | 23 ++ .../core/lib/config/G2pUnirestHelper.java | 88 ++++++++ .../g2pc/core/lib/config/RedisConfig.java | 64 ++++++ .../g2pc/core/lib/constants/Constants.java | 6 - .../core/lib/constants/CoreConstants.java | 33 +++ .../lib/constants/G2pSecurityConstants.java | 11 + .../g2pc/core/lib/dto/common/PurposeDTO.java | 17 ++ .../common/message/request/AuthorizeDTO.java | 11 +- .../common/message/request/ConsentDTO.java | 11 +- .../common/message/request/RequestDTO.java | 3 +- ...MessageDTO.java => RequestMessageDTO.java} | 6 +- ...tionDTO.java => RequestPaginationDTO.java} | 6 +- .../message/request/SearchCriteriaDTO.java | 2 +- .../dto/common/message/request/SortDTO.java | 4 +- .../common/message/response/ResponseDTO.java | 2 +- ...essageDTO.java => ResponseMessageDTO.java} | 6 +- ...ionDTO.java => ResponsePaginationDTO.java} | 2 +- .../message/response/SearchResponseDTO.java | 3 +- .../dto/common/security/G2pTokenResponse.java | 17 ++ .../dto/common/security/TokenExpiryDto.java | 21 ++ .../java/g2pc/core/lib/enums/ActionsENUM.java | 28 +++ .../g2pc/core/lib/enums/AlgorithmENUM.java | 4 +- .../g2pc/core/lib/enums/ExceptionsENUM.java | 6 + .../g2pc/core/lib/enums/HeaderStatusENUM.java | 4 +- .../lib/enums/HeaderStatusReasonCodeENUM.java | 4 +- .../java/g2pc/core/lib/enums/LocalesENUM.java | 29 +++ .../g2pc/core/lib/enums/QueryTypeEnum.java | 4 +- .../g2pc/core/lib/enums/SortOrderEnum.java | 4 +- .../lib/exceptionhandler/ErrorResponse.java | 13 +- .../GlobalExceptionHandler.java | 4 +- .../ValidationErrorResponse.java | 27 +++ .../core/lib/exceptions/G2pHttpException.java | 26 +++ .../InvalidTokenCustomException.java | 26 +++ .../core/lib/security/BearerTokenUtil.java | 11 + .../lib/security/G2pTokenVerification.java | 91 ++++++++ .../core/lib/security/RandomIVGenerator.java | 20 ++ .../lib/security/context/UnirestContext.java | 21 ++ .../security/service/G2pEncryptDecrypt.java | 13 ++ .../lib/security/service/G2pTokenService.java | 32 +++ .../serviceImpl/G2pEncryptDecryptImpl.java | 92 ++++++++ .../serviceImpl/G2pTokenServiceImpl.java | 206 ++++++++++++++++++ .../java/g2pc/core/lib/utils/CommonUtils.java | 73 ++++--- .../{headerschema.json => HeaderSchema.json} | 0 ...{messageschema.json => MessageSchema.json} | 0 ...rschema.json => ResponseHeaderSchema.json} | 0 ...schema.json => ResponseMessageSchema.json} | 0 48 files changed, 1127 insertions(+), 87 deletions(-) create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirest.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/config/RedisConfig.java delete mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/constants/G2pSecurityConstants.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/PurposeDTO.java rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/{MessageDTO.java => RequestMessageDTO.java} (77%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/{PaginationDTO.java => RequestPaginationDTO.java} (75%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/{MessageDTO.java => ResponseMessageDTO.java} (84%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/{PaginationDTO.java => ResponsePaginationDTO.java} (92%) create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/LocalesENUM.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ValidationErrorResponse.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pHttpException.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/InvalidTokenCustomException.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/BearerTokenUtil.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/RandomIVGenerator.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java rename g2pc-core-lib/src/main/resources/schema/{headerschema.json => HeaderSchema.json} (100%) rename g2pc-core-lib/src/main/resources/schema/{messageschema.json => MessageSchema.json} (100%) rename g2pc-core-lib/src/main/resources/schema/{ResponseHeaderschema.json => ResponseHeaderSchema.json} (100%) rename g2pc-core-lib/src/main/resources/schema/{ResponseMessageschema.json => ResponseMessageSchema.json} (100%) diff --git a/g2pc-core-lib/README.md b/g2pc-core-lib/README.md index c5c8dee..f1b8410 100644 --- a/g2pc-core-lib/README.md +++ b/g2pc-core-lib/README.md @@ -1 +1,73 @@ -# G2pc Core Lib \ No newline at end of file +# G2pc Core Lib + +## JSON schema validations +This project is an implementation of the JSON Schema Draft v4, + +### When to use Json schema +Let'+s assume that you already know what JSON Schema is, +and you want to utilize it in a Java application to validate JSON data. But - as you may have already discovered - there is also an other Java implementation of the JSON Schema specification. So here are some advices about which one to use: + +1. if you use Jackson to handle JSON in Java code, then java-json-tools/json-schema-validator is obviously a better choice, since it uses Jackson +2. if you want to use the org.json API then this library is the better choice +3. if you need JSON Schema Draft 6 / 7 support, then you need this library. + +### Maven Dependency +```` + + com.github.erosb + everit-json-schema + 1.14.2 + +```` + +### Where we used the json schema +* We have 2 end-points , /search and /on-search. +* In these end-points we are receiving 2 payloads respectively. +* Each payload had header and message. +* We are using JSON schema to validate the header and message as per G2p specifications. + +#### Below are some samples schema which written in this project. +```` +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "header schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "version" : { + "type": [ "string", "null" ] + }, + }, + "total_count": { + "type": "number" + }, + "is_msg_encrypted": { + "type": ["boolean","null"], + "default": "false" + }, + "meta": { + "type": [ "object", "null" ] + } + }, + "required": ["message_id","message_ts","action","sender_id","total_count"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} +```` + +Using below code we can read the above schema json +```` +InputStream schemaStream = CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/ResponseMessageschema.json"); +```` + + diff --git a/g2pc-core-lib/pom.xml b/g2pc-core-lib/pom.xml index cbea19e..178a958 100644 --- a/g2pc-core-lib/pom.xml +++ b/g2pc-core-lib/pom.xml @@ -5,19 +5,18 @@ org.springframework.boot spring-boot-starter-parent - 2.7.16 + 3.0.12 g2pc.core.lib g2pc-core-library 0.0.1-SNAPSHOT g2p-core - Common g2pc specifications + Common-g2pc-specifications-library - 11 + 17 - org.projectlombok lombok @@ -28,20 +27,69 @@ jackson-dataformat-xml 2.15.0 - org.springframework.boot spring-boot-starter-web - com.konghq - unirest-java - 3.0.00 + org.yaml + snakeyaml + 2.2 com.networknt json-schema-validator - 1.0.57 + 1.0.82 + + + javax.validation + validation-api + 2.0.1.Final + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.springframework.security + spring-security-web + 6.2.0 + + + com.auth0 + java-jwt + 4.4.0 + + + jakarta.validation + jakarta.validation-api + 2.0.2 + + + org.springframework.security + spring-security-config + 6.1.2 + + + com.konghq + unirest-java + 3.14.2 + + + redis.clients + jedis + 5.0.0-alpha2 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.apache.commons + commons-lang3 + 3.14.0 diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirest.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirest.java new file mode 100644 index 0000000..4105a43 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirest.java @@ -0,0 +1,23 @@ +package g2pc.core.lib.config; + + +import kong.unirest.GetRequest; +import kong.unirest.HttpRequestWithBody; +import kong.unirest.UnirestException; + +public interface G2pUnirest { + + String getG2pApiCall(String uri, String token) throws UnirestException; + + String getG2pApiCall(String uri) throws UnirestException; + + HttpRequestWithBody g2pPost(String uri); + + HttpRequestWithBody g2pPut(String uri); + + HttpRequestWithBody g2pDelete(String uri); + + HttpRequestWithBody g2pPatch(String uri); + + GetRequest g2pGet(String uri); +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java new file mode 100644 index 0000000..b7b0212 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java @@ -0,0 +1,88 @@ +package g2pc.core.lib.config; + + +import g2pc.core.lib.security.context.UnirestContext; +import kong.unirest.GetRequest; +import kong.unirest.HttpRequestWithBody; +import kong.unirest.Unirest; +import kong.unirest.UnirestException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.Collections; +import java.util.Map; + +@Configuration +@Slf4j +@EnableScheduling +@Lazy +public class G2pUnirestHelper implements G2pUnirest { + + + @Autowired + UnirestContext unirestContext; + + private static final String AUTH = "Authorization"; + + + protected GetRequest setG2pHeaders(GetRequest request) { + return setG2pHeaders(request, Collections.emptyMap()); + } + + protected GetRequest setG2pHeaders(GetRequest request, Map keyVal) { + + if (null != keyVal) + keyVal.forEach((k, v) -> request.header(k, v)); + return request; + } + + protected HttpRequestWithBody setG2pHeaders(HttpRequestWithBody request) { + + + if (null != unirestContext && StringUtils.isNotBlank(unirestContext.getJwtHeader())) { + request.header(AUTH, unirestContext.getJwtHeader()); + } + return request; + } + + public String getG2pApiCall(String uri, String token) throws UnirestException { + + return setG2pHeaders(Unirest.get(uri), Map.of(AUTH, "Bearer " + token)) + .asString() + .getBody(); + + } + + public String getG2pApiCall(String uri) throws UnirestException { + + return setG2pHeaders(Unirest.get(uri)) + .asString() + .getBody(); + + } + + public HttpRequestWithBody g2pPost(String uri) { + return setG2pHeaders(Unirest.post(uri)); + } + + public HttpRequestWithBody g2pPut(String uri) { + return setG2pHeaders(Unirest.put(uri)); + } + + public HttpRequestWithBody g2pDelete(String uri) { + return setG2pHeaders(Unirest.delete(uri)); + } + + public HttpRequestWithBody g2pPatch(String uri) { + return setG2pHeaders(Unirest.patch(uri)); + } + + public GetRequest g2pGet(String uri) { + return setG2pHeaders(Unirest.get(uri)); + } + +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/RedisConfig.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/RedisConfig.java new file mode 100644 index 0000000..ca482a3 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/RedisConfig.java @@ -0,0 +1,64 @@ +package g2pc.core.lib.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericToStringSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.JedisPoolConfig; + +@Configuration +@Slf4j +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Value("${spring.data.redis.password}") + private String redisPassword; + + public RedisConnectionFactory redisConnectionFactory() { + + JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); + jedisPoolConfig.setMaxTotal(10); + jedisPoolConfig.setTestOnBorrow(true); + jedisPoolConfig.setTestOnReturn(true); + + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort); + + log.info("Redis connection using redisHost: {}, redisPort: {}", redisHost, redisPort); + + redisStandaloneConfiguration.setHostName(redisHost); + if (redisPassword != null && !redisPassword.trim().isEmpty()) { + log.info("redisPassword set for Redis: {}", redisPassword); + redisStandaloneConfiguration.setPassword(redisPassword); + } + redisStandaloneConfiguration.setPort(redisPort); + + JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration); + jedisConnectionFactory.setPoolConfig(jedisPoolConfig); + + jedisConnectionFactory.afterPropertiesSet(); + + return jedisConnectionFactory; + } + + @Bean + RedisTemplate redisTemplate() { + final RedisTemplate redisTemplate = new RedisTemplate<>(); + log.info("String template used by Redis..."); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(new GenericToStringSerializer<>(String.class)); + redisTemplate.setValueSerializer(new GenericToStringSerializer<>(String.class)); + return redisTemplate; + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java deleted file mode 100644 index ac9ba64..0000000 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/Constants.java +++ /dev/null @@ -1,6 +0,0 @@ -package g2pc.core.lib.constants; - -public class Constants { - - public static final String CANNOT_DESERIALIZE_TYPE= "Cannot deserialize Type"; -} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java new file mode 100644 index 0000000..b8180b6 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java @@ -0,0 +1,33 @@ +package g2pc.core.lib.constants; + +public class CoreConstants { + + private CoreConstants() { + } + + public static final String CANNOT_DESERIALIZE_TYPE = "Cannot deserialize Type"; + + public static final String QUERY_NAME = "query_name"; + + public static final String DP_SEARCH_URL = "dp_search_url"; + + public static final String REG_TYPE = "reg_type"; + + public static final String REG_SUB_TYPE = "reg_sub_type"; + + public static final String QUERY_TYPE = "query_type"; + + public static final String SORT_ATTRIBUTE = "sort_attribute"; + + public static final String SORT_ORDER = "sort_order"; + + public static final String PAGE_NUMBER = "page_number"; + + public static final String PAGE_SIZE = "page_size"; + + public static final String CLIENT_ID = "client_id"; + + public static final String CLIENT_SECRET = "client_secret"; + + public static final String KEYCLOAK_URL = "keycloak_url"; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/G2pSecurityConstants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/G2pSecurityConstants.java new file mode 100644 index 0000000..82e68cf --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/G2pSecurityConstants.java @@ -0,0 +1,11 @@ +package g2pc.core.lib.constants; + +public class G2pSecurityConstants { + + private G2pSecurityConstants() { + } + + public static final String TOKEN_HEADER = "Authorization"; + + public static final String SECRET_KEY= "00112233445566778899AABBCCDDEEFF"; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/PurposeDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/PurposeDTO.java new file mode 100644 index 0000000..b99d41c --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/PurposeDTO.java @@ -0,0 +1,17 @@ +package g2pc.core.lib.dto.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PurposeDTO { + + private String text; + + private String code; + + private String refUri; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java index 66b17ad..3cc5443 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java @@ -1,9 +1,16 @@ package g2pc.core.lib.dto.common.message.request; -import com.fasterxml.jackson.annotation.JsonInclude; +import g2pc.core.lib.dto.common.PurposeDTO; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data -@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@NoArgsConstructor public class AuthorizeDTO { + + private String ts; + + private PurposeDTO purpose; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java index b8a6ef5..35b1d7f 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java @@ -1,9 +1,16 @@ package g2pc.core.lib.dto.common.message.request; -import com.fasterxml.jackson.annotation.JsonInclude; +import g2pc.core.lib.dto.common.PurposeDTO; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data -@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@NoArgsConstructor public class ConsentDTO { + + private String ts; + + private PurposeDTO purpose; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java index 99c2330..7ebb2cb 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java @@ -18,6 +18,5 @@ public class RequestDTO { private HeaderDTO header; @JsonProperty("message") - private MessageDTO message; - + private Object message; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestMessageDTO.java similarity index 77% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestMessageDTO.java index 01bf646..26992a0 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/MessageDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestMessageDTO.java @@ -4,15 +4,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; +import java.util.List; + @Data @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) -public class MessageDTO { +public class RequestMessageDTO { @JsonProperty("transaction_id") private String transactionId; @JsonProperty("search_request") - private SearchRequestDTO searchRequest; + private List searchRequest; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestPaginationDTO.java similarity index 75% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestPaginationDTO.java index d023a96..5514353 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/PaginationDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestPaginationDTO.java @@ -8,11 +8,11 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class PaginationDTO { +public class RequestPaginationDTO { @JsonProperty("page_size") - private int pageSize; + private int pageSize = 100; @JsonProperty("page_number") - private int pageNumber; + private int pageNumber = 1; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java index 6cb615f..84beddf 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java @@ -29,7 +29,7 @@ public class SearchCriteriaDTO { private List sort; @JsonProperty("pagination") - private PaginationDTO pagination; + private RequestPaginationDTO pagination; @JsonProperty("consent") private ConsentDTO consent; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java index e47626a..12d19ca 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java @@ -12,8 +12,8 @@ public class SortDTO { @JsonProperty("attribute_name") - private String attributeName; + private String attributeName="YOB"; @JsonProperty("sort_order") - private String sortOrder; + private String sortOrder="asc"; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java index 6d04c17..3c7f3bb 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java @@ -19,5 +19,5 @@ public class ResponseDTO { private HeaderDTO header; @JsonProperty("message") - private MessageDTO message; + private Object message; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseMessageDTO.java similarity index 84% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseMessageDTO.java index ae058ad..398d028 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/MessageDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseMessageDTO.java @@ -7,12 +7,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public class MessageDTO { +public class ResponseMessageDTO { @JsonProperty("transaction_id") private String transactionId; @@ -21,5 +23,5 @@ public class MessageDTO { private String correlationId; @JsonProperty("search_response") - private SearchResponseDTO searchResponse; + private List searchResponse; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponsePaginationDTO.java similarity index 92% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponsePaginationDTO.java index e419780..186f982 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/PaginationDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponsePaginationDTO.java @@ -8,7 +8,7 @@ @Data @AllArgsConstructor @NoArgsConstructor -public class PaginationDTO { +public class ResponsePaginationDTO { @JsonProperty("page_size") private Integer pageSize; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java index cc59979..cdf9bc3 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java @@ -1,6 +1,5 @@ package g2pc.core.lib.dto.common.message.response; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; @@ -30,7 +29,7 @@ public class SearchResponseDTO { private DataDTO data; @JsonProperty("pagination") - private PaginationDTO pagination; + private ResponsePaginationDTO pagination; @JsonProperty("locale") private String locale; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java new file mode 100644 index 0000000..2c811eb --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java @@ -0,0 +1,17 @@ +package g2pc.core.lib.dto.common.security; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class G2pTokenResponse { + + private String access_token; + private String token_type; + private String expires_in; +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java new file mode 100644 index 0000000..4d4a1d1 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java @@ -0,0 +1,21 @@ +package g2pc.core.lib.dto.common.security; + + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.sql.Timestamp; + +@Data +@Getter +@Setter +public class TokenExpiryDto { + + private String token; + + private String expires_in; + + private Timestamp dateSaved; + +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java new file mode 100644 index 0000000..8715201 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java @@ -0,0 +1,28 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.CoreConstants; + +import java.io.IOException; + +public enum ActionsENUM { + + SEARCH, ON_SEARCH; + + public String toValue() { + switch (this) { + case SEARCH: return "search"; + case ON_SEARCH: return "on-search"; + } + return null; + } + + public static ActionsENUM forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "search": return SEARCH; + case "on-search": return ON_SEARCH; + } + } + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java index a9ff021..94f03d8 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java @@ -1,6 +1,6 @@ package g2pc.core.lib.enums; -import g2pc.core.lib.constants.Constants; +import g2pc.core.lib.constants.CoreConstants; import java.io.IOException; @@ -23,6 +23,6 @@ public static AlgorithmENUM forValue(String value) throws IOException { case "rsa": return RSA; } } - throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); } } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java new file mode 100644 index 0000000..3e0af08 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java @@ -0,0 +1,6 @@ +package g2pc.core.lib.enums; + +public class ExceptionsENUM { + + +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java index 87815c5..1574bd7 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusENUM.java @@ -1,6 +1,6 @@ package g2pc.core.lib.enums; -import g2pc.core.lib.constants.Constants; +import g2pc.core.lib.constants.CoreConstants; import java.io.IOException; @@ -38,6 +38,6 @@ public static HeaderStatusENUM forValue(String value) throws IOException { return RJCT; } } - throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); } } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java index 39710a6..dd7e7d6 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/HeaderStatusReasonCodeENUM.java @@ -1,6 +1,6 @@ package g2pc.core.lib.enums; -import g2pc.core.lib.constants.Constants; +import g2pc.core.lib.constants.CoreConstants; import java.io.IOException; @@ -58,6 +58,6 @@ public static HeaderStatusReasonCodeENUM forValue(String value) throws IOExcepti return RJCT_ERRORS_TOO_MANY; } } - throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); } } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/LocalesENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/LocalesENUM.java new file mode 100644 index 0000000..78c2742 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/LocalesENUM.java @@ -0,0 +1,29 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.CoreConstants; + +import java.io.IOException; + +public enum LocalesENUM { + + EN, // English + HI; // Hindi + + public String toValue() { + switch (this) { + case EN: return "en"; + case HI: return "hi"; + } + return null; + } + + public static LocalesENUM forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "en": return EN; + case "hi": return HI; + } + } + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java index befcc3e..b9a7628 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/QueryTypeEnum.java @@ -1,6 +1,6 @@ package g2pc.core.lib.enums; -import g2pc.core.lib.constants.Constants; +import g2pc.core.lib.constants.CoreConstants; import java.io.IOException; @@ -25,6 +25,6 @@ public static QueryTypeEnum forValue(String value) throws IOException { case "predicate":return PREDICATE; } } - throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); } } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java index 8f60f59..51ac0a8 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/SortOrderEnum.java @@ -1,6 +1,6 @@ package g2pc.core.lib.enums; -import g2pc.core.lib.constants.Constants; +import g2pc.core.lib.constants.CoreConstants; import java.io.IOException; @@ -23,6 +23,6 @@ public static SortOrderEnum forValue(String value) throws IOException { case "desc": return DESC; } } - throw new IOException(Constants.CANNOT_DESERIALIZE_TYPE); + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); } } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java index 25d7214..af4b51e 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ErrorResponse.java @@ -6,22 +6,19 @@ import java.util.List; -/** - * The type Error response. - */ @Data @NoArgsConstructor public class ErrorResponse { - private List g2PcErrors; + private G2pcError g2PcError; /** * Instantiates a new Error response. * - * @param g2PcErrorList the error list + * @param g2PcError the error list */ - public ErrorResponse(List g2PcErrorList) + public ErrorResponse(G2pcError g2PcError) { super(); - this.g2PcErrors = g2PcErrorList; + this.g2PcError = g2PcError; } -} \ No newline at end of file +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java index 479e939..637b727 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/GlobalExceptionHandler.java @@ -22,9 +22,9 @@ public class GlobalExceptionHandler { @ExceptionHandler(value = G2pcValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public @ResponseBody ErrorResponse + public @ResponseBody ValidationErrorResponse handleException(G2pcValidationException ex) { - return new ErrorResponse(ex.getG2PcErrorList()); + return new ValidationErrorResponse(ex.getG2PcErrorList()); } } \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ValidationErrorResponse.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ValidationErrorResponse.java new file mode 100644 index 0000000..208aa93 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptionhandler/ValidationErrorResponse.java @@ -0,0 +1,27 @@ +package g2pc.core.lib.exceptionhandler; + +import g2pc.core.lib.exceptions.G2pcError; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * The type Error response. + */ +@Data +@NoArgsConstructor +public class ValidationErrorResponse { + private List g2PcErrors; + + /** + * Instantiates a new Error response. + * + * @param g2PcErrorList the error list + */ + public ValidationErrorResponse(List g2PcErrorList) + { + super(); + this.g2PcErrors = g2PcErrorList; + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pHttpException.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pHttpException.java new file mode 100644 index 0000000..8c92263 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pHttpException.java @@ -0,0 +1,26 @@ +package g2pc.core.lib.exceptions; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + + +@Data +@Getter +@Setter +public class G2pHttpException extends Exception{ + + private G2pcError g2PcError; + + /** + * Instantiates a new Validation exception. + * + * @param g2PcError the error list + */ + public G2pHttpException(G2pcError g2PcError){ + this.g2PcError = g2PcError; + + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/InvalidTokenCustomException.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/InvalidTokenCustomException.java new file mode 100644 index 0000000..098c0b8 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/InvalidTokenCustomException.java @@ -0,0 +1,26 @@ +package g2pc.core.lib.exceptions; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +@Getter +@Setter +/** + * The type Token is not valid exception. + */ +public class InvalidTokenCustomException extends Exception{ + + private G2pcError g2PcError; + + /** + * Instantiates a new Token is not valid exception. + * + * @param g2PcError the message + */ + public InvalidTokenCustomException(G2pcError g2PcError){ + this.g2PcError = g2PcError; + + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/BearerTokenUtil.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/BearerTokenUtil.java new file mode 100644 index 0000000..d3178e8 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/BearerTokenUtil.java @@ -0,0 +1,11 @@ +package g2pc.core.lib.security; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +public class BearerTokenUtil { + + public static String getBearerTokenHeader() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("Authorization"); + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java new file mode 100644 index 0000000..8b73203 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java @@ -0,0 +1,91 @@ +package g2pc.core.lib.security; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.Claim; +import g2pc.core.lib.constants.G2pSecurityConstants; +import g2pc.core.lib.security.service.G2pTokenService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.filter.OncePerRequestFilter; +import java.io.IOException; +import java.util.Date; +import com.auth0.jwt.interfaces.DecodedJWT; + +/** + * The G2p token verification. + */ +@Slf4j +public class G2pTokenVerification extends OncePerRequestFilter { + + @Autowired + G2pTokenService g2pTokenService; + + /** + * Method to validate token + * @param httpRequest + * @param httpResponse + * @param filterChain + * @throws ServletException + * @throws IOException + */ + @Override + protected void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws ServletException, IOException + { + String stringToken = httpRequest.getHeader(G2pSecurityConstants.TOKEN_HEADER); + + if(null != stringToken) { + if (stringToken.trim().equals("undefined")) { + log.warn(" Invalid token received with undefined value for path:'{}'", httpRequest.getRequestURI()); + filterChain.doFilter(httpRequest, httpResponse); + return; + } try { + stringToken = stringToken.replaceAll("Bearer ", ""); + DecodedJWT g2pDecodedJWT = JWT.decode(stringToken); + + if (g2pDecodedJWT.getExpiresAt().before(new Date())) { + log.warn("Token Expired:{}", httpRequest.getRequestURI()); + httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpResponse.getWriter().println("Token Expired"); + return; + } else { + log.info("Setting context details:{}", httpRequest.getRequestURI()); + try { + Claim preferredUserNameClaim = g2pDecodedJWT.getClaim("preferred_username"); + String path = httpRequest.getRequestURI(); + if (preferredUserNameClaim.isMissing() || preferredUserNameClaim.isNull()) { + log.warn("Invalid userNameClaim in token:{}", g2pDecodedJWT.getToken()); + throw new IllegalStateException("Invalid Token"); + } + if (preferredUserNameClaim.isMissing() && path != null && path.contains("/private/")) { + log.warn("Invalid httpRequest made for path: {}", path); + throw new IllegalStateException("Unauthorized"); + } + + } catch (IllegalStateException e) { + httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpResponse.getWriter().println("Login Required"); + return; + } + } + } catch (Exception e) { + log.error("Authentication Failure For Token: `{}` Path:`{}` error:{}", stringToken, httpRequest.getRequestURI(), e.getMessage(), e); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Token"); + return; + } + } else { + if (httpRequest.getRequestURI().contains("/private/")) { + log.warn("Invalid httpRequest made for path: {}", httpRequest.getRequestURI()); + httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpResponse.getWriter().println("Login Required"); + return; + } + } + filterChain.doFilter(httpRequest, httpResponse); + } + + +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/RandomIVGenerator.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/RandomIVGenerator.java new file mode 100644 index 0000000..20703f0 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/RandomIVGenerator.java @@ -0,0 +1,20 @@ +package g2pc.core.lib.security; + +import javax.crypto.spec.IvParameterSpec; +import java.security.SecureRandom; + +/** + * The type Iv generator. + */ +public class RandomIVGenerator { + /** + * Generate iv parameter spec. + * + * @return the iv parameter spec + */ + public static IvParameterSpec generateIv() { + byte[] iv = new byte[16]; // IV size for AES-128 + new SecureRandom().nextBytes(iv); + return new IvParameterSpec(iv); + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java new file mode 100644 index 0000000..ca37cc6 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java @@ -0,0 +1,21 @@ +package g2pc.core.lib.security.context; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@Scope(value="request", proxyMode= ScopedProxyMode.TARGET_CLASS) +@Getter +@Setter +public class UnirestContext { + + List roles; + + String jwtHeader; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java new file mode 100644 index 0000000..5af4a38 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java @@ -0,0 +1,13 @@ +package g2pc.core.lib.security.service; + +import java.security.NoSuchAlgorithmException; + +public interface G2pEncryptDecrypt { + + + public String g2pEncrypt(String data, String key) throws Exception; + + public String g2pDecrypt(String encryptedData, String key) throws Exception; + + public String sha256Hashing(String data) throws NoSuchAlgorithmException; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java new file mode 100644 index 0000000..66d18df --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java @@ -0,0 +1,32 @@ +package g2pc.core.lib.security.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.security.G2pTokenResponse; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import kong.unirest.UnirestException; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Map; + +@Service +public interface G2pTokenService { + + public G2pTokenResponse getToken(String URL ,String clientId, String clientSecret) throws IOException, UnirestException; + + + public TokenExpiryDto createTokenExpiryDto(G2pTokenResponse g2pTokenResponse); + + public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseException; + + public ArrayList> getClientByRealm(String masterAdminUrl , String getClientUrl) throws JsonProcessingException; + + public boolean validateToken(String masterAdminUrl , String getClientUrl , String clientId) throws JsonProcessingException; + + public String decodeToken(String token) throws JsonProcessingException; + + public ResponseEntity getInterSpectResponse(String url, String token, String clientId, String clientSecret) throws UnirestException; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java new file mode 100644 index 0000000..f9ad94f --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java @@ -0,0 +1,92 @@ +package g2pc.core.lib.security.serviceImpl; +import g2pc.core.lib.security.RandomIVGenerator; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import org.springframework.stereotype.Service; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + + +@Service +public class G2pEncryptDecryptImpl implements G2pEncryptDecrypt { + + /** + * This method is used to encrypt the data in symmetric method + * @param data string to encrypt + * @param key public key + * @return + * @throws Exception + */ + @Override + public String g2pEncrypt(String data, String key) throws Exception { + IvParameterSpec ivParameterSpec = RandomIVGenerator.generateIv(); + + byte[] keyBytes = key.getBytes("UTF-8"); + SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); + + Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec); + byte[] encryptedData = aesCipher.doFinal(data.getBytes("UTF-8")); + + String ivStr = Base64.getEncoder().encodeToString(ivParameterSpec.getIV()); + String encryptedDataStr = Base64.getEncoder().encodeToString(encryptedData); + + return ivStr + ":" + encryptedDataStr; + } + + /** + * This method is used to decrypt the encrypted string in symmetric method + * @param encryptedData string to decrypt + * @param key public key + * @return + * @throws Exception + */ + @Override + public String g2pDecrypt(String encryptedData, String key) throws Exception { + String[] parts = encryptedData.split(":"); + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid encrypted data format"); + } + + String ivStr = parts[0]; + String encryptedDataStr = parts[1]; + + byte[] ivBytes = Base64.getDecoder().decode(ivStr); + byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedDataStr); + + byte[] keyBytes = key.getBytes("UTF-8"); + SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); + + Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + aesCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ivBytes)); + byte[] decryptedDataBytes = aesCipher.doFinal(encryptedDataBytes); + + return new String(decryptedDataBytes, "UTF-8"); + } + + /** + * Used to apply hashing algorithm to signature + * @param data data to get hashed + * @return + * @throws NoSuchAlgorithmException + */ + @Override + public String sha256Hashing(String data) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance( "SHA-256" ) ; + byte[ ] hash = md.digest( data.getBytes( StandardCharsets.UTF_8 ) ) ; + BigInteger number = new BigInteger( 1, hash ) ; + StringBuilder hexString = new StringBuilder( number.toString( 16 ) ) ; + while ( hexString.length( ) < 32 ) + { + hexString.insert( 0, " 0 " ) ; + } + return hexString.toString( ) ; + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java new file mode 100644 index 0000000..67104d3 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java @@ -0,0 +1,206 @@ +package g2pc.core.lib.security.serviceImpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.dto.common.security.G2pTokenResponse; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.security.service.G2pTokenService; +import kong.unirest.HttpResponse; +import kong.unirest.JsonNode; +import kong.unirest.Unirest; +import kong.unirest.UnirestException; +import kong.unirest.json.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import java.io.IOException; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + + +/** + * Service contains methods related to token + */ +@Service +@Slf4j +public class G2pTokenServiceImpl implements G2pTokenService { + + + @Autowired + G2pUnirestHelper g2pUnirestHelper; + + public static String CLIENT_ID= "clientId"; + + /** + * Method to generate token + * @param URL keycloak url + * @param clientId clientID + * @param clientSecret clientSecret + * @return + * @throws IOException + * @throws UnirestException + */ + @Override + public G2pTokenResponse getToken(String URL, String clientId, String clientSecret) throws IOException, UnirestException { + + String grantType = "client_credentials"; + ObjectMapper objectMapper = new ObjectMapper(); + // Make an HTTP POST request using Unirest + HttpResponse response = Unirest.post(URL) + .header("Content-Type", "application/x-www-form-urlencoded") + .field("grant_type", grantType) + .field("client_id", clientId) + .field("client_secret", clientSecret) + .asJson(); + + Map body = objectMapper.readValue(response.getBody().toString(), new TypeReference>() { + }); + G2pTokenResponse tokenResponse = new G2pTokenResponse(); + tokenResponse.setAccess_token(body.get("access_token").toString()); + tokenResponse.setToken_type(body.get("token_type").toString()); + tokenResponse.setExpires_in(body.get("expires_in").toString()); + return tokenResponse; + } + + /** + * Method to create tokenExpiryDto + * @param g2pTokenResponse + * @return + */ + @Override + public TokenExpiryDto createTokenExpiryDto(G2pTokenResponse g2pTokenResponse) { + TokenExpiryDto tokenExpiryDto = new TokenExpiryDto(); + tokenExpiryDto.setToken(g2pTokenResponse.getAccess_token()); + tokenExpiryDto.setExpires_in(g2pTokenResponse.getExpires_in()); + Timestamp currentTimeStamp = new Timestamp(System.currentTimeMillis()); + tokenExpiryDto.setDateSaved(currentTimeStamp); + return tokenExpiryDto; + } + + /** + * Method to check whether token is expired or not by calculations + * @param tokenExpiryDto + * @return + * @throws ParseException + */ + @Override + public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseException { + if (tokenExpiryDto != null) { + String accessToken = tokenExpiryDto.getToken(); + String lastSaved = tokenExpiryDto.getDateSaved().toString(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); + Date parsedDate = sdf.parse(lastSaved); + Timestamp lastTimeStamp = new Timestamp(parsedDate.getTime()); + Timestamp currentTimeStamp = new Timestamp(System.currentTimeMillis()); + long milliseconds = currentTimeStamp.getTime() - lastTimeStamp.getTime(); + int seconds = (int) milliseconds / 1000; + int minutes = seconds / 60; + int expiry = Integer.parseInt(tokenExpiryDto.getExpires_in()) / 60; + return minutes > expiry; + + } + return true; + } + + /** + * Method to return clients present in realm of keycloak to validate token + * @param masterAdminUrl + * @param getClientUrl + * @return + * @throws JsonProcessingException + */ + @Override + public ArrayList> getClientByRealm(String masterAdminUrl, String getClientUrl) throws JsonProcessingException { + String grantType = "client_credentials"; + ObjectMapper objectMapper = new ObjectMapper(); + HttpResponse response = Unirest.post(masterAdminUrl) + .header("Content-Type", "application/x-www-form-urlencoded") + .field("grant_type", grantType) + .field("client_id", "admin-cli") + .field("client_secret", "PKCxos6O3Sg7odXuGVeguP946EEUozY0") + .asJson(); + Map responseBody = objectMapper.readValue(response.getBody().toString(), new TypeReference>() {}); + String token = responseBody.get("access_token").toString(); + HttpResponse clientResponse = g2pUnirestHelper.g2pGet(getClientUrl) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + token) + .asString(); + ArrayList> responseMap = objectMapper.readValue(clientResponse.getBody(), ArrayList.class); + return responseMap; + + } + + /** + * Method to validate the token whether its present in client list of respective realm + * @param masterAdminUrl + * @param getClientUrl + * @param clientId + * @return + * @throws JsonProcessingException + */ + @Override + public boolean validateToken(String masterAdminUrl, String getClientUrl , String clientId) throws JsonProcessingException { + ArrayList> responseMap = getClientByRealm(masterAdminUrl, getClientUrl); + boolean isValid = false; + for (int i = 0; i < responseMap.size(); i++) { + String responseClientId = responseMap.get(i).get("clientId"); + isValid = responseClientId.equals(clientId); + if (isValid) { + return true; + } + } + return false; + } + + /** + * Method to decode the token + * @param jwtToken + * @return + * @throws JsonProcessingException + */ + @Override + public String decodeToken(String jwtToken) throws JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + String[] split_string = jwtToken.split("\\."); + String base64EncodedPayload = split_string[1]; + org.apache.commons.codec.binary.Base64 base64Url = new Base64(true); + String body = new String(base64Url.decode(base64EncodedPayload)); + HashMap payLoad = objectMapper.readValue(body, HashMap.class); + return payLoad.get(CLIENT_ID); + } + + /** + * Method to do introspect of token using keycloak api + * @param url + * @param token + * @param clientId + * @param clientSecret + * @return + * @throws UnirestException + */ + @Override + public ResponseEntity getInterSpectResponse(String url, String token, String clientId, String clientSecret) throws UnirestException { + ObjectMapper objectMapper = new ObjectMapper(); + HttpResponse introResponse = Unirest.post(url) + .header("Content-Type", "application/x-www-form-urlencoded") + .field("token", token) + .field("client_id", clientId) + .field("client_secret", clientSecret) + .asString(); + + JSONObject json = new JSONObject(introResponse.getBody()); + String isValid = json.getString("active"); + if (isValid.equals("true")) { + return ResponseEntity.status(HttpStatus.OK).body("Token is valid"); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Token is not valid"); + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java index e59929e..db62046 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.text.SimpleDateFormat; +import java.util.Date; import java.util.UUID; /** @@ -19,19 +20,10 @@ public class CommonUtils { * * @return the current time stamp */ - public String getCurrentTimeStamp() - { - return new SimpleDateFormat("yyyy-MM-dd HH.mm.ss").format(new java.util.Date()); - } - - /** - * Gets uuid. - * - * @return the uuid - */ - public String getUUID() - { - return UUID.randomUUID().toString(); + public static String getCurrentTimeStamp() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + Date date = new Date(); + return dateFormat.format(date); } @@ -40,10 +32,9 @@ public String getUUID() * * @return the input stream */ - public InputStream getRequestHeaderString(){ - InputStream schemaStream = CommonUtils.class.getClassLoader() - .getResourceAsStream("schema/headerschema.json"); - return schemaStream; + public InputStream getRequestHeaderString() { + return CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/HeaderSchema.json"); } /** @@ -51,10 +42,9 @@ public InputStream getRequestHeaderString(){ * * @return the input stream */ - public InputStream getRequestMessageString(){ - InputStream schemaStream = CommonUtils.class.getClassLoader() - .getResourceAsStream("schema/messageschema.json"); - return schemaStream; + public InputStream getRequestMessageString() { + return CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/MessageSchema.json"); } /** @@ -62,10 +52,9 @@ public InputStream getRequestMessageString(){ * * @return the input stream */ - public InputStream getResponseHeaderString(){ - InputStream schemaStream = CommonUtils.class.getClassLoader() - .getResourceAsStream("schema/ResponseHeaderschema.json"); - return schemaStream; + public InputStream getResponseHeaderString() { + return CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/ResponseHeaderSchema.json"); } /** @@ -73,13 +62,35 @@ public InputStream getResponseHeaderString(){ * * @return the input stream */ - public InputStream getResponseMessageString(){ - InputStream schemaStream = CommonUtils.class.getClassLoader() - .getResourceAsStream("schema/ResponseMessageschema.json"); - return schemaStream; + public InputStream getResponseMessageString() { + return CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/ResponseMessageSchema.json"); } + /** + * Generate unique ID + * + * @param idType whether transactionId, correlationId or referenceId + * @return unique ID + */ + public static String generateUniqueId(String idType) { + String uniqueNumString = Long.toString(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE); + return idType + uniqueNumString.substring(0, 3) + + "-" + uniqueNumString.substring(3, 7) + + "-" + uniqueNumString.substring(7, 11) + + "-" + uniqueNumString.substring(11, 15) + + "-" + uniqueNumString.substring(15); + } - - + /** + * Format a string + * + * @param data required + * @return formatted string + */ + public static String formatString(String data) { + return data.replace("\\", ""). + replace("\"{", "{"). + replace("}\"", "}"); + } } diff --git a/g2pc-core-lib/src/main/resources/schema/headerschema.json b/g2pc-core-lib/src/main/resources/schema/HeaderSchema.json similarity index 100% rename from g2pc-core-lib/src/main/resources/schema/headerschema.json rename to g2pc-core-lib/src/main/resources/schema/HeaderSchema.json diff --git a/g2pc-core-lib/src/main/resources/schema/messageschema.json b/g2pc-core-lib/src/main/resources/schema/MessageSchema.json similarity index 100% rename from g2pc-core-lib/src/main/resources/schema/messageschema.json rename to g2pc-core-lib/src/main/resources/schema/MessageSchema.json diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseHeaderschema.json b/g2pc-core-lib/src/main/resources/schema/ResponseHeaderSchema.json similarity index 100% rename from g2pc-core-lib/src/main/resources/schema/ResponseHeaderschema.json rename to g2pc-core-lib/src/main/resources/schema/ResponseHeaderSchema.json diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseMessageschema.json b/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json similarity index 100% rename from g2pc-core-lib/src/main/resources/schema/ResponseMessageschema.json rename to g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json From 1b459c7c3f7738364a59005ca49cf432bd4cf376 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 16:04:18 +0530 Subject: [PATCH 14/53] Refactored g2pc-dc-core-lib changes. --- g2pc-dc-core-lib/README.md | 26 +- g2pc-dc-core-lib/pom.xml | 22 +- .../lib/G2pcDcCoreLibraryApplication.java | 2 +- .../g2pc/dc/core/lib/config/RedisConfig.java | 67 ---- .../dc/core/lib/constants/DcConstants.java | 3 + .../lib/service/RequestBuilderService.java | 26 +- .../lib/service/ResponseHandlerService.java | 4 +- .../core/lib/service/TxnTrackerService.java | 18 ++ .../RequestBuilderServiceImpl.java | 288 ++++++++++++++++-- .../ResponseHandlerServiceImpl.java | 6 +- .../serviceimpl/TxnTrackerServiceImpl.java | 84 +++++ .../G2pcDcCoreLibraryApplicationTests.java | 10 +- 12 files changed, 435 insertions(+), 121 deletions(-) delete mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java diff --git a/g2pc-dc-core-lib/README.md b/g2pc-dc-core-lib/README.md index 54ca6f2..1c5fa29 100644 --- a/g2pc-dc-core-lib/README.md +++ b/g2pc-dc-core-lib/README.md @@ -1 +1,25 @@ -# G2pc DC Core Lib \ No newline at end of file +# G2pc DC Core Lib + +## Overview +### Json Schema validations +* In this project Json schema input stream return by parent g2p-core lib. +* In ResponseHandlerServiceImpl class input stream is called and will validate the Response DTO header and message +* If any thing doesn't match with the json schema exception handling is written for same. +* Below are some reference code for same. + +```` + InputStream schemaStream = commonUtils.getRequestMessageString(); + JsonNode jsonNodeMessage = objectMapper.readTree(messageString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } +```` \ No newline at end of file diff --git a/g2pc-dc-core-lib/pom.xml b/g2pc-dc-core-lib/pom.xml index 1fc99c8..ac08ca5 100644 --- a/g2pc-dc-core-lib/pom.xml +++ b/g2pc-dc-core-lib/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.17 + 3.0.12 g2pc.dc.core.lib @@ -14,14 +14,18 @@ g2pc-dc-core-library g2pc-dc-core-library - 11 + 17 org.springframework.boot spring-boot-starter - + + org.yaml + snakeyaml + 2.2 + org.projectlombok lombok @@ -40,7 +44,7 @@ org.postgresql postgresql - 42.3.5 + 42.5.4 org.springframework.boot @@ -51,15 +55,5 @@ spring-boot-starter-test test - - redis.clients - jedis - 3.7.1 - jar - - - org.springframework.boot - spring-boot-starter-data-redis - diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java index aacdefe..789116d 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplication.java @@ -5,7 +5,7 @@ import org.springframework.context.annotation.ComponentScan; @SpringBootApplication -@ComponentScan({"g2pc.core.lib"}) +@ComponentScan({"g2pc.dc.core.lib","g2pc.core.lib","g2pc.core.lib.security.service"}) public class G2pcDcCoreLibraryApplication { public static void main(String[] args) { diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java deleted file mode 100644 index 6282b0a..0000000 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/config/RedisConfig.java +++ /dev/null @@ -1,67 +0,0 @@ -package g2pc.dc.core.lib.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericToStringSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import redis.clients.jedis.JedisPoolConfig; - - -@Configuration -@Slf4j -public class RedisConfig { - - @Value("${spring.data.redis.host}") - String host; - - @Value("${spring.data.redis.port}") - int port; - - @Value("${spring.data.redis.password:}") - String password; - - public RedisConnectionFactory redisConnectionFactory() { - - JedisPoolConfig poolConfig = new JedisPoolConfig(); - poolConfig.setMaxTotal(10); - poolConfig.setTestOnBorrow(true); - poolConfig.setTestOnReturn(true); - - RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port); - - log.info("Redis Connection Details Host: {}, Port: {}", host, port); - - configuration.setHostName(host); - if (password != null && !password.trim().isEmpty()) { - log.info("Redis Password Set: {}", password); - configuration.setPassword(password); - } - configuration.setPort(port); - - JedisConnectionFactory connectionFactory = new JedisConnectionFactory(configuration); - connectionFactory.setPoolConfig(poolConfig); - - connectionFactory.afterPropertiesSet(); - - return connectionFactory; - } - - @Bean - RedisTemplate redisTemplate() { - final RedisTemplate template = new RedisTemplate<>(); - log.info("Redis Template for String..."); - template.setConnectionFactory(redisConnectionFactory()); - template.setKeySerializer(new StringRedisSerializer()); - template.setHashValueSerializer(new GenericToStringSerializer<>(String.class)); - template.setValueSerializer(new GenericToStringSerializer<>(String.class)); - return template; - } - - -} \ No newline at end of file diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java index 57abd83..8e56442 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java @@ -2,6 +2,9 @@ public class DcConstants { + private DcConstants() { + } + public static final String PENDING = "PENDING"; public static final String COMPLETED = "COMPLETED"; diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java index ca6febe..21e2fa2 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java @@ -3,20 +3,38 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import kong.unirest.UnirestException; +import java.io.IOException; +import java.text.ParseException; + +import java.util.List; +import java.util.Map; +import java.util.Set; public interface RequestBuilderService { - MessageDTO buildMessage(SearchCriteriaDTO searchCriteriaDTO) ; + List> createQueryMap(List> payloadMapList, Set> entrySet) throws JsonProcessingException; + + SearchCriteriaDTO getSearchCriteriaDTO(Map queryParamsMap, Map registrySpecificConfigMap); + + RequestMessageDTO buildMessage(List searchCriteriaDTOList); HeaderDTO buildHeader(); - String buildRequest(SearchCriteriaDTO searchCriteriaDTO,String transactionId) throws JsonProcessingException; + String buildRequest(List searchCriteriaDTOList, String transactionId) throws JsonProcessingException; - void sendRequest(String requestString, String uri); + Integer sendRequest(String requestString, String uri, String clientId, String clientSecret , String keyClockClientTokenUrl) throws Exception; CacheDTO createCache(String data, String status); void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; + + public void saveToken(String cacheKey , TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; + + public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException; + + public String getValidatedToken(String keyCloakUrl , String clientId , String clientSecret) throws IOException, UnirestException, ParseException; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java index ca8590a..24b90e8 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; /** @@ -34,6 +34,6 @@ public interface ResponseHandlerService { * @throws G2pcValidationException the g 2 pc validation exception * @throws JsonProcessingException the json processing exception */ - public void validateResponseMessage( MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; + public void validateResponseMessage( ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java new file mode 100644 index 0000000..02310e0 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java @@ -0,0 +1,18 @@ +package g2pc.dc.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; + +import java.util.List; +import java.util.Map; + +public interface TxnTrackerService { + + void saveInitialTransaction(List> payloadMapList, String transactionId, String status) throws JsonProcessingException; + + void saveRequestTransaction(String requestString, String regType, String transactionId) throws JsonProcessingException; + + CacheDTO createCache(String data, String status); + + void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java index dd00421..1d27271 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java @@ -2,58 +2,163 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.*; -import g2pc.core.lib.enums.SortOrderEnum; +import g2pc.core.lib.dto.common.security.G2pTokenResponse; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.enums.ActionsENUM; +import g2pc.core.lib.enums.LocalesENUM; +import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pTokenService; import g2pc.core.lib.utils.CommonUtils; -import g2pc.dc.core.lib.constants.DcConstants; import g2pc.dc.core.lib.service.RequestBuilderService; import kong.unirest.HttpResponse; -import kong.unirest.Unirest; +import kong.unirest.UnirestException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.io.IOException; +import java.util.*; +import java.text.ParseException; @Service @Slf4j public class RequestBuilderServiceImpl implements RequestBuilderService { @Autowired - private CommonUtils commonUtils; + private RedisTemplate redisTemplate; @Autowired - private RedisTemplate redisTemplate; + G2pUnirestHelper g2pUnirestHelper; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + + /** + * Create a query from payload + * + * @param payloadMapList required query params data + * @param entrySet required query params map to be matched in config + * @return query map + */ + @SuppressWarnings("unchecked") @Override - public MessageDTO buildMessage(SearchCriteriaDTO searchCriteriaDTO) { - SearchRequestDTO searchRequestDTO = new SearchRequestDTO(); - searchRequestDTO.setReferenceId("txn98765"); - searchRequestDTO.setTimestamp("2022-12-04T17:20:07-04:00"); - searchRequestDTO.setLocale("en"); - searchRequestDTO.setSearchCriteria(searchCriteriaDTO); + public List> createQueryMap(List> payloadMapList, Set> entrySet) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + List> queryList = new ArrayList<>(); + for (Map payloadMap : payloadMapList) { + Map queryMap = new HashMap<>(); + for (Map.Entry entry : entrySet) { + Map queryParamsMap = objectMapper.readValue(objectMapper.writeValueAsString(entry.getValue()), HashMap.class); + queryParamsMap.forEach((key, value) -> { + if (payloadMap.containsKey(key)) { + queryParamsMap.put(key, payloadMap.get(key)); + } + }); + queryMap.put(entry.getKey(), queryParamsMap); + } + queryList.add(queryMap); + } + return queryList; + } - MessageDTO messageDTO = new MessageDTO(); - messageDTO.setTransactionId("123456789"); - messageDTO.setSearchRequest(searchRequestDTO); + + /** + * Create a search criteria from query params + * + * @param queryParamsMap required + * @param registrySpecificConfigMap required + * @return SearchCriteriaDTO + */ + @Override + public SearchCriteriaDTO getSearchCriteriaDTO(Map queryParamsMap, Map registrySpecificConfigMap) { + RequestPaginationDTO paginationDTO = new RequestPaginationDTO(10, 1); + ConsentDTO consentDTO = new ConsentDTO(); + AuthorizeDTO authorizeDTO = new AuthorizeDTO(); + List sortDTOList = new ArrayList<>(); + SortDTO sortDTO = new SortDTO(); + sortDTO.setAttributeName(registrySpecificConfigMap.get("sort_attribute").toString()); + sortDTO.setSortOrder(registrySpecificConfigMap.get("sort_order").toString()); + sortDTOList.add(sortDTO); + + SearchCriteriaDTO searchCriteriaDTO = new SearchCriteriaDTO(); + searchCriteriaDTO.setVersion("1.0.0"); + searchCriteriaDTO.setRegType(registrySpecificConfigMap.get("reg_type").toString()); + searchCriteriaDTO.setRegSubType(registrySpecificConfigMap.get("reg_sub_type").toString()); + searchCriteriaDTO.setQueryType(registrySpecificConfigMap.get("query_type").toString()); + + QueryDTO queryDTO = new QueryDTO(); + queryDTO.setQueryName(registrySpecificConfigMap.get("query_name").toString()); + queryDTO.setQueryParams(queryParamsMap.values().iterator().hasNext() ? queryParamsMap.values().iterator().next() : ""); + + searchCriteriaDTO.setQuery(queryDTO); + searchCriteriaDTO.setSort(sortDTOList); + searchCriteriaDTO.setPagination(paginationDTO); + searchCriteriaDTO.setConsent(consentDTO); + searchCriteriaDTO.setAuthorize(authorizeDTO); + return searchCriteriaDTO; + } + + /** + * Create message component of request + * + * @param searchCriteriaDTOList required + * @return MessageDTO + */ + @Override + public RequestMessageDTO buildMessage(List searchCriteriaDTOList) { + List searchRequestDTOList = new ArrayList<>(); + for (SearchCriteriaDTO searchCriteriaDTO : searchCriteriaDTOList) { + SearchRequestDTO searchRequestDTO = new SearchRequestDTO(); + searchRequestDTO.setReferenceId(CommonUtils.generateUniqueId("R")); + searchRequestDTO.setTimestamp(CommonUtils.getCurrentTimeStamp()); + searchRequestDTO.setLocale(LocalesENUM.EN.toValue()); + searchRequestDTO.setSearchCriteria(searchCriteriaDTO); + searchRequestDTOList.add(searchRequestDTO); + } + + RequestMessageDTO messageDTO = new RequestMessageDTO(); + messageDTO.setTransactionId(CommonUtils.generateUniqueId("T")); + messageDTO.setSearchRequest(searchRequestDTOList); return messageDTO; } + + /** + * Create header component of request + * + * @return HeaderDTO + */ @Override public HeaderDTO buildHeader() { RequestHeaderDTO requestHeaderDTO = new RequestHeaderDTO(); - requestHeaderDTO.setMessageId("123"); - requestHeaderDTO.setMessageTs("2022-12-04T17:20:07-04:00"); - requestHeaderDTO.setAction("search"); + requestHeaderDTO.setMessageId(CommonUtils.generateUniqueId("M")); + requestHeaderDTO.setMessageTs(CommonUtils.getCurrentTimeStamp()); + requestHeaderDTO.setAction(ActionsENUM.SEARCH.toValue()); requestHeaderDTO.setSenderId("spp.example.org"); requestHeaderDTO.setReceiverId("pymts.example.org"); requestHeaderDTO.setTotalCount(21800); @@ -63,10 +168,18 @@ public HeaderDTO buildHeader() { return requestHeaderDTO; } + /** + * Create whole request from message and header + * + * @param searchCriteriaDTOList required + * @param transactionId required + * @return request + */ @Override - public String buildRequest(SearchCriteriaDTO searchCriteriaDTO, String transactionId) throws JsonProcessingException { + public String buildRequest(List searchCriteriaDTOList, String transactionId) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); - MessageDTO messageDTO = buildMessage(searchCriteriaDTO); + RequestMessageDTO messageDTO = buildMessage(searchCriteriaDTOList); messageDTO.setTransactionId(transactionId); HeaderDTO headerDTO = buildHeader(); @@ -76,27 +189,84 @@ public String buildRequest(SearchCriteriaDTO searchCriteriaDTO, String transacti requestDTO.setHeader(headerDTO); requestDTO.setMessage(messageDTO); - return new ObjectMapper().writeValueAsString(requestDTO); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(requestDTO); } @Override - public void sendRequest(String requestString, String uri) { + public Integer sendRequest(String requestString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception { + Boolean isStatusOk = true; log.info("Save requests to DPs"); - HttpResponse response = Unirest.post(uri) + ObjectMapper objectMapper = new ObjectMapper(); + requestString = createSignature(isSign, isEncrypt, requestString); + String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); + + HttpResponse response = g2pUnirestHelper.g2pPost(uri) .header("Content-Type", "application/json") + .header("Authorization", jwtToken) .body(requestString) .asString(); + if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { + G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); + throw new G2pHttpException(g2pcError); + } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { + ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). + readValue(response.getBody()); + throw new G2pHttpException(errorResponse.getG2PcError()); + } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { + G2pcError g2pcError = new G2pcError("err.request.bad", response.getBody()); + throw new G2pHttpException(g2pcError); + } else if (response.getStatus() != HttpStatus.OK.value()) { + G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); + throw new G2pHttpException(g2pcError); + } + log.info("request send response status = {}", response.getStatus()); + return response.getStatus(); } + private String createSignature(String isSign, String isEncrypt, String requestString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + + String messageString = requestDTO.getMessage().toString(); + RequestHeaderDTO requestHeaderDTO = (RequestHeaderDTO) requestDTO.getHeader(); + String requestHeaderString = objectMapper.writeValueAsString(requestHeaderDTO); + String signature = null; + + if (isSign.equals("false") && isEncrypt.equals("false")) { + requestDTO.getHeader().setIsMsgEncrypted(false); + } else if (isSign.equals("false") && isEncrypt.equals("true")) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + requestDTO.setMessage(encryptedMessageString);// how to store encrypted string message dto + requestDTO.getHeader().setIsMsgEncrypted(true); + + } else if (isSign.equals("true") && isEncrypt.equals("false")) { + requestDTO.getHeader().setIsMsgEncrypted(false); + signature = encryptDecrypt.sha256Hashing(messageString + requestHeaderString); + + } else { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + requestDTO.setMessage(encryptedMessageString); + requestDTO.getHeader().setIsMsgEncrypted(true); + signature = encryptDecrypt.sha256Hashing(encryptedMessageString + requestHeaderString); + } + requestDTO.setSignature(signature); + requestString = objectMapper.writeValueAsString(requestDTO); + return requestString; + } + + @Override public CacheDTO createCache(String data, String status) { log.info("Save requests in cache with status pending"); CacheDTO cacheDTO = new CacheDTO(); cacheDTO.setData(data); cacheDTO.setStatus(status); - cacheDTO.setCreatedDate(commonUtils.getCurrentTimeStamp()); - cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); return cacheDTO; } @@ -105,4 +275,66 @@ public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingE ValueOperations val = redisTemplate.opsForValue(); val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); } + + /** + * Method to save token in cache + * + * @param cacheKey + * @param tokenExpiryDto + * @throws JsonProcessingException + */ + @Override + public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(tokenExpiryDto)); + } + + /** + * Method to get token from cache + * + * @param clientId + * @return + * @throws JsonProcessingException + */ + @Override + public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + Set redisKeys = this.redisTemplate.keys(clientId); + List cacheKeysList = new ArrayList((Collection) Objects.requireNonNull(redisKeys)); + if (!cacheKeysList.isEmpty()) { + String cacheKey = cacheKeysList.get(0); + String tokenData = (String) this.redisTemplate.opsForValue().get(cacheKey); + TokenExpiryDto tokenExpiryDto = objectMapper.readerFor(TokenExpiryDto.class).readValue(tokenData); + return tokenExpiryDto; + } + return null; + } + + + /** + * Method to get validated token + * + * @param keyCloakUrl + * @param clientId + * @param clientSecret + * @return + * @throws IOException + * @throws UnirestException + * @throws ParseException + */ + @Override + public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException { + TokenExpiryDto tokenExpiryDto = getTokenFromCache(clientId); + + String jwtToken = ""; + if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { + G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); + jwtToken = tokenResponse.getAccess_token(); + saveToken(clientId, g2pTokenService.createTokenExpiryDto(tokenResponse)); + } else { + jwtToken = tokenExpiryDto.getToken(); + } + return jwtToken; + } + } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java index 46d664b..337b95d 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java @@ -9,7 +9,7 @@ import com.networknt.schema.ValidationMessage; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.MessageDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.utils.CommonUtils; @@ -45,7 +45,7 @@ public void updateCache(String cacheKey) throws JsonProcessingException { CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(redisTemplate.opsForValue().get(cacheKey)); cacheDTO.setStatus(DcConstants.COMPLETED); - cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); ValueOperations val = redisTemplate.opsForValue(); val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); @@ -79,7 +79,7 @@ public void validateResponseHeader(ResponseHeaderDTO responseHeaderDTO) throws G } @Override - public void validateResponseMessage(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + public void validateResponseMessage(ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); String messageString = new ObjectMapper() diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java new file mode 100644 index 0000000..0bfaf5b --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java @@ -0,0 +1,84 @@ +package g2pc.dc.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.service.TxnTrackerService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +public class TxnTrackerServiceImpl implements TxnTrackerService { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * Save initial payload to Redis and create a new transaction in DB table + * + * @param payloadMapList required + * @param transactionId required + * @param status required + * + */ + @Override + public void saveInitialTransaction(List> payloadMapList, String transactionId, String status) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + + CacheDTO cacheDTO = createCache(objectMapper.writeValueAsString(payloadMapList), HeaderStatusENUM.PDNG.toValue()); + saveCache(cacheDTO, "initial-" + transactionId); + } + + /** + * Save request transactions to Redis + * + * @param requestString required + * @param regType required + * @param transactionId required + * + */ + @Override + public void saveRequestTransaction(String requestString, String regType, String transactionId) throws JsonProcessingException { + CacheDTO cacheDTO = createCache(requestString, HeaderStatusENUM.PDNG.toValue()); + saveCache(cacheDTO, regType + "-" + transactionId); + } + + /** + * Create cache to save in Redis + * + * @param data required + * @param status required + * @return CacheDTO + */ + @Override + public CacheDTO createCache(String data, String status) { + log.info("Save requests in cache with status pending"); + CacheDTO cacheDTO = new CacheDTO(); + cacheDTO.setData(data); + cacheDTO.setStatus(status); + cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + return cacheDTO; + } + + /** + * Save cache in Redis + * + * @param cacheDTO required + * @param cacheKey required + */ + @Override + public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + } +} diff --git a/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java b/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java index 6849175..7182128 100644 --- a/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java +++ b/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java @@ -1,13 +1,21 @@ package g2pc.dc.core.lib; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; +import g2pc.dc.core.lib.service.RequestBuilderService; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class G2pcDcCoreLibraryApplicationTests { + @Autowired + RequestBuilderService requestBuilderService; + @Test void contextLoads() { } - } From 6bb06864cc4a7d96b4bef99932ca8ce437c6c054 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 16:07:00 +0530 Subject: [PATCH 15/53] Refactored g2pc-dp-core-lib changes. --- g2pc-dp-core-lib/README.md | 26 +- g2pc-dp-core-lib/pom.xml | 8 +- .../core/lib/config/DcObjectMapperConfig.java | 21 -- .../g2pc/dp/core/lib/config/RedisConfig.java | 67 ---- .../dp/core/lib/constants/DpConstants.java | 2 + .../dp/core/lib/entity/MsgTrackerEntity.java | 95 ++++++ .../dp/core/lib/entity/TxnTrackerEntity.java | 72 +++++ .../lib/repository/MsgTrackerRepository.java | 13 + .../lib/repository/TxnTrackerRepository.java | 14 + .../lib/service/RequestHandlerService.java | 13 +- .../lib/service/ResponseBuilderService.java | 26 +- .../core/lib/service/TxnTrackerDbService.java | 26 ++ .../lib/service/TxnTrackerRedisService.java | 17 + .../RequestHandlerServiceImpl.java | 71 ++--- .../ResponseBuilderServiceImpl.java | 295 ++++++++++++------ .../serviceimpl/TxnTrackerDbServiceImpl.java | 209 +++++++++++++ .../TxnTrackerRedisServiceImpl.java | 78 +++++ .../src/main/resources/application.yml | 6 +- .../main/resources/schemas/headerschema.json | 55 ---- .../main/resources/schemas/messageschema.json | 110 ------- 20 files changed, 807 insertions(+), 417 deletions(-) delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java delete mode 100644 g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json delete mode 100644 g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json diff --git a/g2pc-dp-core-lib/README.md b/g2pc-dp-core-lib/README.md index 9e22c68..e6b8cdb 100644 --- a/g2pc-dp-core-lib/README.md +++ b/g2pc-dp-core-lib/README.md @@ -1 +1,25 @@ -# G2pc DP Core Lib \ No newline at end of file +# G2pc DP Core Lib + +## Overview +### Json Schema validations +* In this project Json schema input stream return by parent g2p-core lib. +* In RequestHandlerServiceImpl class input stream is called and will validate the Request DTO header and message +* If any thing doesn't match with the json schema exception handling is written for same. +* Below are some reference code for same. + +```` + InputStream schemaStream = commonUtils.getRequestMessageString(); + JsonNode jsonNodeMessage = objectMapper.readTree(messageString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } +```` \ No newline at end of file diff --git a/g2pc-dp-core-lib/pom.xml b/g2pc-dp-core-lib/pom.xml index c4ef9ab..232de7e 100644 --- a/g2pc-dp-core-lib/pom.xml +++ b/g2pc-dp-core-lib/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.17 + 3.0.12 g2pc.dp.core.lib @@ -14,7 +14,7 @@ g2pc-dp-core-library g2pc-dp-core-library - 11 + 17 @@ -40,7 +40,7 @@ org.postgresql postgresql - 42.3.5 + 42.5.4 org.springframework.boot @@ -54,7 +54,7 @@ redis.clients jedis - 3.7.1 + 4.4.3 jar diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java deleted file mode 100644 index 7f6074e..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/DcObjectMapperConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package g2pc.dp.core.lib.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class DcObjectMapperConfig { - - @Bean - public ObjectMapper dcObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class); - - return objectMapper; - } -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java deleted file mode 100644 index 11a57f0..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/config/RedisConfig.java +++ /dev/null @@ -1,67 +0,0 @@ -package g2pc.dp.core.lib.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericToStringSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import redis.clients.jedis.JedisPoolConfig; - - -@Configuration -@Slf4j -public class RedisConfig { - - @Value("${spring.data.redis.host}") - String host; - - @Value("${spring.data.redis.port}") - int port; - - @Value("${spring.data.redis.password:}") - String password; - - public RedisConnectionFactory redisConnectionFactory() { - - JedisPoolConfig poolConfig = new JedisPoolConfig(); - poolConfig.setMaxTotal(10); - poolConfig.setTestOnBorrow(true); - poolConfig.setTestOnReturn(true); - - RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port); - - log.info("Redis Connection Details Host: {}, Port: {}", host, port); - - configuration.setHostName(host); - if (password != null && !password.trim().isEmpty()) { - log.info("Redis Password Set: {}", password); - configuration.setPassword(password); - } - configuration.setPort(port); - - JedisConnectionFactory connectionFactory = new JedisConnectionFactory(configuration); - connectionFactory.setPoolConfig(poolConfig); - - connectionFactory.afterPropertiesSet(); - - return connectionFactory; - } - - @Bean - RedisTemplate redisTemplate() { - final RedisTemplate template = new RedisTemplate<>(); - log.info("Redis Template for String..."); - template.setConnectionFactory(redisConnectionFactory()); - template.setKeySerializer(new StringRedisSerializer()); - template.setHashValueSerializer(new GenericToStringSerializer<>(String.class)); - template.setValueSerializer(new GenericToStringSerializer<>(String.class)); - return template; - } - - -} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java index f0e72d7..7ecbd7c 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java @@ -10,4 +10,6 @@ private DpConstants() { public static final String COMPLETED = "COMPLETED"; public static final String SEARCH_REQUEST_RECEIVED = "Search request received"; + + public static final String RECORD_NOT_FOUND = "record_not_found"; } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java new file mode 100644 index 0000000..afeb94d --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java @@ -0,0 +1,95 @@ +package g2pc.dp.core.lib.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "msg_tracker") +public class MsgTrackerEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "version") + private String version; + + @Column(name = "message_id") + private String messageId; + + @Column(name = "message_ts") + private String messageTs; + + @Column(name = "action") + private String action; + + @Column(name = "status") + private String status; + + @Column(name = "status_reason_code") + private String statusReasonCode; + + @Column(name = "status_reason_message") + private String statusReasonMessage; + + @Column(name = "total_count") + private Integer totalCount; + + @Column(name = "completed_count") + private Integer completedCount; + + @Column(name = "sender_id") + private String senderId; + + @Column(name = "receiver_id") + private String receiverId; + + @Column(name = "is_msg_encrypted") + private Boolean isMsgEncrypted; + + @Column(name = "meta") + private String meta; + + @Column(name = "transaction_id") + private String transactionId; + + @Column(name = "correlation_id") + private String correlationId; + + @Column(name = "locale") + private String locale; + + @Column(name = "rcvd_count") + private Integer rcvdCount; + + @Column(name = "pdng_count") + private Integer pdngCount; + + @Column(name = "succ_count") + private Integer succCount; + + @Column(name = "rjct_count") + private Integer rjctCount; + + @Column(name = "raw_message") + private String rawMessage; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; + + @ToString.Exclude + @OneToMany(mappedBy = "msgTrackerEntity", cascade = CascadeType.ALL) + private List txnTrackerEntityList = new ArrayList<>(); +} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java new file mode 100644 index 0000000..9c4a416 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java @@ -0,0 +1,72 @@ +package g2pc.dp.core.lib.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.sql.Timestamp; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "txn_tracker") +public class TxnTrackerEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "reference_id") + private String referenceId; + + @Column(name = "consent") + private Boolean consent; + + @Column(name = "authorize") + private Boolean authorize; + + @Column(name = "timestamp") + private String timestamp; + + @Column(name = "status") + private String status; + + @Column(name = "status_reason_code") + private String statusReasonCode; + + @Column(name = "status_reason_message") + private String statusReasonMessage; + + @Column(name = "version") + private String version; + + @Column(name = "reg_type") + private String regType; + + @Column(name = "reg_sub_type") + private String regSubType; + + @Column(name = "query_type") + private String queryType; + + @Column(name = "query") + private String query; + + @Column(name = "reg_record_type") + private String regRecordType; + + @Column(name = "no_of_records") + private Integer noOfRecords; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; + + @ManyToOne(targetEntity = MsgTrackerEntity.class, cascade = CascadeType.ALL) + @JoinColumn(name = "msg_tracker_id", referencedColumnName = "id") + private MsgTrackerEntity msgTrackerEntity; +} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java new file mode 100644 index 0000000..2811472 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java @@ -0,0 +1,13 @@ +package g2pc.dp.core.lib.repository; + +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface MsgTrackerRepository extends JpaRepository { + + Optional findByTransactionId(String transactionId); +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java new file mode 100644 index 0000000..d605737 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java @@ -0,0 +1,14 @@ +package g2pc.dp.core.lib.repository; + +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface TxnTrackerRepository extends JpaRepository { + + Optional findByMsgTrackerEntity(MsgTrackerEntity msgTrackerEntity); +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java index 7970636..d9a1056 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java @@ -2,24 +2,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; -import java.util.List; - public interface RequestHandlerService { AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) throws Exception; - void saveCacheRequest(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; - - List getCacheKeys(String cacheKeySearchString); - - String getRequestData(String cacheKey) throws JsonProcessingException; - public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException; - public void validateRequestMessage(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; + public void validateRequestMessage(RequestMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java index 3eb88b0..05a7305 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java @@ -2,19 +2,33 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; +import kong.unirest.UnirestException; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; public interface ResponseBuilderService { - String buildResponseMessage(DataDTO dataDTO) throws JsonProcessingException; + ResponseMessageDTO buildResponseMessage(String transactionId, List searchResponseDTOList); + + ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity); - String buildResponseHeader(String headerInfoString, String messageString) throws JsonProcessingException; + String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException; - String getResponse(String headerInfoString, String messageString, String algorithm) throws JsonProcessingException; + Integer sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception; - String buildResponseString(String cacheKeySearchString, DataDTO dataDTO) throws JsonProcessingException; + public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; - void sendOnSearchResponse(String responseString, String uri); + public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException; - void updateRequestStatus(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException; + public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException; } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java new file mode 100644 index 0000000..4206f5c --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java @@ -0,0 +1,26 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.request.QueryDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; + +import java.io.IOException; +import java.util.List; + +public interface TxnTrackerDbService { + + MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO) throws JsonProcessingException ; + + int getRecordCount(Object records); + + List getUpdatedSearchResponseList(RequestDTO requestDTO, + List refRecordsStringsList) throws IOException; + + DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity); + + SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO); +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java new file mode 100644 index 0000000..c90edee --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java @@ -0,0 +1,17 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; + +import java.util.List; + +public interface TxnTrackerRedisService { + + void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; + + void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException; + + List getCacheKeys(String cacheKeySearchString); + + String getRequestData(String cacheKey) throws JsonProcessingException; +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java index 178fe07..e8d837d 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java @@ -10,16 +10,19 @@ import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.utils.CommonUtils; import g2pc.dp.core.lib.constants.DpConstants; import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import g2pc.core.lib.exceptions.G2pcError; @@ -39,6 +42,12 @@ public class RequestHandlerServiceImpl implements RequestHandlerService { @Autowired private CommonUtils commonUtils; + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + @Autowired + private TxnTrackerDbService txnTrackerDbService; + /** * Build a request to save in Redis cache * @@ -48,14 +57,19 @@ public class RequestHandlerServiceImpl implements RequestHandlerService { */ @Override public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); log.info("Request saved in cache with status pending"); CacheDTO cacheDTO = new CacheDTO(); cacheDTO.setData(requestData); - cacheDTO.setStatus(DpConstants.PENDING); - cacheDTO.setCreatedDate(commonUtils.getCurrentTimeStamp()); - cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); + cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); - saveCacheRequest(cacheDTO, cacheKey); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + + txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); + txnTrackerDbService.saveRequestDetails(requestDTO); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); @@ -64,47 +78,13 @@ public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) return acknowledgementDTO; } - /** - * Save a request in Redis cache - * - * @param cacheDTO required - */ - @Override - public void saveCacheRequest(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { - ValueOperations val = redisTemplate.opsForValue(); - val.set(cacheKey, (new ObjectMapper()).writeValueAsString(cacheDTO)); - } - - /** - * Get all cache keys from Redis - * - * @param cacheKeySearchString required unique to DP - * @return List of cache keys - */ - @Override - public List getCacheKeys(String cacheKeySearchString) { - Set redisKeys = redisTemplate.keys(cacheKeySearchString); - return new ArrayList<>(Objects.requireNonNull(redisKeys)); - } - - /** - * Geta a single request with its cache key - * - * @param cacheKey required unique to DP - * @return Request data - */ - @Override - public String getRequestData(String cacheKey) { - return redisTemplate.opsForValue().get(cacheKey); - } - /** * The Object mapper. */ - ObjectMapper objectMapper = new ObjectMapper(); @Override public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException { - + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); String headerInfoString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(headerDTO); @@ -128,8 +108,11 @@ public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidat } @Override - public void validateRequestMessage(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { - String messageString = new ObjectMapper() + public void validateRequestMessage(RequestMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + String messageString = objectMapper .writerWithDefaultPrettyPrinter() .writeValueAsString(messageDTO); log.info("MessageString -> " + messageString); diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java index 773042f..96308d0 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java @@ -2,31 +2,41 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.G2pSecurityConstants; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.response.*; -import g2pc.core.lib.enums.AlgorithmENUM; -import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.dto.common.security.G2pTokenResponse; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pTokenService; import g2pc.core.lib.utils.CommonUtils; -import g2pc.dp.core.lib.constants.DpConstants; -import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.repository.MsgTrackerRepository; import g2pc.dp.core.lib.service.ResponseBuilderService; import kong.unirest.HttpResponse; -import kong.unirest.Unirest; +import kong.unirest.UnirestException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import java.util.List; +import java.io.IOException; +import java.text.ParseException; +import java.util.*; @Service @Slf4j public class ResponseBuilderServiceImpl implements ResponseBuilderService { - @Autowired - private RequestHandlerService requestService; @Autowired private RedisTemplate redisTemplate; @@ -34,120 +44,213 @@ public class ResponseBuilderServiceImpl implements ResponseBuilderService { @Autowired private CommonUtils commonUtils; - @Override - public String buildResponseMessage(DataDTO dataDTO) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); + @Autowired + G2pUnirestHelper g2pUnirestHelper; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Autowired + G2pTokenService g2pTokenService; - PaginationDTO paginationDTO = new PaginationDTO(); - paginationDTO.setPageSize(10); - paginationDTO.setPageNumber(1); - paginationDTO.setTotalCount(100); - - SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); - searchResponseDTO.setReferenceId("12345678901234567890"); - searchResponseDTO.setTimestamp("2022-12-04T17:20:07-04:00"); - searchResponseDTO.setStatus(HeaderStatusENUM.SUCC.toValue()); - searchResponseDTO.setStatusReasonCode(""); - searchResponseDTO.setStatusReasonMessage(""); - searchResponseDTO.setData(dataDTO); - searchResponseDTO.setPagination(paginationDTO); - searchResponseDTO.setLocale("en"); - - MessageDTO messageDTO = new MessageDTO(); - messageDTO.setTransactionId("123456789"); - messageDTO.setCorrelationId("9876543210"); - messageDTO.setSearchResponse(searchResponseDTO); - - return objectMapper.writerWithDefaultPrettyPrinter() - .writeValueAsString(messageDTO); + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + + @Autowired + private MsgTrackerRepository msgTrackerRepository; + + /** + * Get response header + * + * @param msgTrackerEntity required + * @return ResponseHeaderDTO + */ + @Override + public ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity) { + ResponseHeaderDTO headerDTO = new ResponseHeaderDTO(); + headerDTO.setVersion(msgTrackerEntity.getVersion()); + headerDTO.setMessageId(msgTrackerEntity.getMessageId()); + headerDTO.setMessageTs(msgTrackerEntity.getMessageTs()); + headerDTO.setAction(msgTrackerEntity.getAction()); + headerDTO.setSenderId(msgTrackerEntity.getSenderId()); + headerDTO.setReceiverId(msgTrackerEntity.getReceiverId()); + headerDTO.setIsMsgEncrypted(msgTrackerEntity.getIsMsgEncrypted()); + headerDTO.setStatus(msgTrackerEntity.getStatus()); + headerDTO.setStatusReasonCode(msgTrackerEntity.getStatusReasonCode()); + headerDTO.setStatusReasonMessage(msgTrackerEntity.getStatusReasonMessage()); + headerDTO.setTotalCount(msgTrackerEntity.getTotalCount()); + headerDTO.setCompletedCount(msgTrackerEntity.getCompletedCount()); + return headerDTO; } + /** + * Build a response message + * + * @param transactionId required + * @param searchResponseDTOList required + * @return ResponseMessageDTO + */ @Override - public String buildResponseHeader(String headerInfoString, String messageString) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). - readValue(headerInfoString); - return new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(headerDTO); + public ResponseMessageDTO buildResponseMessage(String transactionId, List searchResponseDTOList) { + ResponseMessageDTO messageDTO = new ResponseMessageDTO(); + messageDTO.setTransactionId(transactionId); + messageDTO.setCorrelationId(CommonUtils.generateUniqueId("C")); + messageDTO.setSearchResponse(searchResponseDTOList); + return messageDTO; } + /** + * Build a response string + * + * @param signatureString required + * @param responseHeaderDTO required + * @param messageDTO required + * @return response String + */ @Override - public String getResponse(String headerInfoString, String messageString, String algorithm) throws JsonProcessingException { + public String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - ResponseHeaderDTO responseHeaderDTO = objectMapper.readerFor(ResponseHeaderDTO.class). - readValue(headerInfoString); - - MessageDTO messageDTO = objectMapper.readerFor(MessageDTO.class). - readValue(messageString); - ResponseDTO responseDTO = new ResponseDTO(); - responseDTO.setSignature("new signature to be generated for response"); + responseDTO.setSignature(signatureString); responseDTO.setHeader(responseHeaderDTO); responseDTO.setMessage(messageDTO); - - return objectMapper - .writerWithDefaultPrettyPrinter() - .writeValueAsString(responseDTO); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(responseDTO); } @Override - public String buildResponseString(String cacheKeySearchString,DataDTO dataDTO) throws JsonProcessingException { - String responseString = ""; - - String message = buildResponseMessage(dataDTO); - - ResponseHeaderDTO responseHeaderDTO = getResponseHeaderDTO(); + public Integer sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception { + log.info("Send on-search response"); + ObjectMapper objectMapper = new ObjectMapper(); + responseString = createSignature(isSign, isEncrypt, responseString); + String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); + HttpResponse response = g2pUnirestHelper.g2pPost(uri) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + jwtToken) + .body(responseString) + .asString(); + log.info("on-search response status = {}", response.getStatus()); + if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { + G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); + throw new G2pHttpException(g2pcError); + } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { + ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). + readValue(response.getBody()); + throw new G2pHttpException(errorResponse.getG2PcError()); + } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { + G2pcError g2pcError = new G2pcError("err.request.bad", response.getBody()); + throw new G2pHttpException(g2pcError); + } else if (response.getStatus() != HttpStatus.OK.value()) { + G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); + throw new G2pHttpException(g2pcError); + } + return response.getStatus(); + } - String headerInfoString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(responseHeaderDTO); + /** + * Method to store token in cache + * + * @param cacheKey + * @param tokenExpiryDto + * @throws JsonProcessingException + */ + @Override + public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException { - String header = buildResponseHeader(headerInfoString, message); - responseString = getResponse(header, message, AlgorithmENUM.ED25519.toValue()); - return responseString; + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(tokenExpiryDto)); } /** - * Get response header + * Method to get token stored in cache * - * @return ResponseHeaderDTO currently using static header data + * @param clientId + * @return + * @throws JsonProcessingException */ - private static ResponseHeaderDTO getResponseHeaderDTO() { - ResponseHeaderDTO responseHeaderDTO = new ResponseHeaderDTO(); - responseHeaderDTO.setMessageId("123"); - responseHeaderDTO.setMessageTs("2022-12-04T17:20:07-04:00"); - responseHeaderDTO.setAction("on-search"); - responseHeaderDTO.setSenderId("spp.example.org"); - responseHeaderDTO.setReceiverId("pymts.example.org"); - responseHeaderDTO.setTotalCount(21800); - responseHeaderDTO.setIsMsgEncrypted(false); - responseHeaderDTO.setMeta(null); - responseHeaderDTO.setStatus(HeaderStatusENUM.SUCC.toValue()); - responseHeaderDTO.setStatusReasonCode(""); - responseHeaderDTO.setStatusReasonMessage(""); - responseHeaderDTO.setCompletedCount(50); - return responseHeaderDTO; + @Override + public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + Set redisKeys = this.redisTemplate.keys(clientId); + List cacheKeysList = new ArrayList((Collection) Objects.requireNonNull(redisKeys)); + if (!cacheKeysList.isEmpty()) { + String cacheKey = cacheKeysList.get(0); + String tokenData = (String) this.redisTemplate.opsForValue().get(cacheKey); + TokenExpiryDto tokenExpiryDto = objectMapper.readerFor(TokenExpiryDto.class).readValue(tokenData); + return tokenExpiryDto; + } + return null; } + /** + * The method to get validated token + * + * @param keyCloakUrl + * @param clientId + * @param clientSecret + * @return + * @throws IOException + * @throws ParseException + * @throws UnirestException + */ @Override - public void sendOnSearchResponse(String responseString, String uri) { - log.info("Send on-search response"); - HttpResponse response = Unirest.post(uri) - .header("Content-Type", "application/json") - .body(responseString) - .asString(); - log.info("on-search response status = {}", response.getStatus()); + public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, ParseException, UnirestException { + TokenExpiryDto tokenExpiryDto = getTokenFromCache(clientId); + + String jwtToken = ""; + if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { + G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); + jwtToken = tokenResponse.getAccess_token(); + saveToken(clientId, g2pTokenService.createTokenExpiryDto(tokenResponse)); + } else { + jwtToken = tokenExpiryDto.getToken(); + } + return jwtToken; } - @Override - public void updateRequestStatus(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException { - log.info("Updated cache status"); - cacheDTO.setStatus(status); - cacheDTO.setLastUpdatedDate(commonUtils.getCurrentTimeStamp()); + /** + * The method is to create signature + * + * @param isSign + * @param isEncrypt + * @param responseString + * @return + * @throws Exception + */ + private String createSignature(String isSign, String isEncrypt, String responseString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); - ValueOperations val = redisTemplate.opsForValue(); - val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + String messageString = responseDTO.getMessage().toString(); + ResponseHeaderDTO responseHeaderDTO = (ResponseHeaderDTO) responseDTO.getHeader(); + String requestHeaderString = objectMapper.writeValueAsString(responseHeaderDTO); + String signature = null; + + if (isSign.equals("false") && isEncrypt.equals("false")) { + responseDTO.getHeader().setIsMsgEncrypted(false); + } else if (isSign.equals("false") && isEncrypt.equals("true")) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString);// how to store encrypted string message dto + responseDTO.getHeader().setIsMsgEncrypted(true); + + } else if (isSign.equals("true") && isEncrypt.equals("false")) { + responseDTO.getHeader().setIsMsgEncrypted(false); + signature = encryptDecrypt.sha256Hashing(messageString + requestHeaderString); + + } else { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); + responseDTO.getHeader().setIsMsgEncrypted(true); + signature = encryptDecrypt.sha256Hashing(encryptedMessageString + requestHeaderString); + } + responseDTO.setSignature(signature); + responseString = objectMapper.writeValueAsString(responseDTO); + return responseString; } } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java new file mode 100644 index 0000000..16285d8 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java @@ -0,0 +1,209 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.common.message.response.ResponsePaginationDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.enums.LocalesENUM; +import g2pc.core.lib.enums.QueryTypeEnum; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; +import g2pc.dp.core.lib.repository.MsgTrackerRepository; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +@Slf4j +public class TxnTrackerDbServiceImpl implements TxnTrackerDbService { + + @Autowired + private MsgTrackerRepository msgTrackerRepository; + + /** + * Save request details + * + * @param requestDTO required + * @return request details entity + */ + @Override + public MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + MsgTrackerEntity entity; + + HeaderDTO headerDTO = requestDTO.getHeader(); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + if (msgTrackerEntityOptional.isEmpty()) { + MsgTrackerEntity msgTrackerEntity = new MsgTrackerEntity(); + msgTrackerEntity.setVersion(headerDTO.getVersion()); + msgTrackerEntity.setMessageId(headerDTO.getMessageId()); + msgTrackerEntity.setMessageTs(headerDTO.getMessageTs()); + msgTrackerEntity.setAction(headerDTO.getAction()); + msgTrackerEntity.setSenderId(headerDTO.getSenderId()); + msgTrackerEntity.setReceiverId(headerDTO.getReceiverId()); + msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); + msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(requestDTO)); + + List searchRequestDTOList = messageDTO.getSearchRequest(); + for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { + TxnTrackerEntity txnTrackerEntity = new TxnTrackerEntity(); + txnTrackerEntity.setReferenceId(searchRequestDTO.getReferenceId()); + txnTrackerEntity.setTimestamp(searchRequestDTO.getTimestamp()); + txnTrackerEntity.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); + txnTrackerEntity.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); + txnTrackerEntity.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); + txnTrackerEntity.setQueryType(searchRequestDTO.getSearchCriteria().getQueryType()); + txnTrackerEntity.setQuery(objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery())); + txnTrackerEntity.setCreatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setMsgTrackerEntity(msgTrackerEntity); + msgTrackerEntity.getTxnTrackerEntityList().add(txnTrackerEntity); + } + entity = msgTrackerRepository.save(msgTrackerEntity); + } else { + entity = msgTrackerEntityOptional.get(); + } + return entity; + } + + /** + * Get record count + * + * @param records required + * @return record count + */ + @SuppressWarnings("unchecked") + @Override + public int getRecordCount(Object records) { + Map objectMap = new ObjectMapper().convertValue(records, Map.class); + return objectMap.size(); + } + + /** + * Build a search response + * + * @param txnTrackerEntity required + * @param dataDTO required + * @return SearchResponseDTO + */ + @Override + public SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO) { + ResponsePaginationDTO paginationDTO = new ResponsePaginationDTO(); + paginationDTO.setPageSize(10); + paginationDTO.setPageNumber(1); + paginationDTO.setTotalCount(100); + + SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); + searchResponseDTO.setReferenceId(txnTrackerEntity.getReferenceId()); + searchResponseDTO.setTimestamp(txnTrackerEntity.getTimestamp()); + searchResponseDTO.setStatus(txnTrackerEntity.getStatus()); + searchResponseDTO.setStatusReasonCode(txnTrackerEntity.getStatusReasonCode()); + searchResponseDTO.setStatusReasonMessage(txnTrackerEntity.getStatusReasonMessage()); + searchResponseDTO.setData(dataDTO); + searchResponseDTO.setPagination(paginationDTO); + searchResponseDTO.setLocale(LocalesENUM.EN.toValue()); + + return searchResponseDTO; + } + + /** + * Build data + * + * @param regRecordsString required + * @return DataDTO + */ + @Override + public DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity) { + DataDTO dataDTO = new DataDTO(); + dataDTO.setVersion(txnTrackerEntity.getVersion()); + dataDTO.setRegType(txnTrackerEntity.getRegType()); + dataDTO.setRegSubType(txnTrackerEntity.getRegSubType()); + dataDTO.setRegRecordType(txnTrackerEntity.getRegRecordType()); + if (StringUtils.isNotEmpty(regRecordsString)) { + dataDTO.setRegRecords(regRecordsString); + } else { + dataDTO.setRegRecords(null); + } + return dataDTO; + } + + /** + * Get updated search response list + * + * @param requestDTO required + * @param refRecordsStringsList required + * @return updated search response list + */ + @Override + public List getUpdatedSearchResponseList(RequestDTO requestDTO, + List refRecordsStringsList) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + List searchResponseDTOList = new ArrayList<>(); + + MsgTrackerEntity msgTrackerEntity = saveRequestDetails(requestDTO); + + List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); + int totalCount = txnTrackerEntityList.size(); + int completedCount = 0; + int index = 0; + for (TxnTrackerEntity txnTrackerEntity : txnTrackerEntityList) { + String refRecordsString = refRecordsStringsList.get(index); + + txnTrackerEntity.setConsent(true); + txnTrackerEntity.setAuthorize(true); + txnTrackerEntity.setRegRecordType(QueryTypeEnum.NAMEDQUERY.toValue()); + DataDTO dataDTO = buildData(refRecordsString, txnTrackerEntity); + if (refRecordsString.isEmpty()) { + txnTrackerEntity.setStatus(HeaderStatusENUM.RJCT.toValue()); + txnTrackerEntity.setStatusReasonCode(DpConstants.RECORD_NOT_FOUND); + txnTrackerEntity.setStatusReasonMessage(DpConstants.RECORD_NOT_FOUND); + txnTrackerEntity.setNoOfRecords(0); + } else { + txnTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); + txnTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); + txnTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); + txnTrackerEntity.setNoOfRecords(1); + completedCount++; + } + searchResponseDTOList.add(buildSearchResponse(txnTrackerEntity, dataDTO)); + index++; + } + msgTrackerEntity.setTxnTrackerEntityList(txnTrackerEntityList); + msgTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setTotalCount(totalCount); + msgTrackerEntity.setCompletedCount(completedCount); + msgTrackerEntity.setCorrelationId(CommonUtils.generateUniqueId("C")); + msgTrackerEntity.setLocale(LocalesENUM.EN.toValue()); + msgTrackerRepository.save(msgTrackerEntity); + return searchResponseDTOList; + } +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java new file mode 100644 index 0000000..c2242fc --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java @@ -0,0 +1,78 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Service +@Slf4j +public class TxnTrackerRedisServiceImpl implements TxnTrackerRedisService { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * Save a request in Redis cache + * + * @param cacheDTO required + * @param cacheKey required + */ + @Override + public void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, (new ObjectMapper()).writeValueAsString(cacheDTO)); + } + + /** + * Update a request in Redis cache after response + * + * @param cacheKey required + * @param status required + * @param cacheDTO required + */ + @Override + public void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException { + log.info("Updated cache status"); + + cacheDTO.setStatus(status); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + } + + /** + * Get all cache keys from Redis + * + * @param cacheKeySearchString required unique to DP + * @return List of cache keys + */ + @Override + public List getCacheKeys(String cacheKeySearchString) { + Set redisKeys = redisTemplate.keys(cacheKeySearchString); + + return new ArrayList<>(Objects.requireNonNull(redisKeys)); + } + + /** + * Geta a single request with its cache key + * + * @param cacheKey required unique to DP + * @return Request data + */ + @Override + public String getRequestData(String cacheKey) { + return redisTemplate.opsForValue().get(cacheKey); + } +} diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml index f6479aa..51cc241 100644 --- a/g2pc-dp-core-lib/src/main/resources/application.yml +++ b/g2pc-dp-core-lib/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2p?currentSchema=common + url: jdbc:postgresql://localhost:5432/g2p?currentSchema=dp username: postgres password: postgres hikari: @@ -44,4 +44,6 @@ client: api_urls: mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" - +crypto: + support_encryption: false + support_signature: false diff --git a/g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json b/g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json deleted file mode 100644 index 188edfb..0000000 --- a/g2pc-dp-core-lib/src/main/resources/schemas/headerschema.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-04/schema#", - "$id": "https://example.com/message.schema.json", - "title": "header schema", - "description": "", - "additionalProperties": false, - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "version" : { - "type": [ "string", "null" ] - }, - "message_id": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "message_ts": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "action": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "sender_id": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "sender_uri": { - "type": ["string","null"] - }, - "receiver_id": { - "type": ["string","null"] - }, - "total_count": { - "type": "number" - }, - "is_msg_encrypted": { - "type": ["boolean","null"], - "default": "false" - }, - "meta": { - "type": [ "object", "null" ] - } - }, - "required": ["message_id","message_ts","action","sender_id","total_count"], - "definitions": { - "nonEmptyString": { - "type": "string", - "minLength": 1 - } - } -} diff --git a/g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json b/g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json deleted file mode 100644 index 26e8971..0000000 --- a/g2pc-dp-core-lib/src/main/resources/schemas/messageschema.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-04/schema#", - "$id": "https://example.com/message.schema.json", - "title": "Message schema", - "description": "", - "additionalProperties": false, - "type": "object", - "properties" : { - "type": { - "type": "string" - }, - "transaction_id": { - "type" : "string", - "minLength": 0, - "maxLength": 99, - "$ref": "#/definitions/nonEmptyString" - }, - "search_request" : { - "type" : "object", - "properties" : { - "reference_id": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "timestamp": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "search_criteria" : { - "type": "object" , - "properties": { - "version": { - "type": "string" - }, - "reg_type": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "reg_sub_type" : { - "type": "string" - }, - "query_type": { - "type": "string", - "$ref": "#/definitions/nonEmptyString", - "enum": ["namedQuery","idtype","predicate"] - }, - - "sort" : { - "type": "array", - "items": [ - { - "type": "object" , - "properties": { - "attribute_name" : { - "type": "string" - }, - "sort_order" : { - "type": "string", - "enum": ["asc" , "desc"] - } - } - } - ] - }, - "pagination" : { - "type": "object", - "properties": { - "page_size": { - "type": "number" - }, - "page_number": { - "type": "number" - } - }, - "required": ["page_size"] - }, - "consent": { - "type": "object" - }, - "authorize": { - "type": "object" - } - }, - "required": [ - "reg_type" ,"query_type" - ] - }, - "locale" : { - "type": "string" - - } - }, - "required": [ - "reference_id" ,"timestamp" , "search_criteria" - ] - - - } - } , - "required": [ - "transaction_id" , "search_request" - ], - "definitions": { - "nonEmptyString": { - "type": "string", - "minLength": 1 - } - } -} - From 9eacb0e8c40c15a3204fa9b5710667f24bd243da Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 16:10:33 +0530 Subject: [PATCH 16/53] Refactored g2pc-ref-dc-client changes. --- .../g2pc-ref-dc-client/pom.xml | 158 +++++++------- .../dc/client/G2pcRefDcClientApplication.java | 6 +- .../ref/dc/client/constants/Constants.java | 11 +- .../client/controller/rest/DcController.java | 169 ++++++++++++--- .../dto/farmer/request/QueryFarmerDTO.java | 17 -- .../farmer/request/QueryParamsFarmerDTO.java | 24 --- .../dto/mobile/request/QueryMobileDTO.java | 17 -- .../mobile/request/QueryParamsMobileDTO.java | 22 -- .../ref/dc/client/dto/payload/PayloadDTO.java | 15 -- .../dc/client/dto/payload/PayloadDataDTO.java | 26 --- .../entity/RegistryTransactionsEntity.java | 2 +- .../service/DcRequestBuilderService.java | 14 +- .../client/service/DcValidationService.java | 6 +- .../DcRequestBuilderServiceImpl.java | 204 +++++++++--------- .../DcResponseHandlerServiceImpl.java | 12 +- .../serviceimpl/DcValidationServiceImpl.java | 79 ++++--- .../src/main/resources/application.yml | 29 ++- .../schema/RegRecordFarmerSchema.json | 4 +- 18 files changed, 421 insertions(+), 394 deletions(-) delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml index 7219d54..5bb539a 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml @@ -1,85 +1,85 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.7.17 - - - g2pc.ref.dc.client - g2pc-ref-dc-client - 0.0.1-SNAPSHOT - g2pc-ref-dc-client - g2pc-ref-dc-client - - 11 - - - - org.springframework.boot - spring-boot-starter - - - org.projectlombok - lombok - true - - - org.postgresql - postgresql - 42.3.5 - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-test - test - - - g2pc.dc.core.lib - g2pc-dc-core-library - 0.0.1-SNAPSHOT - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.15.0 - - - org.springdoc - springdoc-openapi-ui - 1.6.8 - - - redis.clients - jedis - 3.7.1 - jar - - - org.springframework.boot - spring-boot-starter-data-redis - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.12 + + + g2pc.ref.dc.client + g2pc-ref-dc-client + 0.0.1-SNAPSHOT + g2pc-ref-dc-client + g2pc-ref-dc-client + + 17 + + + + org.springframework.boot + spring-boot-starter + + + org.projectlombok + lombok + true + + + org.postgresql + postgresql + 42.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + g2pc.dc.core.lib + g2pc-dc-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.springdoc + springdoc-openapi-ui + 1.6.15 + - - com.networknt - json-schema-validator - 1.0.57 - - + + com.networknt + json-schema-validator + 1.0.82 + + + org.apache.commons + commons-csv + 1.10.0 + + + jakarta.validation + jakarta.validation-api + 2.0.2 + + - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java index e16d517..4b4a8da 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.ComponentScan; -@SpringBootApplication -@ComponentScan({"g2pc.core.lib","g2pc.dc.core.lib","g2pc.ref.dc.client"}) + +@ComponentScan({"g2pc.ref.dc.client","g2pc.core.lib","g2pc.dc.core.lib","g2pc.ref.dc.client"}) +@SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) public class G2pcRefDcClientApplication { public static void main(String[] args) { diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java index b9fbf2c..d8f4836 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java @@ -17,15 +17,10 @@ private Constants() { public static final String COMPLETED = "COMPLETED"; - public static final String CACHE_KEY_STRING = "req*"; - public static final String ON_SEARCH_RESPONSE_RECEIVED = "On-Search response received successfully"; - public static final String PAID_FARMER = "paid_farmer"; - - public static final String IS_REGISTERED = "is_registered"; - - public static final String FARMER_CACHE_STRING = "farmer-"; + //registry name constants + public static final String FARMER_REGISTRY = "farmer_registry"; - public static final String MOBILE_CACHE_STRING = "mno-"; + public static final String MOBILE_REGISTRY = "mobile_registry"; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java index 11d01d7..30e9119 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java @@ -2,12 +2,22 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pTokenService; import g2pc.ref.dc.client.constants.Constants; -import g2pc.ref.dc.client.dto.payload.PayloadDTO; import g2pc.ref.dc.client.service.DcRequestBuilderService; import g2pc.ref.dc.client.service.DcResponseHandlerService; import g2pc.ref.dc.client.service.DcValidationService; @@ -16,13 +26,19 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; -import java.io.IOException; +import java.util.*; @RestController @Slf4j @@ -39,10 +55,62 @@ public class DcController { @Autowired DcValidationService dcValidationService; + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${keycloak.realm}") + private String keycloakRealm; + + @Value("${keycloak.url}") + private String keycloakURL; + + @Value("${keycloak.consumer.admin-url}") + private String masterAdminUrl; + + @Value("${keycloak.consumer.get-client-url}") + private String getClientUrl; + + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + + @Value("${keycloak.admin.client-id}") + private String adminClientId; + + @Value("${keycloak.admin.client-secret}") + private String adminClientSecret; + + /** + * Get consumer search request + * + * @param payloadMap required + * @return Search request received acknowledgement + */ + @Operation(summary = "Receive consumer search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/public/api/v1/consumer/search/payload") + public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { + log.info("Payload received from postman"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(payloadMap)) { + acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); + } + return acknowledgementDTO; + } + /** * Get consumer search request * - * @param payloadDTO required + * @param payloadFile required * @return Search request received acknowledgement */ @Operation(summary = "Receive consumer search request") @@ -51,20 +119,30 @@ public class DcController { @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) - @PostMapping("/private/api/v1/consumer/search") - public AcknowledgementDTO createSearchRequests(@RequestBody PayloadDTO payloadDTO) throws JsonProcessingException { - log.info("Payload received"); + @PostMapping("/public/api/v1/consumer/search/csv") + public AcknowledgementDTO createSearchRequestsFromCsv(@RequestPart(value = "file") MultipartFile payloadFile) throws Exception { + log.info("Payload received from csv file"); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - if (ObjectUtils.isNotEmpty(payloadDTO.getData())) { - acknowledgementDTO = dcRequestBuilderService.generateRequest(new ObjectMapper().writeValueAsString(payloadDTO)); + if (ObjectUtils.isNotEmpty(payloadFile)) { + acknowledgementDTO = dcRequestBuilderService.generatePayloadFromCsv(payloadFile); } return acknowledgementDTO; } + @PostMapping("/public/api/v1/registry/on-search") + public AcknowledgementDTO demoOnSearch(@RequestBody String responseString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); + return dcResponseHandlerService.getResponse(responseDTO); + } + /** * Listen to registry response * - * @param responseDTO required + * @param responseString required * @return On-Search response received acknowledgement */ @Operation(summary = "Listen to registry response") @@ -74,23 +152,44 @@ public AcknowledgementDTO createSearchRequests(@RequestBody PayloadDTO payloadDT @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/on-search") - public AcknowledgementDTO createSearchRequests(@RequestBody ResponseDTO responseDTO) throws G2pcValidationException{ + public AcknowledgementDTO createSearchRequests(@RequestBody String responseString) throws Exception { + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspect = keycloakURL + "/realms/" + keycloakRealm + "/protocol/openid-connect/token/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, adminClientId, adminClientSecret); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } + if (!g2pTokenService.validateToken(masterAdminUrl, getClientUrl, g2pTokenService.decodeToken(token))) { + throw new G2pHttpException(new G2pcError("err.request.unauthorized", "User is not authorized")); + } + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - try { - dcValidationService.validateResponseDto(responseDTO); - if (ObjectUtils.isNotEmpty(responseDTO)) { - acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO); - } - } catch (G2pcValidationException e) { - - throw new G2pcValidationException(e.getG2PcErrorList()); - } - catch (JsonProcessingException e){ - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); - } - catch (Exception e){ - throw new ResponseStatusException(HttpStatus.BAD_REQUEST , e.getMessage()); - } + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); + ResponseMessageDTO messageDTO = null; + if (isEncrypt.equals("true")) { + String messageString = objectMapper.convertValue(responseDTO.getMessage(), String.class); + String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). + readValue(deprecatedMessageString); + } else { + messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); + } + try { + dcValidationService.validateResponseDto(responseDTO); + if (ObjectUtils.isNotEmpty(responseDTO)) { + acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO); + } + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } return acknowledgementDTO; } @@ -104,11 +203,23 @@ public AcknowledgementDTO createSearchRequests(@RequestBody ResponseDTO response @ExceptionHandler(value = G2pcValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse + public ValidationErrorResponse handleValidationException( - G2pcValidationException ex) - { - return new ErrorResponse( + G2pcValidationException ex) { + return new ValidationErrorResponse( ex.getG2PcErrorList()); } + + /** + * Handle validation exception error response. + * + * @param ex the ValidationException + * @return the error response + */ + @ExceptionHandler(value = G2pHttpException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) { + return new ErrorResponse(ex.getG2PcError()); + + } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java deleted file mode 100644 index cb1e751..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryFarmerDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package g2pc.ref.dc.client.dto.farmer.request; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.*; - -@Getter -@Setter -@ToString -@AllArgsConstructor -@NoArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class QueryFarmerDTO { - - @JsonProperty("query_params") - private QueryParamsFarmerDTO queryParams; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java deleted file mode 100644 index 21a2544..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/farmer/request/QueryParamsFarmerDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package g2pc.ref.dc.client.dto.farmer.request; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -import java.util.List; - -@Getter -@Setter -@ToString -@NoArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class QueryParamsFarmerDTO { - - @JsonProperty("farmer_id") - private List farmerId; - - @JsonProperty("season") - private String season; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java deleted file mode 100644 index 0e35e2d..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryMobileDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package g2pc.ref.dc.client.dto.mobile.request; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@Getter -@Setter -@ToString -@NoArgsConstructor -public class QueryMobileDTO{ - - @JsonProperty("query_params") - private QueryParamsMobileDTO queryParams; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java deleted file mode 100644 index 6dc40d2..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/mobile/request/QueryParamsMobileDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package g2pc.ref.dc.client.dto.mobile.request; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -import java.util.List; - -@Getter -@Setter -@ToString -@NoArgsConstructor -public class QueryParamsMobileDTO { - - @JsonProperty("mobile_number") - private List mobileNumber; - - @JsonProperty("season") - private String season; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java deleted file mode 100644 index 739e497..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package g2pc.ref.dc.client.dto.payload; - -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@JsonInclude(JsonInclude.Include.NON_NULL) -public class PayloadDTO { - - PayloadDataDTO data; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java deleted file mode 100644 index 18e2a41..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/payload/PayloadDataDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -package g2pc.ref.dc.client.dto.payload; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@JsonInclude(JsonInclude.Include.NON_NULL) -public class PayloadDataDTO { - - @JsonProperty("farmer_id") - private String farmerId; - - @JsonProperty("farmer_name") - private String farmerName; - - @JsonProperty("mobile_number") - private String mobileNumber; - - @JsonProperty("season") - private String season; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java index 1d8a068..5bce42f 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java @@ -1,11 +1,11 @@ package g2pc.ref.dc.client.entity; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.*; import java.sql.Timestamp; @Builder diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java index c3edda0..0cc700f 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java @@ -1,20 +1,16 @@ package g2pc.ref.dc.client.service; -import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.cache.CacheDTO; -import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; +import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Map; public interface DcRequestBuilderService { - List createQuery(String payloadString) throws JsonProcessingException; + void createInitialTransactionInDB(String transactionId); - SearchCriteriaDTO getSearchCriteriaDTO(QueryDTO queryDTO, String regType); + AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception; - AcknowledgementDTO generateRequest(String payloadString) throws JsonProcessingException; + AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java index 042e1e5..3349cd0 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java @@ -1,8 +1,8 @@ package g2pc.ref.dc.client.service; import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.message.response.MessageDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; import org.springframework.stereotype.Service; @@ -20,7 +20,7 @@ public interface DcValidationService { * @throws G2pcValidationException the g 2 pc validation exception * @throws JsonProcessingException the json processing exception */ - public void validateResponseDto(ResponseDTO responseDTO) throws G2pcValidationException, JsonProcessingException; + public void validateResponseDto(ResponseDTO responseDTO) throws Exception; /** * Validate reg records. @@ -29,5 +29,5 @@ public interface DcValidationService { * @throws G2pcValidationException the g 2 pc validation exception * @throws JsonProcessingException the json processing exception */ - public void validateRegRecords(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; + public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java index 7c5cead..b6bef31 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java @@ -1,155 +1,153 @@ package g2pc.ref.dc.client.serviceimpl; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.message.request.*; -import g2pc.core.lib.enums.SortOrderEnum; +import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.utils.CommonUtils; import g2pc.dc.core.lib.service.RequestBuilderService; +import g2pc.dc.core.lib.service.TxnTrackerService; +import g2pc.ref.dc.client.config.RegistryConfig; import g2pc.ref.dc.client.constants.Constants; -import g2pc.ref.dc.client.dto.farmer.request.QueryParamsFarmerDTO; -import g2pc.ref.dc.client.dto.mobile.request.QueryParamsMobileDTO; -import g2pc.ref.dc.client.dto.payload.PayloadDTO; -import g2pc.ref.dc.client.dto.payload.PayloadDataDTO; import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; import g2pc.ref.dc.client.repository.RegistryTransactionsRepository; import g2pc.ref.dc.client.service.DcRequestBuilderService; import kong.unirest.HttpResponse; import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.*; +import java.util.stream.Collectors; @Service @Slf4j public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { - @Value("${registry.api_urls.farmer_search_api}") - private String farmerSearchURL; - - @Value("${registry.api_urls.mobile_search_api}") - private String mobileSearchURL; - @Autowired - private CommonUtils commonUtils; + private RegistryTransactionsRepository registryTransactionsRepository; @Autowired - private RedisTemplate redisTemplate; + private RequestBuilderService requestBuilderService; @Autowired - private RegistryTransactionsRepository registryTransactionsRepository; + RegistryConfig registryConfig; @Autowired - private RequestBuilderService requestBuilderService; + TxnTrackerService txnTrackerService; + /** + * Create initial transaction in DB + * + * @param transactionId unique transaction id + */ @Override - public List createQuery(String payloadString) throws JsonProcessingException { - PayloadDTO payloadDTO = new ObjectMapper().readValue(payloadString, PayloadDTO.class); - List queryDTOList = new ArrayList<>(); - PayloadDataDTO payloadDataDTO = payloadDTO.getData(); - if (StringUtils.isNotEmpty(payloadDataDTO.getFarmerId())) { - QueryParamsFarmerDTO queryParamsDTO = new QueryParamsFarmerDTO(); - queryParamsDTO.setFarmerId(Collections.singletonList(payloadDataDTO.getFarmerId())); - queryParamsDTO.setSeason(payloadDataDTO.getSeason()); - - QueryDTO queryDTO = new QueryDTO(); - queryDTO.setQueryName(Constants.PAID_FARMER); - queryDTO.setQueryParams(queryParamsDTO); - queryDTOList.add(queryDTO); - } - if (StringUtils.isNotEmpty(payloadDataDTO.getMobileNumber())) { - QueryParamsMobileDTO queryParamsDTO = new QueryParamsMobileDTO(); - queryParamsDTO.setMobileNumber(Collections.singletonList(payloadDataDTO.getMobileNumber())); - queryParamsDTO.setSeason(payloadDataDTO.getSeason()); - - QueryDTO queryDTO = new QueryDTO(); - queryDTO.setQueryName(Constants.IS_REGISTERED); - queryDTO.setQueryParams(queryParamsDTO); - queryDTOList.add(queryDTO); - } - return queryDTOList; - } - - @Override - public AcknowledgementDTO generateRequest(String payloadString) throws JsonProcessingException { - String requestString = ""; - PayloadDTO payloadDTO = new ObjectMapper().readValue(payloadString, PayloadDTO.class); - ObjectMapper objectMapper = new ObjectMapper(); - - String transactionId = "123456789"; - CacheDTO cacheDTO = requestBuilderService.createCache(objectMapper.writeValueAsString(payloadDTO.getData()), "RECEIVED"); - requestBuilderService.saveCache(cacheDTO, "initial-" + transactionId); - + public void createInitialTransactionInDB(String transactionId) { Optional entityOptional = registryTransactionsRepository.getByTransactionId(transactionId); if (entityOptional.isEmpty()) { RegistryTransactionsEntity entity = new RegistryTransactionsEntity(); entity.setTransactionId(transactionId); registryTransactionsRepository.save(entity); } + } - List queryDTOList = createQuery(objectMapper.writeValueAsString(payloadDTO)); - for (QueryDTO queryDTO : queryDTOList) { - SearchCriteriaDTO searchCriteriaDTO = getSearchCriteriaDTO(queryDTO, queryDTO.getQueryName()); + /** + * Create, save and send a request from payload + * + * @param payloadMapList required query params data + * @return acknowledgement of the request + */ + @SuppressWarnings("unchecked") + @Override + public AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(Constants.SEARCH_REQUEST_RECEIVED); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + + String transactionId = CommonUtils.generateUniqueId("T"); + + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); - requestString = requestBuilderService.buildRequest(searchCriteriaDTO, transactionId); - if (queryDTO.getQueryName().equals(Constants.PAID_FARMER)) { - requestBuilderService.sendRequest(requestString, farmerSearchURL); + createInitialTransactionInDB(transactionId); - cacheDTO = requestBuilderService.createCache(requestString, Constants.PENDING); - requestBuilderService.saveCache(cacheDTO, Constants.FARMER_CACHE_STRING + transactionId); - } else if (queryDTO.getQueryName().equals(Constants.IS_REGISTERED)) { - requestBuilderService.sendRequest(requestString, mobileSearchURL); + List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + List> queryMapFilteredList = queryMapList.stream() + .map(map -> map.entrySet().stream() + .filter(entry -> entry.getKey().equals(configEntryMap.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))).toList(); - cacheDTO = requestBuilderService.createCache(requestString, Constants.PENDING); - requestBuilderService.saveCache(cacheDTO, Constants.MOBILE_CACHE_STRING + transactionId); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + List searchCriteriaDTOList = new ArrayList<>(); + for (Map queryParamsMap : queryMapFilteredList) { + SearchCriteriaDTO searchCriteriaDTO = requestBuilderService.getSearchCriteriaDTO(queryParamsMap, registrySpecificConfigMap); + searchCriteriaDTOList.add(searchCriteriaDTO); } + String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId); + txnTrackerService.saveRequestTransaction(requestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); + log.info("requestString = {}", requestString); + //sendRequestDemo(requestString, registrySpecificConfigMap.get("url").toString()); + /* requestBuilderService.sendRequest(requestString, + registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString());*/ } + return acknowledgementDTO; + } + + /** + * Create a payload for request + * + * @param payloadFile csv file containing query params data + * @return acknowledgement of the request + */ + @Override + public AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); acknowledgementDTO.setMessage(Constants.SEARCH_REQUEST_RECEIVED); acknowledgementDTO.setStatus("RECEIVED"); + + Reader reader = new BufferedReader(new InputStreamReader(payloadFile.getInputStream())); + CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); + List> payloadMapList = getPayloadMapList(csvParser); + acknowledgementDTO = generateRequest(payloadMapList); return acknowledgementDTO; } - @Override - public SearchCriteriaDTO getSearchCriteriaDTO(QueryDTO queryDTO, String regType) { - PaginationDTO paginationDTO = new PaginationDTO(10, 1); - ConsentDTO consentDTO = new ConsentDTO(); - AuthorizeDTO authorizeDTO = new AuthorizeDTO(); - List sortDTOList = new ArrayList<>(); - SortDTO sortDTO = new SortDTO(); - if (queryDTO.getQueryName().equals("paid_farmer")) { - sortDTO.setAttributeName("farmer_id"); - sortDTO.setSortOrder(SortOrderEnum.ASC.toValue()); - } else if (queryDTO.getQueryName().equals("is_registered")) { - sortDTO.setAttributeName("mobile_number"); - sortDTO.setSortOrder(SortOrderEnum.ASC.toValue()); + private static List> getPayloadMapList(CSVParser csvParser) { + List csvRecordList = csvParser.getRecords(); + CSVRecord headerRecord = csvRecordList.get(0); + List headerList = new ArrayList<>(); + for (int i = 0; i < headerRecord.size(); i++) { + headerList.add(headerRecord.get(i)); } - sortDTOList.add(sortDTO); - - SearchCriteriaDTO searchCriteriaDTO = new SearchCriteriaDTO(); - searchCriteriaDTO.setVersion("1.0.0"); - if (queryDTO.getQueryName().equals("paid_farmer")) { - searchCriteriaDTO.setRegType("ns:FARMER_REGISTRY"); - } else if (queryDTO.getQueryName().equals("is_registered")) { - searchCriteriaDTO.setRegType("ns:MOBILE_REGISTRY"); + List> payloadMapList = new ArrayList<>(); + for (int i = 1; i < csvRecordList.size(); i++) { + CSVRecord csvRecord = csvRecordList.get(i); + Map payloadMap = new HashMap<>(); + for (int j = 0; j < headerRecord.size(); j++) { + payloadMap.put(headerList.get(j), csvRecord.get(j)); + } + payloadMapList.add(payloadMap); } - searchCriteriaDTO.setRegSubType(""); - searchCriteriaDTO.setQueryType("namedQuery"); - searchCriteriaDTO.setQuery(queryDTO); - searchCriteriaDTO.setSort(sortDTOList); - searchCriteriaDTO.setPagination(paginationDTO); - searchCriteriaDTO.setConsent(consentDTO); - searchCriteriaDTO.setAuthorize(authorizeDTO); - return searchCriteriaDTO; + return payloadMapList; + } + + private void sendRequestDemo(String requestString, String uri) { + HttpResponse response = Unirest.post(uri) + .body(requestString) + .asString(); + log.info("request send response status = {}", response.getStatus()); } } + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java index eadd1fc..10c9f03 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.MessageDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; import g2pc.core.lib.utils.CommonUtils; import g2pc.dc.core.lib.service.ResponseHandlerService; @@ -98,12 +98,12 @@ public void updateTransactionDbAndCache(String transactionId, SearchResponseDTO @Override public AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException { AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - - MessageDTO messageDTO = responseDTO.getMessage(); + ObjectMapper objectMapper = new ObjectMapper(); + ResponseMessageDTO messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); String transactionId = messageDTO.getTransactionId(); - SearchResponseDTO searchResponseDTO = messageDTO.getSearchResponse(); - - updateTransactionDbAndCache(transactionId, searchResponseDTO); + List searchResponseDTO = messageDTO.getSearchResponse(); + //TODO handle this update + //updateTransactionDbAndCache(transactionId, searchResponseDTO); acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED); acknowledgementDTO.setStatus(Constants.COMPLETED); diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java index d804f84..6c6899a 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java @@ -7,19 +7,22 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.MessageDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.dc.core.lib.service.ResponseHandlerService; import g2pc.ref.dc.client.service.DcValidationService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import javax.xml.crypto.Data; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -33,8 +36,17 @@ public class DcValidationServiceImpl implements DcValidationService { @Autowired ResponseHandlerService responseHandlerService; + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + @Override - public void validateResponseDto(ResponseDTO responseDTO) throws G2pcValidationException, JsonProcessingException { + public void validateResponseDto(ResponseDTO responseDTO) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() @@ -42,43 +54,48 @@ public void validateResponseDto(ResponseDTO responseDTO) throws G2pcValidationEx ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). readValue(headerString); responseHandlerService.validateResponseHeader(headerDTO); - - validateRegRecords(responseDTO.getMessage()); - - responseHandlerService.validateResponseMessage(responseDTO.getMessage()); - - - + ResponseMessageDTO messageDTO = null; + if (isEncrypt.equals("true")) { + String messageString = objectMapper.convertValue(responseDTO.getMessage(), String.class); + String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). + readValue(deprecatedMessageString); + } else { + messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); + } + validateRegRecords(messageDTO); + responseHandlerService.validateResponseMessage(messageDTO); } @Override - public void validateRegRecords(MessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - DataDTO dataDTO = messageDTO.getSearchResponse().getData(); - - String regRecordString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(dataDTO.getRegRecords()); - log.info("MessageString -> " + regRecordString); - InputStream schemaStream = DcValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/RegRecordFarmerSchema.json"); - JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); - JsonSchema schemaRegRecord = null; - if(schemaStream !=null){ - schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } + //TODO: work on commented line + //DataDTO dataDTO = messageDTO.getSearchResponse().getData(); + DataDTO dataDTO = new DataDTO(); + String regRecordString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(dataDTO.getRegRecords()); + log.info("MessageString -> " + regRecordString); + InputStream schemaStream = DcValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/RegRecordFarmerSchema.json"); + JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); + JsonSchema schemaRegRecord = null; + if (schemaStream != null) { + schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); - List errorCombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorCombinedMessage.add(new G2pcError("",error.getMessage())); + List errorCombinedMessage = new ArrayList<>(); + for (ValidationMessage error : errorMessage) { + log.info("Validation errors in Reg records" + error); + errorCombinedMessage.add(new G2pcError("", error.getMessage())); } - if (errorMessage.size()>0){ + if (errorMessage.size() > 0) { throw new G2pcValidationException(errorCombinedMessage); } - } } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml index 0ce0a34..93a893b 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -32,6 +32,8 @@ spring: show-sql: false open-in-view: false generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration server: port: 8000 @@ -44,10 +46,35 @@ spring.data.redis: password: 123456789 port: 6379 -registry: api_urls: #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" +keycloak: + farmer: + url: "http://127.0.0.1:8081/auth/realms/dp-farmer/protocol/openid-connect/token" + clientId: "dp-farmer-client" + clientSecret: "EaXspS2bAcCmh5XrDWYrAzWP1Q1uQEIA" + mobile: + url: "http://127.0.0.1:8081/auth/realms/dp-mobile/protocol/openid-connect/token" + clientId: "dp-mobile-client" + clientSecret: "d544H8DTnZXREmX6jgmAfoFCeFXQ1oVV" + consumer: + admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token + get-client-url: http://127.0.0.1:8081/auth/admin/realms/data-consumer/clients + realm: data-consumer + url: http://127.0.0.1:8081/auth + admin: + client-id: admin-cli + client-secret: qCyT7XM24KGjb5j6ZU5YC68H5OiI6LRm + +crypto: + support_encryption: false + support_signature: false + +registry: + api_urls: + farmer_search_api: "http://localhost:9001/public/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/public/api/v1/registry/search" \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json index 94475f2..bb31876 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json @@ -4,6 +4,8 @@ "title": "Message schema", "description": "", "additionalProperties": false, + "type": "array", + "items" : [{ "type": "object", "properties": { "farmer_id": { @@ -29,7 +31,7 @@ "payment_amount": { "type": "number" } - }, + } } ] , "definitions": { "nonEmptyString": { "type": "string", From 8f00c0aab5fc00ecc63a3565de7d0f2dfd9912b8 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 16:10:59 +0530 Subject: [PATCH 17/53] Refactored g2pc-ref-dc-client changes-1. --- .../ref/dc/client/config/RegistryConfig.java | 126 ++++++++++++++++++ .../resources/static/.Well-known/data.json | 3 + .../resources/static/.Well-known/hello.txt | 1 + 3 files changed, 130 insertions(+) create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java new file mode 100644 index 0000000..382d0f8 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java @@ -0,0 +1,126 @@ +package g2pc.ref.dc.client.config; + +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.enums.SortOrderEnum; +import g2pc.ref.dc.client.constants.Constants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class RegistryConfig { + + @Value("${registry.api_urls.farmer_search_api}") + private String farmerSearchURL; + + @Value("${registry.api_urls.mobile_search_api}") + private String mobileSearchURL; + + @Value("${keycloak.farmer.clientId}") + private String farmerClientId; + + @Value("${keycloak.farmer.clientSecret}") + private String farmerClientSecret; + + @Value("${keycloak.farmer.url}") + private String keycloakFarmerTokenUrl; + + @Value("${keycloak.mobile.clientId}") + private String mobileClientId; + + @Value("${keycloak.mobile.clientSecret}") + private String mobileClientSecret; + + @Value("${keycloak.mobile.url}") + private String keycloakMobileTokenUrl; + + /** + * Map to represent which query params are required for which registry + * + * + * @return query params specific to registry + */ + public Map getQueryParamsConfig() { + Map queryParamsConfig = new HashMap<>(); + + Map farmerRegistryMap = new HashMap<>(); + farmerRegistryMap.put("farmer_id", ""); + farmerRegistryMap.put("season", ""); + + + Map mobileRegistryMap = new HashMap<>(); + mobileRegistryMap.put("mobile_number", ""); + mobileRegistryMap.put("season", ""); + + queryParamsConfig.put(Constants.FARMER_REGISTRY, farmerRegistryMap); + queryParamsConfig.put(Constants.MOBILE_REGISTRY, mobileRegistryMap); + return queryParamsConfig; + } + + /** + * Map to represent which common values to be used to generate request for a registry + * + * + * @return Map to represent registry specific config values + */ + public Map getRegistrySpecificConfig() { + Map queryParamsConfig = new HashMap<>(); + + Map farmerRegistryMap = getFarmerRegistryMap(); + Map mobileRegistryMap = getMobileRegistryMap(); + + queryParamsConfig.put(Constants.FARMER_REGISTRY, farmerRegistryMap); + queryParamsConfig.put(Constants.MOBILE_REGISTRY, mobileRegistryMap); + return queryParamsConfig; + } + + /** + * Set farmer registry specific config values + * + * + * @return Map to represent registry specific config values for farmer + */ + private Map getFarmerRegistryMap() { + Map farmerRegistryMap = new HashMap<>(); + farmerRegistryMap.put(CoreConstants.QUERY_NAME, "paid_farmer"); + farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchURL); + farmerRegistryMap.put(CoreConstants.REG_TYPE, "ns:FARMER_REGISTRY"); + farmerRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); + farmerRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); + farmerRegistryMap.put(CoreConstants.SORT_ATTRIBUTE, "farmer_id"); + farmerRegistryMap.put(CoreConstants.SORT_ORDER, SortOrderEnum.ASC.toValue()); + farmerRegistryMap.put(CoreConstants.PAGE_NUMBER, "1"); + farmerRegistryMap.put(CoreConstants.PAGE_SIZE, "10"); + farmerRegistryMap.put(CoreConstants.CLIENT_ID, farmerClientId); + farmerRegistryMap.put(CoreConstants.CLIENT_SECRET, farmerClientSecret); + farmerRegistryMap.put(CoreConstants.KEYCLOAK_URL, keycloakFarmerTokenUrl); + return farmerRegistryMap; + } + + /** + * Set mobile registry specific config values + * + * + * @return Map to represent registry specific config values for mobile + */ + private Map getMobileRegistryMap() { + Map mobileRegistryMap = new HashMap<>(); + mobileRegistryMap.put(CoreConstants.QUERY_NAME, "mobile_registered"); + mobileRegistryMap.put(CoreConstants.DP_SEARCH_URL, mobileSearchURL); + mobileRegistryMap.put(CoreConstants.REG_TYPE, "ns:MOBILE_REGISTRY"); + mobileRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); + mobileRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); + mobileRegistryMap.put(CoreConstants.SORT_ATTRIBUTE, "mobile_number"); + mobileRegistryMap.put(CoreConstants.SORT_ORDER, SortOrderEnum.ASC.toValue()); + mobileRegistryMap.put(CoreConstants.PAGE_NUMBER, "1"); + mobileRegistryMap.put(CoreConstants.PAGE_SIZE, "10"); + mobileRegistryMap.put(CoreConstants.CLIENT_ID, mobileClientId); + mobileRegistryMap.put(CoreConstants.CLIENT_SECRET, mobileClientSecret); + mobileRegistryMap.put(CoreConstants.KEYCLOAK_URL, keycloakMobileTokenUrl); + return mobileRegistryMap; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json new file mode 100644 index 0000000..795e9d3 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json @@ -0,0 +1,3 @@ +{ +"name": "abhi" +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt new file mode 100644 index 0000000..2615228 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt @@ -0,0 +1 @@ +welcome to the project \ No newline at end of file From 3b169d8bdc46a27db23f55c63ac77787f88750fb Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 16:12:38 +0530 Subject: [PATCH 18/53] Refactored g2pc-ref-farmer-regsvc changes. --- .../.wellknown/README.md | 1 - .../g2pc-ref-farmer-regsvc/README.md | 88 +++++++++++- .../g2pc-ref-farmer-regsvc/pom.xml | 64 +++++++-- .../G2pcRefFarmerRegsvcApplication.java | 12 +- .../regsvc/config/ObjectMapperConfig.java | 3 +- .../farmer/regsvc/config/SecurityConfig.java | 33 +++++ .../controller/rest/RegistryController.java | 134 +++++++++++++++--- .../dto/request/QueryParamsFarmerDTO.java | 2 +- .../regsvc/entity/FarmerInfoEntity.java | 2 +- .../repository/FarmerInfoRepository.java | 2 +- .../farmer/regsvc/scheduler/Scheduler.java | 96 +++++++++---- .../service/FarmerResponseBuilderService.java | 8 +- .../service/FarmerValidationService.java | 3 +- .../FarmerResponseBuilderServiceImpl.java | 86 ++++------- .../FarmerValidationServiceImpl.java | 26 ++-- .../src/main/resources/application.yml | 21 ++- .../resources/schema/farmerQuerySchema.json | 2 +- .../G2pcRefFarmerRegsvcApplicationTests.java | 5 +- 18 files changed, 447 insertions(+), 141 deletions(-) delete mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/SecurityConfig.java diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md deleted file mode 100644 index cc2ca81..0000000 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/.wellknown/README.md +++ /dev/null @@ -1 +0,0 @@ -# well known file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md index 7ef31ba..c606d2c 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md @@ -1 +1,87 @@ -# G2pc Ref Farmer Reg Svc \ No newline at end of file +# G2pc Ref Farmer Reg Svc + +## JSON schema validations +This project is an implementation of the JSON Schema Draft v4, + +### Custom validation using json schema +For Farmer registry Query params are the 20% are custom changes , in which for this query param different schema has been written +in this repo. + +### Maven Dependency +```` + + com.github.erosb + everit-json-schema + 1.14.2 + +```` + + + +#### Below are some samples schema which written in this project. +```` +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Query schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "query_name" : { + "type": "string" + }, + "query_params": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + }, + "farmer_id": { + "type": "array", + "items": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "season": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "required": ["farmer_id","season"] + } + }, + "required": ["query_params"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + + +```` + +Using below code we can read the above schema json +```` + InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/farmerQuerySchema.json"); + JsonNode jsonNode = objectMapper.readTree(queryString); + JsonSchema schema = null; + if(schemaStreamQuery !=null){ + schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStreamQuery); + } + Set errorMessage = schema.validate(jsonNode); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } +```` + + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml index 557136d..6846ffe 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.17 + 3.0.12 g2pc.ref.farmer.regsvc @@ -14,13 +14,17 @@ g2pc-ref-farmer-regsvc g2pc-ref-farmer-regsvc - 11 + 17 org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-web + org.projectlombok @@ -40,7 +44,7 @@ org.postgresql postgresql - 42.3.5 + 42.5.4 org.springframework.boot @@ -52,20 +56,56 @@ test - redis.clients - jedis - 3.7.1 - jar + org.springdoc + springdoc-openapi-ui + 1.6.8 + + + + + + + + + + - org.springframework.boot - spring-boot-starter-data-redis + org.springframework.security + spring-security-web + 6.1.2 - org.springdoc - springdoc-openapi-ui - 1.6.8 + com.auth0 + java-jwt + 4.4.0 + + + + com.mashape.unirest + unirest-java + 1.4.9 + + + + + jakarta.validation + jakarta.validation-api + 2.0.2 + + + com.auth0 + java-jwt + 4.4.0 + + + org.springframework.security + spring-security-config + 6.1.2 + + + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java index 99aacfd..f9383ae 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplication.java @@ -2,16 +2,22 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -@SpringBootApplication -@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib","g2pc.ref.farmer.regsvc","g2pc.dp.core.lib.service"}) +@SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) +@ComponentScan({"g2pc.ref.farmer.regsvc","g2pc.core.lib","g2pc.dp.core.lib","g2pc.ref.farmer.regsvc", + "g2pc.ref.farmer.regsvc.auth.service","g2pc.ref.farmer.regsvc.auth.serviceImpl", + "org.springframework.security.config.annotation.web.builders.HttpSecurity", + "g2pc.dp.core.lib.service","g2pc.ref.farmer.regsvc.auth.controller"}) @EnableScheduling +@EnableWebSecurity public class G2pcRefFarmerRegsvcApplication { public static void main(String[] args) { SpringApplication.run(G2pcRefFarmerRegsvcApplication.class, args); } -} +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java index c4de97d..ef07586 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/ObjectMapperConfig.java @@ -1,6 +1,7 @@ package g2pc.ref.farmer.regsvc.config; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import org.springframework.context.annotation.Bean; @@ -13,7 +14,7 @@ public class ObjectMapperConfig { public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class); + ResponseHeaderDTO.class, HeaderDTO.class); return objectMapper; } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/SecurityConfig.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/SecurityConfig.java new file mode 100644 index 0000000..8229381 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/SecurityConfig.java @@ -0,0 +1,33 @@ +package g2pc.ref.farmer.regsvc.config; + +import g2pc.core.lib.security.G2pTokenVerification; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +@Configuration +public class SecurityConfig { + @Bean + public G2pTokenVerification JWTTokenFilter() { + return new G2pTokenVerification(); + } + + @Bean + public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().permitAll() + .and() + .addFilterBefore(JWTTokenFilter(), BasicAuthenticationFilter.class) + ) + .sessionManagement(sessionManagement -> + sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .csrf().disable(); + + return http.build(); + } +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java index 6489789..272ed3d 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java @@ -1,14 +1,25 @@ package g2pc.ref.farmer.regsvc.controller.rest; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pTokenService; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.farmer.regsvc.constants.Constants; -import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; import g2pc.ref.farmer.regsvc.service.FarmerValidationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -16,11 +27,11 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpMethod; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import g2pc.core.lib.exceptionhandler.ErrorResponse; /** * The type Registry controller. @@ -40,15 +51,63 @@ public class RegistryController { @Autowired FarmerValidationService farmerValidationService; + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${keycloak.realm}") + private String keycloakRealm; + + @Value("${keycloak.url}") + private String keycloakURL; + + @Value("${keycloak.farmer.admin-url}") + private String masterAdminUrl; + + @Value("${keycloak.farmer.get-client-url}") + private String getClientUrl; + + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + + @Value("${keycloak.admin.client-id}") + private String adminClientId; + + @Value("${keycloak.admin.client-secret}") + private String adminClientSecret; + + @Operation(summary = "Receive search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/public/api/v1/registry/search") + public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + return requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey); + } /** * Get search request from DC * - * @param requestDTO required + * @param requestString required * @return Search request received acknowledgement * @throws JsonProcessingException the json processing exception * @throws ResponseStatusException the response status exception - * @throws G2pcValidationException the validation exception + * @throws G2pcValidationException the validation exception */ @Operation(summary = "Receive search request") @ApiResponses(value = { @@ -57,29 +116,50 @@ public class RegistryController { @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/search") - public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO requestDTO) throws JsonProcessingException, ResponseStatusException, G2pcValidationException { + public AcknowledgementDTO getRequestForSearch(@RequestBody String requestString) throws Exception { + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspect = keycloakURL + "/realms/" + keycloakRealm + "/protocol/openid-connect/token/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, adminClientId, adminClientSecret); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } + if (!g2pTokenService.validateToken(masterAdminUrl, getClientUrl, g2pTokenService.decodeToken(token))) { + throw new G2pHttpException(new G2pcError("err.request.unauthorized", "User is not authorized")); + } ObjectMapper objectMapper = new ObjectMapper(); - String cacheKey = Constants.CACHE_KEY_STRING + requestDTO.getMessage().getTransactionId(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO = null; + + if (isEncrypt.equals("true")) { + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue((JsonParser) requestDTO.getMessage()); + } + + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { farmerValidationService.validateRequestDTO(requestDTO); return requestHandlerService.buildCacheRequest( objectMapper.writeValueAsString(requestDTO), cacheKey); } catch (G2pcValidationException e) { - throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } - catch (JsonProcessingException e){ - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); - } - catch (Exception e){ - throw new ResponseStatusException(HttpStatus.BAD_REQUEST , e.getMessage()); - } - - } - /** + * TokenIsNotValidException * Handle validation exception error response. * * @param ex the ValidationException @@ -88,11 +168,23 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO r @ExceptionHandler(value = G2pcValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse + public ValidationErrorResponse handleValidationException( - G2pcValidationException ex) - { - return new ErrorResponse( + G2pcValidationException ex) { + return new ValidationErrorResponse( ex.getG2PcErrorList()); } + + /** + * Handle validation exception error response. + * + * @param ex the ValidationException + * @return the error response + */ + @ExceptionHandler(value = G2pHttpException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) { + return new ErrorResponse(ex.getG2PcError()); + + } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java index bea2396..26bcc91 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java @@ -14,7 +14,7 @@ public class QueryParamsFarmerDTO { @JsonProperty("farmer_id") - private List farmerId; + private String farmerId; @JsonProperty("season") private String season; diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java index aff84f7..1515de7 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java @@ -1,11 +1,11 @@ package g2pc.ref.farmer.regsvc.entity; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.*; @Builder @Data diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java index fcea09a..c7227da 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java @@ -9,5 +9,5 @@ @Repository public interface FarmerInfoRepository extends JpaRepository { - Optional> findBySeasonAndFarmerIdIn(String season,List farmerId); + Optional findBySeasonAndFarmerId(String season, String farmerId); } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java index ff72cea..45a38c0 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java @@ -1,15 +1,27 @@ package g2pc.ref.farmer.regsvc.scheduler; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.repository.MsgTrackerRepository; +import g2pc.dp.core.lib.repository.TxnTrackerRepository; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.dp.core.lib.service.ResponseBuilderService; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; import g2pc.ref.farmer.regsvc.constants.Constants; import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; +import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -35,37 +47,73 @@ public class Scheduler { @Autowired private FarmerResponseBuilderService farmerResponseBuilderService; + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + @Value("${keycloak.data-consumer.client-id}") + private String dcClientId; + + @Value("${keycloak.data-consumer.client-secret}") + private String dcClientSecret; + + @Value("${keycloak.data-consumer.url}") + private String keyClockClientTokenUrl; + + @Autowired + private MsgTrackerRepository msgTrackerRepository; + + @Autowired + private TxnTrackerRepository txnTrackerRepository; + + @Autowired + private TxnTrackerDbService txnTrackerDbService; /** * Response scheduler */ @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. + @Transactional public void responseScheduler() throws IOException { try { ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class); - - List cacheKeysList = requestHandlerService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); - // TODO: this logic has to be handled to get single cacheKey - String cacheKey = cacheKeysList.get(0); - String requestData = requestHandlerService.getRequestData(cacheKey); - CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); - - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - - String refRecordsString = farmerResponseBuilderService.getRegFarmerRecords( - objectMapper.writeValueAsString(requestDTO.getMessage())); - - DataDTO dataDTO = farmerResponseBuilderService.buildData(refRecordsString); - - String responseString = responseBuilderService.buildResponseString(Constants.CACHE_KEY_SEARCH_STRING, dataDTO); - log.info("Scheduler responseString : {}", responseString); - - responseBuilderService.sendOnSearchResponse(responseString, onSearchURL); - - responseBuilderService.updateRequestStatus(cacheKey, DpConstants.COMPLETED, cacheDTO); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + List cacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); + for (String cacheKey : cacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() + .map(txnTrackerEntity -> { + try { + return objectMapper.readValue(txnTrackerEntity.getQuery(), QueryDTO.class); + } catch (JsonProcessingException e) { + return null; + } + }).toList(); + List refRecordsStringsList = farmerResponseBuilderService.getRegFarmerRecords(queryDTOList); + + List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( + requestDTO, refRecordsStringsList); + + ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); + + ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); + String responseString = responseBuilderService.buildResponseString("signature", + headerDTO, responseMessageDTO); + responseString = CommonUtils.formatString(responseString); + log.info("on-search response = {}", responseString); + //responseBuilderService.sendOnSearchResponse(responseString, onSearchURL,dcClientId,dcClientSecret ,keyClockClientTokenUrl); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } } catch (Exception ex) { - log.error("Scheduler error : {}", ex.getMessage()); + log.error("Exception in responseScheduler: {}", ex.getMessage()); } } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java index 4987a24..173afcf 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java @@ -1,17 +1,17 @@ package g2pc.ref.farmer.regsvc.service; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; import g2pc.ref.farmer.regsvc.dto.response.RegRecordFarmerDTO; import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; import java.io.IOException; import java.util.List; +import java.util.Map; public interface FarmerResponseBuilderService { - List getRegRecordFarmerDTO(List farmerInfoList); + RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity); - String getRegFarmerRecords(String messageString) throws IOException; - - DataDTO buildData(String regRecordsString) throws IOException; + List getRegFarmerRecords(List queryDTOList) throws IOException; } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java index e6cecfe..3860585 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java @@ -1,6 +1,7 @@ package g2pc.ref.farmer.regsvc.service; import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; @@ -17,7 +18,7 @@ public interface FarmerValidationService { * @throws G2pcValidationException the validation exception * @throws JsonProcessingException the json processing exception */ - void validateRequestDTO (RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException; + void validateRequestDTO (RequestDTO requestDTO) throws Exception; /** * Validate query dto. diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java index b1eaeb7..bbad5b7 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; import g2pc.ref.farmer.regsvc.dto.request.QueryParamsFarmerDTO; import g2pc.ref.farmer.regsvc.dto.response.RegRecordFarmerDTO; @@ -11,13 +11,12 @@ import g2pc.ref.farmer.regsvc.repository.FarmerInfoRepository; import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; @Service @Slf4j @@ -29,67 +28,44 @@ public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderSe /** * Get farmer records information from DB * - * @param farmerInfoList required - * @return List of farmer records + * @param farmerInfoEntity required + * @return Farmer records */ @Override - public List getRegRecordFarmerDTO(List farmerInfoList) { - List regRecordFarmerDTOList = new ArrayList<>(); - for (FarmerInfoEntity farmerInfoEntity : farmerInfoList) { - RegRecordFarmerDTO dto = new RegRecordFarmerDTO(); - dto.setFarmerId(farmerInfoEntity.getFarmerId()); - dto.setFarmerName(farmerInfoEntity.getFarmerName()); - dto.setSeason(farmerInfoEntity.getSeason()); - dto.setPaymentStatus(farmerInfoEntity.getPaymentStatus()); - dto.setPaymentDate(farmerInfoEntity.getPaymentDate()); - dto.setPaymentAmount(farmerInfoEntity.getPaymentAmount()); - regRecordFarmerDTOList.add(dto); - } - return regRecordFarmerDTOList; + public RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity) { + RegRecordFarmerDTO dto = new RegRecordFarmerDTO(); + dto.setFarmerId(farmerInfoEntity.getFarmerId()); + dto.setFarmerName(farmerInfoEntity.getFarmerName()); + dto.setSeason(farmerInfoEntity.getSeason()); + dto.setPaymentStatus(farmerInfoEntity.getPaymentStatus()); + dto.setPaymentDate(farmerInfoEntity.getPaymentDate()); + dto.setPaymentAmount(farmerInfoEntity.getPaymentAmount()); + return dto; } /** * Get farmer records information string * - * @param messageString required - * @return String of farmer records + * @param queryDTOList required + * @return List of farmer records */ @Override - public String getRegFarmerRecords(String messageString) throws IOException { + public List getRegFarmerRecords(List queryDTOList) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); - //remove only use while testing - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class); - - MessageDTO messageDTO = objectMapper.readerFor(MessageDTO.class).readValue(messageString); - String queryParams = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery().getQueryParams()); - QueryParamsFarmerDTO queryParamsFarmerDTO = objectMapper.readValue(queryParams, QueryParamsFarmerDTO.class); - - List farmerIds = queryParamsFarmerDTO.getFarmerId(); - String season = queryParamsFarmerDTO.getSeason(); - - List regRecordFarmerDTOList = new ArrayList<>(); - Optional> optionalList = farmerInfoRepository. - findBySeasonAndFarmerIdIn(season, farmerIds); - if (optionalList.isPresent()) { - regRecordFarmerDTOList = getRegRecordFarmerDTO(optionalList.get()); + List regFarmerRecordsList = new ArrayList<>(); + for (QueryDTO queryDTO : queryDTOList) { + String queryParams = objectMapper.writeValueAsString(queryDTO.getQueryParams()); + QueryParamsFarmerDTO queryParamsFarmerDTO = objectMapper.readValue(queryParams, QueryParamsFarmerDTO.class); + String farmerId = queryParamsFarmerDTO.getFarmerId(); + String season = queryParamsFarmerDTO.getSeason(); + Optional optional = farmerInfoRepository.findBySeasonAndFarmerId(season, farmerId); + if (optional.isPresent()) { + RegRecordFarmerDTO regRecordFarmerDTO = getRegRecordFarmerDTO(optional.get()); + regFarmerRecordsList.add(objectMapper.writeValueAsString(regRecordFarmerDTO)); + } else { + regFarmerRecordsList.add(StringUtils.EMPTY); + } } - - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(regRecordFarmerDTOList); - } - - @Override - public DataDTO buildData(String regRecordsString) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - List regRecordFarmerDTOList = objectMapper.readerFor(List.class). - readValue(regRecordsString); - - DataDTO dataDTO = new DataDTO(); - dataDTO.setVersion("1.0.0"); - dataDTO.setRegType("ns:FARMER_REGISTRY"); - dataDTO.setRegSubType(""); - dataDTO.setRegRecordType("ns:FARMER_REGISTRY:FARMER"); - dataDTO.setRegRecords(regRecordFarmerDTOList); - return dataDTO; + return regFarmerRecordsList; } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java index fec317a..62bdb3e 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java @@ -9,6 +9,7 @@ import com.networknt.schema.ValidationMessage; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.dp.core.lib.service.RequestHandlerService; @@ -39,10 +40,11 @@ public class FarmerValidationServiceImpl implements FarmerValidationService { @Override public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); + /* ObjectMapper objectMapper = new ObjectMapper(); + RequestMessageDTO messageDTO=(RequestMessageDTO) requestDTO.getMessage(); String queryString = new ObjectMapper() .writerWithDefaultPrettyPrinter() - .writeValueAsString(requestDTO.getMessage().getSearchRequest().getSearchCriteria().getQuery()); + .writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery()); QueryFarmerDTO queryFarmerDTO = objectMapper.readerFor(QueryFarmerDTO.class). readValue(queryString); validateQueryDto(queryFarmerDTO); @@ -54,7 +56,7 @@ public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationExcep readValue(headerString); requestHandlerService.validateRequestHeader(headerDTO); requestHandlerService.validateRequestMessage(requestDTO.getMessage()); - +*/ } @@ -62,7 +64,7 @@ public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationExcep public void validateQueryDto(QueryFarmerDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { ObjectMapper objectMapper = new ObjectMapper(); - log.info("Query object -> "+queryFarmerDTO); + log.info("Query object -> " + queryFarmerDTO); String queryString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(queryFarmerDTO); @@ -71,20 +73,20 @@ public void validateQueryDto(QueryFarmerDTO queryFarmerDTO) throws JsonProcessin .getResourceAsStream("schema/farmerQuerySchema.json"); JsonNode jsonNode = objectMapper.readTree(queryString); JsonSchema schema = null; - if(schemaStreamQuery !=null){ - schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + if (schemaStreamQuery != null) { + schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). getSchema(schemaStreamQuery); } Set errorMessage = schema.validate(jsonNode); - List errorcombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorcombinedMessage.add(new G2pcError("",error.getMessage())); + List errorcombinedMessage = new ArrayList<>(); + for (ValidationMessage error : errorMessage) { + log.info("Validation errors" + error); + errorcombinedMessage.add(new G2pcError("", error.getMessage())); } - if (errorMessage.size()>0){ + if (errorMessage.size() > 0) { throw new G2pcValidationException(errorcombinedMessage); } } - } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml index 669abd2..44137fd 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml @@ -4,7 +4,7 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2p?currentSchema=farmer + url: jdbc:postgresql://localhost:5432/g2p?currentSchema=dp username: postgres password: postgres hikari: @@ -32,6 +32,8 @@ spring: show-sql: false open-in-view: false generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration server: port: 9001 @@ -48,3 +50,20 @@ client: api_urls: client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" +keycloak: + data-consumer: + url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" + client-id: dc-client + client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K + farmer: + admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token + get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-farmer/clients + realm: dp-farmer + url: http://127.0.0.1:8081/auth + admin: + client-id: admin-cli + client-secret: D50Wdnxc7JiQxCPi884aVDAiQy1nfPpq + +crypto: + support_encryption: false + support_signature: false \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json index 7a0fb85..3d0a0a5 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/schema/farmerQuerySchema.json @@ -17,7 +17,7 @@ "type": "string" }, "farmer_id": { - "type": "array", + "type": "string", "items": { "$ref": "#/definitions/nonEmptyString", "type": "string" diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java index 150e82e..d1891c9 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java @@ -1,5 +1,6 @@ package g2pc.ref.farmer.regsvc; +import g2pc.core.lib.security.service.G2pTokenService; import g2pc.ref.farmer.regsvc.scheduler.Scheduler; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -13,6 +14,9 @@ class G2pcRefFarmerRegsvcApplicationTests { @Autowired private Scheduler scheduler; + + @Autowired + G2pTokenService g2pTokenService ; @Test void contextLoads() { } @@ -21,5 +25,4 @@ void contextLoads() { void testResponseScheduler() throws IOException { scheduler.responseScheduler(); } - } From 667ae0ae7a5f47f63065746a993a70a2ceaa8c07 Mon Sep 17 00:00:00 2001 From: Abhilash Date: Fri, 1 Dec 2023 16:13:47 +0530 Subject: [PATCH 19/53] Refactored g2pc-ref-mno-regsvc changes. --- .../g2pc-ref-mno-regsvc/.wellknown/README.md | 1 - .../g2pc-ref-mno-regsvc/README.md | 88 +++++++++++++++++- .../g2pc-ref-mno-regsvc/pom.xml | 16 +--- .../regsvc/G2pcRefMnoRegsvcApplication.java | 5 +- .../controller/rest/RegistryController.java | 90 +++++++++++++++++-- .../ref/mno/regsvc/scheduler/Scheduler.java | 23 ++++- .../service/MobileValidationService.java | 15 +++- .../MobileResponseBuilderServiceImpl.java | 9 +- .../MobileValidationServiceImpl.java | 66 ++++++++++++-- .../src/main/resources/application.yml | 19 ++++ .../resources/schema/mobileQuerySchema.json | 2 +- 11 files changed, 293 insertions(+), 41 deletions(-) delete mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md b/g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md deleted file mode 100644 index cc2ca81..0000000 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/.wellknown/README.md +++ /dev/null @@ -1 +0,0 @@ -# well known file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md b/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md index d01317f..e80db5b 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/README.md @@ -1 +1,87 @@ -# G2pc Ref Mno Reg Svc \ No newline at end of file +# G2pc Ref Mno Reg Svc + +## JSON schema validations +This project is an implementation of the JSON Schema Draft v4, + +### Custom validation using json schema +For Mobile registry Query params are the 20% are custom changes , in which for this query param different schema has been written +in this repo. + +### Maven Dependency +```` + + com.github.erosb + everit-json-schema + 1.14.2 + +```` + + + +#### Below are some samples schema which written in this project. +```` +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Query schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "query_name" : { + "type": "string" + }, + "query_params": { + "type": "object", + "properties": { + "type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "mobile_number": { + "type": "array", + "items": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "season": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "required": ["mobile_number","season"] + } + }, + "required": ["query_params"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + + + + +```` + +Using below code we can read the above schema json +```` + InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/mobileQuerySchema.json"); + JsonNode jsonNode = objectMapper.readTree(queryString); + JsonSchema schema = null; + if(schemaStreamQuery !=null){ + schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStreamQuery); + } + Set errorMessage = schema.validate(jsonNode); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } +```` \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml index 6c8649a..2a9c21d 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.17 + 3.0.12 g2pc.ref.mno.regsvc @@ -14,7 +14,7 @@ g2pc-ref-mno-regsvc g2pc-ref-mno-regsvc - 11 + 17 @@ -40,7 +40,7 @@ org.postgresql postgresql - 42.3.5 + 42.5.4 org.springframework.boot @@ -51,16 +51,6 @@ spring-boot-starter-test test - - redis.clients - jedis - 3.7.1 - jar - - - org.springframework.boot - spring-boot-starter-data-redis - org.springdoc springdoc-openapi-ui diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java index e927eb3..22554ae 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java @@ -3,11 +3,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) -@ComponentScan({"g2pc.core.lib", "g2pc.dp.core.lib", "g2pc.ref.mno.regsvc","g2pc.dp.core.lib.service"}) +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class ,SecurityAutoConfiguration.class}) +@ComponentScan({"g2pc.core.lib", "g2pc.dp.core.lib", "g2pc.ref.mno.regsvc","g2pc.dp.core.lib.service","g2pc.core.lib.security.serviceImpl"}) @EnableScheduling public class G2pcRefMnoRegsvcApplication { diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java index 61c1268..42253c4 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java @@ -2,10 +2,21 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pTokenService; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.mno.regsvc.constants.Constants; import g2pc.ref.mno.regsvc.service.MobileValidationService; @@ -15,7 +26,9 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; @@ -36,11 +49,40 @@ public class RegistryController { */ @Autowired MobileValidationService mobileValidationService; + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Value("${keycloak.realm}") + private String keycloakRealm; + + @Value("${keycloak.url}") + private String keycloakURL; + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${keycloak.mobile.admin-url}") + private String masterAdminUrl; + + @Value("${keycloak.mobile.get-client-url}") + private String getClientUrl; + + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + + @Value("${keycloak.admin.client-id}") + private String adminClientId; + + @Value("${keycloak.admin.client-secret}") + private String adminClientSecret; /** * Get search request from DC * - * @param requestDTO required + * @param requestString required * @return Search request received acknowledgement * @throws JsonProcessingException the json processing exception * @throws ResponseStatusException the response status exception @@ -53,10 +95,33 @@ public class RegistryController { @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/search") - public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO requestDTO) throws JsonProcessingException, ResponseStatusException, G2pcValidationException { + public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); - String cacheKey = Constants.CACHE_KEY_STRING + requestDTO.getMessage().getTransactionId(); + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspect = keycloakURL+"/realms/"+keycloakRealm+"/protocol/openid-connect/token/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token,adminClientId,adminClientSecret); + if(introspectResponse.getStatusCode().value()==401){ + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(),introspectResponse.getBody())); + } + if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token))){ + throw new G2pHttpException(new G2pcError("err.request.unauthorized","User is not authorized")); + } + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + MessageDTO messageDTO = null; + if(isEncrypt.equals("true")){ + String messageString = requestDTO.getMessage(); + String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + messageDTO = objectMapper.readerFor(MessageDTO.class). + readValue(deprecatedMessageString); + }else{ + messageDTO = objectMapper.readerFor(MessageDTO.class). + readValue(requestDTO.getMessage()); + } + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { mobileValidationService.validateRequestDTO(requestDTO); @@ -74,6 +139,7 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO r } } + /** * Handle validation exception error response. * @@ -83,11 +149,25 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody RequestDTO r @ExceptionHandler(value = G2pcValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse + public ValidationErrorResponse handleValidationException( G2pcValidationException ex) { - return new ErrorResponse( + return new ValidationErrorResponse( ex.getG2PcErrorList()); } + + /** + * Handle validation exception error response. + * + * @param ex the ValidationException + * @return the error response + */ + @ExceptionHandler(value = G2pHttpException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) + { + return new ErrorResponse(ex.getG2PcError()); + + } } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java index 5df3e74..b658a12 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java @@ -5,6 +5,8 @@ import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.dp.core.lib.constants.DpConstants; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.dp.core.lib.service.ResponseBuilderService; @@ -13,10 +15,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.io.IOException; +import java.util.ArrayList; import java.util.List; @Slf4j @@ -35,11 +39,25 @@ public class Scheduler { @Autowired private MobileResponseBuilderService mobileResponseBuilderService; + @Value("${keycloak.data-consumer.client-id}") + private String dcClientId; + + @Value("${keycloak.data-consumer.client-secret}") + private String dcClientSecret; + + @Value("${keycloak.realm}") + private String keycloakRealm; + + @Value("${keycloak.url}") + private String keycloakURL; + + @Value("${keycloak.data-consumer.url}") + private String keyClockClientTokenUrl; /** * Response scheduler */ - @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. + // @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. public void responseScheduler() throws IOException { try { ObjectMapper objectMapper = new ObjectMapper(); @@ -61,7 +79,8 @@ public void responseScheduler() throws IOException { String responseString = responseBuilderService.buildResponseString(Constants.CACHE_KEY_SEARCH_STRING, dataDTO); log.info("Scheduler responseString : {}", responseString); - responseBuilderService.sendOnSearchResponse(responseString, onSearchURL); + responseBuilderService.sendOnSearchResponse(responseString, onSearchURL,dcClientId,dcClientSecret ,keyClockClientTokenUrl); + responseBuilderService.updateRequestStatus(cacheKey, DpConstants.COMPLETED, cacheDTO); } catch (Exception ex) { diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java index f34485a..69d8aa9 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java @@ -1,9 +1,17 @@ package g2pc.ref.mno.regsvc.service; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import kong.unirest.UnirestException; +import kong.unirest.json.JSONObject; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; /** * The interface Mobile validation service. @@ -17,7 +25,7 @@ public interface MobileValidationService { * @throws G2pcValidationException the validation exception * @throws JsonProcessingException the json processing exception */ - void validateRequestDTO (RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException; + void validateRequestDTO (RequestDTO requestDTO) throws Exception; /** * Validate query dto. @@ -26,6 +34,5 @@ public interface MobileValidationService { * @throws G2pcValidationException the validation exception * @throws JsonProcessingException the json processing exception */ - void validateQueryDto (QueryMobileDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException; - -} + void validateQueryDto (QueryDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException; + } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java index f13b823..8007439 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.config.G2pUnirestHelper; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.MessageDTO; @@ -11,8 +12,8 @@ import g2pc.ref.mno.regsvc.dto.response.RegRecordMobileDTO; import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; import kong.unirest.HttpResponse; -import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -26,6 +27,8 @@ public class MobileResponseBuilderServiceImpl implements MobileResponseBuilderSe @Value("${client.api_urls.mobile_info_api}") String mobileInfoURL; + @Autowired + G2pUnirestHelper g2pUnirestHelper; /** * Get farmer records information string from API * @@ -44,11 +47,9 @@ public String getRegMobileRecords(String messageString) throws JsonProcessingExc String queryParams = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery().getQueryParams()); QueryParamsMobileDTO queryParamsMobileDTO = objectMapper.readValue(queryParams, QueryParamsMobileDTO.class); - List mobileNumbers = queryParamsMobileDTO.getMobileNumber(); - String season = queryParamsMobileDTO.getSeason(); String uri = mobileInfoURL; - HttpResponse response = Unirest.post(uri) + HttpResponse response = g2pUnirestHelper.g2pPost(uri) .body(objectMapper.writeValueAsString(queryParamsMobileDTO)) .asString(); diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java index dd79fab..f53c5e0 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java @@ -7,15 +7,29 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.constants.G2pSecurityConstants; +import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.MessageDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; +import g2pc.ref.mno.regsvc.dto.request.QueryParamsMobileDTO; import g2pc.ref.mno.regsvc.service.MobileValidationService; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; +import kong.unirest.UnirestException; +import kong.unirest.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.io.InputStream; @@ -36,15 +50,43 @@ public class MobileValidationServiceImpl implements MobileValidationService { */ @Autowired RequestHandlerService requestHandlerService; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Value("${crypto.support_encryption}") + private String isEncrypt; + + @Value("${crypto.support_signature}") + private String isSign; + + /** + * Method to validate Request dto + * @param requestDTO the request dto + * @throws Exception + */ @Override - public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException { + public void validateRequestDTO(RequestDTO requestDTO) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); - String queryString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(requestDTO.getMessage().getSearchRequest().getSearchCriteria().getQuery()); - QueryMobileDTO queryMobileDTO = objectMapper.readerFor(QueryMobileDTO.class). + objectMapper.registerSubtypes(QueryDTO.class, + QueryMobileDTO.class, QueryParamsMobileDTO.class); + + MessageDTO messageDTO = null; + if(isEncrypt.equals("true")){ + String messageString = requestDTO.getMessage(); + String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + messageDTO = objectMapper.readerFor(MessageDTO.class). + readValue(deprecatedMessageString); + }else{ + messageDTO = objectMapper.readerFor(MessageDTO.class). + readValue(requestDTO.getMessage()); + } + + String queryString = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery()); + + QueryDTO queryMobileDTO = objectMapper.readerFor(QueryDTO.class). readValue(queryString); - validateQueryDto(queryMobileDTO); + validateQueryDto(queryMobileDTO); String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() @@ -52,11 +94,17 @@ public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationExcep RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). readValue(headerString); requestHandlerService.validateRequestHeader(headerDTO); - requestHandlerService.validateRequestMessage(requestDTO.getMessage()); + requestHandlerService.validateRequestMessage(messageDTO); } + /** + * Method to validate query deto + * @param queryMobileDTO the query mobile dto + * @throws G2pcValidationException + * @throws JsonProcessingException + */ @Override - public void validateQueryDto(QueryMobileDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException { + public void validateQueryDto(QueryDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); log.info("Query object -> "+queryMobileDTO); String queryString = new ObjectMapper() @@ -82,4 +130,6 @@ public void validateQueryDto(QueryMobileDTO queryMobileDTO) throws G2pcValidatio throw new G2pcValidationException(errorcombinedMessage); } } + + } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml index 5b47634..1f1101c 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml @@ -2,6 +2,8 @@ spring: mvc: pathmatch: matching-strategy: ANT_PATH_MATCHER + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration server: port: 9002 @@ -19,3 +21,20 @@ client: client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" +keycloak: + data-consumer: + url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" + client-id: dc-client + client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K + mobile: + admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token + get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-mobile/clients + realm: dp-mobile + url: http://127.0.0.1:8081/auth + admin: + client-id: admin-cli + client-secret: eJ7bErtDvu0D5yXP37zLjAgGC28S1ofT + +crypto: + support_encryption: false + support_signature: false diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json index fc5a883..c8b7b09 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/schema/mobileQuerySchema.json @@ -17,7 +17,7 @@ "$ref": "#/definitions/nonEmptyString" }, "mobile_number": { - "type": "array", + "type": ["string"], "items": { "$ref": "#/definitions/nonEmptyString", "type": "string" From d1c971570d3a44fcbce889f2adebb6f6496f77df Mon Sep 17 00:00:00 2001 From: Abhilash Date: Mon, 11 Dec 2023 23:16:57 +0530 Subject: [PATCH 20/53] g2pc-core-lib changes. --- .../core/lib/constants/CoreConstants.java | 5 + .../core/lib/dto/common/header/MetaDTO.java | 2 +- .../g2pc/core/lib/enums/AlgorithmENUM.java | 12 +- .../g2pc/core/lib/enums/ExceptionsENUM.java | 38 ++++- .../service/AsymmetricSignatureService.java | 22 +++ .../security/service/G2pEncryptDecrypt.java | 5 +- .../lib/security/service/G2pTokenService.java | 7 +- .../AsymmetricSignatureServiceImpl.java | 97 ++++++++++++ .../serviceImpl/G2pEncryptDecryptImpl.java | 33 +++- .../serviceImpl/G2pTokenServiceImpl.java | 19 ++- g2pc-core-lib/src/main/resources/private.p12 | Bin 0 -> 2547 bytes .../main/resources/schema/MessageSchema.json | 144 +++++++++--------- .../schema/ResponseHeaderSchema.json | 10 +- .../schema/ResponseMessageSchema.json | 123 +++++++-------- 14 files changed, 362 insertions(+), 155 deletions(-) create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java create mode 100644 g2pc-core-lib/src/main/resources/private.p12 diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java index b8180b6..3577eca 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java @@ -30,4 +30,9 @@ private CoreConstants() { public static final String CLIENT_SECRET = "client_secret"; public static final String KEYCLOAK_URL = "keycloak_url"; + + public static final String HASHING_ALGORITHM = "hashing_algorithm"; + + public static final String IS_SIGN = "is_sign"; + } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java index ba1446d..54e5029 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/header/MetaDTO.java @@ -7,5 +7,5 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class MetaDTO { - + private Object data; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java index 94f03d8..e3da60b 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AlgorithmENUM.java @@ -4,14 +4,21 @@ import java.io.IOException; + +/** + * This enum class is used to return algorithm strings + */ public enum AlgorithmENUM { - ED25519, RSA; + ED25519, RSA , HMAC , SHA256 , AES; public String toValue() { switch (this) { case ED25519: return "Ed25519"; case RSA: return "rsa"; + case HMAC: return "HmacSHA256"; + case SHA256:return "SHA-256"; + case AES : return "AES"; } return null; } @@ -21,6 +28,9 @@ public static AlgorithmENUM forValue(String value) throws IOException { switch (value.toLowerCase()) { case "ed25519": return ED25519; case "rsa": return RSA; + case "HmacSHA256" : return HMAC; + case "SHA-256" : return SHA256; + case "AES" : return AES; } } throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java index 3e0af08..0ef0f34 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java @@ -1,6 +1,42 @@ package g2pc.core.lib.enums; -public class ExceptionsENUM { +import g2pc.core.lib.constants.CoreConstants; +import java.io.IOException; +public enum ExceptionsENUM { + + ERROR_SIGNATURE_INVALID , + ERROR_VERSION_NOT_VALID , + ERROR_ENCRYPTION_INVALID , + ERROR_USER_UNAUTHORIZED , + ERROR_BAD_REQUEST , + ERROR_SERVICE_UNAVAILABLE; + + public String toValue() { + switch (this) { + case ERROR_SIGNATURE_INVALID: return "err.signature.invalid"; + case ERROR_VERSION_NOT_VALID: return "err.version.not_supported"; + case ERROR_ENCRYPTION_INVALID: return "err.encryption.invalid"; + case ERROR_USER_UNAUTHORIZED: return "err.request.unauthorized"; + case ERROR_BAD_REQUEST: return "err.request.bad"; + case ERROR_SERVICE_UNAVAILABLE: return "err.service.unavailable"; + + } + return null; + } + + public static ExceptionsENUM forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "err.signature.invalid": return ERROR_SIGNATURE_INVALID; + case "err.version.not_supported": return ERROR_VERSION_NOT_VALID; + case "err.encryption.invalid":return ERROR_ENCRYPTION_INVALID; + case "err.request.unauthorized" :return ERROR_USER_UNAUTHORIZED; + case "err.request.bad" :return ERROR_BAD_REQUEST; + case "err.service.unavailable" : return ERROR_SERVICE_UNAVAILABLE; + } + } + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); + } } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java new file mode 100644 index 0000000..e1b19fe --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java @@ -0,0 +1,22 @@ +package g2pc.core.lib.security.service; + +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; + + +@Service +public interface AsymmetricSignatureService { + + public byte[] sign(String data) throws InvalidKeyException, Exception; + + public PrivateKey getPrivate() throws Exception ; + + public PublicKey getPublic() throws Exception ; + + public boolean verifySignature(byte[] data, byte[] signature) throws Exception ; + + public KeyStore.PrivateKeyEntry extractP12Certificate() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java index 5af4a38..cfce5e3 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pEncryptDecrypt.java @@ -1,13 +1,16 @@ package g2pc.core.lib.security.service; +import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public interface G2pEncryptDecrypt { - public String g2pEncrypt(String data, String key) throws Exception; public String g2pDecrypt(String encryptedData, String key) throws Exception; public String sha256Hashing(String data) throws NoSuchAlgorithmException; + + public String hmacHashing(String data , String secret) throws NoSuchAlgorithmException, InvalidKeyException; + } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java index 66d18df..7a32b83 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pTokenService.java @@ -22,9 +22,12 @@ public interface G2pTokenService { public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseException; - public ArrayList> getClientByRealm(String masterAdminUrl , String getClientUrl) throws JsonProcessingException; + public ArrayList> getClientByRealm(String masterAdminUrl, String getClientUrl , String clientId , String clientSecret + , String username , String password) throws JsonProcessingException; - public boolean validateToken(String masterAdminUrl , String getClientUrl , String clientId) throws JsonProcessingException; + public boolean validateToken(String masterAdminUrl, String getClientUrl , String clientId , + String adminClientId , String adminClientSecret + , String username , String password) throws JsonProcessingException; public String decodeToken(String token) throws JsonProcessingException; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java new file mode 100644 index 0000000..4340c20 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java @@ -0,0 +1,97 @@ +package g2pc.core.lib.security.serviceImpl; + +import g2pc.core.lib.security.service.AsymmetricSignatureService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Service; +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; + + +@Service +public class AsymmetricSignatureServiceImpl implements AsymmetricSignatureService { + + @Autowired + private ResourceLoader resourceLoader; + + /** + * + * @param data + * @return + * @throws InvalidKeyException + * @throws Exception + */ + @Override + public byte[] sign(String data ) throws InvalidKeyException, Exception { + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(getPrivate()); + signature.update(data.getBytes()); + return signature.sign(); + } + + /** + * + * @return + * @throws Exception + */ + @Override + public PrivateKey getPrivate() throws Exception { + return extractP12Certificate().getPrivateKey(); + } + + /** + * + * @return + * @throws Exception + */ + @Override + public PublicKey getPublic() throws Exception { + Certificate certificate = extractP12Certificate().getCertificate(); + return certificate.getPublicKey(); + } + + /** + * + * @param data + * @param signature + * @return + * @throws Exception + */ + @Override + public boolean verifySignature(byte[] data, byte[] signature) throws Exception { + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(getPublic()); + sig.update(data); + + return sig.verify(signature); + } + + /** + * + * @return + * @throws IOException + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws UnrecoverableEntryException + */ + @Override + public KeyStore.PrivateKeyEntry extractP12Certificate() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException { + Resource resource = resourceLoader.getResource("classpath:private.p12"); + InputStream fis = resource.getInputStream(); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + + char[] password = "tekdi".toCharArray(); + ks.load(fis, password); + + KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(password); + KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("1", protectionParameter); + + return pkEntry; + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java index f9ad94f..2d08ad2 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java @@ -1,19 +1,25 @@ package g2pc.core.lib.security.serviceImpl; +import g2pc.core.lib.enums.AlgorithmENUM; import g2pc.core.lib.security.RandomIVGenerator; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import org.springframework.stereotype.Service; import javax.crypto.Cipher; +import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; +/** + * This class is used to return method related to digital signature and encryption + */ @Service public class G2pEncryptDecryptImpl implements G2pEncryptDecrypt { @@ -29,7 +35,7 @@ public String g2pEncrypt(String data, String key) throws Exception { IvParameterSpec ivParameterSpec = RandomIVGenerator.generateIv(); byte[] keyBytes = key.getBytes("UTF-8"); - SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); + SecretKey secretKey = new SecretKeySpec(keyBytes, AlgorithmENUM.AES.toValue()); Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec); @@ -62,7 +68,7 @@ public String g2pDecrypt(String encryptedData, String key) throws Exception { byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedDataStr); byte[] keyBytes = key.getBytes("UTF-8"); - SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); + SecretKey secretKey = new SecretKeySpec(keyBytes, AlgorithmENUM.AES.toValue()); Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ivBytes)); @@ -79,7 +85,7 @@ public String g2pDecrypt(String encryptedData, String key) throws Exception { */ @Override public String sha256Hashing(String data) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance( "SHA-256" ) ; + MessageDigest md = MessageDigest.getInstance( AlgorithmENUM.SHA256.toValue() ) ; byte[ ] hash = md.digest( data.getBytes( StandardCharsets.UTF_8 ) ) ; BigInteger number = new BigInteger( 1, hash ) ; StringBuilder hexString = new StringBuilder( number.toString( 16 ) ) ; @@ -89,4 +95,25 @@ public String sha256Hashing(String data) throws NoSuchAlgorithmException { } return hexString.toString( ) ; } + + /** + * Used to apply Hmac hashing algorithm to signature + * @param data data to get hashed + * @param secret secret key + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + @Override + public String hmacHashing(String data , String secret) throws NoSuchAlgorithmException, InvalidKeyException { + Mac sha256HMAC = Mac.getInstance(AlgorithmENUM.HMAC.toValue()); + SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + sha256HMAC.init(secretKey); + + byte[] hashByte = sha256HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8)); + String hash = Base64.getEncoder().encodeToString(hashByte); + + return hash; + } + } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java index 67104d3..822ef41 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java @@ -26,7 +26,7 @@ /** - * Service contains methods related to token + * Service contains methods related to security using token */ @Service @Slf4j @@ -117,14 +117,17 @@ public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseExcepti * @throws JsonProcessingException */ @Override - public ArrayList> getClientByRealm(String masterAdminUrl, String getClientUrl) throws JsonProcessingException { - String grantType = "client_credentials"; + public ArrayList> getClientByRealm(String masterAdminUrl, String getClientUrl , String adminClientId , String adminClientSecret + , String username , String password) throws JsonProcessingException { + String grantType = "password"; ObjectMapper objectMapper = new ObjectMapper(); HttpResponse response = Unirest.post(masterAdminUrl) .header("Content-Type", "application/x-www-form-urlencoded") .field("grant_type", grantType) - .field("client_id", "admin-cli") - .field("client_secret", "PKCxos6O3Sg7odXuGVeguP946EEUozY0") + .field("client_id", adminClientId) + .field("client_secret", adminClientSecret) + .field("username",username) + .field("password",password) .asJson(); Map responseBody = objectMapper.readValue(response.getBody().toString(), new TypeReference>() {}); String token = responseBody.get("access_token").toString(); @@ -146,8 +149,10 @@ public ArrayList> getClientByRealm(String masterAdminUrl, St * @throws JsonProcessingException */ @Override - public boolean validateToken(String masterAdminUrl, String getClientUrl , String clientId) throws JsonProcessingException { - ArrayList> responseMap = getClientByRealm(masterAdminUrl, getClientUrl); + public boolean validateToken(String masterAdminUrl, String getClientUrl , String clientId , + String adminClientId , String adminClientSecret + , String username , String password) throws JsonProcessingException { + ArrayList> responseMap = getClientByRealm(masterAdminUrl, getClientUrl , adminClientId , adminClientSecret , username , password); boolean isValid = false; for (int i = 0; i < responseMap.size(); i++) { String responseClientId = responseMap.get(i).get("clientId"); diff --git a/g2pc-core-lib/src/main/resources/private.p12 b/g2pc-core-lib/src/main/resources/private.p12 new file mode 100644 index 0000000000000000000000000000000000000000..c067bffa1a9b5b24e01686440e6eb38f44699e81 GIT binary patch literal 2547 zcmai$X*3iH8^_Itk&!j~*w+k08pe{C5=o&UvNUQYX+{jP(-@&7g+Z?+WeE{Ymc|ld z=$1XwbupRj>x|UR?sd=mp89w{Jm)$8|NlJa`SkqGgQP+^KmZ6yg%8J zfG1RFCYTCM+qYAYR50*&#QTH_T0>Gn`$IhYZSHphKwkJIATZ=xrr6e@^P zH%r^wIxQP#`i-&aS=W$nm(?I-l_$@zy4Pph>+}fIme})T=q34{XhRd91uY_+8JSqq zyo;$VY9xJ5?9km93FAYBgi7kE(UDwjoPULfF^9c#=`DyLS0sxXd8#lF*+35XaW3Ny zH>myR? zype(#xCOd}EAJ{OyEk{*@z&VlO6M`d>mx(7q>Z7-yk%XI>OkjE$+x2^S6rO{I)_zt z4J&8`jq{K%$JpM{1xv4^%d+lG!yheRCj=$9=9-#oDQ5x^{?%PWYb>Dpv7w>8H|Cjm zpA9Ma`NVY*LbsH<3R!s6ncY$tC?=h!+N^MC(a-pe){4sBg}0*9{fo7`f_Qi$9EZ+Y(D+Cg8kLcf!{He0E?j0{Nks#6GO}R3rWrFMK6b zmw47o%DweS7Y+)1V*Z**lj;CQq2)@(dwYQ;$)f$Sld3>U%I$z}8N^G?#BroB)RNH? z+rt{gOJjQAA;RMbNpit_@}7DyyHzT$d~R;;v=n6n{MM#ls|uQEG1E6@@zn}GimHs7 z6Cvg+%Bd=1$R#0TKz#|hPI$1UF4BSoECU+qFL)R2cVALpaEz}{?Q zuj4*J^hZZB23j#a>KBY-;Ale$-%`RuS&*;bg56Y+0(81G3foVn5(@W=qfR$)&$~1vO%T3h{&^ z%L_@F#61X-3c3C3p8o*^ z|M--`RzdC~9z%Uo+x033ctNqhln;xcojU>sK1>({mH4>3FEOioL*&Oj<}|Ll2$Xph z*2~KZjgu$iI6S2&tpk5?S6J>1=l^mMx95tv#b_TMv^0*(df>Br^>c)oU4Qkgg7kc;d2*5{IgM*Z@HCSx{Bp*?cEmBQC&H**fHLu zHkn|9GG#>345`sY)-o|yUfYw`xyI=?tJMR-KZhxVP1q_=U<+6Ui{%{>|7kYIEmv5 z#;j%;z)du#nZD?QdqpH)bHp&g9_^8m*;r5ZUCU#(HJ+uu$pJTb5v|cE$+)x~O(f{4 z{i&jpM2wKv^i;W0qX?m>eHaCv{UYi@Qr_q%gA~L&s#zOP~9N zRgrphrmgK{^Yvy9O8jh$>rkr)W$Z*d9~nqwDoK9v3e(o)cb>2VG1T1(hxdU%%WhjqjGw>F#X_?&qiq~6tU zMWmmkL|yk&X)YA_Z20->^>y2*#}RA<$1z$VmZjbKymS6PGdF9GMohAw9oHA|9tv#(i6TkD?_$oo#P?YE5J4S*_6hF6eYl)t>gr0HrD z((-Bo+RWdP1OQb#$_Ul~&0w6qK#SDk3 uNiVYG!VsooVaCX^wiOm_+jQ(iBL@0C!2EXfk5QM*3KaiA?SBC%Gnn`Q literal 0 HcmV?d00001 diff --git a/g2pc-core-lib/src/main/resources/schema/MessageSchema.json b/g2pc-core-lib/src/main/resources/schema/MessageSchema.json index 26e8971..378085b 100644 --- a/g2pc-core-lib/src/main/resources/schema/MessageSchema.json +++ b/g2pc-core-lib/src/main/resources/schema/MessageSchema.json @@ -15,86 +15,90 @@ "maxLength": 99, "$ref": "#/definitions/nonEmptyString" }, - "search_request" : { - "type" : "object", - "properties" : { - "reference_id": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "timestamp": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "search_criteria" : { - "type": "object" , - "properties": { - "version": { - "type": "string" - }, - "reg_type": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "reg_sub_type" : { - "type": "string" - }, - "query_type": { - "type": "string", - "$ref": "#/definitions/nonEmptyString", - "enum": ["namedQuery","idtype","predicate"] - }, + "search_request" :{ + "type": "array", + "items" : [{ + + "type" : "object", + "properties" : { + "reference_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "timestamp": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "search_criteria" : { + "type": "object" , + "properties": { + "version": { + "type": "string" + }, + "reg_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "reg_sub_type" : { + "type": "string" + }, + "query_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString", + "enum": ["namedQuery","idtype","predicate"] + }, - "sort" : { - "type": "array", - "items": [ - { - "type": "object" , - "properties": { - "attribute_name" : { - "type": "string" - }, - "sort_order" : { - "type": "string", - "enum": ["asc" , "desc"] - } + "sort" : { + "type": "array", + "items": [ + { + "type": "object" , + "properties": { + "attribute_name" : { + "type": "string" + }, + "sort_order" : { + "type": "string", + "enum": ["asc" , "desc"] } } - ] - }, - "pagination" : { - "type": "object", - "properties": { - "page_size": { - "type": "number" - }, - "page_number": { - "type": "number" - } + } + ] + }, + "pagination" : { + "type": "object", + "properties": { + "page_size": { + "type": "number" }, - "required": ["page_size"] - }, - "consent": { - "type": "object" + "page_number": { + "type": "number" + } }, - "authorize": { - "type": "object" - } + "required": ["page_size"] + }, + "consent": { + "type": "object" }, - "required": [ - "reg_type" ,"query_type" - ] + "authorize": { + "type": "object" + } }, - "locale" : { - "type": "string" - - } + "required": [ + "reg_type" ,"query_type" + ] }, - "required": [ - "reference_id" ,"timestamp" , "search_criteria" - ] + "locale" : { + "type": "string" + + } + }, + "required": [ + "reference_id" ,"timestamp" , "search_criteria" + ] + }] } } , "required": [ diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseHeaderSchema.json b/g2pc-core-lib/src/main/resources/schema/ResponseHeaderSchema.json index 7e4ad75..2582f72 100644 --- a/g2pc-core-lib/src/main/resources/schema/ResponseHeaderSchema.json +++ b/g2pc-core-lib/src/main/resources/schema/ResponseHeaderSchema.json @@ -33,7 +33,7 @@ "enum": ["rjct.reference_id.invalid", "rjct.reference_id.duplicate", "rjct.timestamp.invalid" ,"rjct.search_criteria.invalid" , "rjct.filter.invalid" ,"rjct.sort.invalid" , - "rjct.pagination.invalid" ,"rjct.search.too_many_records_found"] + "rjct.pagination.invalid" ,"rjct.search.too_many_records_found","succ"] }, "status_reason_message": { "anyOf": [ @@ -59,13 +59,7 @@ "type": "boolean" }, "meta": { - "anyOf": [ - { "type": "string" }, - { "type": "null" } - ], - "properties": { - - } + "type": [ "object", "null" ] } }, "required": ["message_id","message_ts","action","total_count"], diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json b/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json index c27cbb8..b0d44b4 100644 --- a/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json +++ b/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json @@ -22,72 +22,73 @@ "$ref": "#/definitions/nonEmptyString" }, "search_response" : { - "type" : "object", - "properties" : { - "reference_id": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "timestamp": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "status": { - "type": "string", - "enum": ["rcvd","pdng","succ","rjct"], - "$ref": "#/definitions/nonEmptyString" - }, - "status_reason_code" : { - "type": "string", - "enum": ["rjct.reference_id.invalid", "rjct.reference_id.duplicate", - "rjct.timestamp.invalid" ,"rjct.search_criteria.invalid" , - "rjct.filter.invalid" ,"rjct.sort.invalid" , - "rjct.pagination.invalid" ,"rjct.search.too_many_records_found"] - }, - "status_reason_message": { - "type": "string", - "minLength": 0, - "maxLength": 99 - }, - "data" : { - "type": "object", - "properties": { - "version" : { - "type": "string" - }, - "reg_type": { - "type": "string" - }, - "reg_sub_type": { - "type": "string" + "type": "array", + "items": [{ + "type" : "object", + "properties" : { + "reference_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "timestamp": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "status": { + "type": "string", + "enum": ["rcvd","pdng","succ","rjct"], + "$ref": "#/definitions/nonEmptyString" + }, + "status_reason_code" : { + "type": "string", + "enum": ["rjct.reference_id.invalid", "rjct.reference_id.duplicate", + "rjct.timestamp.invalid" ,"rjct.search_criteria.invalid" , + "rjct.filter.invalid" ,"rjct.sort.invalid" , + "rjct.pagination.invalid" ,"rjct.search.too_many_records_found" ,"succ"] + }, + "status_reason_message": { + "type": "string", + "minLength": 0, + "maxLength": 99 + }, + "data" : { + "type": "object", + "properties": { + "version" : { + "type": "string" + }, + "reg_type": { + "type": "string" + }, + "reg_sub_type": { + "type": "string" + }, + "reg_record_type": { + "type": "string" + } }, - "reg_record_type": { - "type": "string" - } + "required": ["reg_type","reg_record_type"] }, - "required": ["reg_type","reg_record_type"] - }, - "pagination" : { - "type": "object", - "properties": { - "page_size": { - "type": "number" + "pagination" : { + "type": "object", + "properties": { + "page_size": { + "type": "number" + }, + "page_number": { + "type": "number" + } }, - "page_number": { - "type": "number" - } + "required": ["page_size"] }, - "required": ["page_size"] + "locale" : { + "type": "string" + } }, - "locale" : { - "type": "string" - } - }, - "required": [ - "reference_id" ,"timestamp" , "status" - ] - - + "required": [ + "reference_id" ,"timestamp" , "status" + ] + }] } } , "required": [ From 79ed5f4b9943d0c4610474506594888a9e014ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Mon, 11 Dec 2023 23:29:18 +0530 Subject: [PATCH 21/53] g2pc-dc-core-lib changes. --- .../core/lib/entity/ResponseDataEntity.java | 62 ++++++++ .../lib/entity/ResponseTrackerEntity.java | 79 ++++++++++ .../repository/ResponseDataRepository.java | 11 ++ .../repository/ResponseTrackerRepository.java | 11 ++ .../lib/service/RequestBuilderService.java | 4 +- .../core/lib/service/TxnTrackerService.java | 15 ++ .../RequestBuilderServiceImpl.java | 138 +++++++++++------- .../serviceimpl/TxnTrackerServiceImpl.java | 107 ++++++++++++++ .../src/main/resources/application.yml | 6 +- 9 files changed, 377 insertions(+), 56 deletions(-) create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java new file mode 100644 index 0000000..1f5c3e5 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java @@ -0,0 +1,62 @@ +package g2pc.dc.core.lib.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.sql.Timestamp; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "response_data", schema = "g2pc") +public class ResponseDataEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "reference_id") + private String referenceId; + + @Column(name = "timestamp") + private String timestamp; + + @Column(name = "status") + private String status; + + @Column(name = "status_reason_code") + private String statusReasonCode; + + @Column(name = "status_reason_message") + private String statusReasonMessage; + + @Column(name = "version") + private String version; + + @Column(name = "reg_type") + private String regType; + + @Column(name = "reg_sub_type") + private String regSubType; + + @Column(name = "reg_record_type") + private String regRecordType; + + @Column(name = "reg_records") + private String regRecords; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; + + @ManyToOne(targetEntity = ResponseTrackerEntity.class, cascade = CascadeType.ALL) + @JoinColumn(name = "registry_transactions_id", referencedColumnName = "id") + private ResponseTrackerEntity responseTrackerEntity; +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java new file mode 100644 index 0000000..b18899c --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java @@ -0,0 +1,79 @@ +package g2pc.dc.core.lib.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "response_tracker", schema = "g2pc") +public class ResponseTrackerEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "version") + private String version; + + @Column(name = "message_id") + private String messageId; + + @Column(name = "message_ts") + private String messageTs; + + @Column(name = "action") + private String action; + + @Column(name = "status") + private String status; + + @Column(name = "status_reason_code") + private String statusReasonCode; + + @Column(name = "status_reason_message") + private String statusReasonMessage; + + @Column(name = "total_count") + private Integer totalCount; + + @Column(name = "completed_count") + private Integer completedCount; + + @Column(name = "sender_id") + private String senderId; + + @Column(name = "receiver_id") + private String receiverId; + + @Column(name = "is_msg_encrypted") + private Boolean isMsgEncrypted; + + @Column(name = "meta") + private String meta; + + @Column(name = "transaction_id") + private String transactionId; + + @Column(name = "correlation_id") + private String correlationId; + + @Column(name = "registry_type") + private String registryType; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; + + @ToString.Exclude + @OneToMany(mappedBy = "responseTrackerEntity", cascade = CascadeType.ALL) + private List responseDataEntityList = new ArrayList<>(); +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java new file mode 100644 index 0000000..93e2740 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java @@ -0,0 +1,11 @@ +package g2pc.dc.core.lib.repository; + +import g2pc.dc.core.lib.entity.ResponseDataEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ResponseDataRepository extends JpaRepository { + + Optional findByReferenceId(String referenceId); +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java new file mode 100644 index 0000000..0bfe043 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java @@ -0,0 +1,11 @@ +package g2pc.dc.core.lib.repository; + +import g2pc.dc.core.lib.entity.ResponseTrackerEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ResponseTrackerRepository extends JpaRepository { + + Optional findByTransactionId(String transactionId); +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java index 21e2fa2..c7c63d4 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java @@ -22,11 +22,11 @@ public interface RequestBuilderService { RequestMessageDTO buildMessage(List searchCriteriaDTOList); - HeaderDTO buildHeader(); + HeaderDTO buildHeader() throws JsonProcessingException; String buildRequest(List searchCriteriaDTOList, String transactionId) throws JsonProcessingException; - Integer sendRequest(String requestString, String uri, String clientId, String clientSecret , String keyClockClientTokenUrl) throws Exception; + Integer sendRequest(String requestString, String uri, String clientId, String clientSecret , String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign) throws Exception; CacheDTO createCache(String data, String status); diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java index 02310e0..e2d3d37 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java @@ -1,10 +1,21 @@ package g2pc.dc.core.lib.service; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.dc.core.lib.entity.ResponseTrackerEntity; +import g2pc.dc.core.lib.repository.ResponseTrackerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import java.sql.Timestamp; import java.util.List; import java.util.Map; +import java.util.Optional; public interface TxnTrackerService { @@ -15,4 +26,8 @@ public interface TxnTrackerService { CacheDTO createCache(String data, String status); void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; + + void saveRequestInDB(String requestString, String regType) throws JsonProcessingException; + + void updateTransactionDbAndCache(ResponseDTO responseDTO) throws JsonProcessingException; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java index 1d27271..def1b0c 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java @@ -3,19 +3,23 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.MetaDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.*; import g2pc.core.lib.dto.common.security.G2pTokenResponse; import g2pc.core.lib.dto.common.security.TokenExpiryDto; import g2pc.core.lib.enums.ActionsENUM; +import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.enums.LocalesENUM; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; import g2pc.core.lib.utils.CommonUtils; @@ -29,7 +33,6 @@ import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; - import java.io.IOException; import java.util.*; import java.text.ParseException; @@ -50,12 +53,9 @@ public class RequestBuilderServiceImpl implements RequestBuilderService { @Autowired G2pTokenService g2pTokenService; + @Autowired + AsymmetricSignatureService asymmetricSignatureService; - @Value("${crypto.support_encryption}") - private String isEncrypt; - - @Value("${crypto.support_signature}") - private String isSign; /** * Create a query from payload @@ -163,7 +163,10 @@ public HeaderDTO buildHeader() { requestHeaderDTO.setReceiverId("pymts.example.org"); requestHeaderDTO.setTotalCount(21800); requestHeaderDTO.setIsMsgEncrypted(false); - requestHeaderDTO.setMeta(null); + Map metaMap = new HashMap<>(); + MetaDTO metaDTO = new MetaDTO(); + metaDTO.setData(metaMap); + requestHeaderDTO.setMeta(metaDTO); requestHeaderDTO.setSenderUri("https://spp.example.org/{namespace}/callback/on-search"); return requestHeaderDTO; } @@ -193,72 +196,43 @@ public String buildRequest(List searchCriteriaDTOList, String } @Override - public Integer sendRequest(String requestString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception { + public Integer sendRequest(String requestString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign) throws Exception { Boolean isStatusOk = true; - log.info("Save requests to DPs"); + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); ObjectMapper objectMapper = new ObjectMapper(); - requestString = createSignature(isSign, isEncrypt, requestString); + requestString = createSignature(isEncrypt ,isSign,requestString); String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); - + log.info("Updated Request -> "+requestString); HttpResponse response = g2pUnirestHelper.g2pPost(uri) .header("Content-Type", "application/json") .header("Authorization", jwtToken) .body(requestString) .asString(); if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { - G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); + G2pcError g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), response.getBody()); + log.info("Exception is thrown by search endpoint", response.getStatus()); throw new G2pHttpException(g2pcError); } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). readValue(response.getBody()); + log.info("Exception is thrown by search endpoint", response.getStatus()); throw new G2pHttpException(errorResponse.getG2PcError()); } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { - G2pcError g2pcError = new G2pcError("err.request.bad", response.getBody()); + G2pcError g2pcError = new G2pcError(ExceptionsENUM.ERROR_BAD_REQUEST.toValue(), response.getBody()); + log.info("Exception is thrown by search endpoint", response.getStatus()); throw new G2pHttpException(g2pcError); } else if (response.getStatus() != HttpStatus.OK.value()) { - G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); - throw new G2pHttpException(g2pcError); + ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). + readValue(response.getBody()); + log.info("Exception is thrown by search endpoint", response.getStatus()); + throw new G2pHttpException(errorResponse.getG2PcError()); } log.info("request send response status = {}", response.getStatus()); return response.getStatus(); } - private String createSignature(String isSign, String isEncrypt, String requestString) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). - readValue(requestString); - - String messageString = requestDTO.getMessage().toString(); - RequestHeaderDTO requestHeaderDTO = (RequestHeaderDTO) requestDTO.getHeader(); - String requestHeaderString = objectMapper.writeValueAsString(requestHeaderDTO); - String signature = null; - - if (isSign.equals("false") && isEncrypt.equals("false")) { - requestDTO.getHeader().setIsMsgEncrypted(false); - } else if (isSign.equals("false") && isEncrypt.equals("true")) { - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); - requestDTO.setMessage(encryptedMessageString);// how to store encrypted string message dto - requestDTO.getHeader().setIsMsgEncrypted(true); - - } else if (isSign.equals("true") && isEncrypt.equals("false")) { - requestDTO.getHeader().setIsMsgEncrypted(false); - signature = encryptDecrypt.sha256Hashing(messageString + requestHeaderString); - - } else { - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); - requestDTO.setMessage(encryptedMessageString); - requestDTO.getHeader().setIsMsgEncrypted(true); - signature = encryptDecrypt.sha256Hashing(encryptedMessageString + requestHeaderString); - } - requestDTO.setSignature(signature); - requestString = objectMapper.writeValueAsString(requestDTO); - return requestString; - } - - @Override public CacheDTO createCache(String data, String status) { log.info("Save requests in cache with status pending"); @@ -337,4 +311,68 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie return jwtToken; } + /** + * This method is used to create signature and add appropriate message in request body according to configurations. + * @param isEncrypt flag of encryption + * @param isSign flag of signature + * @param requestString created request in string format + * @return + * @throws Exception exception is thrown general because there more than 6 exception being thrown by this method. + */ + private String createSignature( boolean isEncrypt , boolean isSign, String requestString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + RequestHeaderDTO requestHeaderDTO = (RequestHeaderDTO) requestDTO.getHeader(); + String requestHeaderString = objectMapper.writeValueAsString(requestHeaderDTO); + String messageString = objectMapper.writeValueAsString(messageDTO); + String signature = null; + + if(isSign){ + if(isEncrypt){ + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); + requestDTO.setMessage(encryptedMessageString); + requestDTO.getHeader().setIsMsgEncrypted(true); + Map meta= (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,true); + requestDTO.getHeader().getMeta().setData(meta); + requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + byte[] asymmetricSignature = asymmetricSignatureService.sign( "salt"+requestHeaderString+encryptedMessageString); + signature = Base64.getEncoder().encodeToString(asymmetricSignature); + log.info("Encrypted message ->"+encryptedMessageString); + log.info("Hashed Signature ->"+signature); + }else{ + requestDTO.getHeader().setIsMsgEncrypted(false); + Map meta= (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,true); + requestDTO.getHeader().getMeta().setData(meta); + requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + byte[] asymmetricSignature = asymmetricSignatureService.sign( requestHeaderString+messageString); + signature = Base64.getEncoder().encodeToString(asymmetricSignature); + log.info("Hashed Signature ->"+signature); + } + } else { + if(isEncrypt){ + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); + requestDTO.setMessage(encryptedMessageString); + requestDTO.getHeader().setIsMsgEncrypted(true); + Map meta= (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,false); + requestDTO.getHeader().getMeta().setData(meta); + log.info("Encrypted message ->"+encryptedMessageString); + }else{ + requestDTO.getHeader().setIsMsgEncrypted(false); + Map meta= (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,false); + requestDTO.getHeader().getMeta().setData(meta); + } + } + requestDTO.setSignature(signature); + requestString = objectMapper.writeValueAsString(requestDTO); + return requestString; + } } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java index 0bfaf5b..0853fa4 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java @@ -3,8 +3,21 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.entity.ResponseDataEntity; +import g2pc.dc.core.lib.entity.ResponseTrackerEntity; +import g2pc.dc.core.lib.repository.ResponseDataRepository; +import g2pc.dc.core.lib.repository.ResponseTrackerRepository; import g2pc.dc.core.lib.service.TxnTrackerService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -12,8 +25,10 @@ import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; +import java.sql.Timestamp; import java.util.List; import java.util.Map; +import java.util.Optional; @Service @Slf4j @@ -22,6 +37,15 @@ public class TxnTrackerServiceImpl implements TxnTrackerService { @Autowired private RedisTemplate redisTemplate; + @Autowired + ResponseTrackerRepository responseTrackerRepository; + + @Autowired + private ResponseDataRepository responseDataRepository; + + @Autowired + private ResponseHandlerServiceImpl responseHandlerService; + /** * Save initial payload to Redis and create a new transaction in DB table * @@ -81,4 +105,87 @@ public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingE ValueOperations val = redisTemplate.opsForValue(); val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); } + + @Override + public void saveRequestInDB(String requestString, String regType) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readValue(requestString, RequestDTO.class); + HeaderDTO headerDTO = requestDTO.getHeader(); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionId); + if (responseTrackerEntityOptional.isEmpty()) { + ResponseTrackerEntity responseTrackerEntity = new ResponseTrackerEntity(); + responseTrackerEntity.setVersion(headerDTO.getVersion()); + responseTrackerEntity.setMessageId(headerDTO.getMessageId()); + responseTrackerEntity.setMessageTs(headerDTO.getMessageTs()); + responseTrackerEntity.setAction(headerDTO.getAction()); + responseTrackerEntity.setSenderId(headerDTO.getSenderId()); + responseTrackerEntity.setReceiverId(headerDTO.getReceiverId()); + responseTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + responseTrackerEntity.setTransactionId(transactionId); + responseTrackerEntity.setRegistryType(regType); + + List searchRequestDTOList = messageDTO.getSearchRequest(); + for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { + ResponseDataEntity responseDataEntity = new ResponseDataEntity(); + responseDataEntity.setReferenceId(searchRequestDTO.getReferenceId()); + responseDataEntity.setTimestamp(searchRequestDTO.getTimestamp()); + responseDataEntity.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); + responseDataEntity.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); + responseDataEntity.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); + responseDataEntity.setStatus(HeaderStatusENUM.PDNG.toValue()); + responseDataEntity.setResponseTrackerEntity(responseTrackerEntity); + responseTrackerEntity.getResponseDataEntityList().add(responseDataEntity); + } + responseTrackerRepository.save(responseTrackerEntity); + } + } + + @Override + public void updateTransactionDbAndCache(ResponseDTO responseDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(ResponseHeaderDTO.class, HeaderDTO.class); + + ResponseHeaderDTO headerDTO = objectMapper.convertValue(responseDTO.getHeader(), ResponseHeaderDTO.class); + ResponseMessageDTO messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + List searchResponseDTOList = messageDTO.getSearchResponse(); + + Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionId); + if (responseTrackerEntityOptional.isPresent()) { + ResponseTrackerEntity responseTrackerEntity = responseTrackerEntityOptional.get(); + String cacheKey = responseTrackerEntity.getRegistryType() + "-" + transactionId; + + for (SearchResponseDTO searchResponseDTO : searchResponseDTOList) { + Optional entityOptional = responseDataRepository.findByReferenceId(searchResponseDTO.getReferenceId()); + if (entityOptional.isPresent()) { + ResponseDataEntity responseDataEntity = entityOptional.get(); + responseDataEntity.setStatus(searchResponseDTO.getStatus()); + responseDataEntity.setStatusReasonCode(searchResponseDTO.getStatusReasonCode()); + responseDataEntity.setStatusReasonMessage(searchResponseDTO.getStatusReasonMessage()); + responseDataEntity.setRegSubType(searchResponseDTO.getData().getRegSubType()); + responseDataEntity.setRegRecordType(searchResponseDTO.getData().getRegRecordType()); + //TODO: check for object conversion method + responseDataEntity.setRegRecords(objectMapper.writeValueAsString(searchResponseDTO.getData().getRegRecords())); + responseDataEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + responseDataRepository.save(responseDataEntity); + } + } + responseTrackerEntity.setStatus(headerDTO.getStatus()); + responseTrackerEntity.setStatusReasonCode(headerDTO.getStatusReasonCode()); + responseTrackerEntity.setStatusReasonMessage(headerDTO.getStatusReasonMessage()); + responseTrackerEntity.setTotalCount(headerDTO.getTotalCount()); + responseTrackerEntity.setCompletedCount(headerDTO.getCompletedCount()); + responseTrackerEntity.setCorrelationId(messageDTO.getCorrelationId()); + responseTrackerEntity.setMeta(objectMapper.writeValueAsString(headerDTO.getMeta())); + responseTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + responseTrackerRepository.save(responseTrackerEntity); + + responseHandlerService.updateCache(cacheKey); + } + } } diff --git a/g2pc-dc-core-lib/src/main/resources/application.yml b/g2pc-dc-core-lib/src/main/resources/application.yml index f6479aa..459d3fb 100644 --- a/g2pc-dc-core-lib/src/main/resources/application.yml +++ b/g2pc-dc-core-lib/src/main/resources/application.yml @@ -38,10 +38,8 @@ spring.data.redis: repositories.enabled: false host: localhost password: 123456789 - port: 6379 + port: 6376 + -client: - api_urls: - mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" From 3b3309f6627ba06dc765fb41512b7338d13a4be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Mon, 11 Dec 2023 23:30:38 +0530 Subject: [PATCH 22/53] g2pc-dp-core-lib changes. --- .../lib/G2pcDpCoreLibraryApplication.java | 3 +- .../lib/service/ResponseBuilderService.java | 6 +- .../ResponseBuilderServiceImpl.java | 103 ++++++++++++------ .../src/main/resources/application.yml | 13 +-- 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java index 4163301..21fedb7 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java @@ -5,7 +5,8 @@ import org.springframework.context.annotation.ComponentScan; @SpringBootApplication -@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib"}) +@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib","g2pc.dp.core.lib.serviceimpl", + "g2pc.dp.core.lib.repository"}) public class G2pcDpCoreLibraryApplication { public static void main(String[] args) { diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java index 05a7305..b02dca2 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java @@ -1,13 +1,11 @@ package g2pc.dp.core.lib.service; import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.dp.core.lib.entity.MsgTrackerEntity; import g2pc.dp.core.lib.entity.TxnTrackerEntity; import kong.unirest.UnirestException; @@ -24,7 +22,7 @@ public interface ResponseBuilderService { String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException; - Integer sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception; + G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception; public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java index 96308d0..8e4fd49 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java @@ -3,16 +3,20 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.MetaDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.dto.common.message.response.*; import g2pc.core.lib.dto.common.security.G2pTokenResponse; import g2pc.core.lib.dto.common.security.TokenExpiryDto; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; import g2pc.core.lib.utils.CommonUtils; @@ -53,14 +57,14 @@ public class ResponseBuilderServiceImpl implements ResponseBuilderService { @Autowired G2pTokenService g2pTokenService; - @Value("${crypto.support_encryption}") - private String isEncrypt; + @Value("${crypto.consumer.support_encryption}") + private boolean isEncrypt; - @Value("${crypto.support_signature}") - private String isSign; + @Value("${crypto.consumer.support_signature}") + private boolean isSign; @Autowired - private MsgTrackerRepository msgTrackerRepository; + AsymmetricSignatureService asymmetricSignatureService; /** * Get response header @@ -83,6 +87,10 @@ public ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity) headerDTO.setStatusReasonMessage(msgTrackerEntity.getStatusReasonMessage()); headerDTO.setTotalCount(msgTrackerEntity.getTotalCount()); headerDTO.setCompletedCount(msgTrackerEntity.getCompletedCount()); + Map metaMap = new HashMap<>(); + MetaDTO metaDTO = new MetaDTO(); + metaDTO.setData(metaMap); + headerDTO.setMeta(metaDTO); return headerDTO; } @@ -122,10 +130,12 @@ public String buildResponseString(String signatureString, ResponseHeaderDTO resp } @Override - public Integer sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception { + public G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception { log.info("Send on-search response"); ObjectMapper objectMapper = new ObjectMapper(); - responseString = createSignature(isSign, isEncrypt, responseString); + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); + responseString = createSignature( isEncrypt, isSign , responseString); String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); HttpResponse response = g2pUnirestHelper.g2pPost(uri) .header("Content-Type", "application/json") @@ -134,20 +144,20 @@ public Integer sendOnSearchResponse(String responseString, String uri, String cl .asString(); log.info("on-search response status = {}", response.getStatus()); if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { - G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); - throw new G2pHttpException(g2pcError); + G2pcError g2pcError = new G2pcError(HttpStatus.INTERNAL_SERVER_ERROR.toString(), response.getBody()); + return g2pcError; } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { - ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). - readValue(response.getBody()); - throw new G2pHttpException(errorResponse.getG2PcError()); + G2pcError g2pcError = new G2pcError( HttpStatus.UNAUTHORIZED.toString(), response.getBody()); + return g2pcError; } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { - G2pcError g2pcError = new G2pcError("err.request.bad", response.getBody()); - throw new G2pHttpException(g2pcError); + G2pcError g2pcError = new G2pcError(HttpStatus.BAD_REQUEST.toString(), response.getBody()); + return g2pcError; } else if (response.getStatus() != HttpStatus.OK.value()) { G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); - throw new G2pHttpException(g2pcError); + return g2pcError; } - return response.getStatus(); + G2pcError g2pcError = new G2pcError(HttpStatus.OK.toString(), response.getBody()); + return g2pcError; } /** @@ -220,34 +230,59 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie * @return * @throws Exception */ - private String createSignature(String isSign, String isEncrypt, String responseString) throws Exception { + private String createSignature( boolean isEncrypt, boolean isSign, String responseString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). readValue(responseString); - String messageString = responseDTO.getMessage().toString(); + byte[] json = objectMapper.writeValueAsBytes( responseDTO.getMessage()); + ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); ResponseHeaderDTO responseHeaderDTO = (ResponseHeaderDTO) responseDTO.getHeader(); - String requestHeaderString = objectMapper.writeValueAsString(responseHeaderDTO); + String responseHeaderString = objectMapper.writeValueAsString(responseHeaderDTO); + String messageString = objectMapper.writeValueAsString(messageDTO); String signature = null; - if (isSign.equals("false") && isEncrypt.equals("false")) { - responseDTO.getHeader().setIsMsgEncrypted(false); - } else if (isSign.equals("false") && isEncrypt.equals("true")) { - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedMessageString);// how to store encrypted string message dto - responseDTO.getHeader().setIsMsgEncrypted(true); - - } else if (isSign.equals("true") && isEncrypt.equals("false")) { - responseDTO.getHeader().setIsMsgEncrypted(false); - signature = encryptDecrypt.sha256Hashing(messageString + requestHeaderString); - + if(isSign){ + if(isEncrypt){ + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); + responseDTO.getHeader().setIsMsgEncrypted(true); + Map meta= (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,true); + responseDTO.getHeader().getMeta().setData(meta); + responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+encryptedMessageString); + signature = Base64.getEncoder().encodeToString(asymmetricSignature); + log.info("Encrypted message ->"+encryptedMessageString); + log.info("Hashed Signature ->"+signature); + }else{ + responseDTO.getHeader().setIsMsgEncrypted(false); + Map meta= (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,true); + responseDTO.getHeader().getMeta().setData(meta); + responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+messageString); + signature = Base64.getEncoder().encodeToString(asymmetricSignature); + log.info("Hashed Signature ->"+signature); + } } else { - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedMessageString); - responseDTO.getHeader().setIsMsgEncrypted(true); - signature = encryptDecrypt.sha256Hashing(encryptedMessageString + requestHeaderString); + if(isEncrypt){ + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); + responseDTO.getHeader().setIsMsgEncrypted(true); + Map meta= (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,false); + responseDTO.getHeader().getMeta().setData(meta); + log.info("Encrypted message ->"+encryptedMessageString); + }else{ + responseDTO.getHeader().setIsMsgEncrypted(false); + Map meta= (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN,false); + responseDTO.getHeader().getMeta().setData(meta); + } } responseDTO.setSignature(signature); responseString = objectMapper.writeValueAsString(responseDTO); diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml index 51cc241..a800889 100644 --- a/g2pc-dp-core-lib/src/main/resources/application.yml +++ b/g2pc-dp-core-lib/src/main/resources/application.yml @@ -5,9 +5,9 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2p?currentSchema=dp + url: jdbc:postgresql://localhost:5432/gtwop?currentSchema=dp username: postgres - password: postgres + password: root hikari: data-source-properties: stringtype: unspecified @@ -38,12 +38,7 @@ spring.data.redis: repositories.enabled: false host: localhost password: 123456789 - port: 6379 + port: 6376 + -client: - api_urls: - mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" -crypto: - support_encryption: false - support_signature: false From 2e71a66546b6d8fc9aefc51cf974b2817dbdaa52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Mon, 11 Dec 2023 23:33:07 +0530 Subject: [PATCH 23/53] g2pc-ref-dc-client changes. --- .../g2pc-ref-dc-client/Dockerfile | 3 + .../g2pc-ref-dc-client/pom.xml | 16 ++ .../ref/dc/client/config/RegistryConfig.java | 10 +- .../ref/dc/client/constants/Constants.java | 3 + .../client/controller/rest/DcController.java | 38 ++-- .../rest/DcDashboardController.java | 27 +++ .../service/DcResponseHandlerService.java | 2 - .../client/service/DcValidationService.java | 23 +-- .../DcRequestBuilderServiceImpl.java | 78 +++++-- .../DcResponseHandlerServiceImpl.java | 85 +------- .../serviceimpl/DcValidationServiceImpl.java | 194 ++++++++++++++---- .../src/main/resources/application-local.yml | 104 ++++++++++ .../src/main/resources/application.yml | 60 ++++-- .../schema/RegRecordFarmerSchema.json | 4 +- .../schema/RegRecordMobileSchema.json | 40 ++++ .../resources/static/.Well-known/data.json | 3 - .../resources/static/.Well-known/hello.txt | 1 - .../src/main/webapp/WEB-INF/jsp/dashboard.jsp | 176 ++++++++++++++++ .../G2pcRefDcClientApplicationTests.java | 7 +- 19 files changed, 675 insertions(+), 199 deletions(-) create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/Dockerfile create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordMobileSchema.json delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/Dockerfile b/g2pc-reference-apps/g2pc-ref-dc-client/Dockerfile new file mode 100644 index 0000000..5ff45e5 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/Dockerfile @@ -0,0 +1,3 @@ +FROM openjdk:17-jdk-alpine +COPY target/*.war dc-client-0.1.war +ENTRYPOINT ["java","-jar","/dc-client-0.1.war"] \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml index 5bb539a..ae164d8 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml @@ -8,6 +8,7 @@ 3.0.12 + war g2pc.ref.dc.client g2pc-ref-dc-client 0.0.1-SNAPSHOT @@ -71,6 +72,21 @@ jakarta.validation-api 2.0.2 + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + jstl + 1.2 + + + org.springframework.boot + spring-boot-devtools + true + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java index 382d0f8..e94fd2f 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java @@ -20,6 +20,12 @@ public class RegistryConfig { @Value("${registry.api_urls.mobile_search_api}") private String mobileSearchURL; + @Value("${registry.api_urls.farmer_search_public_api}") + private String farmerSearchPublicURL; + + @Value("${registry.api_urls.mobile_search_public_api}") + private String mobileSearchPublicURL; + @Value("${keycloak.farmer.clientId}") private String farmerClientId; @@ -87,7 +93,7 @@ public Map getRegistrySpecificConfig() { private Map getFarmerRegistryMap() { Map farmerRegistryMap = new HashMap<>(); farmerRegistryMap.put(CoreConstants.QUERY_NAME, "paid_farmer"); - farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchURL); + farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchPublicURL); farmerRegistryMap.put(CoreConstants.REG_TYPE, "ns:FARMER_REGISTRY"); farmerRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); farmerRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); @@ -110,7 +116,7 @@ private Map getFarmerRegistryMap() { private Map getMobileRegistryMap() { Map mobileRegistryMap = new HashMap<>(); mobileRegistryMap.put(CoreConstants.QUERY_NAME, "mobile_registered"); - mobileRegistryMap.put(CoreConstants.DP_SEARCH_URL, mobileSearchURL); + mobileRegistryMap.put(CoreConstants.DP_SEARCH_URL, mobileSearchPublicURL); mobileRegistryMap.put(CoreConstants.REG_TYPE, "ns:MOBILE_REGISTRY"); mobileRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); mobileRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java index d8f4836..b286f1a 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java @@ -23,4 +23,7 @@ private Constants() { public static final String FARMER_REGISTRY = "farmer_registry"; public static final String MOBILE_REGISTRY = "mobile_registry"; + + public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; + } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java index 30e9119..91e6c44 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java @@ -2,13 +2,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; @@ -26,9 +26,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -73,11 +70,11 @@ public class DcController { @Value("${keycloak.consumer.get-client-url}") private String getClientUrl; - @Value("${crypto.support_encryption}") - private String isEncrypt; + @Value("${keycloak.admin.realm.client-id}") + private String adminRealmClientId; - @Value("${crypto.support_signature}") - private String isSign; + @Value("${keycloak.admin.realm.client-secret}") + private String adminRealmClientSecret; @Value("${keycloak.admin.client-id}") private String adminClientId; @@ -85,6 +82,12 @@ public class DcController { @Value("${keycloak.admin.client-secret}") private String adminClientSecret; + @Value("${keycloak.admin.username}") + private String adminUsername; + + @Value("${keycloak.admin.password}") + private String adminPassword; + /** * Get consumer search request * @@ -121,8 +124,8 @@ public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, adminClientId, adminClientSecret); + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, adminRealmClientId, adminRealmClientSecret); if (introspectResponse.getStatusCode().value() == 401) { throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); } - if (!g2pTokenService.validateToken(masterAdminUrl, getClientUrl, g2pTokenService.decodeToken(token))) { - throw new G2pHttpException(new G2pcError("err.request.unauthorized", "User is not authorized")); + if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , adminClientId , adminClientSecret , adminUsername , adminPassword)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); } AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); @@ -170,14 +173,9 @@ public AcknowledgementDTO createSearchRequests(@RequestBody String responseStrin ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). readValue(responseString); ResponseMessageDTO messageDTO = null; - if (isEncrypt.equals("true")) { - String messageString = objectMapper.convertValue(responseDTO.getMessage(), String.class); - String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); - messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). - readValue(deprecatedMessageString); - } else { - messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); - } + Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); + messageDTO= dcValidationService.signatureValidation(metaData, responseDTO); + responseDTO.setMessage(messageDTO); try { dcValidationService.validateResponseDto(responseDTO); if (ObjectUtils.isNotEmpty(responseDTO)) { diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java new file mode 100644 index 0000000..157e328 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java @@ -0,0 +1,27 @@ +package g2pc.ref.dc.client.controller.rest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class DcDashboardController { + + @Value("${dashboard.left_panel_url}") + private String leftPanelUrl; + + @Value("${dashboard.right_panel_url}") + private String rightPanelUrl; + + @Value("${dashboard.bottom_panel_url}") + private String bottomPanelUrl; + + @GetMapping("/dashboard") + public String showDashboardPage(Model model) { + model.addAttribute("left_panel_url", leftPanelUrl); + model.addAttribute("right_panel_url", rightPanelUrl); + model.addAttribute("bottom_panel_url", bottomPanelUrl); + return "dashboard"; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java index 3fb9766..9a345b9 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java @@ -7,7 +7,5 @@ public interface DcResponseHandlerService { - void updateTransactionDbAndCache(String transactionId, SearchResponseDTO searchResponseDTO) throws JsonProcessingException; - AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java index 3349cd0..b922125 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java @@ -1,33 +1,22 @@ package g2pc.ref.dc.client.service; -import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.message.response.ResponseDTO; import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.util.Map; + /** * The interface Dc validation service. */ @Service public interface DcValidationService { - - /** - * Validate response dto. - * - * @param responseDTO the response dto - * @throws G2pcValidationException the g 2 pc validation exception - * @throws JsonProcessingException the json processing exception - */ public void validateResponseDto(ResponseDTO responseDTO) throws Exception; - /** - * Validate reg records. - * - * @param messageDTO the message dto - * @throws G2pcValidationException the g 2 pc validation exception - * @throws JsonProcessingException the json processing exception - */ - public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; + public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException; + + ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java index b6bef31..5b31606 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java @@ -1,7 +1,12 @@ package g2pc.ref.dc.client.serviceimpl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.*; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.utils.CommonUtils; @@ -19,6 +24,7 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -35,6 +41,12 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { @Autowired private RegistryTransactionsRepository registryTransactionsRepository; + @Value("${registry.api_urls.farmer_search_api}") + private String farmerSearchURL; + + @Value("${registry.api_urls.mobile_search_api}") + private String mobileSearchURL; + @Autowired private RequestBuilderService requestBuilderService; @@ -44,6 +56,37 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { @Autowired TxnTrackerService txnTrackerService; + @Value("${keycloak.farmer.clientId}") + private String farmerClientId; + + @Value("${keycloak.farmer.clientSecret}") + private String farmerClientSecret; + + @Value("${keycloak.mobile.clientId}") + private String mobileClientId; + + @Value("${keycloak.mobile.clientSecret}") + private String mobileClientSecret; + + @Value("${keycloak.farmer.url}") + private String keycloakFarmerTokenUrl; + + @Value("${keycloak.mobile.url}") + private String keycloakMobileTokenUrl; + + @Value("${crypto.farmer.support_encryption}") + private boolean isFarmerEncrypt; + + @Value("${crypto.farmer.support_signature}") + private boolean isFarmerSign; + + @Value("${crypto.mobile.support_encryption}") + private boolean isMobileEncrypt; + + @Value("${crypto.mobile.support_signature}") + private boolean isMobileSign; + + /** * Create initial transaction in DB * @@ -65,19 +108,12 @@ public void createInitialTransactionInDB(String transactionId) { * @param payloadMapList required query params data * @return acknowledgement of the request */ - @SuppressWarnings("unchecked") @Override public AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception { AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); acknowledgementDTO.setMessage(Constants.SEARCH_REQUEST_RECEIVED); acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); - String transactionId = CommonUtils.generateUniqueId("T"); - - txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); - - createInitialTransactionInDB(transactionId); - List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { List> queryMapFilteredList = queryMapList.stream() @@ -91,16 +127,29 @@ public AcknowledgementDTO generateRequest(List> payloadMapLi SearchCriteriaDTO searchCriteriaDTO = requestBuilderService.getSearchCriteriaDTO(queryParamsMap, registrySpecificConfigMap); searchCriteriaDTOList.add(searchCriteriaDTO); } + + String transactionId = CommonUtils.generateUniqueId("T"); + //txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); + String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId); txnTrackerService.saveRequestTransaction(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); + txnTrackerService.saveRequestInDB(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); log.info("requestString = {}", requestString); - //sendRequestDemo(requestString, registrySpecificConfigMap.get("url").toString()); + sendRequestDemo(requestString, registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); /* requestBuilderService.sendRequest(requestString, registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), registrySpecificConfigMap.get(CoreConstants.CLIENT_ID).toString(), registrySpecificConfigMap.get(CoreConstants.CLIENT_SECRET).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString());*/ + + log.info("Initial Request String for " + configEntryMap.getKey() + ": {}", requestString); + + /*if (configEntryMap.getKey().equals("mobile_registry")) { + requestBuilderService.sendRequest(requestString, mobileSearchURL, mobileClientId, mobileClientSecret, keycloakMobileTokenUrl, isMobileEncrypt, isMobileSign); + } else { + requestBuilderService.sendRequest(requestString, farmerSearchURL, farmerClientId, farmerClientSecret, keycloakFarmerTokenUrl, isFarmerEncrypt, isFarmerSign); + }*/ } return acknowledgementDTO; } @@ -144,10 +193,15 @@ private static List> getPayloadMapList(CSVParser csvParser) } private void sendRequestDemo(String requestString, String uri) { - HttpResponse response = Unirest.post(uri) - .body(requestString) - .asString(); - log.info("request send response status = {}", response.getStatus()); + try { + HttpResponse response = Unirest.post(uri) + .header("Content-Type", "application/json") + .body(requestString) + .asString(); + log.info("request send response status = {}", response.getStatus()); + } catch (Exception ex) { + log.error("request send error "); + } } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java index 10c9f03..1c53022 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java @@ -1,110 +1,35 @@ package g2pc.ref.dc.client.serviceimpl; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.common.message.response.ResponseDTO;; import g2pc.core.lib.utils.CommonUtils; import g2pc.dc.core.lib.service.ResponseHandlerService; +import g2pc.dc.core.lib.service.TxnTrackerService; import g2pc.ref.dc.client.constants.Constants; -import g2pc.ref.dc.client.dto.farmer.response.RegRecordFarmerDTO; -import g2pc.ref.dc.client.dto.mobile.response.RegRecordMobileDTO; -import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; import g2pc.ref.dc.client.repository.RegistryTransactionsRepository; import g2pc.ref.dc.client.service.DcResponseHandlerService; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Optional; - @Service @Slf4j public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { @Autowired - private CommonUtils commonUtils; - - @Autowired - private RedisTemplate redisTemplate; - - @Autowired - private RegistryTransactionsRepository registryTransactionsRepository; - - @Autowired - private ResponseHandlerService responseHandlerService; - - @Override - public void updateTransactionDbAndCache(String transactionId, SearchResponseDTO searchResponseDTO) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - - String regType = searchResponseDTO.getData().getRegType(); - String cacheKey = ""; - Optional entityOptional = registryTransactionsRepository.getByTransactionId(transactionId); - RegistryTransactionsEntity entity = new RegistryTransactionsEntity(); - if (entityOptional.isPresent()) { - entity = entityOptional.get(); - } - if (regType.equals("ns:FARMER_REGISTRY")) { - log.info("on-search response received from farmer registry"); - cacheKey = "farmer-" + transactionId; - - DataDTO dataDTO = searchResponseDTO.getData(); - List recordFarmerDTOList = objectMapper.convertValue(dataDTO.getRegRecords(), new TypeReference<>() { - }); - for (RegRecordFarmerDTO regRecordFarmerDTO : recordFarmerDTOList) { - Optional optional = registryTransactionsRepository.getByTransactionIdAndFarmerId(transactionId, regRecordFarmerDTO.getFarmerId()); - if (optional.isEmpty() && (ObjectUtils.isNotEmpty(entity))) { - entity.setFarmerId(regRecordFarmerDTO.getFarmerId()); - entity.setFarmerName(regRecordFarmerDTO.getFarmerName()); - entity.setSeason(regRecordFarmerDTO.getSeason()); - entity.setPaymentStatus(regRecordFarmerDTO.getPaymentStatus()); - entity.setPaymentDate(regRecordFarmerDTO.getPaymentDate()); - entity.setPaymentAmount(regRecordFarmerDTO.getPaymentAmount()); - registryTransactionsRepository.save(entity); - } - } - } else if (regType.equals("ns:MOBILE_REGISTRY")) { - log.info("on-search response received from mobile registry"); - cacheKey = "mno-" + transactionId; - - DataDTO dataDTO = searchResponseDTO.getData(); - List recordMobileDTOList = objectMapper.convertValue(dataDTO.getRegRecords(), new TypeReference<>() { - }); - for (RegRecordMobileDTO regRecordMobileDTO : recordMobileDTOList) { - Optional optional = registryTransactionsRepository.getByTransactionIdAndMobileNumber(transactionId, regRecordMobileDTO.getMobileNumber()); - if (optional.isEmpty() && (ObjectUtils.isNotEmpty(entity))) { - entity.setFarmerId(regRecordMobileDTO.getFarmerId()); - entity.setFarmerName(regRecordMobileDTO.getFarmerName()); - entity.setSeason(regRecordMobileDTO.getSeason()); - entity.setMobileNumber(regRecordMobileDTO.getMobileNumber()); - entity.setMobileStatus(regRecordMobileDTO.getMobileStatus()); - registryTransactionsRepository.save(entity); - } - } - } - log.info("transaction table updated with combined data"); - responseHandlerService.updateCache(cacheKey); - } + private TxnTrackerService txnTrackerService; @Override public AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException { AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); ObjectMapper objectMapper = new ObjectMapper(); - ResponseMessageDTO messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - List searchResponseDTO = messageDTO.getSearchResponse(); - //TODO handle this update - //updateTransactionDbAndCache(transactionId, searchResponseDTO); + txnTrackerService.updateTransactionDbAndCache(responseDTO); + log.info("on-search response received from registry : {}", objectMapper.writeValueAsString(responseDTO)); acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED); acknowledgementDTO.setStatus(Constants.COMPLETED); return acknowledgementDTO; diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java index 6c6899a..430d26b 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java @@ -7,26 +7,31 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.dc.core.lib.service.ResponseHandlerService; +import g2pc.ref.dc.client.constants.Constants; import g2pc.ref.dc.client.service.DcValidationService; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import javax.xml.crypto.Data; +import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; @Service @@ -39,12 +44,23 @@ public class DcValidationServiceImpl implements DcValidationService { @Autowired G2pEncryptDecrypt encryptDecrypt; - @Value("${crypto.support_encryption}") - private String isEncrypt; - @Value("${crypto.support_signature}") - private String isSign; + @Autowired + private AsymmetricSignatureService asymmetricSignatureService; + + @Value("${crypto.consumer.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.consumer.support_signature}") + private boolean isSign; + /** + * Validate response dto. + * + * @param responseDTO the response dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ @Override public void validateResponseDto(ResponseDTO responseDTO) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); @@ -54,48 +70,148 @@ public void validateResponseDto(ResponseDTO responseDTO) throws Exception { ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). readValue(headerString); responseHandlerService.validateResponseHeader(headerDTO); - ResponseMessageDTO messageDTO = null; - if (isEncrypt.equals("true")) { - String messageString = objectMapper.convertValue(responseDTO.getMessage(), String.class); - String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); - messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). - readValue(deprecatedMessageString); - } else { - messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); - } + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + validateRegRecords(messageDTO); responseHandlerService.validateResponseMessage(messageDTO); } + /** + * Validate reg records. + * + * @param messageDTO the message dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ @Override - public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException { ObjectMapper objectMapper = new ObjectMapper(); //TODO: work on commented line - //DataDTO dataDTO = messageDTO.getSearchResponse().getData(); - DataDTO dataDTO = new DataDTO(); - String regRecordString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(dataDTO.getRegRecords()); - log.info("MessageString -> " + regRecordString); - InputStream schemaStream = DcValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/RegRecordFarmerSchema.json"); - JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); - JsonSchema schemaRegRecord = null; - if (schemaStream != null) { - schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); - List errorCombinedMessage = new ArrayList<>(); - for (ValidationMessage error : errorMessage) { - log.info("Validation errors in Reg records" + error); - errorCombinedMessage.add(new G2pcError("", error.getMessage())); + List searchResponseList = messageDTO.getSearchResponse(); + for(SearchResponseDTO searchResponseDTO : searchResponseList){ + DataDTO dataDTO = searchResponseDTO.getData(); + String regRecordString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(dataDTO.getRegRecords()); + log.info("MessageString -> " + regRecordString); + InputStream schemaStream; + if(dataDTO.getRegType().toString().equals("ns:MOBILE_REGISTRY")){ + schemaStream = DcValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/RegRecordMobileSchema.json"); + } else { + schemaStream = DcValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/RegRecordFarmerSchema.json"); + } + JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); + JsonSchema schemaRegRecord = null; + if (schemaStream != null) { + schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); + List errorCombinedMessage = new ArrayList<>(); + for (ValidationMessage error : errorMessage) { + log.info("Validation errors in Reg records" + error); + errorCombinedMessage.add(new G2pcError("", error.getMessage())); + + } + if (errorMessage.size() > 0) { + throw new G2pcValidationException(errorCombinedMessage); + } } - if (errorMessage.size() > 0) { - throw new G2pcValidationException(errorCombinedMessage); + + } + + /** + * Method to validate signature and encrypted message + * @param metaData + * @param responseDTO + * @return + * @throws Exception + */ + @Override + public ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception { + + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); + ObjectMapper objectMapper = new ObjectMapper(); + ResponseMessageDTO messageDTO; + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + String responseSignature = responseDTO.getSignature(); + String messageString = responseDTO.getMessage().toString(); + String data = responseHeaderString+messageString; + if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature)) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + if(responseDTO.getHeader().getIsMsgEncrypted()){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + String responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + String responseSignature = responseDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = responseHeaderString+messageString; + log.info("Signature ->"+responseSignature); + if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature)) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = responseDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(),"Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + } } + return messageDTO; } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml new file mode 100644 index 0000000..25315d8 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml @@ -0,0 +1,104 @@ +spring: + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + + pathmatch: + matching-strategy: ANT_PATH_MATCHER + + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/dc1?currentSchema=g2pc + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + +server: + port: 8000 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + + api_urls: + #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" + #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + +keycloak: + farmer: + url: "http://127.0.0.1:8081/auth/realms/dp-farmer/protocol/openid-connect/token" + clientId: "dp-farmer-client" + clientSecret: "EaXspS2bAcCmh5XrDWYrAzWP1Q1uQEIA" + mobile: + url: "http://127.0.0.1:8081/auth/realms/dp-mobile/protocol/openid-connect/token" + clientId: "dp-mobile-client" + clientSecret: "d544H8DTnZXREmX6jgmAfoFCeFXQ1oVV" + consumer: + admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token + get-client-url: http://127.0.0.1:8081/auth/admin/realms/data-consumer/clients + realm: data-consumer + url: http://127.0.0.1:8081/auth + admin: + client-id: admin-cli + client-secret: qCyT7XM24KGjb5j6ZU5YC68H5OiI6LRm + +crypto: + farmer: + support_encryption: true + support_signature: true + mobile: + support_encryption: true + support_signature: true + consumer: + support_encryption: true + support_signature: true + +registry: + api_urls: + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + farmer_search_public_api: "http://localhost:9001/public/api/v1/registry/search" + mobile_search_public_api: "http://localhost:9002/public/api/v1/registry/search" + +dashboard: + left_panel_url: "http://localhost:3005/d-solo/abce6fd2-95ac-49a6-b743-23d785b3a080/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + right_panel_url: "http://localhost:3005/d-solo/eddad923-f089-4225-a72b-57967e263937/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + bottom_panel_url: "http://localhost:3005/d-solo/e36b8bf2-4bf9-4391-b687-a026426db65f/botton-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml index 93a893b..6f0f5da 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -1,12 +1,18 @@ spring: mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + pathmatch: matching-strategy: ANT_PATH_MATCHER + datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2p?currentSchema=dc + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc username: postgres - password: postgres + password: t3{gxm6J9Gq-RFV&`hY>5N + hikari: data-source-properties: stringtype: unspecified @@ -35,6 +41,11 @@ spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + server: port: 8000 error: @@ -54,27 +65,48 @@ spring.data.redis: keycloak: farmer: - url: "http://127.0.0.1:8081/auth/realms/dp-farmer/protocol/openid-connect/token" + url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" clientId: "dp-farmer-client" - clientSecret: "EaXspS2bAcCmh5XrDWYrAzWP1Q1uQEIA" + clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" mobile: - url: "http://127.0.0.1:8081/auth/realms/dp-mobile/protocol/openid-connect/token" + url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" clientId: "dp-mobile-client" - clientSecret: "d544H8DTnZXREmX6jgmAfoFCeFXQ1oVV" + clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" consumer: - admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token - get-client-url: http://127.0.0.1:8081/auth/admin/realms/data-consumer/clients + admin-url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + get-client-url: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients realm: data-consumer - url: http://127.0.0.1:8081/auth + url: https://g2pc-dc-lab.cdpi.dev/auth/ admin: + realm: + client-id: admin-cli + client-secret: vhqwFSij7JuzImzuCoWSQULx7FMnfijK client-id: admin-cli - client-secret: qCyT7XM24KGjb5j6ZU5YC68H5OiI6LRm + client-secret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs # In realm In master admin-cli -> secret key + username: admin + password: Abhilash@9922 crypto: - support_encryption: false - support_signature: false + farmer: + support_encryption: true + support_signature: true + mobile: + support_encryption: true + support_signature: true + consumer: + support_encryption: true + support_signature: true + registry: api_urls: - farmer_search_api: "http://localhost:9001/public/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/public/api/v1/registry/search" \ No newline at end of file + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + farmer_search_public _api: "http://localhost:9001/public/api/v1/registry/search" + mobile_search_public_api: "http://localhost:9002/public/api/v1/registry/search" + +dashboard: + left_panel_url: "http://localhost:3005/d-solo/abce6fd2-95ac-49a6-b743-23d785b3a080/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + right_panel_url: "http://localhost:3005/d-solo/eddad923-f089-4225-a72b-57967e263937/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + bottom_panel_url: "http://localhost:3005/d-solo/e36b8bf2-4bf9-4391-b687-a026426db65f/botton-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json index bb31876..58ba7a4 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordFarmerSchema.json @@ -4,8 +4,6 @@ "title": "Message schema", "description": "", "additionalProperties": false, - "type": "array", - "items" : [{ "type": "object", "properties": { "farmer_id": { @@ -31,7 +29,7 @@ "payment_amount": { "type": "number" } - } } ] , + } , "definitions": { "nonEmptyString": { "type": "string", diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordMobileSchema.json b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordMobileSchema.json new file mode 100644 index 0000000..1201abe --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/schema/RegRecordMobileSchema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "farmer_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "farmer_name": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "season": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "mobile_number": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "mobile_status": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "created_date": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + } + } , + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json deleted file mode 100644 index 795e9d3..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/data.json +++ /dev/null @@ -1,3 +0,0 @@ -{ -"name": "abhi" -} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt deleted file mode 100644 index 2615228..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/static/.Well-known/hello.txt +++ /dev/null @@ -1 +0,0 @@ -welcome to the project \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp new file mode 100644 index 0000000..4eab858 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp @@ -0,0 +1,176 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> + + + + + Dashboard + + + +
+
+
+ + + + +
+
+ +
+
+
+ +
+
+
+ +
+ + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java index b412b79..b8bddca 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java @@ -1,10 +1,10 @@ package g2pc.ref.dc.client; -import com.fasterxml.jackson.core.JsonProcessingException; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; + @SpringBootTest @Slf4j class G2pcRefDcClientApplicationTests { @@ -13,9 +13,4 @@ class G2pcRefDcClientApplicationTests { void contextLoads() { } - @Test - void testResponse() throws JsonProcessingException { - - - } } From 01d19657a806940280045b7df5f295a115c222eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Mon, 11 Dec 2023 23:34:25 +0530 Subject: [PATCH 24/53] g2pc-ref-farmer-regsvc changes. --- .../g2pc-ref-farmer-regsvc/Dockerfile | 3 + .../g2pc-ref-farmer-regsvc/pom.xml | 222 +++++++++--------- .../farmer/regsvc/constants/Constants.java | 2 + .../rest/DpDashboardController.java | 13 + .../controller/rest/RegistryController.java | 90 ++++--- .../farmer/regsvc/scheduler/Scheduler.java | 32 ++- .../service/FarmerValidationService.java | 22 +- .../FarmerValidationServiceImpl.java | 159 +++++++++++-- .../src/main/resources/application-local.yml | 84 +++++++ .../src/main/resources/application.yml | 47 +++- .../src/main/webapp/WEB-INF/jsp/dashboard.jsp | 45 ++++ .../src/main/webapp/resources/css/styles.css | 25 ++ 12 files changed, 548 insertions(+), 196 deletions(-) create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/Dockerfile create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/Dockerfile b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/Dockerfile new file mode 100644 index 0000000..01a02ac --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/Dockerfile @@ -0,0 +1,3 @@ +FROM openjdk:17-jdk-alpine +COPY target/*.jar dp-farmer-0.1.jar +ENTRYPOINT ["java","-jar","/dp-farmer-0.1.jar"] \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml index 6846ffe..58cfa01 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/pom.xml @@ -1,120 +1,112 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.0.12 - - - g2pc.ref.farmer.regsvc - g2pc-ref-farmer-regsvc - 0.0.1-SNAPSHOT - g2pc-ref-farmer-regsvc - g2pc-ref-farmer-regsvc - - 17 - - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-web - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.12 + + + g2pc.ref.farmer.regsvc + g2pc-ref-farmer-regsvc + 0.0.1-SNAPSHOT + g2pc-ref-farmer-regsvc + g2pc-ref-farmer-regsvc + + 17 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + true + + + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springdoc + springdoc-openapi-ui + 1.6.15 + + + org.springframework.security + spring-security-web + 6.1.2 + + + com.auth0 + java-jwt + 4.4.0 + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.springframework.security + spring-security-config + 6.1.2 + + + org.springframework.boot + spring-boot-starter-validation + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + jstl + 1.2 + + + org.springframework.boot + spring-boot-devtools + true + + - - org.projectlombok - lombok - true - - - g2pc.dp.core.lib - g2pc-dp-core-library - 0.0.1-SNAPSHOT - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.15.0 - - - org.postgresql - postgresql - 42.5.4 - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springdoc - springdoc-openapi-ui - 1.6.8 - - - - - - - - - - - - - org.springframework.security - spring-security-web - 6.1.2 - - - com.auth0 - java-jwt - 4.4.0 - - - - - com.mashape.unirest - unirest-java - 1.4.9 - - - - - jakarta.validation - jakarta.validation-api - 2.0.2 - - - com.auth0 - java-jwt - 4.4.0 - - - org.springframework.security - spring-security-config - 6.1.2 - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java index 3fb299b..9e050af 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java @@ -16,4 +16,6 @@ private Constants() { public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; public static final String CACHE_KEY_STRING = "request-farmer-"; + + public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java new file mode 100644 index 0000000..8b80f49 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java @@ -0,0 +1,13 @@ +package g2pc.ref.farmer.regsvc.controller.rest; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class DpDashboardController { + + @GetMapping("/dashboard") + public String showDashboardPage() { + return "dashboard"; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java index 272ed3d..839ad18 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java @@ -1,6 +1,5 @@ package g2pc.ref.farmer.regsvc.controller.rest; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.constants.G2pSecurityConstants; @@ -10,16 +9,19 @@ import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; import g2pc.ref.farmer.regsvc.service.FarmerValidationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -28,11 +30,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + /** * The type Registry controller. */ @@ -69,11 +75,17 @@ public class RegistryController { @Value("${keycloak.farmer.get-client-url}") private String getClientUrl; - @Value("${crypto.support_encryption}") - private String isEncrypt; + @Value("${crypto.consumer.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.consumer.support_signature}") + private boolean isSign; + + @Value("${keycloak.admin.realm.client-id}") + private String adminRealmClientId; - @Value("${crypto.support_signature}") - private String isSign; + @Value("${keycloak.admin.realm.client-secret}") + private String adminRealmClientSecret; @Value("${keycloak.admin.client-id}") private String adminClientId; @@ -81,6 +93,22 @@ public class RegistryController { @Value("${keycloak.admin.client-secret}") private String adminClientSecret; + @Value("${keycloak.admin.username}") + private String adminUsername; + + @Value("${keycloak.admin.password}") + private String adminPassword; + + @Autowired + private AsymmetricSignatureService asymmetricSignatureService; + + + /** + * Get search request from DC + * + * @param requestDTO required + * @return Search request received acknowledgement + */ @Operation(summary = "Receive search request") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), @@ -88,16 +116,16 @@ public class RegistryController { @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/public/api/v1/registry/search") - public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws Exception { + public AcknowledgementDTO demoSearch(@RequestBody RequestDTO requestDTO) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). - readValue(requestString); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); - return requestHandlerService.buildCacheRequest( - objectMapper.writeValueAsString(requestDTO), cacheKey); + + return requestHandlerService.buildCacheRequest(objectMapper.writeValueAsString(requestDTO), cacheKey); } /** @@ -107,7 +135,7 @@ public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws E * @return Search request received acknowledgement * @throws JsonProcessingException the json processing exception * @throws ResponseStatusException the response status exception - * @throws G2pcValidationException the validation exception + * @throws G2pcValidationException the validation exception */ @Operation(summary = "Receive search request") @ApiResponses(value = { @@ -116,15 +144,17 @@ public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws E @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/search") - public AcknowledgementDTO getRequestForSearch(@RequestBody String requestString) throws Exception { + public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); String token = BearerTokenUtil.getBearerTokenHeader(); - String introspect = keycloakURL + "/realms/" + keycloakRealm + "/protocol/openid-connect/token/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, adminClientId, adminClientSecret); - if (introspectResponse.getStatusCode().value() == 401) { - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + String introspect = keycloakURL+"/realms/"+keycloakRealm+"/protocol/openid-connect/token/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token,adminRealmClientId,adminRealmClientSecret); + if(introspectResponse.getStatusCode().value()==401){ + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(),introspectResponse.getBody())); } - if (!g2pTokenService.validateToken(masterAdminUrl, getClientUrl, g2pTokenService.decodeToken(token))) { - throw new G2pHttpException(new G2pcError("err.request.unauthorized", "User is not authorized")); + if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , adminClientId , adminClientSecret , adminUsername , adminPassword)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); } ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -134,16 +164,10 @@ public AcknowledgementDTO getRequestForSearch(@RequestBody String requestString) readValue(requestString); RequestMessageDTO messageDTO = null; - if (isEncrypt.equals("true")) { - String messageString = requestDTO.getMessage().toString(); - String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); - messageDTO = objectMapper.readerFor(RequestMessageDTO.class). - readValue(deprecatedMessageString); - } else { - messageDTO = objectMapper.readerFor(RequestMessageDTO.class). - readValue((JsonParser) requestDTO.getMessage()); - } + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); + requestDTO.setMessage(messageDTO); String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { farmerValidationService.validateRequestDTO(requestDTO); @@ -151,10 +175,12 @@ public AcknowledgementDTO getRequestForSearch(@RequestBody String requestString) objectMapper.writeValueAsString(requestDTO), cacheKey); } catch (G2pcValidationException e) { throw new G2pcValidationException(e.getG2PcErrorList()); - } catch (JsonProcessingException e) { - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); - } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + catch (JsonProcessingException e){ + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); + } + catch (Exception e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST , e.getMessage()); } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java index 45a38c0..0652b18 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java @@ -11,6 +11,8 @@ import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.utils.CommonUtils; import g2pc.dp.core.lib.entity.MsgTrackerEntity; import g2pc.dp.core.lib.repository.MsgTrackerRepository; @@ -22,9 +24,12 @@ import g2pc.ref.farmer.regsvc.constants.Constants; import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; import jakarta.transaction.Transactional; +import kong.unirest.HttpResponse; +import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -38,6 +43,9 @@ public class Scheduler { @Value("${client.api_urls.client_search_api}") String onSearchURL; + @Value("${client.api_urls.client_search_public_api}") + String onSearchPublicURL; + @Autowired private RequestHandlerService requestHandlerService; @@ -69,7 +77,12 @@ public class Scheduler { private TxnTrackerDbService txnTrackerDbService; /** - * Response scheduler + * This method is a scheduled task that runs every minute. + * It retrieves data from a Redis cache, processes it, and sends a response. + * If the status of the data is 'PDNG', it processes the data and updates the status to 'SUCC'. + * If an exception occurs during the process, it logs the exception message. + * + * @throws IOException if an I/O error occurs */ @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. @Transactional @@ -108,12 +121,27 @@ public void responseScheduler() throws IOException { headerDTO, responseMessageDTO); responseString = CommonUtils.formatString(responseString); log.info("on-search response = {}", responseString); - //responseBuilderService.sendOnSearchResponse(responseString, onSearchURL,dcClientId,dcClientSecret ,keyClockClientTokenUrl); + sendResponseDemo(responseString, onSearchPublicURL); + //G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL,dcClientId,dcClientSecret ,keyClockClientTokenUrl); + //if(!g2pcError.getCode().equals(HttpStatus.OK.toString())){ + // throw new G2pHttpException(g2pcError); + //} else { txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + //} } } + //} catch (G2pHttpException e) { + // log.error("Exception thrown from on-search endpoint" + e.getG2PcError().getMessage()); } catch (Exception ex) { log.error("Exception in responseScheduler: {}", ex.getMessage()); } } + + private void sendResponseDemo(String responseString, String uri) { + HttpResponse response = Unirest.post(uri) + .header("Content-Type", "application/json") + .body(responseString) + .asString(); + log.info("response send response status = {}", response.getStatus()); + } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java index 3860585..ff57645 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java @@ -3,29 +3,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; -import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; - +import java.util.Map; /** * The interface Farmer validation service. */ public interface FarmerValidationService { - /** - * Validate request dto. - * - * @param requestDTO the request dto - * @throws G2pcValidationException the validation exception - * @throws JsonProcessingException the json processing exception - */ void validateRequestDTO (RequestDTO requestDTO) throws Exception; - /** - * Validate query dto. - * - * @param queryFarmerDTO the query farmer dto - * @throws G2pcValidationException the validation exception - * @throws JsonProcessingException the json processing exception - */ - void validateQueryDto (QueryFarmerDTO queryFarmerDTO) throws G2pcValidationException, JsonProcessingException; + void validateQueryDto (QueryDTO queryFarmerDTO) throws G2pcValidationException, JsonProcessingException; + + RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java index 62bdb3e..3757691 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java @@ -7,22 +7,32 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.service.AsymmetricSignatureService; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.farmer.regsvc.constants.Constants; import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; +import g2pc.ref.farmer.regsvc.dto.request.QueryParamsFarmerDTO; import g2pc.ref.farmer.regsvc.service.FarmerValidationService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; /** @@ -38,30 +48,57 @@ public class FarmerValidationServiceImpl implements FarmerValidationService { @Autowired RequestHandlerService requestHandlerService; - @Override - public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, JsonProcessingException { - /* ObjectMapper objectMapper = new ObjectMapper(); - RequestMessageDTO messageDTO=(RequestMessageDTO) requestDTO.getMessage(); - String queryString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery()); - QueryFarmerDTO queryFarmerDTO = objectMapper.readerFor(QueryFarmerDTO.class). - readValue(queryString); - validateQueryDto(queryFarmerDTO); + @Value("${crypto.farmer.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.farmer.support_signature}") + private boolean isSign; + + @Autowired + private AsymmetricSignatureService asymmetricSignatureService; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + /** + * Validate request dto. + * + * @param requestDTO the request dto + * @throws G2pcValidationException the validation exception + * @throws JsonProcessingException the json processing exception + */ + @Override + public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(QueryDTO.class, + QueryFarmerDTO.class, QueryParamsFarmerDTO.class); + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + List searchRequestList = messageDTO.getSearchRequest(); + for(SearchRequestDTO searchRequestDTO : searchRequestList){ + String queryString = objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery()); + QueryDTO queryFarmerDTO = objectMapper.readerFor(QueryDTO.class). + readValue(queryString); + validateQueryDto(queryFarmerDTO); + } String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(requestDTO.getHeader()); RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). readValue(headerString); requestHandlerService.validateRequestHeader(headerDTO); - requestHandlerService.validateRequestMessage(requestDTO.getMessage()); -*/ - + requestHandlerService.validateRequestMessage(messageDTO); } + /** + * Validate query dto. + * + * @param queryFarmerDTO the query farmer dto + * @throws G2pcValidationException the validation exception + * @throws JsonProcessingException the json processing exception + */ @Override - public void validateQueryDto(QueryFarmerDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { + public void validateQueryDto(QueryDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { ObjectMapper objectMapper = new ObjectMapper(); log.info("Query object -> " + queryFarmerDTO); @@ -88,5 +125,93 @@ public void validateQueryDto(QueryFarmerDTO queryFarmerDTO) throws JsonProcessin throw new G2pcValidationException(errorcombinedMessage); } } + + /** + * Method to validate signature and encrypted message + * @param metaData + * @param requestDTO + * @return + * @throws Exception + */ + @Override + public RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO) throws Exception { + + ObjectMapper objectMapper = new ObjectMapper(); + RequestMessageDTO messageDTO; + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = requestDTO.getMessage().toString(); + String data = requestHeaderString+messageString; + if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature)) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + if(requestDTO.getHeader().getIsMsgEncrypted()){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = requestHeaderString+messageString; + log.info("Signature ->"+requestSignature); + if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature)) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(),"signature is not valid ")); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + } + } + requestDTO.setMessage(messageDTO); + return messageDTO; + } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml new file mode 100644 index 0000000..e1f2995 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml @@ -0,0 +1,84 @@ +spring: + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + + pathmatch: + matching-strategy: ANT_PATH_MATCHER + + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/dp1?currentSchema=g2pc + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + +server: + port: 9001 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +client: + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + client_search_public_api: "http://localhost:8000/public/api/v1/registry/on-search" + +keycloak: + data-consumer: + url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" + client-id: dc-client + client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K + farmer: + admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token + get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-farmer/clients + realm: dp-farmer + url: http://127.0.0.1:8081/auth + admin: + client-id: admin-cli + client-secret: D50Wdnxc7JiQxCPi884aVDAiQy1nfPpq + +crypto: + consumer: + support_encryption: true + support_signature: true + farmer: + support_encryption: true + support_signature: true \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml index 44137fd..48c0c1a 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml @@ -1,12 +1,18 @@ spring: mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + pathmatch: matching-strategy: ANT_PATH_MATCHER + datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2p?currentSchema=dp + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc username: postgres - password: postgres + password: t3{gxm6J9Gq-RFV&`hY>5N + hikari: data-source-properties: stringtype: unspecified @@ -35,6 +41,11 @@ spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + server: port: 9001 error: @@ -42,28 +53,38 @@ server: spring.data.redis: repositories.enabled: false - host: localhost - password: 123456789 - port: 6379 + host: 3.109.26.38 + password: abhilash@99222 + port: 6378 client: api_urls: client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + client_search_public_api: "http://localhost:8000/public/api/v1/registry/on-search" keycloak: data-consumer: - url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" client-id: dc-client - client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K + client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY farmer: - admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token - get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-farmer/clients + admin-url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + get-client-url: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients realm: dp-farmer - url: http://127.0.0.1:8081/auth + url: https://g2pc-dp1-lab.cdpi.dev/auth admin: + realm: + client-id: admin-cli + client-secret: Sds0rtxBI4ChXKdVx2ytYsmvRmo9Jc2L # In realm admin-cli -> secret key client-id: admin-cli - client-secret: D50Wdnxc7JiQxCPi884aVDAiQy1nfPpq + client-secret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN # In realm In master admin-cli -> secret key + username: admin + password: Abhilash@9923 crypto: - support_encryption: false - support_signature: false \ No newline at end of file + consumer: + support_encryption: true + support_signature: true + farmer: + support_encryption: true + support_signature: true diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp new file mode 100644 index 0000000..ea8bec0 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp @@ -0,0 +1,45 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> + + + + + Dashboard + + + +
+ +
+ + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css new file mode 100644 index 0000000..73abba4 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css @@ -0,0 +1,25 @@ +body { + background-color: rgba(0, 0, 0, 0.1); + color: #fff; + display: flex; + justify-content: space-around; + align-items: center; + height: 100vh; + margin: 0; + padding: 0; + flex-direction: column; +} + +.panel { + width: 99%; + height: 97%; + display: flex; + justify-content: center; + align-items: center; +} + +.contentPanel { + background-color: lightyellow; + border: 1px solid #fff; +} + From 9f08ca935785fe4445bc26d65a910ec391145e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Mon, 11 Dec 2023 23:35:24 +0530 Subject: [PATCH 25/53] g2pc-ref-mno-regsvc changes. --- .../g2pc-ref-mno-regsvc/Dockerfile | 3 + .../g2pc-ref-mno-regsvc/pom.xml | 22 ++- .../regsvc/G2pcRefMnoRegsvcApplication.java | 9 +- .../ref/mno/regsvc/constants/Constants.java | 4 + .../rest/DpDashboardController.java | 17 ++ .../controller/rest/RegistryController.java | 66 +++++--- .../ref/mno/regsvc/scheduler/Scheduler.java | 95 ++++++++--- .../service/MobileResponseBuilderService.java | 6 +- .../service/MobileValidationService.java | 28 +--- .../MobileResponseBuilderServiceImpl.java | 58 ++----- .../MobileValidationServiceImpl.java | 147 +++++++++++++----- .../src/main/resources/application-local.yml | 84 ++++++++++ .../src/main/resources/application.yml | 74 +++++++-- .../src/main/webapp/WEB-INF/jsp/dashboard.jsp | 45 ++++++ .../src/main/webapp/resources/css/styles.css | 25 +++ 15 files changed, 512 insertions(+), 171 deletions(-) create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/Dockerfile create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/DpDashboardController.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/Dockerfile b/g2pc-reference-apps/g2pc-ref-mno-regsvc/Dockerfile new file mode 100644 index 0000000..6b39e48 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/Dockerfile @@ -0,0 +1,3 @@ +FROM openjdk:17-jdk-alpine +COPY target/*.jar dp-mno-0.1.jar +ENTRYPOINT ["java","-jar","/dp-mno-0.1.jar"] \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml index 2a9c21d..70bd624 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml @@ -54,12 +54,26 @@ org.springdoc springdoc-openapi-ui - 1.6.8 + 1.6.15 - org.springdoc - springdoc-openapi-ui - 1.6.8 + org.springframework.boot + spring-boot-starter-validation + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + jstl + 1.2 + + + org.springframework.boot + spring-boot-devtools + true
diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java index 22554ae..74ef201 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplication.java @@ -7,8 +7,13 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling; -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class ,SecurityAutoConfiguration.class}) -@ComponentScan({"g2pc.core.lib", "g2pc.dp.core.lib", "g2pc.ref.mno.regsvc","g2pc.dp.core.lib.service","g2pc.core.lib.security.serviceImpl"}) +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) +@ComponentScan({"g2pc.core.lib", + "g2pc.dp.core.lib", + "g2pc.ref.mno.regsvc", + "g2pc.dp.core.lib.service", + "g2pc.dp.core.lib.repository", + "g2pc.core.lib.security.serviceImpl"}) @EnableScheduling public class G2pcRefMnoRegsvcApplication { diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java index 2792b53..289903c 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java @@ -16,4 +16,8 @@ private Constants() { public static final String CACHE_KEY_SEARCH_STRING = "request-mno*"; public static final String CACHE_KEY_STRING = "request-mno-"; + + public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; + + } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/DpDashboardController.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/DpDashboardController.java new file mode 100644 index 0000000..c191b1b --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/DpDashboardController.java @@ -0,0 +1,17 @@ +package g2pc.ref.mno.regsvc.controller.rest; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class DpDashboardController { + + private static final String DASHBOARD_URL = "http://localhost:3005/d-solo/c766225a-d5cf-4c9f-99a7-6f8291f407eb/dp-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1"; + + @GetMapping("/dashboard") + public String dashboard(Model model) { + model.addAttribute("dashboardUrl", DASHBOARD_URL); + return "dashboard"; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java index 42253c4..14edae4 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java @@ -2,20 +2,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.security.BearerTokenUtil; -import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.mno.regsvc.constants.Constants; @@ -31,6 +30,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import java.util.Map; /** * The type Registry controller. @@ -49,8 +49,9 @@ public class RegistryController { */ @Autowired MobileValidationService mobileValidationService; + @Autowired - G2pEncryptDecrypt encryptDecrypt; + G2pTokenService g2pTokenService; @Value("${keycloak.realm}") private String keycloakRealm; @@ -58,20 +59,17 @@ public class RegistryController { @Value("${keycloak.url}") private String keycloakURL; - @Autowired - G2pTokenService g2pTokenService; - @Value("${keycloak.mobile.admin-url}") private String masterAdminUrl; @Value("${keycloak.mobile.get-client-url}") private String getClientUrl; - @Value("${crypto.support_encryption}") - private String isEncrypt; + @Value("${keycloak.admin.realm.client-id}") + private String adminRealmClientId; - @Value("${crypto.support_signature}") - private String isSign; + @Value("${keycloak.admin.realm.client-secret}") + private String adminRealmClientSecret; @Value("${keycloak.admin.client-id}") private String adminClientId; @@ -79,6 +77,31 @@ public class RegistryController { @Value("${keycloak.admin.client-secret}") private String adminClientSecret; + @Value("${keycloak.admin.username}") + private String adminUsername; + + @Value("${keycloak.admin.password}") + private String adminPassword; + + @Operation(summary = "Receive search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/public/api/v1/registry/search") + public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + return requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey); + } + /** * Get search request from DC * @@ -99,28 +122,23 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody String reque ObjectMapper objectMapper = new ObjectMapper(); String token = BearerTokenUtil.getBearerTokenHeader(); String introspect = keycloakURL+"/realms/"+keycloakRealm+"/protocol/openid-connect/token/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token,adminClientId,adminClientSecret); + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token,adminRealmClientId,adminRealmClientSecret); if(introspectResponse.getStatusCode().value()==401){ throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(),introspectResponse.getBody())); } - if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token))){ - throw new G2pHttpException(new G2pcError("err.request.unauthorized","User is not authorized")); + if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , adminClientId , adminClientSecret , adminUsername , adminPassword)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); } objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). readValue(requestString); - MessageDTO messageDTO = null; - if(isEncrypt.equals("true")){ - String messageString = requestDTO.getMessage(); - String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); - messageDTO = objectMapper.readerFor(MessageDTO.class). - readValue(deprecatedMessageString); - }else{ - messageDTO = objectMapper.readerFor(MessageDTO.class). - readValue(requestDTO.getMessage()); - } + RequestMessageDTO messageDTO = null; + + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + messageDTO = mobileValidationService.signatureValidation(metaData,requestDTO); + requestDTO.setMessage(messageDTO); String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java index b658a12..f2af9ed 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java @@ -1,17 +1,29 @@ package g2pc.ref.mno.regsvc.scheduler; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.utils.CommonUtils; import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.dp.core.lib.service.ResponseBuilderService; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; import g2pc.ref.mno.regsvc.constants.Constants; import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; +import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -54,37 +66,70 @@ public class Scheduler { @Value("${keycloak.data-consumer.url}") private String keyClockClientTokenUrl; + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + @Autowired + private TxnTrackerDbService txnTrackerDbService; + /** - * Response scheduler + * This method is a scheduled task that runs every minute. + * It retrieves data from a Redis cache, processes it, and sends a response. + * If the status of the data is 'PDNG', it processes the data and updates the status to 'SUCC'. + * If an exception occurs during the process, it logs the exception message. + * + * @throws IOException if an I/O error occurs */ - // @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. + @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. + @Transactional public void responseScheduler() throws IOException { try { ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class); - - List cacheKeysList = requestHandlerService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); - // TODO: this logic has to be handled to get single cacheKey - String cacheKey = cacheKeysList.get(0); - String requestData = requestHandlerService.getRequestData(cacheKey); - CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); - - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - - String refRecordsString = mobileResponseBuilderService.getRegMobileRecords( - objectMapper.writeValueAsString(requestDTO.getMessage())); - - DataDTO dataDTO = mobileResponseBuilderService.buildData(refRecordsString); - - String responseString = responseBuilderService.buildResponseString(Constants.CACHE_KEY_SEARCH_STRING, dataDTO); - log.info("Scheduler responseString : {}", responseString); - - responseBuilderService.sendOnSearchResponse(responseString, onSearchURL,dcClientId,dcClientSecret ,keyClockClientTokenUrl); - - - responseBuilderService.updateRequestStatus(cacheKey, DpConstants.COMPLETED, cacheDTO); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + List cacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); + for (String cacheKey : cacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() + .map(txnTrackerEntity -> { + try { + return objectMapper.readValue(txnTrackerEntity.getQuery(), QueryDTO.class); + } catch (JsonProcessingException e) { + return null; + } + }).toList(); + List refRecordsStringsList = mobileResponseBuilderService.getRegMobileRecords(queryDTOList); + + List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( + requestDTO, refRecordsStringsList); + + ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); + + ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); + String responseString = responseBuilderService.buildResponseString("signature", + headerDTO, responseMessageDTO); + responseString = CommonUtils.formatString(responseString); + log.info("on-search response = {}", responseString); + //G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl); + + //if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + // throw new G2pHttpException(g2pcError); + //} else { + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + //} + } + } + //} catch (G2pHttpException e) { + // log.error("Exception thrown from on-search endpoint" + e.getG2PcError().getMessage()); } catch (Exception ex) { - log.error("Scheduler error : {}", ex.getMessage()); + log.error("Scheduler error : {}", ex); } } } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java index 71b0d14..2035f45 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java @@ -1,13 +1,13 @@ package g2pc.ref.mno.regsvc.service; import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.response.DataDTO; import java.io.IOException; +import java.util.List; public interface MobileResponseBuilderService { - String getRegMobileRecords(String messageString) throws JsonProcessingException; - - DataDTO buildData(String regRecordsString) throws IOException; + List getRegMobileRecords(List queryDTOList) throws IOException; } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java index 69d8aa9..aeb9edc 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java @@ -1,38 +1,20 @@ package g2pc.ref.mno.regsvc.service; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; -import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import kong.unirest.UnirestException; -import kong.unirest.json.JSONObject; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import java.util.Map; /** * The interface Mobile validation service. */ public interface MobileValidationService { - /** - * Validate request dto. - * - * @param requestDTO the request dto - * @throws G2pcValidationException the validation exception - * @throws JsonProcessingException the json processing exception - */ void validateRequestDTO (RequestDTO requestDTO) throws Exception; - /** - * Validate query dto. - * - * @param queryMobileDTO the query mobile dto - * @throws G2pcValidationException the validation exception - * @throws JsonProcessingException the json processing exception - */ void validateQueryDto (QueryDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException; - } + + RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java index 8007439..c5c5235 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java @@ -1,14 +1,9 @@ package g2pc.ref.mno.regsvc.serviceimpl; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.config.G2pUnirestHelper; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; -import g2pc.ref.mno.regsvc.dto.request.QueryParamsMobileDTO; +import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.ref.mno.regsvc.dto.response.RegRecordMobileDTO; import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; import kong.unirest.HttpResponse; @@ -18,59 +13,38 @@ import org.springframework.stereotype.Service; import java.io.IOException; +import java.util.ArrayList; import java.util.List; @Service @Slf4j public class MobileResponseBuilderServiceImpl implements MobileResponseBuilderService { - @Value("${client.api_urls.mobile_info_api}") + @Value("${client.api_urls.mno_info_url}") String mobileInfoURL; @Autowired G2pUnirestHelper g2pUnirestHelper; + /** * Get farmer records information string from API * - * @param messageString required - * @return String of mobile records + * @param queryDTOList required + * @return list of mobile records */ @Override - public String getRegMobileRecords(String messageString) throws JsonProcessingException { + public List getRegMobileRecords(List queryDTOList) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); - //remove only use for testing - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, - QueryMobileDTO.class, - QueryParamsMobileDTO.class); - MessageDTO messageDTO = objectMapper.readerFor(MessageDTO.class).readValue(messageString); - String queryParams = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery().getQueryParams()); - QueryParamsMobileDTO queryParamsMobileDTO = objectMapper.readValue(queryParams, QueryParamsMobileDTO.class); - - + List regMnoRecordsList = new ArrayList<>(); String uri = mobileInfoURL; HttpResponse response = g2pUnirestHelper.g2pPost(uri) - .body(objectMapper.writeValueAsString(queryParamsMobileDTO)) + .body(objectMapper.writeValueAsString(queryDTOList)) .asString(); - - List regRecordMobileDTOList = objectMapper.readerFor(List.class). - readValue(response.getBody()); - - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(regRecordMobileDTOList); - } - - @Override - public DataDTO buildData(String regRecordsString) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - List regRecordFarmerDTOList = objectMapper.readerFor(List.class). - readValue(regRecordsString); - - DataDTO dataDTO = new DataDTO(); - dataDTO.setVersion("1.0.0"); - dataDTO.setRegType("ns:MOBILE_REGISTRY"); - dataDTO.setRegSubType(""); - dataDTO.setRegRecordType("ns:MOBILE_REGISTRY:MOBILE"); - dataDTO.setRegRecords(regRecordFarmerDTOList); - return dataDTO; + List regRecordMobileDTOList = objectMapper.readValue(response.getBody(), + new TypeReference<>() {}); + for (RegRecordMobileDTO regRecordMobileDTO : regRecordMobileDTOList) { + regMnoRecordsList.add(objectMapper.writeValueAsString(regRecordMobileDTO)); + } + return regMnoRecordsList; } } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java index f53c5e0..8805e86 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java @@ -7,37 +7,30 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; -import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.MessageDTO; import g2pc.core.lib.dto.common.message.request.QueryDTO; import g2pc.core.lib.dto.common.message.request.RequestDTO; +import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.mno.regsvc.constants.Constants; import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; import g2pc.ref.mno.regsvc.dto.request.QueryParamsMobileDTO; import g2pc.ref.mno.regsvc.service.MobileValidationService; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import kong.unirest.UnirestException; -import kong.unirest.json.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; - import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - - +import java.util.*; /** * The type Mobile validation service. */ @@ -54,11 +47,14 @@ public class MobileValidationServiceImpl implements MobileValidationService { @Autowired G2pEncryptDecrypt encryptDecrypt; - @Value("${crypto.support_encryption}") - private String isEncrypt; + @Value("${crypto.mobile.support_encryption}") + private boolean isEncrypt; - @Value("${crypto.support_signature}") - private String isSign; + @Value("${crypto.mobile.support_signature}") + private boolean isSign; + + @Autowired + private AsymmetricSignatureService asymmetricSignatureService; /** * Method to validate Request dto @@ -70,24 +66,16 @@ public void validateRequestDTO(RequestDTO requestDTO) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(QueryDTO.class, QueryMobileDTO.class, QueryParamsMobileDTO.class); - - MessageDTO messageDTO = null; - if(isEncrypt.equals("true")){ - String messageString = requestDTO.getMessage(); - String deprecatedMessageString = encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); - messageDTO = objectMapper.readerFor(MessageDTO.class). - readValue(deprecatedMessageString); - }else{ - messageDTO = objectMapper.readerFor(MessageDTO.class). - readValue(requestDTO.getMessage()); + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + List searchRequestList = messageDTO.getSearchRequest(); + for(SearchRequestDTO searchRequestDTO : searchRequestList){ + String queryString = objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery()); + QueryDTO queryMobileDto = objectMapper.readerFor(QueryDTO.class). + readValue(queryString); + validateQueryDto(queryMobileDto); } - String queryString = objectMapper.writeValueAsString(messageDTO.getSearchRequest().getSearchCriteria().getQuery()); - - QueryDTO queryMobileDTO = objectMapper.readerFor(QueryDTO.class). - readValue(queryString); - validateQueryDto(queryMobileDTO); - String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(requestDTO.getHeader()); @@ -124,12 +112,99 @@ public void validateQueryDto(QueryDTO queryMobileDTO) throws G2pcValidationExcep for (ValidationMessage error : errorMessage){ log.info("Validation errors" + error ); errorcombinedMessage.add(new G2pcError("",error.getMessage())); - } if (errorMessage.size()>0){ throw new G2pcValidationException(errorcombinedMessage); } } + /** + * Method to validate signature and encryption of request dto + * @param metaData + * @param requestDTO + * @return + * @throws Exception + */ + @Override + public RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO) throws Exception { + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + ObjectMapper objectMapper = new ObjectMapper(); + RequestMessageDTO messageDTO; + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = requestDTO.getMessage().toString(); + String data = requestHeaderString+messageString; + if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature)) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + if(requestDTO.getHeader().getIsMsgEncrypted()){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = requestHeaderString+messageString; + log.info("Signature ->"+requestSignature); + if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature)) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + } + } + return messageDTO; + } + } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml new file mode 100644 index 0000000..6d50a53 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml @@ -0,0 +1,84 @@ +spring: + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/g2pc?currentSchema=dp + username: postgres + password: postgres + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + +server: + port: 9002 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6379 + +client: + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + mno_info_url: "http://localhost:9200/private/api/v1/registry/mobile/info" + +keycloak: + data-consumer: + url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" + client-id: dc-client + client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K + mobile: + admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token + get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-mobile/clients + realm: dp-mobile + url: http://127.0.0.1:8081/auth + admin: + client-id: admin-cli + client-secret: eJ7bErtDvu0D5yXP37zLjAgGC28S1ofT + +crypto: + consumer: + support_encryption: true + support_signature: true + mobile: + support_encryption: true + support_signature: true + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml index 1f1101c..3653ed7 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml @@ -1,10 +1,50 @@ spring: mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + pathmatch: matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp2?currentSchema=g2pc + username: postgres + password: t3{gxm6J9Gq-RFV&`hY>5N + + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false autoconfigure: exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + server: port: 9002 error: @@ -12,29 +52,39 @@ server: spring.data.redis: repositories.enabled: false - host: localhost - password: 123456789 - port: 6379 + host: 3.109.26.38 + password: abhilash@99223 + port: 6377 client: api_urls: client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - mobile_info_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + mno_info_url: "http://localhost:9200/private/api/v1/registry/mobile/info" keycloak: data-consumer: - url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" client-id: dc-client - client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K + client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY mobile: - admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token - get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-mobile/clients + admin-url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + get-client-url: https://g2pc-dp2-lab.cdpi.dev/auth/admin/realms/dp-mobile/clients realm: dp-mobile - url: http://127.0.0.1:8081/auth + url: https://g2pc-dp2-lab.cdpi.dev/auth admin: + realm: + client-id: admin-cli + client-secret: bISeLIzCYnsaXd0w9NN5yoYQ5w4ofueR client-id: admin-cli - client-secret: eJ7bErtDvu0D5yXP37zLjAgGC28S1ofT + client-secret: awR1cImNnsiOnBeWpZQWUfKFr1gOopBo + username: admin + password: Abhilash@9924 crypto: - support_encryption: false - support_signature: false + consumer: + support_encryption: true + support_signature: true + mobile: + support_encryption: true + support_signature: true + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp new file mode 100644 index 0000000..16c7bf3 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp @@ -0,0 +1,45 @@ +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> + + + + + Dashboard + + + +
+ +
+ + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css new file mode 100644 index 0000000..73abba4 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css @@ -0,0 +1,25 @@ +body { + background-color: rgba(0, 0, 0, 0.1); + color: #fff; + display: flex; + justify-content: space-around; + align-items: center; + height: 100vh; + margin: 0; + padding: 0; + flex-direction: column; +} + +.panel { + width: 99%; + height: 97%; + display: flex; + justify-content: center; + align-items: center; +} + +.contentPanel { + background-color: lightyellow; + border: 1px solid #fff; +} + From 0e0f7ee311d0ddaacdb653ffe349f150c9749ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Mon, 11 Dec 2023 23:44:39 +0530 Subject: [PATCH 26/53] Credentials updated. --- .../src/main/resources/application.yml | 14 +++++++------- .../src/main/resources/application.yml | 16 ++++++++-------- .../src/main/resources/application.yml | 16 ++++++++-------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml index 6f0f5da..31c1007 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -9,9 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc + url: not_set username: postgres - password: t3{gxm6J9Gq-RFV&`hY>5N + password: not_set hikari: data-source-properties: @@ -67,11 +67,11 @@ keycloak: farmer: url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" clientId: "dp-farmer-client" - clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" + clientSecret: not_set mobile: url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" clientId: "dp-mobile-client" - clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" + clientSecret: not_set consumer: admin-url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token get-client-url: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients @@ -80,11 +80,11 @@ keycloak: admin: realm: client-id: admin-cli - client-secret: vhqwFSij7JuzImzuCoWSQULx7FMnfijK + client-secret: not_set client-id: admin-cli - client-secret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs # In realm In master admin-cli -> secret key + client-secret: not_set # In realm In master admin-cli -> secret key username: admin - password: Abhilash@9922 + password: not_set crypto: farmer: diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml index 48c0c1a..393cc90 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml @@ -9,9 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc + url: not_set username: postgres - password: t3{gxm6J9Gq-RFV&`hY>5N + password: not_set hikari: data-source-properties: @@ -53,8 +53,8 @@ server: spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: abhilash@99222 + host: not_set + password: not_set port: 6378 client: @@ -66,7 +66,7 @@ keycloak: data-consumer: url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" client-id: dc-client - client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + client-secret: not_set farmer: admin-url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token get-client-url: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients @@ -75,11 +75,11 @@ keycloak: admin: realm: client-id: admin-cli - client-secret: Sds0rtxBI4ChXKdVx2ytYsmvRmo9Jc2L # In realm admin-cli -> secret key + client-secret: not_set # In realm admin-cli -> secret key client-id: admin-cli - client-secret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN # In realm In master admin-cli -> secret key + client-secret: not_set # In realm In master admin-cli -> secret key username: admin - password: Abhilash@9923 + password: not_set crypto: consumer: diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml index 3653ed7..b7812ec 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml @@ -8,9 +8,9 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp2?currentSchema=g2pc + url: not_set username: postgres - password: t3{gxm6J9Gq-RFV&`hY>5N + password: not_set hikari: data-source-properties: @@ -52,8 +52,8 @@ server: spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: abhilash@99223 + host: not_set + password: not_set port: 6377 client: @@ -65,7 +65,7 @@ keycloak: data-consumer: url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" client-id: dc-client - client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + client-secret: not_set mobile: admin-url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token get-client-url: https://g2pc-dp2-lab.cdpi.dev/auth/admin/realms/dp-mobile/clients @@ -74,11 +74,11 @@ keycloak: admin: realm: client-id: admin-cli - client-secret: bISeLIzCYnsaXd0w9NN5yoYQ5w4ofueR + client-secret: not_set client-id: admin-cli - client-secret: awR1cImNnsiOnBeWpZQWUfKFr1gOopBo + client-secret: not_set username: admin - password: Abhilash@9924 + password: not_set crypto: consumer: From 9b078610eed7c74b5073c985a048c0774a435376 Mon Sep 17 00:00:00 2001 From: ttpl-rt-119 Date: Wed, 13 Dec 2023 20:38:20 +0530 Subject: [PATCH 27/53] Doc README.md and reference images --- docs/README.md | 1072 ++++++++++++++++- docs/src/images/.p12-dp.png | Bin 0 -> 8126 bytes docs/src/images/DC-DP-communication.png | Bin 0 -> 31534 bytes docs/src/images/HeaderDTORelationship.png | Bin 0 -> 73742 bytes docs/src/images/Sign-encry-table.png | Bin 0 -> 13740 bytes docs/src/images/dp-package-strcuture.png | Bin 0 -> 25242 bytes docs/src/images/dp-scheduler-sequence-dia.png | Bin 0 -> 97671 bytes docs/src/images/dp-specs-json.png | Bin 0 -> 6899 bytes docs/src/images/dp-specs.png | Bin 0 -> 99464 bytes docs/src/images/dp_schema.png | Bin 0 -> 6913 bytes docs/src/images/on_search_seqeunce_dia.png | Bin 0 -> 106245 bytes docs/src/images/on_search_spec.png | Bin 0 -> 76083 bytes docs/src/images/patload_postman.png | Bin 0 -> 24377 bytes docs/src/images/payload_sequence_diagram.png | Bin 0 -> 100260 bytes docs/src/images/search-endpoint-spec.png | Bin 0 -> 69766 bytes docs/src/images/search_sequence_diagram.png | Bin 0 -> 114356 bytes docs/src/images/spring_boot_dp_creation.png | Bin 0 -> 50371 bytes 17 files changed, 1071 insertions(+), 1 deletion(-) create mode 100644 docs/src/images/.p12-dp.png create mode 100644 docs/src/images/DC-DP-communication.png create mode 100644 docs/src/images/HeaderDTORelationship.png create mode 100644 docs/src/images/Sign-encry-table.png create mode 100644 docs/src/images/dp-package-strcuture.png create mode 100644 docs/src/images/dp-scheduler-sequence-dia.png create mode 100644 docs/src/images/dp-specs-json.png create mode 100644 docs/src/images/dp-specs.png create mode 100644 docs/src/images/dp_schema.png create mode 100644 docs/src/images/on_search_seqeunce_dia.png create mode 100644 docs/src/images/on_search_spec.png create mode 100644 docs/src/images/patload_postman.png create mode 100644 docs/src/images/payload_sequence_diagram.png create mode 100644 docs/src/images/search-endpoint-spec.png create mode 100644 docs/src/images/search_sequence_diagram.png create mode 100644 docs/src/images/spring_boot_dp_creation.png diff --git a/docs/README.md b/docs/README.md index 3a7c4ad..099ff7c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1 +1,1071 @@ -# docs to be added here \ No newline at end of file +# Overview of G2p connect - Registry +- Governments give money to people for various reasons like subsidies, pensions, scholarships, and emergency help. People can choose how they want to receive this money, like in cash, through their bank, on their phone, or with vouchers. + +- But, each government department has to set up its own system to check if people are eligible for the money, make sure transactions are real, and actually send the money. They have to talk to different departments to gather all the needed information, and this leads to a lot of duplicate work and problems. +- Simply putting all the data in one place doesn't work well because it can be a security risk and needs a lot of new systems. The usual approach of just putting things 'online' doesn't consider the real issues like politics, how people use technology, and the need for new ideas. +- A G2P DPI - Registry , is about creating a system that works together and respects privacy, security, and individual choices. The usual steps for giving money involve checking if people qualify, confirming their identity, and sending the money to their chosen method. +- To make this process better, G2P Connect suggests building a secure, decentralized system that different departments can customize. This way, they can share common elements, solve problems, and make the process more efficient. +- G2P Connect is an open-source project that helps different government agencies in a country work together to deliver digital payments from start to finish. +- The G2P transaction process involves an individual asking for money, providing their ID, and going through some security steps. The system checks if they qualify by looking at different government databases. + +## G2p specifications +- G2P Connect API Specifications is a project that makes it easy for different systems to work together. It sets rules for how they should talk to each other +- The main goals of G2P Connect Specifications are to make sure systems can work seamlessly together and follow the rules set by the country. It also aims to be flexible, meaning it can adapt to existing standards and use common methods like OAuth2 for security. +- The message structure used in G2P Connect is like a package with a signature and a header. The header includes important information like the version, message ID, and what action is being taken. +- The specifications also allow easy integration of different data types and ensure secure communication through digital signatures and encryption. +- The focus is on standardizing core interfaces, acting as connectors between solutions and enabling countries to implement a variety of use cases. +- G2P Connect doesn't care how systems send messages; it can work with different methods like HTTPS, messaging events, or file exchanges. It also makes sure that the dates, times, and currency codes used in the messages are in a format that everyone can understand. +- In simple terms, G2P Connect API Specifications is like a rulebook that different systems follow to work together when giving money to people. It's flexible, secure, and makes sure everyone understands each other. + + +# Overview / List of libraries +### 1. G2pc-core-lib - +- The G2pc-core-lib serves as a central hub for managing shared, reusable elements within the G2p specification +- This includes 80% of reusable features, DTOs, and configurations consistent across all data providers and consumers. +- The core library encapsulates below essential components - + - Redis cache configurations + - Unirest library settings + - Common constants + - G2p-specific DTOs and enums + - Common exception handling + - Security configurations + - Utility functions +- The DTOs outlined in this library correspond to the elements specified in the G2p specification’s endpoints. In essence, they represent the key components integral to the functionality described in the G2p standards. E.g - HeaderDTO , MessageDTO , ResponseDTO , etc. further details are listed in the technical overview. +- DTOs are constructed based on the OOPs principle of reusability. Attributes are shared between request bodies and responses are identified, and common elements are defined in parent DTOs. Any remaining specific attributes are then placed in corresponding child DTOs. These can be more explained by the below example. +E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/HeaderDTORelationship.png "a title") +- This module declares functionalities for token-based authentication, digital signature, and securing messages through encryption using various algorithms. +- As per the requirement , these encryption and digital signature functions can be called in the below combination. + ![Alt text]( /home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/Sign-encry-table.png "a title") +- Custom validation exception - + - JSON schemas have been used for validating both request and response components. + - Below Custom validation exceptions are defined + - G2pHttpException + - G2pcValidationException + - These exceptions are used to handle http requests errors which can be thrown from unirest endpoints. + +- Note - All these changes have been done on the basis of G2p specification. + + +### 2. G2pc-dp-core-lib +- The G2pc-dp-core-lib defines entities and services specific to data providers. +- It incorporates methods for constructing responses and managing requests within custom data provider services. +- This library encapsulates both an entity and repositories responsible for handling the data stored in tables dedicated to message tracking and transaction information. +- Also , service implementation manages the tracking of transactions, including saving request details, determining record counts, constructing search responses, and updating transaction statuses. +- It also includes building cache requests, validating request headers and messages against JSON schemas, and handling transaction tracking in both Redis and a database. +- It integrates with other services and provides detailed error handling and logging. Also constructing response DTOs, managing encryption and signatures, sending responses via HTTP, and handling tokens. + +### 3. G2pc-dc-core-lib +- The G2pc-dc-core-lib defines entities and services specific to data consumers. +- It incorporates methods for constructing requests and managing responses within custom data consumer services. +- This library defines functionality for building and sending requests in a secure manner, involving encryption, digital signatures, token management, and caching. +- Also provides functionality for updating a cache, validating response headers, and validating response messages against predefined JSON schemas. +- This service is basically to handle and process responses within a system. +- Below is the communication diagram of DP and DC - + + ![Alt text]( /home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/DC-DP-communication.png "a title") + +# Data Provider (DP) Implementation +- In DP implementation , as explained in Overview of libraries , dependency of G2pc-dp-core-lib needs to be added. +- Data provider is going to act as provider as well as consumer as. +- As shown in Figure 2 data provider needs to implement the end point and also make calls to the endpoint of the data consumer. +- Implementation explained in below point when it act like data provider - + 1. At first Data provider service needs to write the /search end-point. + 2. This endpoint will receive a requestString transferred by the data consumer. + 3. In this endpoint authentication also needs to be defined to ensure that the correct user is accessing the endpoint or not. + 4. Also need to make sure that the correct signature and valid message is received. + 5. This requestString will get validated as per g2p specification. Please refer to the link mentioned and image below. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/search-endpoint-spec.png "a title") + 6. Once requestString gets validated data provider should save that data in redis cache and transaction data in db and send acknowledgement back to data consumer. Refer below sequence diagram for reference - + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/search_sequence_diagram.png) +- Implementation explained in below point when it act like data consumer - + 1. When it acts like a consumer , it needs to define a scheduler. Scheduler is nothing but a framework that allows you to schedule and execute tasks at specific intervals or times. + 2. In this scheduler , dp will check whether there is any data stored in pending status with a particular cache key corresponding to that data provider. + 3. If it gets data it will build the response data and the call /on-search endpoint is defined in the data consumer . Please refer to Image 2 for the same. + 4. Refer below for understanding of flow from dp to parent libraries. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-scheduler-sequence-dia.png) + +# How to create a Data Provider ? +1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/spring_boot_dp_creation.png) +2. Extract the downloaded jar and open it in IDE. +3. Add below dependencies in pom.xml +```` + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + true + ++ + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springdoc + springdoc-openapi-ui + 1.6.15 + + + org.springframework.security + spring-security-web + 6.1.2 + + + com.auth0 + java-jwt + 4.4.0 + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.springframework.security + spring-security-config + 6.1.2 + + + org.springframework.boot + spring-boot-starter-validation + +```` +4. Create package structure shown below. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-package-strcuture.png) +5. Add .p12 file for search and on-search. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/.p12-dp.png) +6. In the config package , create the ObjectMapperConfig.java class. +```` +@Configuration +public class ObjectMapperConfig { + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + return objectMapper; + } +} + +```` +7. In the constants package , create Constants.java class. +```` +package g2pc.ref.farmer.regsvc.constants; +public class Constants { + private Constants() { + } + public static final String SEARCH_REQUEST_RECEIVED = "Search request received successfully"; + public static final String INVALID_RESPONSE = "Invalid Response received from server"; + public static final String CONFLICT = "CONFLICT"; + public static final String INVALID_AUTHORIZATION = "Invalid Authorization"; + public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; + public static final String CACHE_KEY_STRING = "request-farmer-"; + public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; +} + +```` +8. In the controller.rest package , create a RegistryController.java class. +```` +@RestController +@Slf4j +@RequestMapping(produces = "application/json") +@Tag(name = "Provider", description = "Provider APIs") +public class RegistryController { +} + +```` +9. In the service package , create the respective DP ResponseBuilderService.java interface. Refer below example. +```` +public interface FarmerResponseBuilderService { + RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity); + } +```` +10. In the service package , create the respective ValidationService.java interface. Refer below example. +```` +public interface FarmerValidationService { + void validateRequestDTO (RequestDTO requestDTO) throws Exception; + void validateQueryDto (QueryDTO queryFarmerDTO) throws G2pcValidationException, JsonProcessingException; + RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; +} + +```` +11. In the serviceImpl package create implemented classes of above interfaces for respective dps. +```` +@Service +@Slf4j +public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderService { + +} +```` +```` +@Service +@Slf4j +public class FarmerValidationServiceImpl implements FarmerValidationService { + } +```` +12. Add below autowired dependencies in the RegistryController class. +```` +@Autowired +private RequestHandlerService requestHandlerService; + +@Autowired +FarmerValidationService farmerValidationService; + +@Autowired +G2pTokenService g2pTokenService; +```` +13. Add below application.yml and update as per below instructions. +```` +spring: + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + + + pathmatch: + matching-strategy: ANT_PATH_MATCHER + + + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW + + + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + + + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + + +server: + port: 9001 + error: + include-message: always + + +spring.data.redis: + repositories.enabled: false + host: 3.109.26.38 + password: abhilash@99222 + port: 6378 + + +client: + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + + +keycloak: + from-dc: + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" + client-id: dc-client + client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + dp: + master-admin-token-url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + get-client-url: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients + realm: dp-farmer + url: https://g2pc-dp1-lab.cdpi.dev/auth + admin: + realm: + client-id: admin-cli + client-secret: Sds0rtxBI4ChXKdVx2ytYsmvRmo9Jc2L # In realm admin-cli -> secret key + master-client-id: admin-cli + master-client-secret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN # In realm In master admin-cli -> secret key + username: admin + password: cdpi@9923 + + +crypto: + to_dc: + support_encryption: true + support_signature: true + password: "farmer_on_search" + key.path: "classpath:farmer_on_search.p12" + id: FARMER + from_dc: + support_encryption: true + support_signature: true + password: "farmer_search" + key.path: "classpath:farmer_search.p12" + + +dashboard: + dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" +```` +14. Add below attributes as per your requirement - + 1. Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. + 2. client.api_urls.client_search_api -> change port as respective on-search api. + 3. keycloak.from-dc.url -> create realm for data consumer and replace name with keycloak data-consumer realm name. + 4. keycloak.from-dc.client-id -> respective client id of created realm. + 5. keycloak.from-dc.client.secret -> respective client secret of created realm. + 6. keycloak.dp.admin-url -> replace port with your keycloak port + 7. keycloak.dp.get-client-url -> replace port with your keycloak port and name with respective dp realm name. + 8. keycloak.realm -> add respective dp realm name + 9. keycloak.url -> add keycloak url + 10. keycloak.admin.realm.client-id -> respective dp realm's admin_cli client id + 11. keycloak.admin.realm.client-secret -> respective realm's admin_cli client secret + 12. keycloak.admin.master-client-cli -> In respective keycloak instance , master realm’s admin_cli client-id + 13. keyloak.admin.master-client-secret -> In respective keycloak instance , master realm’s admin_cli client-secret + 14. keycloak.admin.username -> Respective keycloak instance username + 15. keycloak.admin.password -> Respective keycloak instance password + 16. crypto.to_dc.support_encryption -> flag of encryption when scheduler will call /on-search api + 17. crypto.to_dc.support_signature -> flag of signature when scheduler will call /on-search api + 18. crypto.to_dc.password -> password of on_search.p12 password + 19. crypto.to_dc.key.path -> path of on_search.p12 file + 20. crypto.to_dc.id -> flag of Farmer id for data consumer. + 21. crypto.from_dc.support_encryption ->flag of encryption when the data consumer will call /search to validate the configuration. + 22. crypto.from_dc.support_signature -> flag of signature when the data consumer will call /search to validate the configuration. + 23. crypto.from_dc.password -> password of search.p12 password + 24. crypto.from_dc.key.path -> path of search.p12 file + 25. Dashboard.dp_dashboard_url -> +15. Add below values in RegistryController. +```` +@Value("${keycloak.realm}") +private String keycloakRealm; + + +@Value("${keycloak.url}") +private String keycloakURL; + + +@Value("${keycloak.dp.master-admin-token-url}") +private String masterAdminUrl; + + +@Value("${keycloak.dp.get-client-url}") +private String getClientUrl; + + +@Value("${keycloak.admin.realm.client-id}") +private String realmAdmin_cliClientId; + + +@Value("${keycloak.admin.realm.client-secret}") +private String realmAdmin_cliClientSecret; + + +@Value("${keycloak.admin.master-client-id}") +private String masterAdmin_cliClientId; + + +@Value("${keycloak.admin.master-client-secret}") +private String masterAdmin_clientSecret; + + +@Value("${keycloak.admin.username}") +private String adminUsername; + + +@Value("${keycloak.admin.password}") +private String adminPassword; + + +@Value("${crypto.to_dc.support_encryption}") +private boolean isEncrypt; + + +@Value("${crypto.to_dc.support_signature}") +private boolean isSign; + +```` +16. Define below endpoint in RegistryController. +```` +@Operation(summary = "Receive search request") +@ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) +@PostMapping("/private/api/v1/registry/search") +public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { + +```` +17. Add below code snippet in RegistryController for authentication in which parent g2pc.core library’s methods have been called. This will authenticate the user whether its token is valid or not and whether it has been created from keycloak or not using introspect url. +```` +String token = BearerTokenUtil.getBearerTokenHeader(); +String introspect = keycloakURL+"/realms/"+keycloakRealm+"/protocol/openid-connect/token/introspect"; +ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token, realmAdmin_cliClientId, realmAdmin_cliClientSecret); +if(introspectResponse.getStatusCode().value()==401){ + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(),introspectResponse.getBody())); +} +if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , masterAdmin_cliClientId, masterAdmin_clientSecret, adminUsername , adminPassword)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); +} + +```` +18. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO. +```` +ObjectMapper objectMapper = new ObjectMapper(); +objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + +RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); +RequestMessageDTO messageDTO = null; +```` +19. Add below code snippet to validate signature and encryption. +```` +Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); +messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); +requestDTO.setMessage(messageDTO); +```` +20. Add below code snippet to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. +```` +String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); +try { + farmerValidationService.validateRequestDTO(requestDTO); + return requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey); +} catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); +} +catch (JsonProcessingException e){ + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); +} +catch (Exception e){ + throw new ResponseStatusException(HttpStatus.BAD_REQUEST , e.getMessage()); +} + +```` +21. Add below 2 methods Custom Exception handling using spring boot annotations in RegistryController. +```` +@ExceptionHandler(value + = G2pcValidationException.class) +@ResponseStatus(HttpStatus.BAD_REQUEST) +public ValidationErrorResponse +handleValidationException( + G2pcValidationException ex) { + return new ValidationErrorResponse( + ex.getG2PcErrorList()); +} + +@ExceptionHandler(value + = G2pHttpException.class) +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public ErrorResponse handleG2pHttpStatusException( + G2pHttpException ex) { + return new ErrorResponse(ex.getG2PcError()); + } + +```` +22. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. +```` +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryFarmerDTO { + + + @JsonProperty("query_params") + private QueryParamsFarmerDTO queryParams; +} +```` +```` +@Getter +@Setter +@ToString +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryParamsFarmerDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("season") + private String season; +} + +```` +23. Create regRecordDTO for data provider as per on search endpoint requirement in dto.response package shown below. +```` +@Getter +@Setter +@ToString +@NoArgsConstructor +public class DataFarmerDTO { + + @JsonProperty("reg_records") + private List regRecords; +} +```` +```` +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class RegRecordFarmerDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("season") + private String season; + + @JsonProperty("payment_status") + private String paymentStatus; + + @JsonProperty("payment_date") + private String paymentDate; + + @JsonProperty("payment_amount") + private Double paymentAmount; +} + +```` +24. To get data provider information create a data-provider info table in db , entity and repository as shown below. +```` +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "farmer_info") +public class FarmerInfoEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + private String farmerId; + + private String farmerName; + + private String season; + + private String paymentStatus; + + private String paymentDate; + + private Double paymentAmount; + +```` +Write your respective method using data jpa concept. +```` +@Repository +public interface FarmerInfoRepository extends JpaRepository { + Optional findBySeasonAndFarmerId(String season, String farmerId); +} +```` +25. Define below method in ValidationServiceImpl as it has implemented from ValidationService interface. +```` +@Override +public RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO) throws Exception { +```` +26. Define below autowired beans and configurations in ValidationServiceImpl. +```` +@Autowired + RequestHandlerService requestHandlerService; + + @Value("${crypto.from_dc.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.from_dc.support_signature}") + private boolean isSign; + + @Value("${crypto.from_dc.password}") + private String p12Password; + + @Autowired + private AsymmetricSignatureService asymmetricSignatureService; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Autowired + private ResourceLoader resourceLoader; + + @Value("${crypto.from_dc.key.path}") + private String farmer_key_path; +```` +27. Add below code snippet in signatureValidation method. This is the validation for signature and encryption to check whether this is transferred correctly. +```` + ObjectMapper objectMapper = new ObjectMapper(); + RequestMessageDTO messageDTO; + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(farmer_key_path); + InputStream fis = resource.getInputStream(); + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = requestDTO.getMessage().toString(); + String data = requestHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ->"+e.getMessage())); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(requestDTO.getHeader().getIsMsgEncrypted()){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = requestHeaderString+messageString; + log.info("Signature ->"+requestSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(RequestMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + } + } + requestDTO.setMessage(messageDTO); + return messageDTO; + +```` +28. Create a schema folder in the resource folder for respective data provider Query. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp_schema.png) +29. With reference to below Query of farmer data provider. Refer specification - + [specification](https://g2p-connect.github.io/specs/release/html/registry_core_api_v1.0.0.html#tag/Async/operation/post_reg_search) +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-specs-json.png) +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-specs.png) +```` +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Query schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "query_name" : { + "type": "string" + }, + "query_params": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + }, + "farmer_id": { + "type": "string", + "items": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "season": { + "$ref": "#/definitions/nonEmptyString", + "type": "string" + } + }, + "required": ["farmer_id","season"] + } + }, + "required": ["query_params"], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} +```` +30. Implement method from ValidationService and change schema name in path. +```` +@Override +public void validateQueryDto(QueryDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { + +ObjectMapper objectMapper = new ObjectMapper(); +log.info("Query object -> " + queryFarmerDTO); +String queryString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(queryFarmerDTO); +log.info("Query String" + queryString); +InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/farmerQuerySchema.json"); +JsonNode jsonNode = objectMapper.readTree(queryString); +JsonSchema schema = null; +if (schemaStreamQuery != null) { + schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStreamQuery); +} +Set errorMessage = schema.validate(jsonNode); +List errorcombinedMessage = new ArrayList<>(); +for (ValidationMessage error : errorMessage) { + log.info("Validation errors" + error); + errorcombinedMessage.add(new G2pcError("", error.getMessage())); +} +if (errorMessage.size() > 0) { + throw new G2pcValidationException(errorcombinedMessage); + } + +```` +31. Implement below method from ValidationService in ValidationServiceImpl. +```` +@Override +public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, IOException { +```` +Change QueryFarmerDTO , QueryParamsFarmerDTO , with respective data provider DTOs. In this method , validations of message and header methods are called from parent dp-core. +```` +ObjectMapper objectMapper = new ObjectMapper(); +objectMapper.registerSubtypes(QueryDTO.class, + QueryFarmerDTO.class, QueryParamsFarmerDTO.class); +byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); +RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); +List searchRequestList = messageDTO.getSearchRequest(); +for(SearchRequestDTO searchRequestDTO : searchRequestList){ + String queryString = objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery()); + QueryDTO queryFarmerDTO = objectMapper.readerFor(QueryDTO.class). + readValue(queryString); + validateQueryDto(queryFarmerDTO); +} +String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(requestDTO.getHeader()); +RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). + readValue(headerString); +requestHandlerService.validateRequestHeader(headerDTO); +requestHandlerService.validateRequestMessage(messageDTO); +```` +32. In Scheduler class define below autowired bean. these beans are from dp-core library and also custom created in dp. +```` + @Value("${client.api_urls.client_search_api}") + String onSearchURL; + + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + private ResponseBuilderService responseBuilderService; + + @Autowired + private FarmerResponseBuilderService farmerResponseBuilderService; + + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + @Autowired + private MsgTrackerRepository msgTrackerRepository; + + @Autowired + private TxnTrackerRepository txnTrackerRepository; + + @Autowired + private TxnTrackerDbService txnTrackerDbService; + + @Autowired + private ResourceLoader resourceLoader; + + @Value("${keycloak.from-dc.client-id}") + private String dcClientId; + + @Value("${keycloak.from-dc.client-secret}") + private String dcClientSecret; + + @Value("${keycloak.from-dc.url}") + private String keyClockClientTokenUrl; + + @Value("${crypto.to_dc.id}") + private String dp_id; + + @Value("${crypto.to_dc.key.path}") + private String farmer_key_path; +```` +32. Define below method in Scheduler. +```` +@Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. +@Transactional +public void responseScheduler() throws IOException { +```` +33. Define try catch in the same method. +34. Add below snippet in method. +```` +ObjectMapper objectMapper = new ObjectMapper(); +objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); +```` +35. Call method from txnTrackerRedisService to get cache list. +```` +List cacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); +```` +36. Check whether in list the status in PNDG or not +```` +if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { +```` +37. Add below code snippet to in ResponseBuilderServiceImpl +```` +@Autowired +private FarmerInfoRepository farmerInfoRepository; + + +/** +* Get farmer records information from DB +* +* @param farmerInfoEntity required +* @return Farmer records +*/ +@Override +public RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity) { + RegRecordFarmerDTO dto = new RegRecordFarmerDTO(); + dto.setFarmerId(farmerInfoEntity.getFarmerId()); + dto.setFarmerName(farmerInfoEntity.getFarmerName()); + dto.setSeason(farmerInfoEntity.getSeason()); + dto.setPaymentStatus(farmerInfoEntity.getPaymentStatus()); + dto.setPaymentDate(farmerInfoEntity.getPaymentDate()); + dto.setPaymentAmount(farmerInfoEntity.getPaymentAmount()); + return dto; +} + +/** +* Get farmer records information string +* +* @param queryDTOList required +* @return List of farmer records +*/ +@Override +public List getRegFarmerRecords(List queryDTOList) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + List regFarmerRecordsList = new ArrayList<>(); + for (QueryDTO queryDTO : queryDTOList) { + String queryParams = objectMapper.writeValueAsString(queryDTO.getQueryParams()); + QueryParamsFarmerDTO queryParamsFarmerDTO = objectMapper.readValue(queryParams, QueryParamsFarmerDTO.class); + String farmerId = queryParamsFarmerDTO.getFarmerId(); + String season = queryParamsFarmerDTO.getSeason(); + Optional optional = farmerInfoRepository.findBySeasonAndFarmerId(season, farmerId); + if (optional.isPresent()) { + RegRecordFarmerDTO regRecordFarmerDTO = getRegRecordFarmerDTO(optional.get()); + regFarmerRecordsList.add(objectMapper.writeValueAsString(regRecordFarmerDTO)); + } else { + regFarmerRecordsList.add(StringUtils.EMPTY); + } + } + return regFarmerRecordsList; + +```` +38. Add below snippet in scheduler class if condition +```` + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() + .map(txnTrackerEntity -> { + try { + return objectMapper.readValue(txnTrackerEntity.getQuery(), QueryDTO.class); + } catch (JsonProcessingException e) { + return null; + } + }).toList(); + List refRecordsStringsList = farmerResponseBuilderService.getRegFarmerRecords(queryDTOList); + + List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( + requestDTO, refRecordsStringsList); + + ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); + + ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); + Map meta= (Map) headerDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID,dp_id); + requestDTO.getHeader().getMeta().setData(meta); + String responseString = responseBuilderService.buildResponseString("signature", + headerDTO, responseMessageDTO); + responseString = CommonUtils.formatString(responseString); + log.info("on-search response = {}", responseString); + Resource resource = resourceLoader.getResource(farmer_key_path); + String encryptedSalt=""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl , fis , encryptedSalt); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + + } +```` +39. Add below the catch statement at last as mentioned in point 33 that try is already written. +```` +catch ( G2pHttpException e){ + log.error("Exception thrown from on-search endpoint"+ e.getG2PcError().getMessage()); +} +catch (Exception ex) { + log.error("Exception in responseScheduler: {}", ex.getMessage()); +} + +```` +40. This scheduler will run every 1 min. But to test this code write a test case in Test class. +```` +@Test +void testResponseScheduler() throws IOException { + scheduler.responseScheduler(); +} + +```` + +# Data Consumer (DC) Implementation +- In DC implementation , as explained in Overview of libraries , dependency of G2pc-dc-core-lib needs to be added. +- Data consumers are going to act as consumers as well as providers. +Implementation explained in below point when it act like data consumer - + 1. When it acts like a consumer , it needs to define an endpoint which accepts payload . Example Shown in the image below. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/patload_postman.png) + 2. Using this data consumer will decide which data provider’s endpoint it needs to call and which request it needs to build. + 3. Once a request is created /search endpoint it will call and once positive acknowledgement is there it will save pending status in cache for particular transaction id. Refer below for more understanding. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/payload_sequence_diagram.png) +- Implementation explained in below point when it act like data provider - + 1. As shown in Figure 2 data provider needs to implement the end point and also make calls to the endpoint of the data provider. + 2. At first Data consumer service needs to write the /on-search end-point. + 3. This endpoint will receive a responseString transferred by the data provider. + 4. In this endpoint authentication also needs to be defined to ensure that the correct user is accessing the endpoint or not. + 5. Also need to make sure that the correct signature and valid message is received. + 6. This responseString will get validated as per g2p specification. Please refer to the link mentioned and image below. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/on_search_spec.png) + 7. Once responseString gets validated data consumers should update that data in redis cache and send acknowledgement back to the data consumer. Refer below for more understanding. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/on_search_seqeunce_dia.png) + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/images/.p12-dp.png b/docs/src/images/.p12-dp.png new file mode 100644 index 0000000000000000000000000000000000000000..549df4178bf079c0bfced8e66b7af186692e303a GIT binary patch literal 8126 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ﯾ>&QIOt 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/Sign-encry-table.png b/docs/src/images/Sign-encry-table.png new file mode 100644 index 0000000000000000000000000000000000000000..cf0293c89ec26012c0ffb40b15eaa8cb8bd940ec GIT binary patch literal 13740 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/dp-package-strcuture.png b/docs/src/images/dp-package-strcuture.png new file mode 100644 index 0000000000000000000000000000000000000000..27d5d5c3800da81c50f599fd3f95189e52169782 GIT binary patch literal 25242 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 R1OGVzQc=*9ua>oZ^*@Q_kj4N2 literal 0 HcmV?d00001 diff --git a/docs/src/images/dp-scheduler-sequence-dia.png b/docs/src/images/dp-scheduler-sequence-dia.png new file mode 100644 index 0000000000000000000000000000000000000000..547113b7beda2f0e872032f0fd55eb301632fe7f GIT binary patch literal 97671 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

E0 zf=l|T<)S!>h?ewxbk;j{0x!gFa+>q696I>4@3R1^Im$zN2Ve)ejdIJ%Ey7qaV$^MqeHUbMm zC+_E|Edd#YRv3%x)!2WM^*sk$EF!pm)PqAaE2z8q(o~t^QvIp4rPzz^&EAS@QI2IJ zhO1$y(VS60&csw|Y1?V^2^rY;i!^f>uJB_{5d3vOp!#NouX`u?sLMC&tl@OHk>nmoJaz z$fGFf%pJ!czFcU+B$#$c`@*;AGec9KrCscIkK6D4EKmx08%NPn@|sv(u4KyOg&t8} zm8$)OM?}&!n77!!;G+?8-T|~*@;1riL^$?uRQw-{HR2?OY-TfMw{5%-Rnv%$)oSA| zyk77ig3Ux@rfFeuy0^{fnQ$<3+E4%KgaP)1)3=~6X%V20`~rg-r#IEgbg~Pa9m=jb z%E+qji};dv^wjUw_jc&%qFZh0U*F{ybN7M1d8&(_T{B1B1@`OY8h+Yjri)YbN z-l{?xE9VZmHDQ;`@8|j$#R`+Z%L zWp8A&LP7XUbV_*)8nMaHBr_g1IDzdUO%b;$Um_F)Dgr0?bE@5S;B6ZH(qP;mB|23QzpL17c+0 zJch4T8Z)?U5y7l}teuch-JquHLqXxhYLS3cbRShb{3Noj>!cMP7lV(cQ44!E)Sl+g z5Y@m@q32k4--}zBTEwp$1;euzz}ASrV8JYA&Y5)GBV%LWDwDr&4o z7gy+P7Oql06$@{7<{CEPN*M=Jb0F*{^pllcRpGns1H8R@Z7DNlM+HI?)Dlz*d^P&b z92WAgocG4kRVmSkp@G#|kb$y_NF06k|%bHSx_>=3FI;lJ~s+K zg`I;|Jt+Bmn?L|JymF%DkVP!AF?N1O7DNJ9O88%8$K930E&gxw0j$NzEih*J-6%_k zZoLf1Er2{l2b^IpWRXxE^z6%e^?q>0vw;k0y;Tlb@{DvRCURkbw#rPA{ z5xM(^5kGrj1UUyr%Efg=4u9iE=JE=KqIou#v3dLnYKFvSH~5$vw_uKRf4VfU35~S2 zD*as^$F;5|24$3e;x<;FBPB~oI0DITr&u`XniMEvaK-1@XF3n+CV|q{FWd7SUDW5{ zl%|mlbJW?G-iMekB{}(EredD|+x&aj*Lw6U145-3!M2Rs0CuYfn_C>N=*Xt9SB3#v zw@W@ZI4OCxoqT(P#uXCe`z*Mh+WOJu8PD=ji0=Ko$J3BmPuamLKPL)5yhHR0^*MKe z#v?ejBS_Zx-V!Dm!;>q2`nragCV+V$6zA9Buq21$!vkI0XbiU0+WN}vM`qB-)(Lq@ z$>5mhR%P}w5F>^lXafF|YZh?{)f`zXtO&}i{AmIgf@B%lOYT`z`W`+jQa3nBieN~D zl&}vn7K-`yRE%E$*VUoG--mJb;Ou_MC^8N7jKrd`W^FEo_G zU@}g-zrUJv)a{4#p6|Gv#x#Iztc+;W?23^!O^xmHEqdW%jRV&&!Uv(M*;4n(8Tu2o z=aOu5$AP@$y{&hmQiTvx_yngQHV2I*4HA378jkkHbIb{9T-?cMj}qcHW*Zz!wI^@} zL$R_xcOISSIT@CQ#D+mkKJ6_JIO0Ats5|y|Q|@4vu!sdqR|&qbx8IB>RIz8s*=~8YIg6`wk9~if7(lRDOoZpU(GWHvbNepSEVbs#5fVXaL*5&7f zJhu>Xtd6iphD3sEFxjX$W))18ZVqZkQGKh)knYk0K*1OeAdiC{tG94X9B2>grBGKM6nV5^gQ~BUi z6lyx04Qk7&9}auTGC7%io`HUyI6ZFgETG6#IbOHoW@7l{;*eJ;hy|$q^Fi`w%H!0^ z(akA2=mZ;O64zhOxsb~=mcG2%0=w^u&`S*`b21b`av?L~ocxjY4U?iTXn`Cyk1T9{ zJ52I5;K_RIQ(gFSEGPm5DB@T4Dp7yfD|~F0@@DG;R1?Ed-r$6HdXE-vQD*7zqn;U&S zt!B5sD$60Gt*7I1$Yl`OUf_XwEFRE%x>(s<%;;?wi;|@y936iY2gKyqay@HTQlyLP zt;?%@!ETjfTQxe^7;Wj!HJsW_26!y8JsyI2gh*CJAnV zoEx~Mx!dvIdxB(Wfz3PIG5@$C(T(DW@Fb*=u9 z<5`c*l+1@vQ1QDl$IIJ&?PyvWQliM6IBMk+|Rcj7qBxJXlczC8y$dzEGrQ0T~J`4#t{<(a5Az#S6jHbJkXa0N*1R4 zHE`@u8?jw)x%%6GK<{!7@f$2#fU7^;bb_oei>e+?f63P)lKs27tHTFV0|~~A>t)Yp z(KoC}g|FR2)yML;hBf!WMDJi%p7$fxiM`N1mA1j}Ca3%nyXkrT9S@t8cY!FNu8MTe zp0pNXY+{-FG2bCRS&2Q`jo2jd`coUxsd z<4meihSg~FB?~8w|A|UQ9(Y)cq5G8m$21#`S%@bO?TdZ;m%(7Y87>q3-_Rb1l5 zQ1^ZM^n1!MbM(99y3YCi95*!`m#x89oc3?v|rI6E$+UO zyL*wm^rf1pLf=8I$FzEyKo?{J8&=XR9pC2)XW>-eslNUSGLI@z}0Z4F{^X zuXuNW@PiSM{=xTrAHx#Ixdc&Lmh_ z5y+78JszmsX$WwCIm%3g-dJ`fJL{U2pdIrbrI$)zsL#2uKdF{it1_F^x=Gb?`I$Qx&5-s)&*TY>()AShzkR2!-^_Gbfnky~EtAZ_bH5Zj=+{9sN-W*4=rr>^eJR|8qzXJ^=I^U{lOpO!|hm71A6LHuLA|gFKrCs)R2868*5&e7D zW>N7gs1kpTZ^kYHNLN2S4=8NJe}sILVO@+W>FG6}jJaa=o6pK7>>DygyvLfI8^3W;8BW|d(M8u~`iFt!*YZH&55)u-;JUk|*rps-fvy+px zviB$re~_)|GFyDdz}VC#5u?K3;M17XaEV+(ZPpMu05L2oTdjM5yS6 z$d@FwoQZnWCqljI)5nA(nYG&K4_y~B@5&A8I5Y{98S%x8(^#?JCicWlf+I&@!^!Z`T9rNPxH0sv;rgYPwda^QB|ip!{i+|Ynel0 zdkJi^kjr9=M~?`?4dY?2-zq&#RJ*vomr1l~scR}_Z#D*rI9&3I7)_}~Qi~Y4ej_*l zV9f!@QHV0D&_09cnC^RR^TS7G<^GcDmt<3{9&)KDLMc zwto`?F_(44S)ijTqmJHs>hTG|qC~p*XOSVbAtNMZc}5^+n4ETctNUdz*p!0){8i_+ z`{*rx1eCjSCpB&czt!(@=uNL{);6cs;g&e$7Shx9o)UJ0xg)_|JLde+?4sWb&fay3 zUSMfgedl|;YneyZ0E&p}HnkO#jNw!xDYTqAqDlfSi9(eU;@r(k--ML8@HddKF1jeX z!tAUbKCdp+A9liBUq?(;wssTwp3Ulg3$tsHb@`$;!_{bGwLJx2S)@}+X2m+$2OLfb zJ^l#(>C3*v70-e@M!|vTc{At?)lq*+Uj0&o{sn|GLINe$#w2EKeF!W(b51B0)Zw;! z_0c$z5^c+?Z}dUO+X|q^!G~tjZ8#1c0(;edZ9k4NVp&1sh*U+slF0@r%7^~EY_g;B z20G43Dp=lba;9^Be3B+mtO>~E@KjLQLYc^0IzdW_)2z~8B}*K%%Sfm+0moTxJC4kJ zz<12aKKX1C_r{nyoZfPY_Yz)P`{&ar1`%c*Yrw0OPS1YKYY+=ELSw&%u*b)hJ9Z0o zbm9`5fgw)lFoaZ@d{=apdngN@D(;tW-{vfcMs}Mmx z9rms)=srYF48}>vCd|eXgEz_NvW@wm* zP;ZZmg3#$TL;907+2H%S0lac=$2XF1(ynWot65h$_PTj~1%JZKM9QU$S}wT~fdM8G zQogVJK2QAM;w$QpAE8OC27p#_+8%+*$pry|xO97OZ@$I#Sitk@?FT_Y??2&0MxWAo zUEX|kc6B9Z)$gpcm?Y-1_1YRDl5#j*>lvt4EmMcr+XKYVJ1(a!SsZc!kBjM2wYkYW z2<)Rw0;6{0$$Y()M{zj&@@~+~DbD%A@MlCbWR}|+wVB;#w)4@s5I&&v}RFb;K0+xT`a2IoC1K{n#XoQ1VR zndvM*?ZkL__775sp)MSb+B?Arzxeoj`G?`eJc|idMW|DAHEqecS}uJ19&bc@;X~QA z(w)qJLbjsydFs5iGCJv9Jm3)6T48Ct&Kd(;OQn^=5pvm9L#1VR*B{pti$9km%@EQ} z=I!yInC!79vE_L9XRVr70NUchX)i#F!hrkj?=sMY#4 z{G8fF=>mtU;`X~~O-^LpUZ%J8K0f+yq%Xr35_9`EsZBzqUGZgYW6*c@awE4mF7h0n zzrs1eBNkf{mOe?(lLND8ZJP{rXHllDU}#RMeBw@lZgAz8%e1(vOSxil$t**-^LZ%k zyPAJ)|9NQm^=&FBq3?BTWRkKKoL!=x0;ockqMCGbKd#vhVU!*4S}#M78X`IsGk`Oz5tvs zKuFj-IT?*gl7AD!i~V*R{5>3O5ex!bVEu7_m>Iqwwv!7XDklb}qQ?6Y-;>lceOl*0 z{E8QBVL&o_i13k3D6?-MgRPwoF{Ey^{^*lj)t6PfPFz}zRT_#OXO6=D&OSG)GYyMM z8JNlZ(l+4pX>4o+3es%s>@{Yi8GtMOP7#n=5f>L15)s5~(P`=F>1k;}Smc-iBdJ_0 z+}w>o>Ogt*OaKx z`GEKSpyWiJL9c$gf+UFf8EI9C`cjXz6l}Ks_#jEo(do4jYSD?MHvTQip|ZhO!{%ME zJJb5ia2o&rzA8HUCEzA+aZUYXMez3RzqPd*n+49RT4e#QIl#+Ovkfk;fZ8s%-aa`A z=d66Y)-g2&?K$M@lN-{on@YFkfe3^d~%9HhSd; zHkE3*X1&3$ppA`0DeVl zdw6&N_Xxbayc88<=H0K4bY5Vry0EH>m5)y=aO&Gf8k+5id>O#b1IRd&`E6=pSynIy zhw@SbGcB!tlt)tRAy8j(D3ZPJ4W}N=_PjpY+}cX8NaQcSrHPQlBv-ThIajzOHGD3B zF9%akxIa|V*RQ%8`ku6|3D|UiQ}`!u;e<9TtB-k$Qc{n<>fhx1`-@cK3i{m5`~BS~ zH2c=_^)s*O?DKFTf)ip;uvlQ@6!4Ue*5+~ z(`2LFW|>YpzXe@j*8Wj^GI(eQOCqJD9I0?xFt&zgldQ(r=L!xKS3j>hv)U71N!>U& zohjFnjC!wE>(N&2X2@B+PNv(`dA&rXKl2!XJt8h z)e-2v@ILQ<-E;o_{wF8)gKb~SawS*JS|)PTl-1RfE}tJD7dU{Al~j>kMgwjEb;ym+%JGlPzyBu{$rV7@QJP=#UY={*deYX40f%sy43|ap1pE>a5 zN3qI^UxmL$d-Yyic&0314Q=hT*jU4+*+>__bT+6%si;9@%XmF6Mi9H3Z(H*Gd?l#Jdj;P2|eB9MhRL@i@Xq zc?&k=3@@dQoI6rpo+6p~909nU=Fp%7`1pXyiM&Q_IEihY{Q0KW=lR;l=cEh8Wp8S( z${@f?Ntn7ssSUc43V1Rp`DO#Uga30!2EMo>HIUJG{nHd*HWc0HVp3nlF#6`n61z$luLhjd?Xk0 zE;~*HmiNt}?+)DE#cYu0e;lySw|dmA~Q$ z_%^Fe6a!;_+FO6jj1H6ZqGY`0Eii5_BAYwsH5|oxu8V(v&6IY~ayzz>|J(TmI7MKU zv_#%HZ$5%87zAh;oApk{yhf)lhQdx~<9ev`2=EA!$%k9ivdDXEjh8uvLYWKm_9cxf z^8`G!Exo}U`xWjNeD2pnQG$%jO>sBJxoQ z8_O#aL4~TOV|oNTUX_u+DsL4{Ezfg-bL3?5SF*{>m$?g3w=2UPc4VDGlr#JuQ~Ac{ zxm>oJN#slGL@Jth!T0zqEDOuOGPWcSORTRJZR43zcU2p1c@5P}YPY}gIZyrK_x4gl1^ zz`)2$o~Ow|d95RlVt{Vkw?|0pA9u!c4;Ov{OALHX_qnqOV$x~=C_(+`TZO+rf{)L0 zFxH@+D!0p?imYt7$&bXnv&}&R0|Vd^N))*eCMITv;J3RR@vsWL4qKw5e0pZ)_OFL2 zFE?A(|3oCtaYQo=oIZ^!3E(?D`j@RS9;nB^n;W;YW|#ehHvluOF8q8x z$#ZgaJOd)xJfFjCBrYy)%-LhHuC9)~+>Fa+J`OOWb_WLsS8CQ)_+HZi_x3{)xg2guIMlBUg3em<(`~MI0eZOz> zil}vZF7@Uu|uz3yX0;=E3)bAao)Gp;K7gba2^};=hp0&wBs|uL{v-1H25*b#F)V z9lZbxO$K2~4;z~@y+aI7*MWyxq0_G1GG z=exz-#jdDS4G6uO7H;7;r7~qr@Kn(t!0P&>k+Gmx%Jm9843i3WfHJ5drcV{bFf?<|_#vr}O}9(9h8$I;l(ka9`Vog0sYL?*&qLO@cChtKs$)?`(PJ1K#p zva;jJV|OCo?O!~Dnt2-F`|U1>OQ4LI0!qi`NZ#I8b7pBw(S6&QK5~`gc+PAXGL(Y} zOaZC8fmD)_jV&#>66uObW=u8N;gf+y^#3n`EV3Zc{f}+lOI!n8$lPLorhM!d0Obal zeeA5P_Q0O%{0&HGWhEuvuLpl?&Eyq2hlgz*ZcaVK0%JSjKf_7IU}_@}i)20w_w^C- zyRlk54<&N|2J}j1``80UXmN4z!R}Ns?k$e5EIfCg=xAbYu4I&|O0(?}@4o;;*qQ$; zw14R8_JC{!!BE}qt`_Ws8QP@mQCA^iI>7OqycM zc%pvT@Ngdl-q<3F258i=;s>|F(=}pzkQ>iq*4pz2W&Z@K?l_;P6LFO2@RN}9hJfTU zziXEzmOToMt~+w$)j#^OU=UqKDB6M2zi;?JbA7-TnO*;H?4V z0DPmuX(zZUF^P%2>{vFP@^8`qiLida((N*VnBAUjo=|@HTVo>Q@(pcgu`wnSYyq&t z7wvf@$H1umbnn%REZJO0s95Ut&ba6)xxN`@v9;^|Ohf_bl>!cBT^3|f2HQ)Xtv!M;s4;MvopXy!mvFLtbeb5e@10yX0~4iLfX*K@E^K) zNN%ZWxj*O7dIpaZvZ{VQkh}``09;V#d2Rc+6HOsHF$omY07Jjm+HcgcJOFv9(JJ88 z$Bd7c+u*Uy%*^!B4r=?Zfim&izc1OB_218w|L_hba>UVme0>iZXkc4X2(584GS_g*C#uSxuy=@`sR4k}?y>2_ zut7NSOMRf8yWF4Ur+bK!#vr8*Z`2@ zY`oG3iW>9O-{T7VA7XWZYnWg>ekdAkNdO4{hVl~{oUt&hgk$e$g4n zH7~AwAXk?7hh54&h5!Ej!)h>jO`0etHa7MiMbFxr;RhUhKm05PbGUB(=P%Gsj6Apf z8Kxf*o8*KD-C0CyIM@n^vv6I2`mi?#?rN;}Mvcx~{OdxzT%>lGL#_Gjx#4+rF!vt1 zM9Rlbru#?F6Mz{*X*@9Y0G0=)$)tF8`?$>P>}(`t-t4~sc>~(^7efT`Cwh)t zh$A&=jsi>M)$vN^j}t>c|8h%1LtasIjqUB}0~L=sdl#3>v$KJj4ERkJid0UUl$aP{ zQDM%!&#aC#CMD=p{&^oj@aD`la2}l>;Nk>A&HLwX7OBgTbXO*fpR`Tq&bL7!Xx|={P{DS&S%Ke zy3iA%{J*@wE5MMN?arg6-t0_gQc!e|#}^BO(fPX1{3i_yUv41}>zYrj9x13V5@$Ww zBj(i`_s6DuvO@5Q-kd5{?g|4+c!dQ8-57f@!UQ*2Sy{k=$7qHCNhAbFzNMx8_g^a1Awu1DYS@KT)xZk8zAO=oZKHh@`q zqLx-OWfUICS1T{of}G{mS5E{?nqZ?p=%IyhKXOfoMTV0(g3lBJ0mSpAi>CaDl$4X3 z+okG)MLgrT-De9FI`R-Hgemr#-|;xc?cjymNQ+Yfcdln($Fdzy?)qROQ$tIqr2LmoBG*Jay*l8TaHX0|&QfOb*Tki)rXIll>V z6#9VkPnB=C7f?!cK3Ul21o&i$J2T}Zn5YWCO{%y0A9hvVp@YPA^;e=imHU zy`YT~4ZWDJ>3gu>)Ue4_6~Z{eca=yfsWc@WdZWV7Gj-*;^Pk33PN7m*pBXPHBi-A_ z(D6!bYxW(c_m}1Cd*4sL^H;fZJz5;s)1sjMsABRV&x0ojLwZCnr&HpNgu`Q6N^D6_ zwdJXU&#M?pcM zFK9)~3P{D<`H7hbrSGxb1*SDK4smL=t_~C-@fMYPr5IO01JpA$lVQE-5NYel);nd4 zG>#&MTzjGT>3|SjsB>2k1_`&;j~_qATH1$Lb$*mc{a;3LM=Nv$fi+ACy~|dJh=|(T z+X0f}&}&_3WY%jR>Mp=74^%%H3OPK){Z{R=uX>p?G!V})9WTQ0is%4HnR;~Ey_({m zZd0BM?*TDGFInetX*u|-A>pVrG*7&;xNu%e8-TMijoJbjY%*Ik81HEC|3j3Zksg-T z?!o*hV*(fdAI{!8p6kB-8<$kFGE#}ko*5xokxkhvWMzd^5-KyIP^;xS#LFIllLK-@p6uyZ*Ya^Ssi>`~7+y$8#OW=iNKd55aF?@41RiO8N_L zR(nlUeOsQpSF<$Jt~0VE)y$-9xaJqCu@b%)H!wOFce@}{<6`^Lk8hC#+uPys;b8!7 zFd}3PnFFVLefL;DB$*w$JxIllD(%Tla6w`$Ro#v`EnqXu$CVm@!&tz&`^!75_erS_X{u>`kwHf36gb47s zn8TADB7nyG+Uu$7wc;ThRqeyyc$J{~f1TqXZh<3;vG!1lgriz>8h=VBf4!Dp(n-$3 z`_e&6nRq4tHTw)bUe>4z(GUNhUw3Yh zJ!owFdZeQeO3GGd=F$FsH;9qVw8F;v`Z1N2mEA%BZ{+0V;Js%_oYl5H`}TmNHhj=B zrBU&wltoCZ!%8vZRXDINWr|^To}@dW~@EL3xu>XH)h&73K`nUXKKyt zv!<;n%B+oCeG2pkh$Up?i_oJ2?%5TQqjJw~NWk zXOJ5BF$O(KED6OiZVnm_eOG=FKUM)%z zQobqtms8UoOYE@VdZfPBGP2MaGg4 zH40kKkbgo~mzL}M(ACI?=Gi)_l5NSKykwx2iH`J4t?Uu_>-TT9`&F6IXips_NhTN= zGk*4V375os_r9SBIlM^sU0Gf_QL}XS&Ydet7`*qZC@;?+vhw>rh~MUT&;2z|hMg`1 z1-TEkDy(2H%h{pUEF6m!T>X-glGg1~1*e}*4zF6hHYYcC*JJ4MJK?#&7qCL>uRr&F z?#tVCTFZmN{#j8YTkSM8TOJ0iKK#zMAtE3SW$l!QM^D+&&3%1(`^odhJ4?>H^STC$ zOC5R6BFZj1^c^bD*4@teB!&TDvI0|Yh16==*$OC3#VkTBt^T^0f2)zzaip(ENl80R zYW%9QHOO)m%zzt5bKxjFXO_!t@*0)3(rgL+cZ6ezV0;4_?n2{PHI6u4&HIzVH-ZYBK7^W1$0&Xicy_}M)R zdI?UHeM+2{H^irFeWQ@ctq-8joM)iyJifR=Mn>jhaIlejEN;jZe>j%FSsdR^RDV|$ z&ihp%yyJfswkH0K;_DaVoIVR6MbHe0FDomnohEGrH7Z~4Dn6-Yjo-GXsnQ<&^G12v z+f-snGQup`2nlvKj>II$)r-bP)QRtqkiUPQYS?l9yq}+6CR-?rv^)9y?M#h1u#HzB zviJaSq(&+UDI(09q?P9HZ`5a!GsRx>*M1!JF@2J zg^L$g7Qem_5HB%k8^FoQ-!{l&cla<{oo1ZIk){|fG%7E~-}?Yk2;2>QSo+<;BZBWQ zuev+^_3MeA`?=#*0u~R>==tYL3kV#=g#)C8{(G$y=L+QOJh#GL&veO?q+4xMHj-y% z^OAyp(9K-S3&mVyPfyRiV{hXV5)SI>-oo7h6u)@k!gTDF!%ue~ea#|!W)f--_C4ij zM6(_Uxusc|Ms2!X()HX`LpFZ?{XLuV@bMsADf#S5s(BB>9e^k%q zPB%S0I>|C(y`i9Abs+#55n;?9)Ivd(5i_uw6f9dXUyE)VozbtYmfB)c5BLe0K@#l@! zXh~=9-@l)RMY0v29}>XUWIS3|W8-*x)}MxlGJEB(KHT(%QIm`gPPV#{RZ`uo$9Yfn{a5Jx}m-sB*V|T-0bgJ7tr* z-rwqo)WYJ4KRh8lJ-#7cg>nD;;Z00j?JBd4e}qiV6McrygSeaR=0h!6v9{sgmXTsC z0YsV_B6p0gNIYS4Us~|^>BE>}L&d^|G4^O7!X5dml7P$q)d~rs0&J4KhwB$Ox!VSb zdg5IL!}jgzNl8v;$11|Y!bH;raW*7nIU^N+wWRl9`lRiJ7mvf?)UyaZ9kh)85g3`Vfj+`*WX=W z`r9hmHck_~MmE3(ZQ%6DlNDaG{I164<}Q8}%zmU?FH)u2p&y=1)=rl-y|&9yLE%x2 zL0h$DXeVnKVBW3bm#dm-(A}VBtZ^MPeDsR#(u%mwVZ$Qm9&l7$4Ugy=Nz97L$ap`! z!p9xk^y0;dx)3_tBYQJF!7#fU@G7tS;Og!^TuVk`_GR5wFqlKva&@$f*igR@G? zS5?EGHaCBroJ^EF*^lPh{_(uyQ~f*OjG4CiY%56`+Fh`F>O zsvq!NlwxVOhd#9XHr~V0g(e?x!M=MB`88C}Q=eKI8ygoFeQK3TfSdF_ltH^^5_7mE zwrdw%viXBEyF#OqpOPwa2|4x8(Dva;B2LmbHpT)4_bGJGD;I}=7&r_lt_hjwcg@ew z=jZd%JcAU@&fbrVg3?8r4xZyGv3TvdoSfAtOHeO6icDikgMx#1-G?Kv_DJDFX{}O! zWb$yI`(Y(~d$gZIXNp@Vohr7c{N#D-90_2H9nDvh3wsX#C zp5>K)a9{@2tvjstME9N0r%&qYh?GGM)TNF47+)@P0oCj`9{oq5bS!lL(7Wj(6j=9cm^%%|^>r;Gq1#t=J*H_oAroIS6<6cL;-s7GgU!3uC$$m^5mRHx4 zv_cYi7(DPYM}q-!oTLXDr7Ig9gzP3Z@(l%7MIifdl9+psb?%z(=OwGKYoe#nyoGuT zSUi7?n?s85w}7zl2FJDfz$8ZQG6`4xtBeg9V{R%P{M#9ikXvE?qd*Reudf1<9Dz=V zr2@CMT9eUVPkC_JWO6Ft8aBD$GwuuH;^G1ZK=0kUg(73sv5__nM|$a<-rBKIJI3PD z(x}ZkZdfosKi_ojfFsq*T3>}u6cQGBztjxQX*|X3Z1&lAIoh!}WZNWMwwzq0=0WF< zeKU~M>Ik((Ccq+W6$gsj`I7^3&ad+QmKO9#qN0~fO-*w)#3{zQ^6b;JXtZeK)XSfT zoB&nrfHgZ(^4CA_G+JYHXjojM!;$6b`lF@3pdI_-YuAyHNlHm6#GYSEecx|Mx=!|F zzw$OsV%HBA51A7+GKl*aM*A;pM0(fi*tuR!$WF2EWCr7p*|msTYXy1(~m;XNf5T$ zE^0?ERqF|Dnv^y{$U^#XZ4lyRplBrJBgL$)lTfXoPpl%tm67(IbCDG0;W>gmNhIv$ zs?op!`?rwlu6H^=tf&pc*r&H6nsQk-;HSGzlG`WE$c%trApKuIF1i4Zrt zKWiBYJypszOuj;dhpuyzGFViJkZB^`vBYxpCxB?|`%528PrtF;5Pw)w#VLMW zfhNY=C))Ei+&OYKGv?{DXC3kz?5dC?0>=7mG?6&(IIc~AmuNQ1vo2CEK9>3 z0t9&q;S$p@i>OUxUHdJ>?Vl^A#k#qb8O2U6u zQq5fZ{i~OjmrHVS)gzM9{AuN+g5&fT+F#j`((mqU1-gGhmcPl5^t<-==PiqwXSCD3UllGvdW9iuUN%YlWoO-l(LZAVrpBw2ou%uM` z>yHv}s?_Wsb>>^>w@kqG^6>P0Ke^Qm7tqhI+Fs%}3NsS^ z1;TFf&PEnIuC4X@Hoi2NU{!ksT`)2-@`bn2-ZPUAvuX^uh;Tu4<4IxzBZEC57suO z$V|L^`SQU7+2vASz*G6+AGrq-01ysWyrBzXZvHHxyvb{DSkaL0FNpTf*QBGvyyMW? zIM=H{(Fs?sA8|FpR&&8z6#a>Yinl7oY^eRc=^RZnS=MEQs?)n7RmK^xirn z!-fkKnbp@-Em>`o)<(D#PJ$)^QC0e{_-SivgY-cpX}@le2X)EY%gfC$_!UR#DTFiz za_KqO#L=m3K;UtpkxoL=&XO_^Z+V|E&nn3rdX_!M&0Sn}%Fm7zULE-iz(a&7Syh#l z#jz1fPOe(>hj>E4Zobiyd5t>!2S;+0Nh<1ReRzUjmGOVnhbGa-ljk3^R=~m82D$?@ zzo@CM-ifR+5JSLSoFC8ecW2?417YcQLlOujHP%iNUGt(D4nxJM&$v2wkM~d{Vd>K` z2tTmxaT#PhMmC;|+$LjuZIvn18W%i0<);HG6qCk|j=b{gbs{jIv3e~Pi9XVbkaTXC zS12ks7C};QdF)EYarNQDeeBxF;@5#K(alpA<=6upR_8CQqiL21#A7o41RH~37ne6yJti1Ew zIp4I%^HAKbF$yd2PMny~k^3H$L3p0@nrj)q|M&q-ZOc=#oX)QM9!Euvf>fN_{Ax|O zpvf(gu(O)+?-Ag2SpLfE`q1Ok&N_1GpMbMlb@d#Yue;xgn>xwz~R0k32C zQB_^5t6tsK*4Bz;vJcrc6v78+Mp@*1I=i~QgS~^YCrh4W=X)5;$`_)cN|rg9$>tAf zwEKpI34^8}>HEApBqIs3q zZd#4A&Tnpf85kg=FyMT`JP40E)A!T!v;&plOk!)tp0~6FRb&y{K5v+m1d19+P&mDR z6q+->9PnRy;~!1h z7h11P@6>&V?|Og@gl5A%H^Detoao)pI|jVz_4`3h#>wH)#E`kO{f1ZR5u_QeAidhd z66oIHzadX(gfr(Z@Y#@^zP5BP{rfV9h(h8_MnQ8`T#rtZTp zgV*A%7CH;j_tAcNyw)DQ-3%|_p{`kGem=Py7iD+A9H09@gYM5Z=SKc3opL9fwHR?5 zdpoSCj2jkBM2Q(foCXpX7U640WpUbl2`NVC$G!&=;`Uvb|MXEZeamfwO%a1 zcg>nLxQA;S=PRJzIEu2dWr1#Sx?vFD7FDuKYo-FiH>)5NW!+nsY^_U6yLB}!s$DY^ zy*qiRVvyadn^y8+hPNHGN)Kpg40jZ6ZX6mOuCRVY8irfWEz!7jfKVb&Hhn`S0zg+j zeEtEe?9eG_y8|ju2Nd0h;zeCI`sE6Vh)=3nyxM!bpy;V8$?Q8jAD;)Yu?)Hglwj$G z6%2n-I)Cotm012OPN$3Fg)Fur4zVWmXj}KM!tNsQM`=HOv9|v;iQyfH-rv7|UTw0n z@TMm0(xpfiDNzK#K2Y&;x>{R*KpqM58#K*Vj0&|oO-spy$R@b8k_X1ek!LeIw^KsG z6L%I!JeVd+S|oyc&z^fyr#_LGSVJdExO9nfRlKkjjrsW7dm9!wiDQMmj=UtY$#HQi z;|F%H@&_9mAQy#ZOEnIW&{12P=DT*H$g2B$F`wbhyMp6(PHIHaGm1rTZoG$bqZ_ZN zXiRp*4*Bgfgx-eieN*5A^7V4H&o*wi4(7(3+LwumjEsy8IyVp9iR-19|M-`m zf1N9f`xA@z0240^B>+&*VNJy6cn_Nc$6e6Dmp2zsr@A`HumuWi7Zwe zRY_9~jrB5eKJ&DBIZC%Q;oRsgB1d;`|HT@Y z`==+ibH(P5HzP}~o4|HE`)**n<@* z5VOY@s6mK_;e=PJ#R=W^cxGLGR+*=9{?2Bb*p5RihhuMEo_o~=yF+#p88w>eNV`>* zXG~I3h0phCX^)Y`sYw4tI13=dTNoL0#|s%)!uvkarLpP=-`hMrGd&_0aZBUz6#Syo z3ba93)IB?bisC!pcd>JT&`FUrLaUeeyPA|D?Vi{+4O_6Mpi*On>5s zWp7Rqf7j9O)>S%bHXy{Ja4A*DdF|t89MTF;@~ZHJ3+{#BdiLtfGuCmh5p=u%k0 z>UF6zM`L*@$V$OQzm+}jQt~6$uOZq|Q%=Hl+=cffYq_2=MclCJv%L2+A`wM1WU0Tj(}gNCs|V*4bQp$mY*+a@(vuyQxGY_SlNw^XJde zqF6BG16Gei{|3GlKoSfnCs+(C-!oRE+y)vyeE5LnND--$T}b8X?mc+$4ahh34mnv? zR+19iZw}9rgD+k@^iq_>oHS~@xo!EeAk5TY(#El@sv?@J$HZwS_5R-Q>oKqUAG%~3zSQsfQTl^Z{lhM zgcF3-3Lk7}3?`Ew&FwjxnG@1vYSzueif6q4)SiEw=w6LmO{jjQ{o~_uXoK%ppxRI^ z!Hd$92wmKb;B*}HXvnY3DdzXxuVe0+FQgkYLnc{dEid%BB`Tad_+!8K3?M~0Kl?m*Ao?9O{9tXy1*%)Wj7I*JJJ(~zCPY zwXUn_CA@Rz7+iHLI3$~`d>*~V6cjZ~3YcVwg;sli6S`LJ+);pRegtxI($WjiXU8Pn zhjiz%zfKW3g6vygKRp}mtyuwYLU`_G_ZhHJHl+)hmvQF~FTqwS=XhkWj1%cSJK{0a z%En$N%=eD|NOAp;&P_&^fH`+INzy-8No3)>)u{JfVXqR6#}<1nW=jX*(W6{otPpRk z2;sB+H+okUl^7KozrG&9vwL@kiCU||&ze-xoaG?$PBxpt&ZnEQf-;Y&@J)*o4Tqwj zjt~P3i6VW-&coUMIM|%f5$o%xM*acQVXv<%YOzW1IYxKg|NGCEG>r(_#C|}>>F?Kv zupf!F5om`Wdl9w4@79tb@1Lbx$%Hxwi?Y}txceDiK=BLKz|}uLLLK*);7+^QO=_Jv zbQRv;Zp6kKz;Td6yaA0BPnLt5o7UG`*oZ_^s*}ZQ!mo(kWE-e-1<8wj@ zhC;tIUO53rg7eKe%0N~$PRUS@A-aGf%!QWN6E3A5*7m z<>e;zk0S=y{LHep)<+om+gcD$&6)A9d&W#rcylcU1%;2VZ(9S#8ZU2?dHv!=B$dH6 z&651|^AA&ZCOp8%qX%9LZmc8}d~9YMy+rlY_J;7nP48FMY86a|q!?ShWcC+Ls<4Ruc3|A|C?YhJ z3B*>BVbi9&rzb2IuQ{h9=rjN02Smv$EfCvJpFZ7StIKj(3{wf{rB__h8pg)P;HkX? zN+)JbM>+(gFTNB}(aeX@cOT7etD&ZWwMH0w9cyiBZo|A@lt3aFqnzA+&csYBiWZ zzUu1aVV+1$C5u6^H!Dv5G&or1Huw}B9Bsh1(|2J8{aY-T?)(C)n53j6j=qpk#3sFN zw2z&u3^#A>9~i(8t1t9c&h%`Em^F3|4lul76A}*AEv$0>_H0it0hyO`oMskL5QcY% zws(+i+P+;|kA>3y$%)W+!5og!l`Q`|h!kJ(JJW$olt=~GQJ-b!OK`!sC{t)p4JaT@ z=U;@w#@hfB2d9DTc|$WuAnYYaNUAwkO|ML%^ueC^po*A819x;fZpYWx7h~w!2~^<$ z-p?%OBUxsfgi+T9VXaR$b2Ma6kF#)b9827EHz7GWOfLHZ@3w4>svKRv4xaEV-#dX- zt~Ro9_gmFGd+pYGaF94F`}6sC-a2fkXKt?9fvSzXgEt7IM5qQ2Cuh_uDb?al!j_sU z7~y)<0dRTU9tzNWFDh44=-hQ>6-P%$C{{m~s8)%I@YcnU3K$n5t!wn%NF9N*YXTwb zwCcD8d+SdRwXRzIgqv^O=W9|=xt(_N#qVcyIo4I0Yt+xo`dUXPr-nb-^X z(%#3U3P>eAoF(8d zcb0B3#eSFE4vTFP4up;XZbF5_v#kY6;=7z?jYJE!GB7A@u=Y?F6g1kkx(lw-)#^R# zTA2Cjwh8j_x4A8mNOVC0MTRT>u5EaJaXohy`m*DmVex4HqY$rfy>J?W{>C^+q3?@*8gaQS8eTMhawg?n# zq}@L5YowM4b;>g3HB?o7f?W&5m>3Q_85voWl(_hnZ+r7caPv@x!a4a;443UnnlJ^2 zj&9?2hEoO>Q*V8J@Hb6j3}{n*L|tfNb|_RN{XJA!Hij2(#Kpx$N9_0V_QoW~`Tf_E z{pszA?*!w%9huy+BapouBJ!pbDoV<$>%|$2EWlO0`>xnslM)ZRrYD)51VQpAij$%M zHZK{R+x#l2Dt@^IwXe;1t~BQyw2{GQ8H2YqX6ZQ$CDz}$0TzM*FntLPsl;7-U!47l zs|L3*n_27Au$l%Zoy75XCUG1@_b8+OMH9Q%38Zk^qb?_!cgSu^QLb3KVfzCVCq@Bd z)=T+}@wZV$0IILrYy@Z(&vC{p%YlzT6g{w&5eg!auv{vKOohva$IPO3A(fRfPv$xf zS`pgf$y8B$3W{JJLMmNS{J55eB}A(~xUrW_>Y8Po;uZe?Utv8YpO5)YtBLXxZt z8MuqP+HU}yARM-_naBsOYPHySyrikNwzh~LBbT?<)4k#@SiD)*1XNY1EeS0$^eMO2 zr*FNmZlwID)VC^H6XGjIQW^q_w}@#O3=&UD@t6{Uscd5BR@F2;dnPC#@bTtCCi5T! zPs0w`_{yrr)adAdGkwwX48kSsKBGv?b=uk6bJ=WWV$$e`GrNn^J?tf(+xgTfQ8BUD z*}6LAmt6@qZG10@4IizS2k-X7(s0YZHNXeaSx}Hm$J0}S?ny&KLtUNjm4(zuMo+fY z0(05(-(%bWA>#m+yb+fvM-1g*xnVO%^QA?_2;1nqfe){gooPhtYPJMz+tVgnNL z#e!pF6FBJ)nVY|RYi_UTA)Yt#IMi^1;g_Jz4u%fNIu}z~-W;Uk(Z58|&YOI$tLE9# zJ_1=-JN`o9*C-%Y*>MmFB?lddkTZSb`dTve%Zv&3$c zxJk3*8I8(BLqex{)wOP-@?O<1xoLQhVhzmR*CP&ABx0Jbdw2;`Q4|r zy!2De`Pgo`Wohvy0Y9rcbNX(i`+WiM$aAh)WfMEG)@^e{=3rSK<)iT}$G5kw7nS=G z{4Us_3W&{cuy@0Gd>hdLb#;?Fj%{ECm_zkXYBR3m?xiLK7tup+sQyS6Y!aqb^>h+W z^rvHc>l#XWED<_YHFMnNT|c+1&jN%+1XKjgMP|6X23O@MGIT`j<=4uqZ`L<`i-&1kd%h0EHjo4BjZWX=H*LOY6h5LSC!Dd@`}I|| z4g4doi{FBpV;riCvF$yyj)K{OVfCDpltX%YZQ#OL`a3(OPS=MqLalo>Zl5p@-3Y09 zqebN#{e+_B?98uL0VWEpmw)i)cBSU}`Zcpwur&xt{f?u;Ln=KR=>0bzaOIyJ5W4tx zZTLg0p4IfiH805kRLR~7&<-O@nc+p6OameC9DDyA+&(xch*#wdvH?dje6M-J2qmYW z0EaZhLZ0#d&!0a}Oz5nY*b7^{q2Xef>l*vmk?ZjYwSCC1|kChtYT#_;} zAFtpoqg%rtR&)(@MjgDOxpsf1=DX}$w{8^`QSt3c@;7_+=5h=_#d=Ytj*(-pA%t~Z zL2F2lS;g~pZVt2JnI(3aCYCmS8*gk?Ne9Kz{|I2wq;#TnU-qCcbKHA`=r)!@peuQv z)$rE!JL6~kyS8ySQ8CZQ zH>!stOEnhQkyX>lDVTdCpXPi{0{Mm0+dXP-5!y~wcOaKo}J4O zlEIPN&qL2BzefKOm*4%Pu)5);ADm)f<_NHstTrroLdAuEp8mEqi*7JJ4b={bJjd(S z`vKetT3%=T|fyR;i8BHl%-H z0weXtYfPLEtY!jjW|xm{Nt4+lE1Np`y2z9PSj4Z0k}I3NJ6|qb>3N93ZVIJr)!N4y zL$t}NQHB9cZF4vc1YdR8c)*G++EC4*!_`dT%={p35Yq9jq7qqgbvm-jDi@%`>at4j z25zs5DBz>M%_-ZjmWBx-1&^8a9dz5ZuF@`O9gQ7p+@;7eylMa2QhQ1sV~OZTHnApe zoWy1X`)|m-%k?hNSVpIatKyBd4cbi4JQuo^20A$`4Hi0@y`f{z>2fx)AHxouQkWIBekQnz3A$?oXpl%Y?D_qJrckgMf3GwfgXoOx|On@Av$ zqU9U%c6Yzi%`dsNus!$#>mOkxu&;sjj`nVvKV@y3gYV^a$86H>ag#s7z^ER@015L$ zHuVh2liA7B#V+(EVo5b42-_Jzbi^e1ZiF6Q<)P$_v{yfUx_@Wi3;$)Y>fnXUb0}RS zJ(YWC0$e}Xb#kUFOd+j`47?fm@Y>ib_q zKroPb$@ZY$@F00&%Vyh?`=rDPTxxW%OhY>M4EY4cP43J<=h(vbU25j`kr&riYdI7e ztSdyZBYUi;x=!QZiM2&8F@D_3pvVkQU?Oc~VhZ)jelssU_2c{Z!%wIfS$b?@CE0CB zUPvsx)<*b}wlef^B5!2U^Z2Po97wy>4n+Z?Vs~ z(G|CAodfOWoOQFil8(_IvA5cJ(SrQ^MrAeM=FsZPC`wu*c`hgEoM)z|6^)|~8g2EM z(s!brndtRL14QoQh_HR^D`J^eScbv_$BnyRnvezetdDfREGR3>avS3i3hS;X@8@HR z%h3s0cc@=DG*QUDpnI5UR_k8V&PyR1og*aL$nKU3msTQ(k$WMZ(Kli~yWmxbHXXm$ z0*=^MxGX_Lu@GUzRaR3o*!>76G?3hN5$Y3nD>YF_87hLWJ;>$b%U9uo0O zz9z?B>>TTo8w73X9i2>`fH8u_Ei4=B^upIgkgGM&%jfd##&U28PiqOOT(B-L(4rR`(Q#nUJGlP5 z2kFsw|ut54P1g8DLV*`nSTvbF5i zM}Ti%Utg_H%*qHiT&H`ZTt9#5#$gpy5p**gfAOb*uGWSR>a=NTJRga{JUP&zprBx~ z(JeMHF~63h1exnZp;0*Vi7ac+dvB9+Q}*58@D$OM#wQfq3DMCdfFb7LXI#09zTsHU zP(`RjZIgE27bC*6b++epFP)UWin=&;8N2*86+g@57OQ<%9VK@qOJX&mXsabD<@)tm zPFMD&y3=isZYltKZ!h~sRl-#1l@`=h^ae10;oW8}88SEQRy zORG1cviehybkgHR$3ipSf6V+EosH33Bu1*k7stL4HGpYq+rV9p0lij!X!Y44}MtoN? zcTC+l_g4!UB2DEFDD+_6itW5U`D}8_hN+5^AJ;xPUmB(58`s3Wlb@CK+{0V<#>QNN zsNP<6_4uUH*f|Ib1a|TwPo{f355?i8vD|S|?v#aFf-w6F+T_C6wMkV^=SM%G=Lvq` zHA|spcYf7`x}-no z1$SA;a(`Xt#zNzj@~}~Ja?AEIs|vT^+F@GJSbe(-eg>!2qBD#c7`^!zuU3+$giY4i zhz;~nldO4ZQu~pl&33GCJo2!B|MfIm*(@0wMSHZhG}EZUbsrjC~xs-+?|r` zB>9y+U+2!v53?}J`>G$ImmfS-uM^aK*BucSj+gy(X3XPdaZ ziQG$@q7?Z(O}5<^-VIt?ZE0GJoP6ITI?ebO6+7K$t*Uv8gybBFhO&a8;R(7F@ur## znuiJi7{vzuCnmL@+x5R}oY35=trg-?9adhFuE5VkTvF!3HEYqhWzO%+JqoK)-)RZo%iv3xoB6)*Qcz zy^qo>k8ncmct&6&l=LO+v{>xFB#hY7YNU4Beby+PvHj(s5R7lj`q`EqltByvNeT#H z+v&mh13Z8JFaOLIAV}#jRCsHR8fzZq4}D8ZSXp9UK7OPAA90NsfBC+Hv@nMohqM2u zk@SBxP>PC*3}2K=P+ah%IQKzv%M-Wt3D&{?%rg8#3H-_K$@;!2{t|Quk_#nl+wTwp z0t?3$_e!Tt#@CcbVc%WafQTi=X0s{4t|crx=|H;2-${)cQjZ*IFzOq4*nw@}DYK9lJs(1n;Na**Zi1o8bC2H^)OPbi$9vEFm#wg{cp0Zn))!*&zKQ zv8o8D_84Pql{Q*kDv5ADBrSt-D1F98fPp7xhV+bKFw$@VytWz4AF;3qOB&3j%E^2u4WMO`O@eNXUxV;$5(AbqE95^>K z)6&#b;XWLblJXX(4g*Zw+NtH(YJr$kZ^fwweFx~IQ00%n@dYDop$bRTk7D8?Z$KSP zVm9XywHFtunZa2`#>R@|c0TZ1^1^qpo&JF3cDrhzO=COHF=CU`xmB=i_N%Ms7Zst9 zjKKZAK(ozj@F`=rB+&0bWJ}Nz{uan7ZDFW#U*`nG#b5RGr0MFUNyAs9r=wHQmMzQA z4_Snzgna?R!SX1(sFfIiAONMhdL#VR#;$xl4x@Ug^^}y9*As5RKpBckOx$w7HarBP zQ+<4U@Z{WIPwcnr*jJr(Yp`&YCXH#!^XE`0-2qM~D=<4!gslM(53vw+3c{Y#ENHI6 zbBK3AJH^C^u(Bh&Kw=lZ79d2e-#I@$w*^R(@Up*R_l}y|@wa!W8B|^&@|~8PjO3b; zrR96HYAjO0eozQ8`+S?tJGk*Z#uxXq-+UGLux*pRLrY?GdV0hhTg1q{X6;nTfSq1v z&Ybb~MgTWz-Qir8ZpBNJw&w98ll8DwSsRFjAj{Y)XGsX~o#1~LW%@H4xel9`yB$odTv=64+FoKvs?3GouN7kuUT#`c#c9RJ^m293o~#+DaNO-?X8ItKz)uR~rs zn63c2ah*(?@HDF(_t!V=;PWC$vnLT_j2AuI1*L$H zP$vBJbLY;1&9=vbBHSUe_jvRlckl@chM#s2XQ|)0bLTrmA#rb z)9~?;sTvY_Iv#cBq98lD@v=dp#-5k9xg5- zxR6r!;>wxBv571KCJAv`Ks|5m;u|sll+Wlw?|~qS;-}U}moHz&#jLNZW4(ky{N9PQo@kW^w3R9m5R>4=!79|UMr-3>Kk&8WB`kn- z5DX0sTf-1?$xeS2;l6cAY5!IEDq~kN*!LgE|FG151a;v1x_30kGwlWBLOLBZa(|EI zfpn(DCjM;}k8_LYdr1nOKYLc+r0KA(e&s<`)eoG8w26cdJ^=DY2E3okPAWP%@g;TD zdp%CUN4kwMLXW*9D;IGlVW5{jx8wa8uX{JB`?n(dyD}q+W`(E>zAYQ78C+br9hlUT zOOsWlk0Ji4-fw%fpf)}OL+&Ng1`vIKbr^#W?P8`7!xsmbQ2{T7n1`%$3< zHl!kq!Jqr!!6L4+YE7u}nFCv%OqWwXqWChb=t}knm;Ec%N2w-sXp`locZAj7uJU(q z^H0)l@yGg_K;i*vZ~j|_{tdp631S-MnZ*h-JdQ_pUbqAALb*)X{vV?lrK);0gBFIt zy~|oU?*|Q^x%@~;$FSndm-@gB;Fbf1!H1R_%NY%q-pa42d&KuNH#hssh~-}2LG1-9 zeZmm=Oag>u{&5tlv+~Ceu1540$5Tp53TOQz5EaZYwDbGeyw+R(gp}t}NC@j!Oxi(7 zG6@X}E1gO|DKB4xwrG;)%$l>!fp-ziATihI0z`-F@yLRvNuA;r7LMFj3_7dU*Oa*JR6PsjgaB)kg0eS&bJ_=wD>HtqlP0b1qH?>Y3XD$NAhQnwsze|G#6esrf47% zk#0vuA0X!Xm5s9{>{|PFU^Dq)DXuoV*~azG3i=)4e27UR^pW!&Nw(UYj=pOxh*1?( zy{G|5K+iB3mRRGuem;E&(H3#UzGucn%JsdiCMBX#01AG3bcq6 z2$Yh7_fVSU&Q9lKXZIu9P;ryiW^1!_ZP>}`12s2}&g8wJh}f4-WAS%s|A*O@1{KnU zeuGks+|rCe5oH6E!}qub*dm0I0EK4k<44;js}Am|2dJ$R$k;itY`@^0WrJ0UdQ3$w z`W8JQEo?qlqyVy)ZacAy=ay+sZte)|^GVtvfwa1@ zu5RX>O)mO-(TB8+5o?x{wPFI}N*Rw-!N@;)tQ25%=g!L?5Z=`etLNV3f}=RgB={_K z_tALsvY*L-8sR_k9~vAGdiUL9%*Hqqdo4jqT%rEpzgNM5SPr6LH|b2d-_>OkoBtk> zCBMZfHJm(j{6jAKJ!H4R7=T#Nk!Oe7*QMB5&Fo@j{QyM&U=M`e4n$^?a#_DJ^XR3ef}<1Ckw4(w*xc1OqWe3iYI?C+2{w?)Rh^ zW8cjsH}td6FyjxG4An~H7bjN>nDBAY_MsdNr(%^w?DynmJPxLXXQ@O3da(LWXzqhQ zYAW2a`)EQ5R(?$f>?oTKRk(322ffHL=H(b25tFjAvbr40sI1k~bJ^Uo!Ywy5lZ3Ib zs7U!1aDY*t-M&p7(%gB+N1rTuPP`9)h&36z4(3bOgF1&S?wafC2l7K>b3`zA`}XZdd#YkHBYBDKX(@XnIfR60_TS~ zNUvi_W?5EAhvM(L@USMmg!o~lrPD-Q3X7NKKSsX2<8(Fs)dicUzvn9IkjFTY`i4C4 z0z?kDtM8+Iv5{_jMQ;; zz4fk3@WH0PMFj>124WCmu|IIqY|E1;_Z^h-R}Hx9{b$J-7hSH4jc>NP{fY-ga z2^l3sbzfh1K^d=Wa_ljGV{m5x@N(u@n7ItY9 zM+zv+gOjX|uLwRiFyYsKAhD9Ea31R@UxHeKk_H6@A*Xib3b2YVR3}K?DKe*z`}i#4 zQKKPhOYrdILBK)e8_po2D+TnOqo@!C9^eNB;ZR8sid|9BDx>;^;qPV$1>O5=)a0_? z@*4t55+CrO&Re%GH*jOb*8zYL+{SyrUNG?M)>04~?EgtF&WU|wcihj;j&pUi)p{CFWU31=G%;jlJz94v0*7U&M& z9f^%%I7;}Q7FtHeU`jNLN`xEBIf4mr5mAthe~mRZNfwxpM3sY{v4|u#B2Og=Pov`F zRT+`bBg6h6hLYU0r9fv;%q+Ry|>$ zLWu>)84Cec)g9;>z>UJ3uCn&b(MN%65ZX(@{u%7-#aD)eWt1K7*}56&T4`xVzEhBG zFm_u@(?zFLzlLBb3z#;+#}lc?x@qk6{e)yDmcN8YL@ZA?3ok7$h8zaPp5LLWzP3oO zD~CUpAK25mz$Ui*12I3WYp<@UiK)eaI4yd%V+#HV9c;xQK^P{4T=dUuyPJx4*6`2q zAj}oH{P^~mJC6K@Sy=%}GmZsh8^mEhB1}NM^aW%+32(3xZHAAWcew$p`1a5^^WBDe zq<+ZWeh_Emq%wkQ{GxazdAP)2I!hRc!dCG7y5l#Q%%k}6evq^m;fxwJfCQu z#H1-CBlDwjqS|(iuM1!scC_MMBRO+M3g7$!tdQIyL2P|9#`oV~v#~YbZFCo`mm53K zN92klB8)wUK7Kr}h3D*mhLEaHCliKmx*P7Pyw^R2nwQ9Kj}jZ7&^Z943&{#pt{ccc zB!s+OxMtY%^l7zS2pIEX9^@l7)I8zdb#RWoq`W+FYl+5dt12(XjT@C!kS|0^0l&9# z@sjn=j1yJZ?;($nl$4axJ4m1nyuboE!N*T@OHhol#7UHc!w4y{Sw0dV}EK{j&T3p=r+Vt%5qSQ+#zS}72c^Q5n2BH^E&yE;* z91N#dV_m&QpVEGsNd1JQbw`tzG~pFqPrxQA>=(}Br7A5d(&UQCfOR12Jx6t&nuyfooND={VI3R-$g5|#UmeHXErdRT=!auU&Lidn$^07`@3|t6g2@R) zzv;0ghHX%ukr%));*M&f#V(CnA#-1W4@#~0#Q{(N)QB)893T|E)(Bkv!79>!->6|< zZwUsPN13$GaQq$v1SH4zS2(8?*n&`FG4M@`%Dbq7fV+@6@1e-x2?fL4fXM;kMEpqZ zUAx+dUavW@xY{g_Vui*dyn1pjb65Ni+RGuo=k}hc>>XNH%p&P(4IL7r;YFti(j3S- z#QZDB*By?plSD4{i?Gel$q&{cmfzhtd|vCGM+|FX2K|X4VtkABpR|4;W&D@m(G$!e zufp|U(|3E7f6r|E;d%e()ENiSEYso|swi&$9?npS3GYN66Z`Cp*uUUA=8BLbH^3SVNG-J( zLbtZFvqO_{cb|dA;<=)Uwbr5dlN^a4hWQzMFJ8Rh>CtaQ!7oY%DvXJbHwKKfv;-=& zoNahYC^1``BO+hJ1}BLTj&2nISV!t6|=+`im;?91_ow7 z-NSb3BiL*TB@Hrs?xxXa9eLm>a3YNiFt?hp)6@+ery%siW59E4R{p*Jkl2lZ00bnj z#C{N7v|3H!zQDRxOuD}=d4fGe(YOv67TA1%Z7e1=)japrN)??qvH|FG93OCwvhtA5 zG7=-sL2YIDKju3}N(b>riHO)vh{q<{$fhB;fRHn$y3~|E<0L}kJ&Upj?F4OYWPBVe zw!CnzVJJt5?cVKyN;?3!Y>T-yLqlTN&>zXE)O1Fh1a{wo3l88)Ck(0uk-RkU>2+t5>Kix-;>Eizk?^$6FhI74D-PDBt3ha65>{Cz% zu;LDo&BlqBTU1S3nOfy7V%&F?*(Bd#JPvF5F2|Og=uJ;8|AY+yw=`3!1ZvkzRX}{d z#X*0eNkv(iH863L@P=*DL4rtJZr-$sK=$gLP_WxxLw5f$KW}{l6RNlZIVbqmX>s~u zl0%Z0KGZNP5l{8uok^(65+!zzDuSa`J_ zcoU3-g;VF0Q9wWu?ihkXTXM$W5+kz~(ILVxLh^ zQc62ESU=B*ofFU2QR&lRVE9&8k2gjz94*#%vY0@C6Bu6eXQAp<&fK9`=&2AQwS#2X z1@b?Mbs<>*7L})_r?b-2-4#M~c38KCo?>~5Yd@BUR7A|i3E|JMiDXcHz(GaQYV~Lq za$}lAYsWDKTgGnicQ3(|*(LY};W4>GV&-vm^bq=PRuzm&ZQ)BQf zy(u>Bk}A}h+oNqOE0InEud*av@oc4X=DBk!@Z2svoioaAYnjr!`atH!))MdisVkrX z-$H9Lb#zT$yC|SnoYduxGha9e-r4sbAkHryP=ts}OO?2`0Bnh9&1 z@cd*S`P!|gmcHY_7<*>pQ>tyGS(n>s=;tS=oSBoe1m8&w{TqxS+}QwI3L3IwP9mMT znNDj6<;qnw70e|EZ3wos6A{^I_BGh+6o&~aAsL5u$MG_gY&cgR4xy!c#3_jA3YoAz zy9ZO&VF`jklMe7S*&xHWthku@%S-GFNl0J-`ZL`^(`V?L>Wh(}uz#E4_YEJavW#bm z;(fWs^0Y~R7<_1L><02sjLFa7XtR=+n(pkSy>Vkm6sTEw(wgRbn|;`gl6^}AVynL% z6ib#`g1^qhk-HqZ!5`j_ngj}<$OLe~cU$y=xF3## zokr!$?s-}MAXAZgzt$}(2JWWYjtv3L{meyZ*$HTq2L0v^`HL7F?Il~bqDhRa{0gAy zHKM{{uubmX7F)aB7BJC8H65Le^6LFhh@*v<^3ahZx-+QzG5wi6-@c)Tx8XQ_DrFVN z0>@Nz0xu~Whh$tN%P?ZA#rI;Cx;l74rJGWZ~~d(4RUiSF)@({3A|a8#CCY|O4g!7**&3)Q@V*lJ1n`jb(X zRU4&POwLk!ANBXQcsDtN2=u8Q{AQ0hrcb>`*Q&Cqbq_%NVlyu_y(B|cfgoU%4@kA{ zV`Gda)vgy#hv-(Wt62OT)dhZ&DkqyV;50cr)W-EdAR|V3~Soz#j2mB7*gZ!bYxtAT4vyWXlu5~}#L)LL%e^5xp zCAu4@mK&taf{|XqOtHP_7m^};3ED6(w1d^B{7I?f6B3|~yMZx%ms)4cbBk_ph>_9J z4mmwHdKIVddC{Yb80^1HG#yCNaJUyhq3^D#8J0M~$H_Sa0}&ty0v!atez%3EL-~Wu z!1{4&hrbPo|4@Fw4xRXZxxD%D z&;=&N#+DCxo?);(o! z@&D7gjDVH@t=Pc_9eWQp{{DjpGE!3G>32h~J*Hd-effw{ig>02e8SbIGHWC;APv%m z_N03*EabT=-suti*~M%BI?3Z7Ngl8>Gdo{c9QwYv2ubobRnfw6zx~nKlh=%60ESX z`U!#s9{=?nPH6ymfX)Sz?i_kqAX3185afwB0-mQYr)6)`FfoN7L>xuQ#Pg2uUJ$+^ zAD`dslo>w*eKaE@S_H$vhpWibL5gEn6WyLSAhq9*xd`KpJ%u3!$-kiTpTIT?1%x7? zN6*-Jw=`V@bb=OAF&pQJ0t}aO-eV2 zD=RB!kbvs9+N2*GI$Dv!fGM%C2i-QBFIJbUYV$vV!LjTA&Ge$wle_&UF3a--{{}_F z54@>x@xOffbnS)3nJW(gSOZOih({N)>>jkv*o%FFu)I+*LgNG&M+>|Ge%=<}VY%@h z%L$ZLZ~#YUM>UJnPQqLa-^?{c^c$~f)gEOY0tS)U!{uk3r9`XS`c`}#peryIQ5FeE zw5!jZ&0DMJ$#B(*J!SSfI5lvGXJTU~5voAQYaYY@hhoas zjgvC4^96L|4rsUOPX9o=pS4s^b5qwQ&IMgQ1t=+?cRIlQ?>@PC3}XHLQ>35MOl#*- z&zhtLA`G`Ut;W7{UrHB-c7k@Oxu~{rkO}Dk&!U~9q|4~X-pl_Ppzt@^N@VQ7K?iSm zLTzmwz%HOvKqo)5?ju~h_+|7v^I+j=y$du4Qaivs@umykzbA)l3LX|ctcyWALjiY* znMHLnBU6#F(muQcAPl=a-~x+w04u{#jfM&sjHIlr6LM@j3?2XoymAlE)qm^!lRbzQ z6pU@3c>5OQUaWR#s$i*a7SNs-2Fj5{xym_UZmC>RCGe?*mQD1;^891=SOv?{#SdzD z+X&~mADOtgIQ9Vwe~t+SIhJx!+!G?2T~LdxXJ-$ZTYG!TBUqaA%gV$WHe(-3 zPAZk<4CGu=Jaa!?;k9o91a58rvlS3HHuh6C`Q+=z7?W3K)c|ezZEoJrH@9;G+hjcP z!x}u<14Ose+3wh4TA>yl9*&3l9tzFW!BMwI3JVwYR6}(h-kbN-^@H z8gKC|&d3!mp4hZ>M11tvL{<8xnl|Oq9z@|)szf`4_jD#n=X2^DI&SQh=pBAMBCI^~ z@gr`*-NGCN#cSn_&T8iUPY_XXJhW`@J;v#p{50bNmM3|VHn1+ zaRDw0$9wwgmsk%9eL{c4nZ51MIE@6m8p41SkmOx(GF=WDoL4#@1A~AY`?79z_W&&% z%dQ-EtJ2(QU)nf=2)`4r=k){kjpDJZ1nwxgWsOE0PlzGqhfZ&FtJOJztWH6Jhj_$5 z%o7e+s=2)Tn*D)@>U;M>s6hGOp*Z1Vdnf;jT>41WWi@!bd@xa{8Y;Z=vFrKNPp!LJ zSzX;0<0qlFekI0)ijuOxgnJB?c2MU0BUDV22_s22H#cbyIAUL?h<^DJAWrvaOYod{ zl-th0X_WIow_-KsO_}PSi#4c@0o{Mmy|x!{Hd1~K^#*3GZ)L>$>D;xrevSET!xZ(Z zkmT1E7Kxbh-W|ecoXZJ%U7UZHH_dVEu6~uGuPANllun*xG&sG5b&z~OcdVHIDveCk z=;h8HbHpo-b5i{0PzRN z&rw1sAbRqmeM6fgCG%T0_Q3LBdXna+)}!TP(w}g}k7z+`>IBCa_Y6ss5D%;%L=0St z2-d~xe>XuEM-V`g-rinVU*7HiNC8M9dZkp`kK(h5>)$Gu&nCxyMyJ4xss<v{E>MDL{ znfBi%*MBH@^r*`cPM?O!VB&u5yz)L?lPqhaFjt{c$ z=?_CsZaZ}?IVFV!K;Fw0u>=h1RME;8;z7WLyjSTpV4|a|GrhP5XbNbIA6HMs#>d}% z`0%>vNwM-U#Ly^={toJSzwpxTw_IvrO%u14(|LdCjEMM(T1s@VxHDz4F-}~E&*E%z zclQ)3oUce@ef_}a)5g;|T_@CGrSLpvqrfvzih)JpW2Hpio$-3()k%5e3;Yx$&9vI@ zjRb7lCDYt46jlxuW_bqLABZlm{7~gkd9853=Mg48+-jHmU5G76eYx(owazh%1+tA5k45Xy7F=L0D?R43m`N1 z!Ke9e-w?Y$g{-O1G3rZU91VOSyz;I(VECbwk;B;bIQOaziKMQuFNo7yAo{c48ZJKRytp(~mM6!3qUF(}v~##_dO9J$xdCcoPpp}9V0jb9 zLnN7DKK=d@l^v)m5Ir}~C+P4#n?FEds_h1i5Ev8$kWgGeU<|l}IAxR^DS9hx-b=_q zYuPM}8b0>^y-5i)FeR6up@JD)@}^}{>{5kmQBhImtqKR-^Qo!fbh~dJO?>Xpr2slO z!GU z`J7rJVk4clWN0&PomEh6Rjf^e@f#ur0`9e0;{+{ zX{C_qdCuB!V|lo?ACx_dOV}JEG(h(y<|u|O^9|%zX#cSGhlrYG-daHgqV6?0CA6HA z$s~$%!YA(;CGp)a>*f~zG`l7;sgPNZ=8&010^5ku16WTjwea%47j$qixh^+u_qIz^ zOiYoa%fPHo0%{LOFG``vB4VpjLsA5BF^(ARniNS}h@kV?5&mggG@VeQkiK_)x@rC( z`uhN0Z7rM5Sy8!$sEgCoVdW{VJWRIA1|;cv3+(z-cU`yxW`9cH%oHvS-W5eqVPu8xNIflEy8{erM!9JEnW<8B z-ll0O@7?VYCZ21$gC}bFegRnY<73%7V^0(+NVZEzTbjpau(?)%g@4BvFLc(`N$2mp)P@m zd}fB{T4ESrvZ9^8FmME)b=Gfd@ZvvZumm8zNH3bx@h*SCL5aO+h}Ui19fThJR+WIh zR>R@9YaE-$JiP$lCa_b{a`bIx;MjHuc1kpnA|zXqEp|Ni-#u#)oVzp=H4c_-)%6h= z28ngvx!B(O(NEO}<8JdlR7111{>NT)_?esgZOksFwUfx^E*z+OVJLrzyAaG~{pq5J z@2n(Y%XMi9iAx$re>VdR>hn($HUF&wO7Ij|$1!n`0n53JCi+nL6ii%C;T36OfO0M_ zB$>_4S#Rg@F%{!-<0d;Y2U|P4@7-c~ay)JhP-P5rD{lEBU)YOl1zn;-#cLT=HbwcH zD&GCYs43qm4sZJ+e<^e_`N2SvI5X`J%N4ZavG2hr+finw8xg*<=;u zPPXUUaINi?R^1qp7ZJV!eQ)ECC~Y<1&V&sV=`w<1J2XA=OV)WsM;2Ume=K-x^FNeB zzxyKZm1Q`&Vr$3bdx&*`E{i$xZh9Co(@Gi`4nP+G{ewQ(9|AnjJozaVqX<}^+1t!J zH6j3=WImVIicDbXcHf3IHKGm-5pg*yF0K!{yER#m_#nZ>z>Fpznfp1CfycL}8w@r*LGN*aVLDA~CbEH8 z6YY#fW9Lg(E550jsHXz!#)~bb5?emLyko|I3q!|t_zjAmZwWP#=s9>C5dh|DiS~u& z(;FS3oiDMU33nIQwpotLu#kGE6xMU&2XSeveOz#w*^50n71v`ByhpZ3kV#_YSnWYs znG&%rC(8x{q%Re7TPy3yxz7?i<~b4zWkf2tr9Y~9pKi$if=v%4wMsccEAOwrgj1-# z(u8bB@`|ux=P_7)y*4yInvIt{){tK;`b8uGKxT1OMf%cG37qR>gMD9=B9pmU#rcD1 z7Fc zLc&=ZV`Af*T5D1@c;1CVaB`hf3sIW~`>GhS@w1$3hlOKE?iA}5WAWv>Z0p7_&TmtX zVp6oLHtS7GnvuP}$n|c(bt>-|$$tr8kWwB|VYhW+^y7Srn7YcaPbWTii%siM4-v}7 zT}X#h5D`j&-4}Eko2YS9V%M2Ha#yXa(NF1v>=jV-Y0tT9)J7lHdP&Y=fKj?twA;)A zAzE%QSw75zaYr|P$@lT_76WcJ0ARS{P1N1nc(IFLAT48%5FAH zSCr*eepsA)AC=6kptupX9n!3xA#}8!i+dkEeypOZ%B>}b=Thkl%C9o(|JKp5nBk!%=N#;Jwb|OI?=PWW%H5s;_dt3bU zqRx`Q)4Yr&Dsm&5vRW9kU@60^1Th~F_6V9iZHB8*f z-{G&3&oXPQCkuRa5WIY!C_kR1?ZH57F)P=zpllXMrMQ)!AF7<17^|74ly;lg0*QoJ zrMSESvV}h8-7WHTuek3MgS|;9`<}jg0XbURxwyFIi$z<%gXEn{=j9IghE0n#de6MJ z_D1^)dM^|<(ve}&LP3789%qw@nb}VD@Mter$`=o2T`TtmdyT{TZ{Q5I(%d3J(VrCe7uHYa-1OZ2foTjpyEN7}Z|PAjf9@BRbN zi2d|6Y7E$*Y_E-5VyKh~3il4>b?d-t!! zkmeT_U7~|jL`2?z-Zh@}BTu^&T$mJ~`Wp0yarv;k_nko)`LToI!sgs15|NC|vcWSK zdS2PtKFq{ep2FsAd1u&S9KJ_C4l9Mwd-aLrcD5?JH1&qMnM#v+WlnUks8Y@J=q)>_(>?+Xj)fBmmj4!syC zsbVLM^7RbpJY(q`o8C!Zek9G9cWbO3rKw~haZ2j|wiW`p~z6WwOo6*Pr%6Ao_) z_hcnqOxngQ$Kk>#Wvg~t=h~~;vFUVy;s=u0Y9mnhX8RAW0)x)jv=QJUAh{KSKD*Di zA>|AM5?vqRX%Xv(_%I$Uj^hISoXMpSW>1nFH2 zKQ9`N+_OVOI<`Ej8e(nHbIt;Tw4>h#V|A3bU=#@o`r}AtxxbMq*P5L`KEW6e>n?%w zKX~}iZ21u>K|iJR78eOC*KBg5n>^N5?KycRu3SyHjJ`9T{u-KM241Du*7q2# zC(3*Yf3=Q+8lOl(cLVVY|J>T}q-bPhMDlcqr7UaQI>B8pu7t2;{o2(m>@)NGnfzYk z(;-9raUZ=2;J{ui>e;!9%$e!cXDXk1X&{rmxW;QGS+fIVn<9_uqet1w&6QHz+NjdVwn?9uKC= zYo>L(Hf`Dj(Os?SesSP)Sc+q}!b>ElpzXPAe?@Qk@cz@c=BqG%P6Me-Oh{Z=$jf}LkA}Jz*7EP(`YSfuVytEk-j1}^+&yNFP158fE3F9QCIk3A0P*XTjX8QX2hK4PJgfSGoKHLnD>5V|< za_;1d7cWRk>dYM+==`fEPTsEBZ_?%B%&mj(4eAo z4NpVv$T9#v5A5-SN|7YT;AXx(?5@x{LC6QO*5bF11bYR4Bv#qnKws#0_K9dI#InP{ zeE>HjufhvpG+vdCF7!Pi3vametFDx$r?bnbAlCpeL@bLk=%42}$#DL-?nl0EU_Tma zU=QXDoe*7`V?jhC$!;4P8z>oUoZkY;8O^Gy+W1a2uMMz!e0pK)uL&1iAH?yb~!7jXNaLVj1Ohi-${G#nGP{2ZSW~2P;NQX%MKGD3` zLc0Ar6*RjO5A&EVL4~^y6B+n3fF2ji%JxEZ0t;6+>RSYqCUs=Bnr;v$V!EN+7)RB+ zfOTCN85z;h(K35(4g0dykk^oGVwT*znP+SMAap&D2^mP(F*CHsUb*rGQ<8>(WvC=j zj1x_4KE>?2W1|Rh8-kZA3%a2q)AlaL&HYGe_ydkUV!4{A86F@M-hq-63GLli5XKtI zHv1fzA$~-RYA z0+qTHJ6l_-;mki6Z@aTexp6(Cjn_<5ZVEr6CH zyS_(*d%t^5*jAi&oU=D{gk}VwBdr8s?N;(iF6;)^oTirs&eM&OUL*C1=s7h&x$%jK z7ZARIsY4y>r(xlQO4ZjyNG}2Qe-f3D#VIjAA{T*QXdz51u&T5)x)H?z%dL;5X!eam zjO|Cok7s@h9}i|LkZ}b)mEYVnEPL75J2rZqb#yOvgn_(iZ)HBQw;)^;HZx=1w{Ws& zPK)b1Vw?`0^^=8ww5~?Vlf%T4GxPwG%ivkT_Ys8Ti5nTBZ1N{cyKRp(aG3NnlNm{a z<86D7pPR)~KZez%b!WGi(f571_h&`_U9-J!Tz)We`z>jrzs!{rLKM~5e^O^~JT7}4 zA-=YaA1xgn8-1VDAuAv}0nct3jsl1cT{dykPgw139T*q@R8xBhR07RIU$*EU5SQZL zI*BpS=NW=3q=qqR;tNxZMH#aHHaABo8Pos3nj_~VOG4(p-%>fKdx%!W-Bc|-lsb+ z@D%%j)ZG8PYIv5H4&FQdRW`W5K>HmqUjD~G0g%DOc+%1JOuj)-m)_46d*_Ua`&LkC zVYj5T^m9Pva5)^yf_T$;{(b8?GpJ|*?VLVR`SbD+6uS58^2oCQd;_}dz@yUp$nadf zyYcd^FAugNau&E`HnDJXP7ra0E+|Ur|L#9}-JfSH<{O00;2RsN*pl&nCyAF9Zu*dm{kn;U3~oH+f*YUY%IM4s{8`$% zv3chAW$=41Vcec{-=K#px?YyVH!PS^Ui;@4fEU+tHK|=Zb@c{+a3%6(E}OWEY<5&i zAY}MIJ1mg>`-#6RtAy;+vNyFJeO4j|$6|d&%1%e85MC1fj_*EZiz^>*hQr(Md}G`vT`nvtnOxYyv9kgK4(XvE#-$)4V12ATgsT|NpPf5*ejK@K z#OSus0XX47Twwp@1J!L`U2F7b+u1-Oct z91#%>ABsPia(r+yZIF|GFVJLk)9!Yi#u$Q&3s@kT6)3mMPIuOWxMXEb&C5I3f6D3E zMT7l7{lQBl1$X<}+h*Q{NaryaE0l2VR|ju>ivEm+qg4>1D0-j!oXlLGj(xA6v1I)) zh6L0p$+u{mz_TkNL%VL!CvUI~%>}hL|Lv0EVj@(Zx8M@K3r94Gc&G42HlbFq+8G#q z$_bEyvh^#n|Li6{64~U>{vc;T50E}V#6^f}x}j$X4baxs1}szP8hbKJ`&pM>8a9|j z8#Rds#y=reuEbAT@aYj=?{({Pva{)@?Jb2ey}5EaEs^i4cwX9A z3?A5GbguEohnq@a4m1i^UkhZn#fR5lC?&n?9Dq(O-}n{sZS>!_4W01mFuP>6`3^?t z&d*%-Kbx#M(O*MFKtd=}GW~$v4ZkrYuTqt0#zPMqdJng@W)E&?fX%TSqdoWUGscEx z;slboYvkdKQS||Gr+7qS+{80_2n#u*PKL5%;4XtNK@Gy88~0znd1f|9)@)63+Do@K_lS8vq3|}C#*hH*=|3o zPdz1>n+G(eOUo142feyLlp>%sV$~XZj1GOjibfYojIu2d0`;4WEIW;!F-c%!6OR{7 zTQiGQ1(wfe+s&j#1sIjhSOG1U#+io{X@amo9eh;ExN}VaPwpr;N1ywaOVAUw(M>MFr!(qYjcr0VVKcaK zBM_&gc-6BlmRMR?EcWDc?(*q9UUfrWSTi1M&J+&&^raz+DEJyRo{9t&I2&Z87 zr;U!Nf<;5_!}N481{eUB8kgLoyP*$jcj|ey)+P5feVwwC(^nLCM<{AOd zXCNH~ZKTk+KXd9Ghj4Uk6fJ|^bA)B;7h=h|yMT2y5vIZ?@kTd^sJ$ki z;@^~r1s)_RPukRYxEli)O*|>-{p6-bMR(HAQF5^k6!ygP7R-cP+BvsPflz^Ue-B7s z-~IUByZQlYB)Uy`S9N9-bVIsqZEG98h8X^UWeX@LyP`em#agcQ=44h`N?8=70=cM^ zT8_OPD89J;uRB>GCnK5KNHZ^=L5c`J)J+>NuoO3c?h!2t?-MF(Np5#*7bao}`ZZ*` zP&`n_ZcsdRA@qKZ=;jQXY1)c%ZoxW<*N>`HE=duP*LT~jiskIOb_y$?Ye>0yn{j4rxwEcVBU7Fae!_@;-8+NcOL#7$mwylMM} z;2XWe9m+BZq3u??81n*~(LITFiYBL!zpBJ6O!XN1e;e5VM(BW}bp+GnDBB{FmQQz?-k4w!jbi7~1LQ+A*~n>x{Az&YbDI zy%@wa6OkcmmYZ+^&1ATJpqFkMi8+KooEqZKj7&|VYn>&qR7J96D-@qe_RjQ?lp;uU z|HKv{kd<#RQtrj7Lr4@&$(o_)o31r7$|??Nymf0>_e*!g>bgTs8FcL*`-BUe593B8 zSr-;g7AL2NX7NX-k^vQiR-S6Y)3&hTmYe4MmstIzUay@ol}5|iS_SJ1v!GFy@NQuU zAxQ7x2~5(Q3vRROBNm4ZTuxPD%#ZOJ+_xn_>Av-v)4XN>t;tf3aoWFsKcZTI1%wiN zX==NZvG;ujH)4T|Ir%G>k2sLf><-*}J|5P9&7J@>k#mFqCS+tG)Pe6o)pMVZPrjL*7^r&R*$lF+X`K6) ztT%?TnAXvY6>QL_m!#~fiFT_-_$paP$sPvys@FzT9=$s;KJLt^VrQVQPuM#dt&b&} zjPUK9wu#}CAbA2$N9@`s11iUAULv3Qvztlt&~}sR(GV;`G7(*KL+57sGCqr++*R%V z>Aj}6K*}a9+A(Fuov%DMk^CO-m{O$Uu$WY__?2Oc{RK3nWk&#bj&A5d&97`9SY=b)AP2|L9B~@_VX0 zz0&ZzJuRxLtFvshd31H(&iQv)+7zLSi;HAOhqmtF)O4Hp*12kprVGP{tF1~-pj7>8aN@*3|rj2cdrV+4q*f(gkT}CRiV&;53lp2)cy%hVN?Ko zqcrsPJ7@xHDb^>5L3TqcNsI*-p{OLrf>>>{?VDsO)7(#}WdnLy1w`7oFp*8DGgeiF z)YM+|9&7y1PTJ!~NQ186Kl<%Fd~!{U-F!-3=4cHUHlM=k@&l)Piw)MO_kGkSrh|X{ z+wUrYc%qAR0<=?F+M(d+RY#Bt!d?ffnLa~u^mL1XutFwI_uSI$KPIE}O}YNAF>ybI zi^TUk86`^0M?W8d;jh^XZXV)x9T@ibJ+QStFG$`+URY-MA>j0XwEo8$-d+2B3k3Pv z&slfxQWf0QJyq!P*WibL@^j#k{V+>p4lJs?JYoqN&B0-0!)`?onc~PvsHrh;EciO! z&2|atN!B(ty1`)nVclFjHi(~!&t1iJq`}MguiFQ3;jf?i?hQJctEP1pM~-Z=qbmP& z{o1u_g@s!3_E53m5n4AGy2zH7trQ|m@2JHs!?cq70HzMUxRqHqV)2_HM6mz zYGot;156B?2QAx$Or~EBrX?=9=Wm8=V-FEGwlJ|4D|=C2&CDL6FTOoLvwIb4CVdAH=TiZ}55|x{%{RcKr@=P2*U_?b|Pio(6d?KxP(TOzQfJ4+)4U3r|I2XSHdGR=b>maTH|49{(yX;8X zjwgv?eu{Jh;5hl)iH`vVA2~ONw(T;rbI?U}9KtZY<=6*?+$7lPde>pY8r5T@=VP<9XAM(6 z_Q8#;EzjLV{3B`>I2MHN=-TWp?Z#6Fgj5-$F6CVpvby4_0(UB@iS0fGv0nqc zLix|}{nrToKFF6RD4FGAvfLorD z1C;n)clQE9a$&YGGYksae3gjMl8`X4A)etjtn&fPNts?zsrX2E3b7jzw(_}HD!s0( zv{Y@t{i}&P!IPM!{To;NlPQ_mx`d3V6R>jGVi&l}ReYBk%p>KMl@K*jH0@{ry52-B zWSXbO_W;g%ERxCZ!UrfhzkZEYfF6v-{IkmT?azz($hD&xus!|;ByP!n5kRnI*8;|iWW0Nyx6Ee}$k z+;hPHLk85>-AnT#DP2-Q{KbD4@_!(Bt+eq#3BG>%q(dc(iSK!wGHmjoz@rs4OsTF52x3pw~V9og7hJa&7_#=PSk+imn#gD(U<1x{r=hD#UZc1A;>>TS{z?+h`hXu)Opm9uS3G*92^{s zy_F)bpo=2!C8CR6O>nM-**V`#??lT3ICXk86cN1?)YLFMKiQJ@n!*r4%c1<>FJO8b zy2)1z-nnvhz3ENpYF!(j>VdHWYC8epR}bpWHpQ>Y@+J*`{L%N^=fYVumxCW+@LAlh zP{o9h9Je=j&khhdEs!UW_F_)jnJaz)@y_-2>7V3ayTaZJiNmP~ATkIpkMyS~ci~1hV1C^nG!Q_{0GSRDutzNhbLln(vFpw(3LU zvgi7N3kDH8H$DrhgeeQ;n&*qivM1x8n*@#c<~o%Yp6t!bTus~hAuK?SXA?dl?9xSd z?(aH7$HYX<<6?cU7|>varAy84(b}x49gUx;?SLKzaLEjM9)Mb7j!sT52?rxQ z=C&l7ens#q$197|Fuv{pUo>#W4_%m$qI3y+HeeYuW8*gMckMyxt?B~W*ROJ+3Es;?mKutlcl)h!M&i+JA)oBDR3^U6~>xL!ce z6@65(t}~~!hz|BE2&%U_!w`TJgh+OY{!5Nq5sOvYfEWYwdykz$?mP2O1jw+IP9tf{ zCBuPRT2+rl{7OSZFq*42ilK3zaloYCJGKnPt}QY;BErMXp>$%hw;-VjggLXZF#)YP z!pY$7hXl+kJC86%q;U*Ts89*OsW~i|M~cu`PPW!=dywT?6#)H7&+@ zKwN3awt$ug<>7M4MhOv=bY^nRfN!4~r}Z?CFTr&T);9Aa{*$NRTJ>U5GZ$~FzfYHm zsPgl-1kQu9317pV2l{l5+yjkMwEYs?Z!yZlkrt>&&I-(7b-lDX@`uLtN6Gi{o6eBa z!A2*@Cwu`CVx`QpXDPSp>FT=hsLWDtU6MwlTgcx6CU~#A`zY7^rF;shJbm7|S{w|0RGr^lWbK3X=Pp zPu$qiN@}H>#{3YcrQ!qN@bT+~28%4&p;GPMs?Iw+N0MUNa%y%X4ujAVNQ~2M#sW(A zqhQP$m=plLm}=}Z0$}J zof3`CndW*{O7s2|U1zE&K4?V;)hPWj%S|Q`wu5`9o>Ff(;oQGoSQQRH?H8R~X#4vV zyMB>vd_rEVg=9{4)b^v2UgRf&zRwCM)^nJUZCG7=e8+fX+w)=rHr=$I`$WG^7E0!E z>Qg&c=O&BfO!Q}8jNh8Csm4o2$THHtMm!-(4hst@j|Rtg4JuVyK^NJp46yF zUG=amJG#^+HOtP)S?b05BHR;qN_bT=-B&cLUYGdR34B5GRs**k%~h;33Jwemjj(2n z(QE_|g?Bq<^Du(J#mxGdW3PeXQ=;CQ%p9=KD6O9TwnafGFX`4pC}*TuyK7ye8oViO zbGIC!6GMgoQh^aYN6}_3q%`&Bjjo*^iSMRwQTTwn0_U-ZtA+SZUXk~+Y^gLqS?tAn zg#gsqEn2EMOkOE6pCJ$Bk{2+H%sM(o-fU5Gk8XGD)46ucHkV#pE|i(x^mGB1;!yn@ zQZ{@0LfU9pJmlOWJX=1UFVW3cd{SdJ?y_SfJa0N_n|aq8iUS(`ZHdW;qMEp^ipUUY zx)M>$z!03s&$hueoJi?=by38m@HYGh#-az2Zk zFQa|$rDN!~XP$;+WRTHZgCdKRnUub=!{s}ZQgJ%`=r-lH=5&&ROe7Rp&hZ@mgK0-c zc}H~}RcBE*toX((Wji%4>16A>jzRf?8;m@ORw%JB3=B;^Q#zHkjaPUkhJqM>-Lz@3 zT`h2_GQQG&S03AFt3(^XYOIxbaE?!j>R9-W$`J4HLnK3lYiZI;Jdw(hZ@%SCpq4#Z zKe8_z`VdYve~F_MIe5k+YbE)sgAb%ua+qj&Pd9R^rkX~+Ky1Rs`J0!0OwnGQ4X;s> zgfREK&BU_tr7q0O zqNkIRM3->~quHhq;^&xf@C>F2jZ-#EGstcqyHZ>%EO)vNLpUirL2Ha4;E|be(Pbj~ zx?PNure1tZbac3h9|I-NT|e?t%#LRy?zR}GQ(6ul&I#3p8HCOKI`3it6)%FeNbR3$ED{bCWEYgH>(4tVc=s1ohUs2pKYN zP`iZ*8>YJORzL3EDcIeT#-ytgJ^^NB-EU`g)#yFtev^^obE5D4HM-7 z(E7Lyq-gbak;iU;7mmB%WKK1L=c@?01cO%awUkH2x8C&rIRl=c^3M%%|Ke};8&{l) zT7RmS#1FaoOkqL*grH(KYaFKRgHhnpshjlBuIMId1qLRWh+ zjn{BDYi5W>f3~Ze8(I_uKKROxq=VbOPyNCOa>elA;E>H5H*ORWY2RXVuJY{VbE~$p zvjKhOzhT7%218TR57@Gfgv06a@p>3+&=O|W?|m^nwe(W%C(KI>Sk~6o^;*9s(xL%q zWC&Pl=boT9S0C@$*H_pMd5xu&72`>c{RGmfmG)44ex~S`F0rE&b0@R_3r@J!&=G$_ zD|iD}65Jxux9{%{-t5)ycT9YID0H%-^3K-hg0Bt###bNi?L|rjlCd8V5fp)e<=1zb zK=OTwkbIBj=4Nze=$hFqH%195TpADP`}^TgQd1*&;~M~g=`VbLNom)eDtrlnY?tk! zhQDSwwFrzWz8g~5e~C*8p_1A>y{ceJ-<>@hE1CWU!2V-y|Aq4k_MJ}s)zAG_h-^Cg z3(qV5rK+)TJNFx?#N;`5Snb-<@l{E^7&zIbV2O~HMh<)V#fvAwV#B2!7v-abQqRS~ zvAy+<;E8UCL1KO9b1*hv_zQR#m(FRB)=DdVtF~l&X}9-t&SKZCx;&I@LM3@bVKN=|xPEOB$wFaYCU?iZx0d~a8 z&W@u((Sc2F!T{DjAHM|!h4o3?g%}&0C9x`!>5S{CA#X>Jg7CtIOO|A_1mZTH93J4;`%z%JHIRU9A z-T_5LMK)GeBHbOy84R8t9-FsrMV{LD#Dpu6iVGRmK)l= z27f73M$^7c1sM~_Y`_!K zJ~Cj$@(gLY6k7z=`z14sIz#0_X^@G>3N3QQF^CY*(vj=~N}At|;h7}MnlZSgsCU1K z31iJW?0~iJm)Fu{wU-4HCSScD2BW@0UC--l&v17nE|l5#S$RC1(wbZP>qsFW`L}L) z+(-h1{*n9Ck(wFiT;f#qh|4&M979PneCP%GRzf)ex=4m_c^hb}l(Xm{<1AF?zH+k; zWxk)VuHEK<$zH6%!JvXM#()E55UfooKcsxMRDZZW>+GbyM!wQC^!f9lNk8aoFm`|X z`n973SCKO{J?L#`LH4!SMp*$-pfaFE<1`w*x%v5a6{9S$ttv~E3eM}kZa*@eKJp9y z`Q3&AJg3V_h>v%r93f`zQzq!m2L}hSygH>J+#4?jS|$*eV44aHx`;Gh(L~YJ<&Pm( zu$b#Pu4b)br>bG8LpE_()zcd^WLwzs?ZVED3zyoWRzvLHo%{(Ri6adB}?PIh*~ zsCGbI8AdH<@f2Yw#mh~>yz`=iNB_#ZN%ZE;y(%wHD56!_I^HI)X!4WKwQg<@F@dw1 zz1fzn_RuUD-4cj zl_9{Qt~k-}{0K$6^X8jNYOkCm?(fI;oUJcH6`ZHJy^FDMotYu+v?&+s$Xo+CP6$N z`zzrE?tk)`Cyxw$K71Jv6Nsth>8i_~4|Ang%NtVeO*&oX7>pTz*ZVhB`VT?n&(66s z19vnRAD<(1i)UqgtbgLjb=w_dr?vl6R{hhN@J>jpAk2Q{dsrNtYLGbLArTG@ILRjFup4FtGRn7J zbxSHQ8HXDBf@|M4ZeC!W z_{NPzvVMC?o&hlfwsGhF{M$ll#8s78RUfk}hp&%e$>xZ~CUPNZX_I+R7Z-u4VZw~Q z{9^XP59=`qDN=%}6~0bZmwmmh?Y{o$^do5_njwL=d4NykIEJ2GAC5lH0wm~$~&uqj?Cb-*#9P4mE72}b1d&*2bp z@1J=k`{fNGfO-Jq47|VH0I8ADOsqRq)@1uEZagNNVWT-DQ668FW9NK1>mtYP8dHB8 zPHGKm<+JMPf{Jmz7-8-J3WWTe8>(s?EmW~LuU~81f0YzsM@aSzq$P6g)lL=N&tztj zP5vO=XqKrE<%ll`E6?44UD~W)vX&o{1BoTaRXwEBP?x`3;iK+A*9*v+$Sd6Wv+rs& zI5cnXANTwsN|z;40x3+u}FcgewSyXya~0)y^R*(Q=L2I3vk`35$Z77c+{KHZRXYXgb;F zhN@j08vI?4ZS~&bx_ZhOLVCr2(JU_D!PJT z)y8w@9;2R5UBC%c9KcX%oQ~v5lf5XC2T`(#^d|_>%d=7pG(A>Dl8I?$vEA#pwQBM8 zhO=|Bzvz<_dQbX=zlh9^VYk3!uYl+X2G>#_&qoMqZem`FD(O+`rrVsUiftGBC!Fc( zlK2iDJlK_b7AGA23+T3#GzQ~K)#%W8vi;WHwCl9S`Rt)kCzTO~Q3tpw`C)m6b+^Mlx6O^LP*UT>bc8lju)g68y`pheI^VtY0P`++BQQFqW>=$o2$)FC{i zv^-sMG!A`bS_p39d=dsyab$?qYJK^%7aMoZZwJPB;I-L-10MivgemfEgRge))Pw(hr(PjZzRUYx7>I&1M&*!fgAPre0^VEU*uVbir!#LY9@8P z3B!PENpMVf{psL}s0nlW;W9x^Hns=}{c|0b@dpIegSrgdN8P+qXtzE~NZ18%*D8|x^EY#g8GmTfVyEJyX}#o~t1_0k>! zEQH>{o94dQdF#7-R#<})ptMX*V=$LG?!A@M`blA-psN;}U-oIzU{3mXoBUl!O!_Zj z5PUm6-Wv(p_U6r-+G%(BgOcF1){HN|?KW4pFG5r%K9+=~L!l3iS(ECT8q-Dwsx3+o z9OMj-EmCaNIXjeD@AeD{ApS{Tk{7|y(6CQY_#qu9!FY(r6IKr4HFH9mj81jIat6qKKCgn7y9tYN|`tiRT9e{WgBn=g# zoV;Q)+J3IA1qfH2Y5ewr(K^4l^yOiJh&UrfCmRlIjSj=`lS0X_0Yfc)6i<26=y$fe~3)HXvtS z0eiHnCe_CQQI&1IiwXU(wj)H6l}$njNmh2UcSS??D3#S9vRBF8$(E5yQj~;}Eh|My z$_OnWy-F)clfeK@ORkskI>odHwtDuu&bjkk=Wx znir!y1%F-rftqt?NGOd-yqX`EcAgqVLt7M4%#}_mVog@6#-{(iZgcNFopS-?9zloH z)|{nW8$wPJhfi$7?qwHUc(k8Zk+AHf6Q&JKt#IN58OKX5o$BHNgYS<)+RZdyjcJpJ z6fcNoo;?!kw_)EfoK*gdnpK_Qf{;x#5zNBM5xj?Q69}h>5N6dix81}o$gRp~^y^rr z?z5ZilOfDuubr#@^5EOLz+97%2>xVcVKrIsA3~od_o}aXw=}lRA3Pny?g{Fr`RL-z z*KiYj1uf)DRQ{9I#He2a_O(%l)}(vBZ^+whz@!msFncpi$VIuP__igB%slJP@jFP*^x<-`tvql|P zxCXS}f}Ee-Q6hapKq7F0t1(z-G1BA`yD(35!&5{@8oh!B`0`zumff_P*Q6yM+>?37 zue+PP)VFk(d(IqziWIH+SbrqPBrfX7j_8k2gG_G1Cw;iG%x^kVmNV?IGZ|0XoEMn^jiR{3at{ifkLiwp5+8aqh zLsYZV$Z1(ZEv!IIM|tm=K&Eb&;7CLMuJ?>gOzOL*iZ$=3pOWh&A@@b&rqQJ4;>M;) zEfOE7Oi5oH>LYdSvz!@Mb+%M`owGS(q}qk8>4MBr8y?Y47rzlgZ%$LI{EVAzuU|Oj zp`h!1te|6A)B3Z`r~T5HW0$LVu`eIP-$I9bGzwn`LKfYp$owo&!g9 z61tQ$g67RaMA`OFr7&5i6fv!7O}E<&elyqSuP&DUO!%Xz=Oi1rHOXq!ln@yq6RzV2 zC<2H_uoCJNDl%#MYa4~Mr{hF8MTsl|3KGqdl;|`EDKb5&HQRf)(+9J*e~@JS018U~ zzQqLY1=wK3#-*k<3tlRQGLDmETb-iNxrDhzeu_q`L#7UDERM&@AyT7R%qBNm#GeNR zMRF0h8YJE%Jc&WE!s<2i5TnzKnlLu$zdU*2qwTwfWcwLimE5TuJ^C6dn8Bv&B&X7i^=Z)TG^_R%6V}7kZwz z8+dDd>lEMa8;sOq$M?^(mB_5d0h39C^@HoT}v5^>#et{sAS~?Rj%bp z3fTW$V!LFkgy<4yIfyh>@#f& zwv!ae?fn!tzY@W2E<3L0rHF)brR$%O%y8yC;d#hfbC-FD-%IM1^oloSfu|ldTlKvV z44T0f-QO8(dQpBKRnor3?Vm$KL#-(uP^-p1Vt%Sb-$W8o z`dGNV^yu!_BaYj8RVtcUoO}0yz_E#_=1RzeV3VtTbQp&YZ_6k2L~vyC@WSco%7+6GxhT|4rz=-Q!k;^RQx0%)_TWQ(&7 z_jMc*D>J=O<4Ex#_QueQ#t0wk__^|nVMVO5;++ASVHY9-#!jB6w&(NeH+gDz#407d1IvnrmIcdy8~)cfU1dpb(nfEwK0H!O?Ms0po3P z`Bv)rrZ^XZclgY$dS{=acwTn%e(GU#NmB3kB*&b6$88mt)h?4@YF(i|igLHa;QQ$2 zU3%8+CtJm?+!(rQS}XESzPc<=^wDvJbfIB+di$FTVT$C9oEtbi5GVGoUC2N{#l%$Q zs?gC~@$tinrY3NzcMx=wA_*(Wx%TtQt?NI3TosbO{6^JuWYCxP;GIzZ@hW$&+#5IR zY&mk7E^YO0%lQk;u-xx1N<<>E_Dy|PyZskL&FV9cc@WI4H7ne)pn+(K`> z+vAg-yAFq}o9Ra$+5)Ze_buyOx7W)=t<0`^U0Xw%RlgPY>$M|Rr*hGj0Lr>SpWW`{ z{K9OvUSDY7LY`sRb6TatGNOO_S><5lI}tt@P~~`^J)8Q5o$-%NL>DHR9H=^2BV8#b zAY`y8yueY3C*^oWuw;$9GezrgF^mNAy1=pCMLf z8zJKsWZIz%Q{TF{uF+#RYo0?00*wg<7ybrB{st3H8KVzE%{zp^G1#9F5--T=ciEqG z{ttQ}t_I@9Q>Ra7;s;~E3N8^=K~B|4{}QSGniKtA2qu6d=U)VeSK^MdaTkfk70$8x#) z_-)KmLbP;sMZPM+M(oE+&3!(Zy`jzL|3)lea{uc0cp7q(j9xADdmKOh3CHcT>HGex z4}FvhpyPx(`nje5Mw0!ern%>*&K0$Y2A3BrCmy(=Q*bQ^sMXO8v~HOTiWd2As?Qi* z-)hs~iU0wp6~DO8bz&PaJOQj>3_>!DslAQTC~yUFkanqy<)iTzK=KCZ4EX_VN4G6V zIZV9Qo)Yz@-DAI15vbt%#Qzf&95^$NB{KWvjX$JqU|p67;|PG27(GO0dimS9@xD`W>@zjxhwnS^nOO&*z`__6LC@DuK*{GoR@b~eBWTz?)75`uvKDG?nO92^{ILTIlZn)gD2 z!^qAKWIqn3ER0N-A|e8eF)?}{s}TOr+EM}naxVSVjA`q{83hc9|6e8&0{sZtzXO76pe;SLiUsfi3(Gh!S7*2sEn%s> zlT#A9fFb2p|2eO+B2OkAwwA@1K zD5X%Vvr+1ZyUYMU_!y*YnX`bf0t z_)B294q~5zT#DN;On4gL<00hXV)*bd4F0v($Vf45+}H;ljDFk-TzdPTwY1Qj#J68J zZ%T0+n|;He&Q3zhi(pMTd@2>QWM(&(O1`JSJA-eh;wu6>nE25mWV3|O)6xPLnp_x9 z0x=ZmwIW4S3IAltJ&?3MnaI$v9$9m$h&6>I*TV?Y+F`r%T-}{Y;i*v z7uwXM`l#D~eHSO+HtV@zHfv~-W#C`R2UzP2P2M1;SK}WW%_Q=U`Kjid$AxG~vCn{y56iu_O%1Y?jqo!U%$x$BvMque<#WB1>rQ$j9S~-N)X( z{LW&%x9H|!HvjR;JHp44X&Uzrt9DM^V` zFuLEJ+9&pkN5VCI`$)+cAm2Gejj=8Q^9ziTl4k6h=>R%xvv%hmXUZq zIyQC+Am1yIz%)CY=^*cM{zBmz(r2}}0|q;g(cgJGx}KX`8Pi7C*0uu~LnOJ?FfR9p zd(sE4?>93tIE@T67a?5~nkOYC-FN65v>5=l4_@uR`KG$p;2n4&B4L-|#H+iX(hB!J z*yS-A;r$KB3^vT(4|s%!YJ5& zKIhIMzz#9Q(Sf@9B-zkhEKNGpIT#3iddQI1w$%B-g*dKote#IexM^rMqK`e`hP?lZ z(qrzO^Lp3va&yb5?jW3X6-aY(+VLl6$IJLXn%~D!w$XcE>9>LFkb^0G_p=$aDzmQV z$$BVe#7c1*+=HCPnr_vX<&eOuPz77lUD{Jkp?kfDUl5Xf6oUtM-EIGP4qD%F0&bO(A~G?cG3kJM{dP)AYUCx$v<($M zF9qHva=S5?*ED&js_HC+Ao+>O$?+YSv=I@BY>~qhe2sa7rLu&?2~-KOx+hNDP?$** z3nYQP!$hFLQ74a}{m;xhA{QY+CIm4GyutEk@y=9Hf{{1hM#K=3YL_V17par744E?W zq|&8cn+~J{zii*whifW&!%{dIl*0jf9}5o4tPQUC0XiS&lGDq_!-(+e$NWwoA!QMy zR`>0{xM_Op9gzDMX`;eLxHH$RS@U>p`%m!q%=f$|nVt~Jm=D5abW(ne4GqXs-(VGF z-NP195=g##V`@Q7m9Bz=i28??_j#geoDftb?c66w{%LU1wkGx9r682xng_@p@!^b zJS;3>?ng};agXV3DDYk-ceYAO#&XvK!AZmvLop}e9EV$cvI@R~ z#++LqxidUm)eX&s-u|QsB!?2V;Yk9fpf-~rIe34cI#e* zeWmHT2-TDBVG|`Vm*w2^*C~!qI*{$2{+b{akVSV&si6k!qb>V;P-szEvk_BGj~=Nj z|f1`8!$hfwVMwUiQxl5m;&AEP8c^u&?0^uN4WM9lc6&g1CB* zg6yQ&(igzMO(<9j-GYs~ZZkJ{AKFam&$iu*QN?0%D)H*8Vt{oPS6V4jQwdWN297pPaDDR>BlCcvGC3Qw6ymvXB<$7U@ zGnE@Sd)0xCYx6Hlga@G`!ha1B%ZBr8pki*OxgwI0AoTu((DtiRYtLyNidPx`+9Z}HM~1_l=bI)vD+e) zHg^d7JGq41pBxi!J%VoJ1iK|sOPWtO4`%ntT*qJkxH-<7qbg^o3#Go+r~GXeWdSZL z#WFe(sf&{iVWjKNiyHLk^LG}JP_x-pHAUFcxh<>Om(lTAf)kgC+!3{X$eF{BUa{mp z8_lRw5u-jC%jO7Pm{hj2prf?D|k#-m9Ms+IXXy znl{4K@=&*6Fpd)>~bY*9`hCE0>@xOa8?%JQAMK_Y%hI6Yr1FI38?orZQ{ z-e(Gq{aF%bxMkT(zvuGkB_; zO@iqwkGsU5*c(oxVcsoRJslgI{t)>9&u%_cH2tM4$jO+Xs4)LIPV9iZLqq%Krf?nxS)RPJdj-DOu_4<0x9C|+M#nX>Ecsw~S#OZ#IASg~>}=fz%{xlAT;^Ki7Z5JpyAalYOrl<9j<(JWC)36;>E0TpE<81INO~k^kYK!i z28rpoJ zSbiDC&abiKs^Nm_pX~EoLe7K>C7d33%*bB?{R8>s0N(JhG@*MQ)~e|;j7^?eyyp2Q z;sokm`W_NabBQMptRc9${t{lM?7$Xkj)4iqcP%D)H%j$iKH+9%ny>3)p2KO;+O=8E zeC=SDo2NKkOy!&Nm$RjYZ^dwZ9NGA~Re^M;q8-Z#&ob+@$KmUpKO0kV&lUf~@Qqn* zj>o4xKirSo5lg(NBZ=g4gYMzO)(!gK?B|gl_ui^pox14faraKXd03^E zz)qtX_Vsc0UcaWKIwbeGN;vBz@c2##`V0rYUF-L5d$@eC=B`t%X(3;uEZ12ya%22- zgT#T>;sAm4JpF-sdM0^C`6U_w*$$aVy@MYgo?D^&t?Yc!#ZKX8trvgGNBBa23IZw8#c_zf+8w*T_L~^*6x`oIE72q^aq|FvM9KrIE6D ztl>3|^@uWE&3Z~>%5hp%^J3FbQ8+n}RH{*h@RJH0U_t`YU9%Glw@j|;vj-U6Ft|^* zjp0T&<#4>D{;i5YZB-N0I3vO<>Ip=k!e^!(dP$;BxN|A#1s(*J!PjE*TGPZsGP!O) z)l*#ttEBR2(*tZ7**0&1Uy)vjXLP90*nO@wOkOT@?9Eg=$`ej7<+4d=1xV*eQ6*4! zNR}V@>>QkAP9aPkA7m^s;vgVQ>dbp~d$YO08R6uIW)T#V7?mTh8qK#_xlfm#e@tR$ zI;YavvTUQ!wHwg`N0!T^hN-0`HX1hv<^rn@{`o?kc_z)|>KnLhZQj$49OtH5%jTfqTrxP>*fYn@5YDR=)lQe#7NGQU z*f1ecCwMZnU(}#LYdF5$n)AcO_9(vNdA{sCYu`4|Fy&fYO(mCd5n?t@Xxbv!iEjIo zH22qh-}_=?y$7^jjoi|6jdYdSG!=U5(c{PLTcZ;A$T)8?h6U|vmktwjVwR(5k9w$m zn69czaQo8$yB>WOyJivgS;w%=_o;YoPeoCAHd)YA3wU2^xq^=>6f*E}dj`3`3(4+o zv#XIyrR%@VlqecJiq44dwtm@peu-*pdXipClzftO$s%2rmWdq8A(p+j9n&boBHDfJ zE7CMM*B?vX-sm;*L?l7zmXTJb=pbvfZb5lu>4W!vrs=bnBD667A`sOF=L!D$91YRw zdJIz0&~eM?MAd(LXA?=vNMb^0>?zLQm0x?PZq)~NG1Rgq9E>#Q*Vn0%sx$9k8ZF9& z_Mjo^M%bVWlNhg@JFk}>r_V+M-Lu1tQ-tfr@c`~&?x3A}8T%tTLacaUAQH(;CJ~US z$>pPVndk_czh>(&Ua%Vz73qLnm%NF|aGqK#ic2xMZrm_IbKAW=`XuCX6Rf*}xEv@r zWSr~fjRolQ7QJF!ee-EG#m=M)HuhSUn+Uwk3J?u7Rvcq=uu;A+)qhLilp1-6PC5rG z>kprFiWAuz6xuXtXBl6%Ojpy`U*dD|T}{^q>szm* zp~+FCqwR1j)Jd0m#yGCoK9{XSww*j8&_vq4aTIBsAJu!JNDP+Y7W9*iza1*6gq$$i zdP_R;;2bl3(#^N)h20hE*?J@!!MeK3u$^`<)G3Nk%eGpVb7i(WScudhENFl=JtU2n zZ=Jnco$hIMadS=a)Lp%5Uxp7ihD6)BMs4bFJS+yeFkKW4yMY2J)>kExuA* zv(en7PG?Cn0eI=+)*JQw!fneCf;(=D6EH&=uv*9Qxo{C?f4=4G&u2>Vaz^)cD+(QcI$?drRVy<*N2k<}A-!RLh3i1PP`9Ik`kC?qoqbZh z^>6JHboYuT`Pq-{`jXI-e^S$U&E#}y$U*NLJTZ|@wde+4HK^Q63VX99C_XTj>Il=m zvFA+Q!)#uSr#!9b@ zJm=s;!*P0+zIvBrbcW>K3Uu&x#iy z#GBm+*SVFrEv2ftB{1Z8ScIJDi`3l^dMZ^Vzn$}xO|0whQG6udk!_>TzF2&b&z4~! zi(REm{fqPVj%+FYOI`-!9GZExGO5xID*gq_qxm||^cL)|9xX}WE6|_WPVKdDf$u^P z6)%lP58cuCThqwOr37S*Ze*A!U$V(|&ZMLE?^uXGSJT{Tua;=+|} zQ9LHOT9eWRCRd^a7xWT^ZCJMIMHx!X7+pH@HZJMuQQK*U>n-_NvYN*>$LO>3h{V!2 zURvsLbSJZQ9Is?k3XS0MD6KHV5MuMiIp1SeDPtB#Sr-LWcEpG0v1yb?(lTYuwV$PL zn{^qF&%ZN!;D*~pdb8+u*6=O|PHG;&SDg!m<|U@`<{5>Nk6Vn&XEJ}ZNXRVoyBHbO z2FHkR;Dwe`>Bqz32&bV{G0NH7_AN|rM=FbeVw{@n*)1_ibCmkn{kD}=OB3!d-v%#N zGTF`zeh^mMJwDlVjAEh^kWz}3f_pN_q55Nn8$&exB$}SAzdXT3=j3lv)de>QR$=41 z&Q6BM=cgC9J)JrzC$FcQATgpmGImBEDp6h;pU)i|Hp6XI#ec~2%PUOYaz1?QdGj)8 zvTt{_bR0BVl2*XzoIRh@N(mks6H(7SZ%%dQ&n-~Bt9Srljgk(8oV1wOM*tL%s~V@b ztC^;Iv8NQPn~Po~RKQM8IXi1g&K!iZ*7RUOE9I?Oi-62?d;Nv(bnfg$=ME@r>5K;M z_$km$&dxo zGUR{eKRH?@XS<=;@29TekJWuc-;@VJg}8pOM6curi5?tSWeqa--gb)BTYz{M+Mjy& z_Z*>awZvnYoH46W*G&+;_U!QJB~I2ouKah5j_9~ZJBjRc?r}dOJo`LA^q~B*VYukd z|M63BjQ#pRC-sOQD`KEv1oxF8q7}WgX>9fF)q(;{jM<|}o%#CxzV|oB_AjmeU-Iw( z@Yj5{TY7gc+Owv64c<&q@t$e$OxZmvEFds~+Wg#!2|#I_yNyQPy*v8i20io4^;w1O z+rL5n?VIEnakIZO69wSc?&ZqTXdV?}Dg?%pfZT4F7U&{A5sL4h5X3DnFV`Zl8osuo z?^Hz#7#G<0t|&`4G(tGpnk@dRiCB=Ew;%bM!jZ+6s}P;E|cAIZ#|8^1M%_qc|VQXGMJ%?DABVFfvLq$kD3 zGIDY*y*~2>%n0oF!a_n~zkxEw44l1=5hmlHVfUpJz*O-$^x5$GkkVN z%;e08g;U6A#5^CKo?fyTJL+`s`e2{2VKTtN@4bHDI|9IDSG^?mNwJOEOI_0Wf-_a!uD`CPw2Y#8l$DS3D)*@q_ zG#Y$uu0&aqD8^){n~^@~UX}w_w3odA6Qv(lL1Ru>avgdF!LLQ~U9}rBM1Ed?&=~n0 z>l>L*9$FD=_klZWN$sy=hFIFB>FvbI_O4tasV^!LmPI-!G2H=nn$(xD8GZ(~`n@!7 zPp}Yri>(3Lvc@r&p!kQ8^JcY|H|1gsjQp!k2=ChEhq=6uZ-teS5p{W{Y7aOBO?6Fz`=ule$VPR)X z70md*gU5G#Mh3wc+$C+oN>YIr5hk1AzuqO7=#4qc6tx8P3$P!84p6EMMm!5}pJ{1% zi(}3L`mcrhR=h-$bpg}{Xg*SpmvCc{v+;0&ZW0DD%Ga=jaEUBD| zl*OzCo?Zt`?eL9A9I%d5^;OHjBZ4Vm9724LTp_#C*=b;ahZ=V`OiVb-;^o^6O`c{q zvLGIw2FCw~fLemKMPm9201z-@NM$^VOmi7aguFcDl67T^pjpycz&fk~Qjd7S^ROC@ z46|+!){qz}*>e0G2Han9_SO2YA}PE3e1s>Y=2&mG{!rxLeQ^1``*Y47B+emhf=#>5 zPLq5@&H^*<22l1Nz|WU=G-C6?t7r6{M(Rh94Ih265by|1k@fZUxaRKhCL_Y=gVK+Z6@45Trbs`| z#CZe$Djub8=D0{woV|ch?zdOy8L4R^BGI_ebv`-z?HBUDLs4Wcdqkr$stC) zo0x|Becc)z5fK*Fc9C9#%Vq{=4Zg$7;4wQmC+K8u8wAe|=c2V6Ma}Pfx!gVS;OyJ9 zz27A!iTUJZmo-VRL#lt>-hX{Wrt$aAt0#$i`ml4)Js*tMVCB@c#>U3N_pUkSW6`f0 z$5dYX_cc}W`~TK__*4H(Rw*+398*oYK5JOnD*7K+rQ&OOy?bg2(dTIQtZqyaJviyV z&)l-$-*@iMtc{+@i9(0(FPYzr?aBYozfYV;t@Okd_jC%+v^=TTzh+<3Cet%AO zL-glP-Eo0u#GJyMewOkwuKCf(OP30Yi4zcU#a-;!R~UTn4EpoH5B+}NKegQY!Htqi z`}?l~qJUEB?2`){vA()Sv9w4q%B{)kfjbWanphyPP?7U}jxC0=Q-=nZ;62Q?TMk3c zN44i_Vc}!XJ4gHO+fu71wT0M2W^(t?4*TF3bFZ)OcK_HS>)Z({bsdlLp*$^m&%$cw zPgq+no%o@sBe?8}fccwK7)V%_hdjcuu5}rQ`serW_iO}T6{h&T_SEmK*ZF7b^|%`u zokIXl4`HkU6~04jV)6c)#EcJ)=k&xx&06Grk%q*rkqMd8XC@yP(oZF+sE>d)glL0N z;DJ`mN=~M#*?Z&AJy0(=pg~W`xR{=}f=W0We-47F4{#X)!ll|r*h~9;#1SCz3Ui$E zFv>=V-W`PhXyL!o&CekJrf#m+q-Ane*C?J0-fS6s74e&?CgAZq*n#5- z;PB4P%R>bPQbp(mvz@Iirn+`>TU=_#;tFLxSmC^$ot^Ms#-Oxbi}exE#bbs$la$ZR zMa39%9f5tD9Y&>GRg-b=*^;|*;R8Y7f^d>H{QX<+F&)_@z6o=wqwGJb1yy_?Q86}V z+^u#?Cg$bg#&|Y)k5LrasN-7qh%Qg$#qz14!r-5idDy@$HDSE zw+u}S`jpK(c6^5q8N#)Yc66m}KC~Yh#!#?~U@71R2wF#^P3==p0KlhmZcfY_Zrh5- zo{O!d*VfY-$U1k`)!cyiKk%&wyA1orjZYfUOQ0AVAUTL%9g$j?l(%dlbT)W>_oB{p z3?IlbZID&uYsS;)d4vmfX1y$>{$Z+ll%|BI69JQpQ}7R1uj0Ow+s9pO?y!}(`Z=zD z(~7<*ARG6x@MgYD+U>+mGB~)}P~~^l$R;=4BsmMW_CRX56b$rZC>*jpoC=8OmPX}; zE9kaKsfAS0`k7%yXC|vd<@PkH2>xn;|BN=X0tLkYR8QJTqI*Yk8bK4Q=62;jTW1jD z=j!_AVGXLCZ0-ue=o^YZc$rvnoW>7^I3lIndajMjvtbV760qn)*R|bk!;J!VkZeT< z?ud4L`|;!b)~OP+!ga56iq*4Jr$HsG&UWOSu}r>Edis z#^=@RK;Ft}4HOP%Y|ywg&GV7++pR64qM|U7aFCNLx&@jHPw@(D+Mu&)v9=OGI@>O` zH9m8wA3-On7PifQ@r58R>>FGI>pC!nq&JSoT>kN`iT6Aa8UhhqPXMGHxEahwfI_<< zE6~$kWtfeVQ|uLYF5}UZ_~LbmUhFDL&!HB=4r#sQ(OeGB5(2hB-pOXn!8*sUXvh9) zV;1y(A6MhaxVWh1TDJDzd_f2^$jCmfW|>ZQGgg!#?5zp6a>*Q*ErYJGZp(Q$qpH-ks2 z!q3UWN&j=VY9eUoho30oZ2k5t@Myc3IwWa*c<}f4{8Kq9AmFU?Bs~QMg|NXm`S0Fs z)DzXfzQ0!5sMlW?rD=goG0}`BO&xC`nwkQgYJA&YMN!|YKRu^T)ACg5_xDzlk5G*b z&8O%k34IICSov_LWH7exaFK_`|NGk#XJ)m^-Kg&{aFddj_CUcnqr&h;RX1Tl<0ngW ztEgWa-#;DePZqCg(4cBMz8*{j{o%B6y_PbP^y4n2eLABK8@f$_}g_WWN@RM+9;pQ4hyps)}fAY#oiF#5WC|GnQ;bIs+ypclA$J1ujr0&Une zM*#X51BPiW_u}3!iIR59_vn+tOFye8ZTc_OLi9PG13U-!Ic)W#-Q$pS|KdN3Ve${K za`=47{$DHQ-(UQ*Qk4BymheL}&mAv?&e8j`Y4M>OxCCw6QJ#Id`)@>m=9_25YUOXF z==Xv_^M3F*R?+k0C+?b|p93`XXCd%Ca3FoDvkS7)!m#pX*4!DMMu$Zl`&P1<(!KZ( z-NMwFIN>JOZy&zn0&qQYHdnxF@bX+$rApzd^_6z3yT70Fo?lOy=&?T=pB>}~5C`8f zmgQxXE;EUj^7+m9EjZz#?p8sz78q9qvjy+Nv5VaakMsn{ol5DvP@F$Z`ULY8qKQNg zfGdjYX2Xsf-?U)gX4AXKj5 zhf%!F!cyW)C*K40vAG$1&-hpua4SU9L3F*qv&XqDont7 zsYIWUpf=hZJTCET7d#w`{im?@qut{4yD-4Teckf*IA%bzFJWf3;%MJmr4rMEwN z-ag{kaa5I?Q3sU)%Gv$Zr;o1-FMyeHV5)lP+UrMZm6enrPcLu`MAb>4N=;Ghy%<=P zWTrZbHX9NbR4HqhI_)ZYLiuP~~2c zcF@I}!w?%XIH_GstmF81-(btV(j~}giOTn`+I@7gK_^s);Kl{%Gg6}}V5sgv9rfh= zG;IXMZB_l3bRkmIoa=N6xCG$9==)cgG6^a~X_+{O$*_Qc04VUDUfy{Vhho=32941` z%1I7(Oc9Kr&i#ghvGpyZ_-Lz%%qD(LG||-BF?D8Pi5f-e7xFJhE#UUVEPsw$W4K2c z9eWQ7{xDlCf4X%?q(tbfQS@N!LGPxExXcnH-#v8m@$E7(?Ol?$>&mvz(~0F<+&CnU zE6Vw@O@6RB0S7DCS{dz4N6XZ$@Xw*IoAA4-TI{oo$vRg92bgC5%42UCgu^ePx=nAx!;%f8WMl`w_CNR5=iinti>>e&D7^T?4TBjLn05f(UAB9=+&k*q(Lq`FC zJG>9r0U#d_ZRUo+Yu2qmqgovdDGtnzX~zi zTUVy;!mM`}rCQW>iV3%7*d_7uxu(=IrY_vbC*yTxky-e z`0e!@H&V{kU-^9I{*m1Y&zKO+BW-A(bI_Tof^u?HwE|cZ(`H>`UPd||j03z_no2NI z4x}zvHfLpHLzAjC_w5Cuzk-GE#WhJ}rimo#|qg#>h+1V{|>#q_nqtfPWQq z3D#VUjIR3=(n~MGrbAKz$Ic;Xs&B&fe$)&JmYT zX5hEr_Hdxu%uv=QDE)NCV*xFyE!h7OM;>x==N8BEpI!SJ&dY4R`i6!eWYb@w7$uI$ z7eLU8s8jP|2|Q$=ya0HlBqxSWOehT$kZQr6Z4nTE16*|YE}TJwt`VL)L~~8&%!HQ7 zMkdzLI|ISpeJi0m&)%3VJ)f5DH8xlwM7uCF<@n`0D`aDkV|1VHsweO9tBPHMkFB83 z_-U9BU?|011KGIn zRsEURTKAFC(7H5oKd;$@7r*#|zwkCv3FD{OJZ~kvMH(EG9#PXE)S~V1{w|`eh&4WY zw(5b5C*n6$M*%Neci~zw;P+xT-=Z?1;bJWG%GFJ5F;KkU0p=4Pm8{fukCnO{NH!}H z=2p!?!LW|95A(t=W<-l6M&Dn5^dqUK2xmsp!9IzRQ~bO!R{_Jrpe|H0p-fCXL&`3! zwssYVw2d+cNw9=Pne{_HNm^cbQsvci5~Dn#1-FNJuN1^Zk)J|^m5{VLp%cQ*`aRbq~G23GJ4W!!&wI1uYo`gV`-WXvQb9;vSr` zk=p57f>HlLw{wLD$Bbc$Ef9h?$O{B{EOT2xc_&lx|W*SINkhLtM+~0AF};0 z8i{}RJk?$HK0ZHz3+(#w(n5JPhL-43Qg00%GxKhKkGlK%|6qO=?AreBXR@kk_ul=x zKL1m6d~X^{iHmawyaTix*xOqy-xK#dcneyV`2O4DK=V(akOWqY;tkarfOAE3=qN~8 zx9$Vv>kwP;rxzNXRpdhZHKfzN=`Z{<{$CKU&dM%p9N`z4MP>qXFh479QN}xK(1!}{ zhktSI@Af3g>38`)L!X4WwDs#vaA9H$Y+7YyL`50)M^I8jhR*40h8>C4cJMaGZ=SDY zFy${K8GYf>;0FK&@T%#-)GU}cKS007o|m?atd10xU;fi2t|)l@)3yBz5P-{WOcMgx z6AJay2t)%+!~HM&YmW6)x|#2rJh<*(bY-QR2LZ4Sh^su+mU|3$k=g#6K7~X}*g7R) zevj|3cmId{`M=Scf4EiM?|1(!h~Io{U$w+%r(*9|Aw^omxfgA|sGzxQ# z^Bypr>BfcbgMJxDgxoiB>HY}yw(7AhrOMhCy_)0z;4ScblFSG%P=>DfL6HXUhK9fQ6Id6S~a+_ z=Jozw%k7=rfAgKar@)^;$ELYwk47wTdBDBpU4n}MQE}MI8m7Z26ouEiJ%qD3%4K}4 zA7Z=zw9UtVx6L|lCUDi@+tAX}AO2NMf52ds{m`T`+|B_!Utrz#)UpkVEYx?ba9O(x zmxr^-f0pcb*}pN_Gr~$I=wTkos!yc6V4|kdb-;FL(s316XFu2>VxGnIWrYhYknqdI zL`N%ojz^uNDOuvH>AhaA`$cg4AMEgp*L#9-Q~13c>-S7Z4kBCZ86Fa-=NrFSo_kDi zMRS}X&UrUM9(jVw{JgnnCVW<$GimS7WQ~8;_n#y3#n$9E*cqkoH>>6^8mcD5jC!@@ zwyKz-4o^E6;$m|BJu>ZyUOst`k8--g6Ljg zV*BP{(Pfiiac*DUpIyE9_pVM(X8B@9&#g3%mIm<0Z!~`F;cp#W#YG@mi9&ynh>d@i zx);LUmHk~UU8Mbgfly=r0UPC?d^;*8VsPKXW&r>K194c-+^aTr*!q8LzOhF$X6fxb z#7F%i+rD~uFAb@DBmfRt8 z;DFt$lGlD;3fKQ25C6IpU_%My4Tt!TjRr8gJF?1UsEnDwA1}?hd;GiN|GBRI`TYRQ z04(o=e;G`+O%Tb4@GX7IBIBIt>FJ#hd#|_h{O?09c`PM4`7X*=)KL$SRf9$Qf4MGx zkyaT<+4-N8(jS3FN#*AMe!%_zWTyTsOz%9uKmOaxsHiBIo4PJOsRWjl zdG3h#(@(;!5cIGVUaq)s!sonAT+ir-q!Z1aCr|TlBF{8iyw$pRuPrWxfei=e&8-NF;!_ytat-c9p$Kx?5w>40(iwIRun@S1S%CmxL&x02GuFBdd(|(uR(IrX1`PInaIu<=lu8os z{1a>-;Qc!~u|^qchYg&ZQMYc@;G3h__Mu@Prar|56O3=PJ3;i^!rg}&h_E`lu7EjG zUEK>#LMb1JvfA*ndXhZYUg72|-y$nZ=uR_= zB>m9oCO&%Kp4N-T!E<`yG&*UPg3FCaQ0*SzGuXM8sPVuuLm}? z4BF&o9$Auj*}~hqXJHM6u~;y#z_zvY^bWI_(rt)(c5Q#m-VtdnUNNzDJ?WK;hmE~O z{=M}HlJkCcI5;kv0mjQT??YArCNj+o3=14VLKhS|F1X&T*PP?kb`CzyYL5)4yk3KD1hM%)Lx*fmvsA`J)oi8YTm^U+2533GbH7yCgM0mCdSJ6$!)5!$T zna@6Al$XyS8z6)bUCB%=5{IaFxygx(AOr^&0(PE`Nsqd^(6brzi*KsjgIUUt{Nx|& zbwB;+G`90>s8V9*5nYSw)8fv;-^;^$oCUq}?eB|_7?2l*-pWlz!?B@lL3@FzWRm(- zqK8Ga{mrFIY7wl{pjosFs3Ft*pwjq?Rv$f zNH_L8zk0?*s&<_vm6PMf3;KE1U%vu%}3tViSkNUn+Vzwh2# zwod?R32xb9b{0^L6!*6`r=$zZxN1hW9(5{3IcGh|#Q z140Qj%AJ`umj37FF$UP!ovNog(#-~_XkAEB?c!%e#{}CJ)dgnZl=rJ$aws-E(*M$I zQyZchPH8?2wu|2u#A?JX`_c8%9^(Dn%}nkTQDWHaHF6`n0YmCe|Tvq$E)*66*gk(H(pNI16PH5WCP+ z-F^W^k4Bw-qG^lhf>Z)N3)r_ehyf?+C8X?`irmWT70J<=BrXx**siwy;Cl1ehiMg8 z=PGLv-5tr~%y*oZVM3L8%T+AggU<2uIJAznGSJacJac@S0nhM#Dem%@9J{pV>ga(A zBO+KISJYeau%LFlV{V*OVBoS_06a(;H+;b^?N(DzbnfB}b0-4r={5ThBeO1L-e933 zfrCA(zrP<~?c*k|EV}2%3K)AZn#Qqyef|EZIY0%%Jh~H35r*AdKDsXEduY6DaHP0y zU)RpOL5)T_Gip0|^C0OPPVGLh+c`%7Sx}x(Wxsm)UNF}!dIduoH}2*ga5jOhOPXjl zyN|f@V&DEFUpFR5Tz^DIqNtU6T3TqDMP}g%>k&6sb8}|vS<&=-`^F2m*ZHW?qMkk4 zBPjk};8WzqiwV6X4C(nD5VXb7p3KEDXI^BimfCr^;-1apFmSttwZPq+8Yd54n8${pKIZ| z3{6pAkx6h5FOB*a(9~~9UTQQH#D#eG2gz3V3U*w*f%!-o=Pqk-$1-dlp$kNuy)M;( z-Dj_=k_4#_y2PjIF7l~Ug=kEq584QBcb<@z_$EmWC;9xN?{=AFOi``l6kgS!c6)2V z8$i#<@+cxQlJ9=-zJObS{hVx%+c`1{ln%AyZx z6ck7C1!&g}3%(Q+?5Hpm82t+Io9gatZXUG_S9`7qk%~>nF$pJ3j=1Swy@HE2R55K^ zK$v@@taa7??tFcA&%nbp%IPvXFW4#|$1@da)N*7<7u*u{#QUQBA%J`VA3I_`Ke`c8 zVIRB@bVP)+BDXN(WI&Wm5?uh7UpRS}DY|#OlRAk~}lm<-G)Y<{GRU5dtIx->EAv5X{v>@3A zoG*2Gn%?--AfrjFF?~?;?HCH{5)$5KjSv9~^#o?aOZJXufr2)zK+kG>8uf(L?u2X- zHCE{W`BBxNU2E1F^fW=;Xhun)QH5;HOhtoudQQF@(B5%$TILSBzghPf%~Qy^wb1 zDSLpV80m-GBi-eUaD@r~Qb5Da*~7fkUFlT^jI$44s?PL%UV60_80YkrpF zcHXY?o+^W*rulP=d4dr8QMn$6>@DLInbNn#=}pAQ`9B)Iz`6IBJxI~O<6*s=_5N~B zHmfx1Mhc6aH{+!pNGi^KifFg4S%{mHY<|{LY_`SFTGg#Lb1cwAKk@iJ_V*tBbR)il z7Pk8Q3kK=-TSmlUcyM|Q`=kF*ABxzxg@I4qO#7oI zGOs`0HH3bJm%P6SRVE2L=kr>B|A@7c{OL^2cq&fOPsSs38opy$B2hch)tEM8&mf{- z8&ECOX-0&2K)?mrsl_x_k7IWBHCzsHfwnDmZ$lb!Rr)V|ZAvuKFOcAkbTb?l6imp9 zi6y-QA7aLl;!4o!4IdFFOQuvr@fGZK;2F&()LCW%e0QOedkNVh1`o%`^8zdhf#XV0G9Kh}TboOfo<%p1>h-`9Nw zn$;)dp2(jelq$J^2%C({0zMl(7}dps$hYA`3nHAoG8T^-qJ4~?MGmtG&;-uAa5y(J zST6MCki2Ai(Gy6M@9;v z8**Abw47G^IAok%D&y!+I+WeA$J1w-A{=O*^_}Yy9ihwy=NO@&MLgCfRZ$kT`~>D^ z0RXKBEhM`dAzH(@~sh0^s5zly~5n5j1}GFufZDV5}rZ)4eN!(>&vS+~u;7 zB~6#36B{QG5@->Fr%ojJiBR2QBwuc)3Oc`If&NQn{0qWjI=whZMD%0LFw zZ(D434R)$u`R#)^h`7e{RAl=wj`yuIoIQXkM_!E5DY7J)=RGK~)@G3uX*bFzP7r3` z=&Sn1)ih)LEA8Qb$VUJ4^4tjC2_I<046X(-@3tmrmO!x33~D)OPH;alJ|V8ZAeUxB zY3BlOoJWof0o*p(1{CyQf=6#vR32;)Uaz~bR%M8t&G>9#XtHzyaxAiNU&1p{k4<%z zcPi?m1+?$DPKb5I@53i;Ky)>CGEk%=YZIA@r#d_VcBx8PeCl&zA5?gMloH~hrt}e* z$zg$d;gO&$1p14DRyK2De0)YLlOyE!%qKX&s}55T4Hr1J(!z?p?Rj9uPztS2RL)8Q zd%6H=?fX=~7U)sikR_&>RKw(eQuk|lEvLD@1|rK0`S5GZJqgW)bRo^`~(o!1w1=p3Y1zUl=e~4 zKF7ZjO)>J2or8bsp!*_8E6KVHcPMOjs|>JZ&M({PWf-u(A3RJhLNqui4PChT(f^SDzD@1TGYu7*_)9KSzh7(|19SOIor$!ojC8Pu2WW}mU<^~zdi zr&HCa*M!(E7 z#KruToVm6y8jO#L+G0_gA)XMo|1sr%5~}(~6n%ER;_o{Kz%|TB!GUqGF{0`>soxleI?F%-bj9qxOK1@L&| zzbhmrCwm$fRR0191z1Y==S%(UJ;Agd_(t9XR@s4d&g)X`Ov9171WIXDI-unyqytk$X<>t^MEZ;eX~01qiJZYz~bU1mKv9fkzNv z5WTL$#ozn>Gb0$c`S*thdKm=m>Zg492cZ9NZqR?2Y5$&^6zDYngQNXJ{5dKAXDa&7 zJiz;3r|4gLqvKlX`mzCof6a~l7Yg-1@csXhAN_w(B7yBG4NUX0{NEMD_GY?ucmg;!~w^kbP0S zwX>PBPlkmR%_VPWJZCqzZsMJY>3C6{vNgiyi{Fgs)@-TX{0iw1GY+150hu`(?1O0B>1KYNHTH_(|y~DJm5a4zQp4~j6JOI!Vz`7VeNzciH zNTAn%f;7@BK9+i7n`=5+OTyw@ALGB*1}@l* z%nosZUoaQprJa_vYfMwvAL+Qg%9kJ*AYDBH(}kvOU(f!TtKU65my)luhrD!kWw8X- zsGtb28NB~Km!(2jcVOCM0lWr*+0O$kEdH}hp#50_0wJlh7I{F4m-gBJwDn@GHLBiq zY0<+~v({z|`;XX9PBYDxRsrsgnuQBBiQF^htQMEQ-&uPLY}Gm??L{jTj~5zTBI_Wq zfXv*l^bPJC1Qg@veVR_18jD3kG++JavGn~@^G{Ff#|uy9Udch*tN>LS-whM@HraKmcHBQ8NZA9}qeLnq1fqI$d2|5GD$q49K?@7#Ky7kaz%O51<^nDoRQ{ zfI0>?0aFa+d)NcaLI9=*Mfh-Uf~AaB4NHfVp&tlMLVlPx)yilj8HAM|I(y$HjoY+9 zz-&(X?+m#IFvUGf++vdWA})NdQ87=MTwF8&OGHN$@IQ>a7`)mIwgw~tz;6YLDK{`{ zb~r4pXh9(fx!*6K$S?1Mr7SEgfHxRG89TB-i3Iu!2SBF+J(QQH=P>{U?g`+}4yV%j ztpT`aHO@Q4YWf{P@)wHyLnfG;*?-7_y z;2YEfh7BVl^1Qm>B*@%6Tx#?}jEwMdaQw-*$2lv2JTf10==Sg_@~4hMKxw|Gi4FaJ zQ2-xbPr)H5H*IoV$bL28x6ZN=;w2Mq+VUZ^$TFFAX(QS8albLEeVx>7b}2_u!mDP4 zP`@Xi^7KuRl&wayw!4d-CHbyxwWa5XBSl%iscnm@_~W50p$wYSR9NR&eNOXDiVc2o zq*Gm^cXkncxX{c&M^P6DOHKe+5OCUR0gf`7bwTEN4%{Cr6G1){7~_Cj<7|5(QVKY4 z(+D{bD*Avc3+i3fO+f2YQdR~C&Rl*!K;ANh?x$kF02QPv_ySTlD1J}Q&b+CFz}e1j zeeqik3fXcgGiiRixWm#e$91|BTNl3<|0+_{b;cU}T9}meSOzrh%T)rM=7Wsv(8#DI zVOW#T?dkYaBEVqV8aU#oAlY*^`VKuzd;Fr=zqg9^3qM1lZZB^{#f*wzDzCAJ#a_R& z)J}9B#EI#vnM%`!rN0;xbf;6 zHM`agR-Yq1D(tI^ka4E(Pfvrg#YjNUU#KQqC(#UFZ%{Y(ZT-seSlwFF%IB-^#}Ylz40=fu^wKq- ziT}>p-H9oWWBR!YeZE@1cb>>znjN8O^(%~9n~BSXbdnYa)n(eViu3{SgIg9OuuNur zN8v1T9;sD!9)2aQE?&pCQfy^c<|$y8H7qxcPDqG60z8K2fG`EP!@%ABo^ubD4J}+b zD44>&zR`pNwhl1WgNePi0#va%VBQ8M>uYTQLf4lkt1l+?3^T4_cTv&8rFTuQ)XMq( zWEb}BKh~i63KI*w>|0s;`SmaSFIFrBs>_BRM=P>8pYZkN_~-DH$MYnnnhgFYzeb{m zjP4-f?=T?41&}7e(>0k#pW2GdEBWm=SDl&Q{wOUf6?;yF7BWGFovYwYu1h386j zzVf`4eAMMc9N1GaVo})gv__->_JiOMo`I7g-JxGEgq3n@TGT!X@i5=0dyyTW2^Euy za0y=8|1C2`FJm{bL&dmXP)B>Fh=Jd3(8{F~i??dKlc?xaZ^%+YDah#*P-bqd>-Nm( z3(Tw7c5)wUDDxKOw8}%LZIFK9dQh^pLsCBe&%Z9)M*uqysQfK9Csgj2FZpe9-NIW#GLy=vj`uo12@RUEw@l z1>o}l+CT!WI7%h}M6@k{9tEJqAZnW)e;dU3ii3vhIhb4l%%-#K5L;YXuE5U!1&cRq zeWxU7$+8P|*dW8}TT=VZ=`MezUVk;6gr@{WHnxVy%h4vyFPHhyDvG|grFgnVimR6O zW#qiBw|pBZoY#}ztP?7*-^N(moOPh(kDkGR>whurY>J2!Fv9CXtlUM?o-_<)Vyk2WG8x+8N zhi{_^X`Cp%LfULJUt+9{^y7~ZC_or~(Ft`!=v7Dbt78~chuv-KzUv#U_RVaVv3ag$ z${=Fn*^RBA(YvNR47J=X3%3m_Giov2(6`L2fYn+OhLz z*#Q>*bGez%W;HLC7DM^;Li8G$41ZP{#oHR~`xRq&tz`YI9p-!mb`oOeVR#enuTjqV zM~P&dAEj-PrNW!2gaiopE>z4VWxJ$>I7-jk7NHE+XUfi_G1ig9)jc9Bn`5C{PrP^f zdk$HgsB~~6;+I#cAW=4VSq;=ve@4CIt!Vp33v@@aokT4l^4BbC$(BO`bb7BZyEd{G zuE?%h6n_jiTM@8fYX}%93U5YHE2NcoI^bRPH@0<33h zX4O;dvba+)qqOh@kKpf!d=C@9{eeh2Nez<_q@UF5`qzbK>zlh4hu4w>Z;{ojo=|6R z)Wvc=5~2}(XB`r$`LVCDuQyiLYb|2P|7?dh<^fKTdSmn$N^!93JfGo* zsk|n>v#Y|py4OWxW=dJlf7afncre01)BuAZ*&PJN_lA>6yWe>_}~U$Nk^_@{`kGhZ%TyxK?Jgfz~o7w3taddQ*}F zu@gb+U1htq*z_-i5bH;d&dv(lg@+6FV`EI3)=1nXg^=3(`2_9@l@>D+q7&UI7}&O zc=41m7K&`6ts;8hzq>Vczl}OVw@!aeQ($T)V3Tt*OQi7{hiaAT=kc6&KC=!Rm*qf1WkVjKs?o13gzj=KR!gA?(^H*6HQj=>%@@yJ(b6>LgR0t zW27n+N;X*ral&tx&lRypKK`w*jYAQp%^NgpJL*V(^5AxqJ{i6yp1%%&?wo&mxze{?F|G?TxPRk5+C z623^;QZi@HrIw~}xvw=+885f;re+dQ?sB*nK{hc|&#TlgkZ&T0vck9cgi#0{Mq%2J zA4Gq6)DkT@$WU79g}_&5s}C2hDevpwUW=%c;NJ+k`Wn=G7G5y}?2;Y?d|hX}#MW5l zF*Z~ej0lIqB%Fxw&du-s+;w`2Azph6?LE}E-MhQtRYV^TX@1Q;Y&Mrb{Kfdg9rw9# zjEGI(FW}hL=y*d0L$_GgpYb|Tx_rvT|Jw-m{X0;Lb#cn$oTF1$Qy)ZhVc0Q{wkbS~aJ@Lq z%=LL0;4;zkn4e&tL^BMlTe(JKYAN7`4!t^3exG!S;&GLj4O#q2HhODLK$A?W?DFrkjzxAl0MYYUIOZgI2X zfj~ewqu1U~icjq!I`Q+`-30=bk!RW6Yom0Uf$#60!BRhDEI+1TnoaOEZ*kiTC6Fc^ z$$UE4o8*WQRpWf{k}z_*I=jkU=BSE5L)D59pd<-{3F*j3@;}_ld~B?kkJ~1f^^HT5 zB8ke4*X(_j&?OkBhtr>AOItZB%BkQpA-lMJr^IF9lU649*x87L(~}X_EJx!L3kr+H z`VFMTd0G_m*X%ePf)C_4xur-*4V8toO&kit~23tx%-?!hyu%qH%`(h!hd z7OJMyBT47O>6vjEO9-4(aaxj%mIr1*-K@P0U%VzXe;5W^F(KZl%e zqV}Hn3#9nP1fGRn75KUCy!xFlE);4lj*2`y6Z*XelNi5E!c@wOUsat)`h&5ZZ-+gt zJVn$)ZgSkUQu!sTeb52DzQpY0&)-?8C2YN%wKtZg@{gFJWN{8Awy?EBqg}U2@?a*% zM?kHxFvFNNpE_XVG20WJe`rTl(w123-_96i#t9*QsM(m%zAPF=MB0&S@j?8L1u>QE z6BHnrIDa1p->G`RPBLSwQl$gO2u)z>xEoc014lsTQS=BD)HZFxhzCkONJgn8;%qr+ z(?mk=BsxSMz$md4WBmD*h0Vv-LFZ6mxBYzgMuIkBi#5Xx&h26R-9SnneNB^{IFjA* zuxmCHGhThjJ;47pi#laU#Qo^F;$zqQoe4Hi#IWx=Gy_On zyoU$Fjaw1Tc9;`g7=JC<*xQ`(X#}23`6yP%hKxkB1dy`5^Ih)Pe|GKw?;CVv24w*i8y^2XR!|J9;g|9Fqq}nd5Uc|6b~I8m1wd znIhu8Xg9~NZ*dVumPDHrb9BXmnNYl_Bk>QdzTUYqP%jula%7!DJH^o?dgTLOEycMz zJZ6MLA_w#JxrN+7`9`u&LdCA@Kup@XGt^>HdUvyQ$48?Tw`sVjcs40BZd20!Y8ge3 zkw8)4kh@B6&2j>)5~}nBFq_qNRjgj(FZ}YCQzcLR0mZ@Sft}eXFxj%LbTbyi6+mT= zv(H!yGgcfe;(H&?8q~f+h3A|lMTtSzD-w*6iXk1Tivm*}wZ*0K7;s*y$3aC@4WgW< zzdR?wfO8avH!$Hs9FivV9`-Z|warpU2MvCf9mv5c?Whh&Waf1g@Z5f4AR5v8oV83A z>Z5Xvs>%8%`dxYrDbI)%-8=BiY;Q^|g`t5n4BgLFx?< zr-F~4T0(^!8`nYt^2+~e8~<3a4d`<0v^lrrORG8K>cP>uJ72c$IT99vC z%lKRDOT^}@8785ADtyb10MZbucGiq%sg&9&J(+nhEr%p8ul?nfk8@}AGybt?uXg6V z9}0pDyCw5fObr-I0d(O(>N@OUGo=C z-~}1#1C7<#brS=km5{in?JGWn5g)r}OP-osOH}qHrLJ_>Rs9t2C-?68S{A?>v(Zb% zIUIF<&`p7UAeO{1U4$%V@GMriXPZ@sqkEO;@(Qn;L|csh!?@V&V^Ar4f=sW!^EH;k zk3=6Uq;AQqF{#!@v2SjpLgUciC!1UNe##iUqKMQI{CeVZGL-OTj?_}%(Z>|lH11et zo&k0_g7hI!9Bv@=QMlPjCQzs;q2huRMUBmW2F&*<&cX~kdS+5Z2;KK%abw_}76pob zSa3S4EN2x$p~NmoA45p&U=_6EH*a1&QN#K`NSAg~TwervVp5>vMa$2hPDC&Ga=dIC zM>pIBl()~u z4C1UoCXMUG{%|zwg|Zq}{OoBWk=WpVp!dqC)!62o{LtE2jAW3WK%q2>VFuXdJ6ou! zKN1<-$)s?9FuvD%XWmA>5;@935=PQlK$xrE`;KfZao6l>oKqTGgZSy7pwRf@$KDJ- z3l;3dB0pwdP&zyo!V)L?TcZC3ou!Cc2*Ru>Y?rScx|!4)$|`eL_Q~1f31;cv#n-sU zMTzQoMZ3F2qQRsGNmOtdQKu7rT`_w{@o3>;RoAK9k2Y_LAW9W)hAt^l>5<4G!o#A{ zH^(&yirtLcNIzuN&6`dF8N1|s^(Im4j@iO_Uwe8gC5KNlXDkliAW$Qe0M~fQ^r!Y% z`&PueKRB9-Aiv_E`WkJ1Br~&u0(Xx$&dha4QS; z$!M^YcCIh~xK=%?Q5tp=`{398K3J`h7BtH?smcpMw}W^NI`r%4Kcvr>8Fs(K{cJK1 zc(@G|pxqtfG+S+xGNbD#xcZ0AbM8n3mc(a!@gBUrH2#?)zC5yn@)l$KC+Ga1VL7S& z#OF*x7XAPa_27J~ z!}Owl@AcZ1$;CceG4~S)>RlC+qsD)$)|?G3O|;$Bgu*}Rgog#vaw~)|lCkfL&v16{ zE_l&S3^mriUL~kF5{Onr7N{&dHxMA<;PXmN*{?khXsbOZD8LFTZk=H62`v-F`dt{1Elt0qoT*?OU-;A%%oqHvYUSW#UvGv)k2foRRq;4f+v{RsHDQ8hh^5 zu9$+Txei1!=LM60`i@7B2Xt2`dV)&QjoBkK4Vl8A;wLQ0vuHjcMU2fN{(hf`s9@yZ z=)zTxQ&5K`f0scMiSKSjbgHXDivdewgD)691R7ep<1E&L%B4UPG+7(sB28H5aTQ$B zK|mPsm;TF{hqZyUxUr9z*gMe|(dbKG&mMkG>`%htyj9>RbY4BDs&ksMe*T)u2aOgR zBbe*!I5U;eV3)}JuMcv0eAbjG_&Bhde8YsmU=$%Nw`JQ#uWhpu8{=;VA!-HQddC0- zGI30b%UvQXDxwWfNToUO33$NkFf3piJ-?mmWPxjz;1YPwEBUawukmrFPZ20cAW0V>zdC1j2ECfmC zHC|QTB=Sc>xT`|Dq6f%Ik%cx81`(|NgGpO(C{?7L*jglO^hOv^D1p{53h1r+$2~vh?xPN7e4!=Vh-)HtE68G z?d5d7*A%C)7$K^4{~(-P-ao7n(CGS(gYNc%f=dKm%R}K|$?Pwv^Kzf~4Lr#WFBALS zkz!f8`Qg4dxKn`+qQv7jwavhU9iMg$5NTA`awV6_7f~9$MyOkbAL<)b3f;O5QmuXK zFT?YTvz%xL_*T_BBlL6oM&Rfxc9g_2P)Yt=#1(-xH~us+mXh_LpAB6JKeJ8KfI%&# zZn|jep=@f1)nf%s+IOh-1V{MV-zIEILJ-U0rc1((0z4uacx$^|DA!2k(L8*8vA@Hr zbkF`?W?Soe$U|qoNQy84dtPEwavmedl$pd?F7`BQ;Y`yu@29_i9NoLCPtv8BA}~8; zEVgP%82Wsyh|kgdIMY_!IQlTQAR@8vorwt-9EppD2qC%bTpHsRvG3yr-^xRmaaPC@ z-rw2x{Fy9I2=~8EGjrY1a1faC*(Di-2}`9Pt>-S?v@uu1_=sTx*j6$@RUVsZj3q`b zoJ*^-l(HzIW@`bNO4DVxf(yX!xG|x*YJfd#r32H-4Cz;Fu^cXu9GsKVh*+9oZD%G& zLNTN>nMPSAwK!M~crHgoR@luBRIYi|XsRipPnH;`aahE;tsdMt2y7tN%s1L%Qp;+i z;p4^@UtE@U80`U#R)&B#OkG$r3;=u4MC2o^Lj5IM^SDkqL$Q+Uc!zIxXQ2BbGRZ}f zEE||GARXERjsI@_qi7aVy5`QWhU`-18mpUb(uF3@C(ZM;92+SQL0(b#gfqnh zJwdsKrP)cSUknkGnnX$16UQDyfMQE2Ynn3u0y6ZRPnl)1n_koAWpz0=+dMP3c2oe@ zkEtjj#R<^)*}~(tj?b|8O$RS$!{54}gcC-gcH8bJy1QKt#y7F4{eAYa>#-m%?Tkm# zVe}hPl_??Fd7-%ZCQIHH14Ij>_wC01d?!y2nsCGGQKwnn;(JgdBeQqw^K3R|~p#c%Q;PuRU-Dp%gU z{Zh8nu^}r;XM53|d^&M}Uy<5Sox3T9T-`+&* z*ZPA-bru#uPaSH7xDDt-`2*tOo^7-t3#4|O_-3p5G^DN!Q}*cq-!~eHr2cLvq$OrSPmP5mSvh1B zlOk+wl{Nxu^xEk->Xex(LBm|73H_7G6t2Kps7ElPN;L_>lE8Lct|?L|6YpDDAI5Ox z?s7=n%vbIAw&X_W0+r0pSA)d}_eRZ*g{oidA1YNu43HzOX@o2~f6`I1=?f;gt2)1V zmLz%_k(hZp*tO!X8<&GmDH>O(6MkTNDE)jmt09kql07}4PI5Yk_%9KK_n1$1H14=9 zD;>1K<{Fzo>FE(OZsPhugx=aES1H+m&7y9s=t%yR7@;`dl#ekD>N3|%{72k}8QoY# z)Vi{P&*jayu~P;8I}DwP@uj_gbEYaaQ^a{UV?7$gF-JZwWLsgsOsN|`>#or>Zrc2a zR+9IkrtaKVoWD?A{bRx7n0HiN&zZ-+-L1pd91p?J29X+;dGlH;_Atu~6L<{m`VN{u z(+q09{*vz<$ND#8KejXC`tI+-f|iLbQtyGxPoK4BLsq4{7rtu}3ecf`IBBZR{PXk% zWI-B^2IpVZcn;Mr@Ma{lwNBUvzbqc<)RyrPwj~_##~6r^+nIrb8)#bQXY0Ir)~O?% ztc8;xHDhRWc=18Ma(3(0us=iK{%pS~{Xs_nfnfQGsK~d>%hxU82h?wD{sIAPnOGvQe(F%Ui4^T3{7XQzx2rV8Z2l|k>RP}GAfF7)@zOhtc2hT4}Hf3KY(*!)222!|0g z&w_h8U#FUNB;dy9jUmC0bt=S#NLf#yMtMbrxrt<;rB!cg`4?9g&3PDH+Fs+OIJHGk7-w6SUTh&arO-F9U9CB-ndLS_FI3Hlt7@X`yJr25kR9F#BfJ|Umkk~!=0XXG2=+q;&JgFzjpTZ@@k%S-dtKCb(uG6 zKAxE08NsQM;f=RwHRT*Kg}(*fM7L|wCL*F=dCcIgl7hL}=qw^0Rh4xo_V-@_6BBd8 z39*7DcH1^tY8MDMVZViauT=68q{S8OJ~LEZ`3{ztKg!C=e-lcMe5Wr&nmVE>G+W(> z>*Qkz|;r3sg zLza5i;T^eeez{3PB!fbo7JqVz^5V|$d~u8W_kF6Ygzvj(`V0Gmo0RF>70bvXk*%7e zS4QE=UvCl<&R+O+nnEAX&CN|r$OK#seM^wJJifkQ_;ccSOOJ$ftlM-pPIC)$?yoM> zDPB)NK^hG@SX3KbIt$B8?h?j{)yKTwb;)f&#ghSxbO;kf*)9xAz$!YFz~3 zAR#rA>@^=_G_x-C{9#2#ieOpv-!_IKAwhZmKmHLn0BiC;bEz^KWI#vybhp}^wy09p z52SSD*x}vX-9W{%;fjj%Y2^OE{tuhx7p=hQ2WWD^-BqlIefOai6A9^t6nxK!CRX4! zE-fnhwY7E0?+7@b;NakZoYA`$3J~x8uUC9y?R;ta;swaea*^=?Y*y8RLol>G4F%_0 z>;7DA!no5AQ!h_|hUMED$Qm{_Hg*)E{C{?P@Lni3_As z-v!v(q}m=G9hFm|A&t8Jb1i@yAfO+#A8BzAy`Z)@1xG!)~P*Cs{H9$I)miRYa02y#hqlI;Qafo_G zE(xK+R$Iyu*PDY$d@W(&;cy~9NgXnEyY>+M&$XvF(`5Qud(++h=1S%YbGPSQomIcU zf6V3u;YSh=>$8;?OnHTg(*)Skkto$H=ZQ%f+RzsEAMMI|#>Uqc&dM3Lcf{lEwW%Xe z8>hJx+KkmUHL4woylQl`F+Rz~@SSq6{t-<)R=zdubhgKxK+>A2=|Mw2Eyk8% z9+JH3@eFuAheU}{WG%^=vt zb2iP6Tn(x4t)*#gVDseD?0#F$#bT6^u7rQKsGHkXg9Yt23r^yV9$NrcpQmeIKnif4 zh1wPW)2Bg^sfROIT`vSX8wK*^pgovR=u{$=DcI|($e03u-r%$ra_t8ZW;)Y|VNny$ zt9hQjpDk0$`7lx)_*lut!~z1s&4wdEn~yp~2pf49gEsgVjuA&{F?a8|vV+N7oY}r| z+-GCvmNW(SrR*y_WaO`75D29dc>dUVl{zZNX7y3ggSQUEN9kwD*8UaG>z}G{!6Qy- z*gGQ30(_M9ZJ(2vDyU(0zg`lZ!2TE7=kZ?0s@GPPt$&fPY|c)q+TN%Sfe@R(?U|iCS5cA_?!xB6ms_3&Zv*>B@4G5eQfW~$oceCgE;ee zKPn6Rp;`C`XV)mP#pAk5`M#IZa zN@}oQ+H1`wOHS}@Wjw$oFX`H38mL9g=b`}GFskOofo94g>3}lnJaJS5n*WtG0m0eK z&vST8$?AaMmNxT){HQ5PS=7nQOJ_)XAOdbDJC_6Lscb8+Hxqh{FE!^k^5^bSQ0whVc=p3S^weVQKG-vOEuhpBY?#n8cXG((mMnukI&KtC2 zzzzW4ZSd@n{>3Dneu5)qOw+C2^@9|I?jv`_%dMR$*$W;A%GuD78spM*$IcRZG*hxn z0gQTPFS*@`c_sH)OAF;*t~mp@?Pkwj=4eBW^Zh3BM;(z@AXfgbd$Ds3dn(y8;Yv_D zedoLIa#EID*6^X!sy7l$io00eh*_Q>ZehLavhs6dgcSwlQq!kAdyTN)27E4WFvCq5 zLog=+YZMaBc4Jd^akL-{6=P5%RBinPg-^i8(A4LHOwWB0>z9652D_ew5d>l;->GCI zdOq~?8(d4wfxYEx{pU994ufrwTar-DHT0t^3V`^G+fF`VS^*{(E7V>5vD1%sUOnO zRr`*&kM|DBjLF17^Z@k~lqguvmU5k^+|9=HDd|+5s8aY=3=@Tu5cKS2@_iURwmK6L zb~Y+Mnjig(|CpwnVZYv5AeV$!SN5?fGm*lb8x18yl^JZF1=~H$ZS|62WEy0+ zNE6Ww<;!ER;?6jIBLlF;X$2UjZNIXLG5OvowPhlkE4mY+`H=63?7+~wJ%5Rj`i|DO zew!%zs77`Wn!U4%Jc?n|a^+=7fW(rC&f`(g%~Nq@v!oQX+;CVu?sl>vFt0JDO`}Ab z09IJwaJy9}f6zODu6>M5vbr;F1+X4N!1Gi_aVNM3ehXVKBScFS2GnuxkJ z8=4W}V{@TVu7mBr%XM135UJ)iT&{oHDm)38v_wfiLUQ1^VY?~Jak$!z#MLauyDyYj z!$1Zi?5bjZq|b(m#6R4uSnbj|#FcDsx9G<16j%d*Uk|pIVOM#1Ysk84d+1!XYE(S0$CB=``Y9V|kQPY{n#_z82yV4WR^s*` zqqLORTTcZ!r4yxf4VI?LgT5|ylKAxQc}X?R?Nw!*_86(JRVdeV8`$Y3k$aHYrheAM zPZ0bFD!*Idt7v@8` z1NN+)*DBg*3^0z$*ta$wNl0ZZeX#;Q!k#Z63RVaR4A*Q?XuF6eqIv_{Gsm|J8Z;z1 zDwEZik=@E1quD-}Oz<|QDEB6{8O+=nhzL`L%@qP3f%RPc z(4?c~=yW6D)XClYc;WT?z{wLnB%DljjqNlT)>&a>8ZH#Y_H+Xkq9l#`|OgL zJp?*4!keD(7(lI6o&MqZgQbvmjj@FVyZo2r$k6aAf6dr7dwWh*fh=dE=HlY`A^1AD zA*x$Z%g1(T6`X3>syx0~FPb!K5d8W33|rlPkPG>@H?#6Dcuy>V(ZvGMIY`x?e1yfL zF22b;H*e8s-3&edMk?Apaqp0b<`}fF%wDgEo`LtZ(}IMfb)Dlv)$~@oLNctwp>cE6 zebH8=FNp#68Zq|uC0!JdA9nsD6}7{kmT&hh1W?N*hXmIYVOawZs{-hj(OihWW=yVE zFo-RJU2jM5{cPk%5~`(ZE)#M+0(DYZ3Hi+R+F@4LONZTS{+}(IDdS7_XomSPxkK<> z_kKq%eWNPMQyimj9i4Q%g%za?HO8%&v)dv%UtM%)mi1a8cUdC5C&53{rnrO3{aZTP z2Q>3Mk>G`=6UcTq)ZToBfYkglrkX4<|FeXzk+q4!?|gob0gl+G?4bB&cwOsgd6G+J zdKyx&;GAKMIQl1OTh&ugu-pgRv>BOZ^s_rrL6H!c`hCwUb!MZ;|nrMeBb&FABkLy6Bc^XA_9yjR0^hn)%53oaL}_7O^cUe@L#2Ep@qi3-OC z+qA1y{YDGc1Nv%osdOvR>w>uL1J2s$a%HG(YbT^?lpi&AIP@joVBRO9BT5-> z%ouMuV(98%Ki`gDh2y{ZBQZeWY^o7#5#WgDdK)o{?(7L0=%W9TLJv7Lgp}XUW_+v3 z@Z{|*dz4G_oiig!>i=L~hv8`qXe@n~NYI^Dd*0b10y`q%=+0Kgn0y`}kN7K#)Gt8(}UeNwprSm4-$W)ZV451xQP)1KZHo0;;fU z2$?LU%q1`fw)xJ2k!n;$))0tVP30?>dkc;CzHh7#<@jb(vS-bHyV@HTy+HZ*D6q9+Luja# zJupMEN9YG%&5+YwR`z}R`PdM-#I&*kp(dOZ>Pha@c7;(-1S&AIk#r!SC*FOC0y>o@ zMGJ!Yv*`2pDlH=QJ1#L0`B! zFyzC}KL2JO{Rb&v^846P#fHE^XVJv?Ap)b>A)eHd9uHeB;^`G6xjL%v`@reY|tF z`Z@$t2*f-Y7Q>0#K?QX!QUAji#_ejFpsoN(iMoshmV#^yAo@ZxiDOgZ_Uoowy$^o5sJeeu=1%s@xC|8uCqO zhZwqfUsok4RYdc}U=*^?4p{OpmRxzKo0>=e29AzV{tiUt&pHq0yq*{cRh>={b1ZKB zlC@LnNsA;{lpTAt921v3VLD<7iP0LHAND0ujaV878(QsY)pd2t1!SWX-~=T zqJM3i-(AZo>|uPl;2UoS=gF+0l&&A{2mfz@bkUDPSSFNySSE4j$8UaT%FGi zP>E4uqd=VesH3{2WPMz6_J$vDFmpbbxs0FTRsuy!UfjHTdA9X%lUa1Q80De1hl*HD z$B+5dfOMW1#xO0ylCWUY9jr_p% zY*+(S_fsF2(3%|;w9K($-cWEv#({u>dpQhqq^#Mg%(joo2%$w^+K?QF)2rZxkT*cc zc--`6DJstJ1DfGRN_1~)#u%ZvT$>0rOV;Pb&BH9POVemvZTU)O+a~%o1y3KjGIlM2 z5iW&1J7DCD$@XgB=v@z>_Bqcz!7m^L_O8BJhavAA1tgQUv#Q_kLfz$@)_Xj!7M}`n z{jfoPqEwibt0^d2PJ(#LIR0m{MF350Cb!z6zp%9|x5k^4c{!YJO@ILo`zu@DDs|Ru z|4~4{$6RviuJ$6n;;6;Q?VT%>=D^;~Owoj<2T~SSi;1p9K;$jZ>lMa*8&b1xl6UNv zV1_gK*L?R~V0cZlt|7=TzP%$;;}od)f|jx)Oemhej^KGz#LHI^+bS8*b>!)j7(Wvq zq_1X$uvR<7x-vmF8l2xPuxw5{n}qE0o-CLa7TZ<5Y@PrLA3fcM^)#E_@IkyU?pg+& zkjc-uuv`Sl5=kg~oN2v*j+>`qr_1jVF0_qgTtTtf%2bq#D?3y+2J8;!aU{PmX-Y5c zWsE(*Huh86{q+XH=s=&-%aVcV{Pl=T%J`#h9Osl|eFPSh`x3(_B)6Bh=aZqL%#PLr z?o3B#&-YfL^!;-bXWF+WV+XA<%ny5#cTj1y)rvZb@$EJn9UCH>@M6VtkZ7216VWW!V$sSv#ap%X#pd(QH1dt%K$^-Om# z?TvF<&KLiPbmp+_dB|~Y4gLbN5QF1{>;BcK`L=BX!JXK%4KRanpU4JhQp;GRtrU|= zcksF^P3S@|@7u<2(E}0dFIgvM_o&WY-|m_Z zm9tbHX7^-xd%}^HeFXxl>BZCOB&6_zK~K_^&+z1{&r=!%`yk@0?&WO+9X|KAWxa@5 z^tCV@qJiGO4VbFjW6b%BK@r&QAs-l4sCO{dBV-6&4R4NBFV?8Ru|I#rDQhBKGxZFEFiN*XCmzcFhPW=Fw zm+Z^XHtM^dN;4Ad*|N$8PShvm)M65oncO1eLbivW{0^VK12J{VP|EF~sX$k6N22wnU(31zSb3P;sJ>eNv+^ckQJtn$SBLZ6a0Za(jqW83`N`%H`u*I*77!|L7c#0F^2DZR<};eUU37KZJt zPnd}mqu;ejm8n0;UOr9~Rt?Pl4gyA|Bs6L#$Gv8;>w`thl_)(%7wc&2o|qrd_T)b& z%LRsy6wVpnoabXc0~$^o(uW_}|G*h@y8mHGwfGugI{0gV8CmtYL6|GbDpl0HBM8az zLt>IQ?!8E1iR72E7XY9g+an3E;8*Zi2cX4*1H7|4a}C=yMK&lz-!EPCldoDV%5b`n z=ATIfk_}cSeD78Gbc--qdxzvYCC`eZJdH2GP`?z;40T(VYiSq*SrU};FdhwL3vYoU>5nS=-<>+c0yiq zKa68amC`wl8R~A^nGV9fz!XM4?p{|ju8j^G6dSOXnI0DsuO`AJ=OJ@>v62B(XbFEc zEi#T@O|j-QC{T(<687?{=*(37a$FW?uy+*up*=%Y#{U1 zDobWu16#Qmd>tUzrg4 zgOUM~d;AgOkyDWlYtn~$=-(2m<#n|V1YGijyQMuPeAyFhX;|>is@cRd&*VDdJ(fgD%!Z0Q*ac8CADX`&{>Z-q#*R8By748aX5QlLtoQ zxM?!l1*m+rMV8-3YlD{NsG=s3x4!kIel7BXqBr>AN$c9ZVJiB34M?l7cK z*$9lJBAfk0S|^;jPd3)EAc2zX7f5C+y4ll_nCrfbHHrOQxK;XmH1tB*9C|`P#}qy1 zL-*8|6Jzk*7GmsmCL1`S*DZjCEt!WY*~p=O#aP7E)|P)zi~Lqi!gRqi=3E4 z_47cYR@a$~como2Tk8hJ;r(SKvc#C&@;AJ)w7V^L*#KimJyTD4Hl2$2=J;sUCI93= z`)54rqub5pbU>UNMH4dWX`^HQ}_ay3|zG5H~(H7?9TxOji_V=%-)dK&_ zc-o6yfX3>-4leaqG=4m+nbUy0c4F)^G+T#%9o$b2@gSRzN-|7D&(SC6Bn_<$|6{j) zSkFlOw4YwdiPX=^qIzSFcL^&GNmU Q9 literal 0 HcmV?d00001 diff --git a/docs/src/images/dp-specs.png b/docs/src/images/dp-specs.png new file mode 100644 index 0000000000000000000000000000000000000000..b3bee5aacaa92305b65e70307e0daadd6750844e GIT binary patch literal 99464 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 F{y%^@g{S}k literal 0 HcmV?d00001 diff --git a/docs/src/images/on_search_seqeunce_dia.png b/docs/src/images/on_search_seqeunce_dia.png new file mode 100644 index 0000000000000000000000000000000000000000..1830e63981efb67b43905a1df6bd57df8cb3a989 GIT binary patch literal 106245 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

2#sx7Z(wy&FRQ7 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 Ap8x;= literal 0 HcmV?d00001 diff --git a/docs/src/images/patload_postman.png b/docs/src/images/patload_postman.png new file mode 100644 index 0000000000000000000000000000000000000000..126e8f9d0c7954ab085e78ce854c41b0bd0e8941 GIT binary patch literal 24377 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/payload_sequence_diagram.png b/docs/src/images/payload_sequence_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..929a94f14fcfc77b43b262b32ee8b857e96e569d GIT binary patch literal 100260 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

XZ(m-@hgjI*+qAGb@OU90tD5?{S&NW84ZnBa(>Q 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%0Dc8>iH~iR&4HsKSb6& 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 E0s{BkfdBvi literal 0 HcmV?d00001 diff --git a/docs/src/images/search-endpoint-spec.png b/docs/src/images/search-endpoint-spec.png new file mode 100644 index 0000000000000000000000000000000000000000..a739a0af5056326d5cbd2d7dc540d1047b4a30df GIT binary patch literal 69766 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≤8^q#NGlh~{QmlW`KxlpMaiDh5^A=(&z0=k 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/search_sequence_diagram.png b/docs/src/images/search_sequence_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..7bbb32aadf7266c74e9f333d26b79161b8f42868 GIT binary patch literal 114356 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
@@ -121,8 +12,9 @@
- + +
diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css deleted file mode 100644 index 73abba4..0000000 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/webapp/resources/css/styles.css +++ /dev/null @@ -1,25 +0,0 @@ -body { - background-color: rgba(0, 0, 0, 0.1); - color: #fff; - display: flex; - justify-content: space-around; - align-items: center; - height: 100vh; - margin: 0; - padding: 0; - flex-direction: column; -} - -.panel { - width: 99%; - height: 97%; - display: flex; - justify-content: center; - align-items: center; -} - -.contentPanel { - background-color: lightyellow; - border: 1px solid #fff; -} - diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java index d1891c9..c7a76f6 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java @@ -14,15 +14,16 @@ class G2pcRefFarmerRegsvcApplicationTests { @Autowired private Scheduler scheduler; - @Autowired G2pTokenService g2pTokenService ; + @Test void contextLoads() { } @Test - void testResponseScheduler() throws IOException { + void testResponseScheduler() throws Exception { scheduler.responseScheduler(); } + } From 40e950dfd7133f74e864960edbd9f23492abd7df Mon Sep 17 00:00:00 2001 From: ttpl-rt-119 Date: Tue, 19 Dec 2023 18:15:24 +0530 Subject: [PATCH 32/53] Dc implementation added README.md and reference images --- docs/README.md | 1466 ++++++++++++++--- docs/src/images/.p12-dc.png | Bin 0 -> 34127 bytes docs/src/images/dc-package-structure.png | Bin 0 -> 30372 bytes docs/src/images/keycloak-dc-client.png | Bin 0 -> 45433 bytes docs/src/images/keycloak-dp-client-secret.png | Bin 0 -> 60438 bytes docs/src/images/spring_boot_dc_creation.png | Bin 0 -> 50536 bytes 6 files changed, 1242 insertions(+), 224 deletions(-) create mode 100644 docs/src/images/.p12-dc.png create mode 100644 docs/src/images/dc-package-structure.png create mode 100644 docs/src/images/keycloak-dc-client.png create mode 100644 docs/src/images/keycloak-dp-client-secret.png create mode 100644 docs/src/images/spring_boot_dc_creation.png diff --git a/docs/README.md b/docs/README.md index 099ff7c..009ac57 100644 --- a/docs/README.md +++ b/docs/README.md @@ -158,8 +158,8 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. spring-boot-starter-validation ```` -4. Create package structure shown below. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-package-strcuture.png) +4. Create package structure shown below. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-package-strcuture.png) 5. Add .p12 file for search and on-search. ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/.p12-dp.png) 6. In the config package , create the ObjectMapperConfig.java class. @@ -202,7 +202,12 @@ public class RegistryController { } ```` -9. In the service package , create the respective DP ResponseBuilderService.java interface. Refer below example. +9. Add below controller class for dashboard purpose. +```` +@Controller +public class DpDashboardController { +```` +10. In the service package , create the respective DP ResponseBuilderService.java interface. Refer below example. ```` public interface FarmerResponseBuilderService { RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity); @@ -231,203 +236,243 @@ public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderSe public class FarmerValidationServiceImpl implements FarmerValidationService { } ```` -12. Add below autowired dependencies in the RegistryController class. +12. Create DpCommonUtils for handling token. ```` -@Autowired -private RequestHandlerService requestHandlerService; +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pTokenService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; -@Autowired -FarmerValidationService farmerValidationService; +@Service +@Slf4j +public class DpCommonUtils { + } +} +```` +13. Add below autowired dependent bean and values configured with application.yml to authenticate user. +```` + @Value("${keycloak.dp.client.realm}") + private String keycloakRealm; -@Autowired -G2pTokenService g2pTokenService; + @Value("${keycloak.dp.master.getClientUrl}") + private String getClientUrl; + + @Value("${crypto.to_dc.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.to_dc.support_signature}") + private boolean isSign; + + @Value("${keycloak.dp.client.url}") + private String keycloakURL; + + @Value("${keycloak.dp.client.clientId}") + private String keycloakClientId; + + @Value("${keycloak.dp.client.clientSecret}") + private String keycloakClientSecret; + + @Value("${keycloak.dp.master.url}") + private String masterUrl; + + @Value("${keycloak.dp.master.clientId}") + private String masterClientId; + + @Value("${keycloak.dp.master.clientSecret}") + private String masterClientSecret; + + @Value("${keycloak.dp.username}") + private String adminUsername; + + @Value("${keycloak.dp.password}") + private String adminPassword; + + @Autowired + G2pTokenService g2pTokenService; +```` +14. Create handleToken() method. +```` + public void handleToken() throws G2pHttpException, JsonProcessingException { + +```` +15. Add below code to introspect the token whether it is from valid keycloak dp or not in handleToken() method. +```` + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspectUrl = keycloakURL + "/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspectUrl, token, + keycloakClientId, keycloakClientSecret); + log.info("Introspect response -> " + introspectResponse.getStatusCode()); + log.info("Introspect response body -> " + introspectResponse.getBody()); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } +```` +16. Add below code in handleToken() method for validateToken using g2pc-core predefined methods. +```` + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, + g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, + adminUsername, adminPassword)) { + //TODO:check this -> done + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + } +```` +17. +13. Add below autowired dependencies in the RegistryController class. +```` + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + FarmerValidationService farmerValidationService; + + @Autowired + private DpCommonUtils dpCommonUtils; + + @Autowired + private MsgTrackerRepository msgTrackerRepository; ```` 13. Add below application.yml and update as per below instructions. ```` spring: - mvc: - view: - prefix: /WEB-INF/jsp/ - suffix: .jsp - - - pathmatch: - matching-strategy: ANT_PATH_MATCHER - - - datasource: - driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW - - - hikari: - data-source-properties: - stringtype: unspecified - cachePrepStmts: true - prepStmtCacheSize: 250 - prepStmtCacheSqlLimit: 2048 - useServerPrepStmts: true - useLocalSessionState: true - rewriteBatchedStatements: true - cacheResultSetMetadata: true - cacheServerConfiguration: true - maintainTimeStats: false - maximum-pool-size: 5 - connection-timeout: 5000 - jpa: - properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - dialect: org.hibernate.dialect.PostgreSQLDialect - hibernate.ddl-auto: none - show-sql: false - open-in-view: false - generate-ddl: false - autoconfigure: - exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration - - - devtools: - restart: - additional-paths: src/main/webapp - exclude: static/**,public/** - + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + + pathmatch: + matching-strategy: ANT_PATH_MATCHER + + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW + + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** server: - port: 9001 - error: - include-message: always - + port: 9001 + error: + include-message: always spring.data.redis: - repositories.enabled: false - host: 3.109.26.38 - password: abhilash@99222 - port: 6378 - + repositories.enabled: false + # host: 3.109.26.38 + # password: cdpi@99221 + # port: 6379 + host: localhost + password: 123456789 + port: 6376 client: - api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" keycloak: - from-dc: - url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" - client-id: dc-client - client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY - dp: - master-admin-token-url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - get-client-url: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients - realm: dp-farmer - url: https://g2pc-dp1-lab.cdpi.dev/auth - admin: - realm: - client-id: admin-cli - client-secret: Sds0rtxBI4ChXKdVx2ytYsmvRmo9Jc2L # In realm admin-cli -> secret key - master-client-id: admin-cli - master-client-secret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN # In realm In master admin-cli -> secret key - username: admin - password: cdpi@9923 - + from_dc: + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + dp: + url: https://g2pc-dp1-lab.cdpi.dev/auth + username: admin + password: cdpi@9923 + master: + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients + clientId: admin-cli + clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN + client: + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token + realm: dp-farmer + clientId: dp-farmer-client + clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX crypto: - to_dc: - support_encryption: true - support_signature: true - password: "farmer_on_search" - key.path: "classpath:farmer_on_search.p12" - id: FARMER - from_dc: - support_encryption: true - support_signature: true - password: "farmer_search" - key.path: "classpath:farmer_search.p12" - + to_dc: + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER + from_dc: + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" dashboard: - dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" ```` 14. Add below attributes as per your requirement - 1. Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. 2. client.api_urls.client_search_api -> change port as respective on-search api. 3. keycloak.from-dc.url -> create realm for data consumer and replace name with keycloak data-consumer realm name. 4. keycloak.from-dc.client-id -> respective client id of created realm. - 5. keycloak.from-dc.client.secret -> respective client secret of created realm. - 6. keycloak.dp.admin-url -> replace port with your keycloak port - 7. keycloak.dp.get-client-url -> replace port with your keycloak port and name with respective dp realm name. - 8. keycloak.realm -> add respective dp realm name - 9. keycloak.url -> add keycloak url - 10. keycloak.admin.realm.client-id -> respective dp realm's admin_cli client id - 11. keycloak.admin.realm.client-secret -> respective realm's admin_cli client secret - 12. keycloak.admin.master-client-cli -> In respective keycloak instance , master realm’s admin_cli client-id - 13. keyloak.admin.master-client-secret -> In respective keycloak instance , master realm’s admin_cli client-secret - 14. keycloak.admin.username -> Respective keycloak instance username - 15. keycloak.admin.password -> Respective keycloak instance password - 16. crypto.to_dc.support_encryption -> flag of encryption when scheduler will call /on-search api - 17. crypto.to_dc.support_signature -> flag of signature when scheduler will call /on-search api - 18. crypto.to_dc.password -> password of on_search.p12 password - 19. crypto.to_dc.key.path -> path of on_search.p12 file - 20. crypto.to_dc.id -> flag of Farmer id for data consumer. - 21. crypto.from_dc.support_encryption ->flag of encryption when the data consumer will call /search to validate the configuration. - 22. crypto.from_dc.support_signature -> flag of signature when the data consumer will call /search to validate the configuration. - 23. crypto.from_dc.password -> password of search.p12 password - 24. crypto.from_dc.key.path -> path of search.p12 file + 5. keycloak.from-dc.client.secret -> respective client secret of created realm. + 6. keycloak.dp.url -> keycloak dp url + 7. keycloak.dp.username -> dp keycloak admin username + 8. keycloak.dp.password -> dp keycloak admin password + 9. keycloak.dp.master.url -> master token url for particular dp keycloak , change host name in case of change + 10. keycloak.dp.master.getClientUrl -> client details api , replace host and realm name in case. + 11. keycloak.dp.master.clientId -> client id of admin client of master realm + 12. keycloak.dp.master.clientSecret -> client secret of admin client of master realm + 13. keycloak.dp.client.url -> realm token url , replace realm id + 14. keycloak.dp.realm -> realm name of dp + 15. keycloak.dp.clientId -> client Id of client created in dp realm + 16. keycloak.dp.clientSecret -> client secret of client created in dp realm + 17. crypto.to_dc.support_encryption -> flag of encryption when scheduler will call /on-search api + 17. crypto.to_dc.support_signature -> flag of signature when scheduler will call /on-search api + 18. crypto.to_dc.password -> password of on_search.p12 password + 19. crypto.to_dc.key.path -> path of on_search.p12 file + 20. crypto.to_dc.id -> flag of Farmer id for data consumer. + 21. crypto.from_dc.support_encryption ->flag of encryption when the data consumer will call /search to validate the configuration. + 22. crypto.from_dc.support_signature -> flag of signature when the data consumer will call /search to validate the configuration. + 23. crypto.from_dc.password -> password of search.p12 password + 24. crypto.from_dc.key.path -> path of search.p12 file 25. Dashboard.dp_dashboard_url -> -15. Add below values in RegistryController. -```` -@Value("${keycloak.realm}") -private String keycloakRealm; - - -@Value("${keycloak.url}") -private String keycloakURL; - - -@Value("${keycloak.dp.master-admin-token-url}") -private String masterAdminUrl; - - -@Value("${keycloak.dp.get-client-url}") -private String getClientUrl; - - -@Value("${keycloak.admin.realm.client-id}") -private String realmAdmin_cliClientId; - - -@Value("${keycloak.admin.realm.client-secret}") -private String realmAdmin_cliClientSecret; - - -@Value("${keycloak.admin.master-client-id}") -private String masterAdmin_cliClientId; - - -@Value("${keycloak.admin.master-client-secret}") -private String masterAdmin_clientSecret; - - -@Value("${keycloak.admin.username}") -private String adminUsername; - - -@Value("${keycloak.admin.password}") -private String adminPassword; - -@Value("${crypto.to_dc.support_encryption}") -private boolean isEncrypt; - - -@Value("${crypto.to_dc.support_signature}") -private boolean isSign; - -```` + 16. Define below endpoint in RegistryController. ```` @Operation(summary = "Receive search request") @@ -440,19 +485,8 @@ private boolean isSign; public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { ```` -17. Add below code snippet in RegistryController for authentication in which parent g2pc.core library’s methods have been called. This will authenticate the user whether its token is valid or not and whether it has been created from keycloak or not using introspect url. -```` -String token = BearerTokenUtil.getBearerTokenHeader(); -String introspect = keycloakURL+"/realms/"+keycloakRealm+"/protocol/openid-connect/token/introspect"; -ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token, realmAdmin_cliClientId, realmAdmin_cliClientSecret); -if(introspectResponse.getStatusCode().value()==401){ - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(),introspectResponse.getBody())); -} -if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , masterAdmin_cliClientId, masterAdmin_clientSecret, adminUsername , adminPassword)){ - throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); -} -```` + 18. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO. ```` ObjectMapper objectMapper = new ObjectMapper(); @@ -509,6 +543,15 @@ public ErrorResponse handleG2pHttpStatusException( } ```` +22. Create below endpoint for clearing the db. +```` + @GetMapping("/public/api/v1/registry/clear-db") + public void clearDb() throws G2pHttpException, IOException { + //dpCommonUtils.handleToken(); + msgTrackerRepository.deleteAll(); + log.info("DP-1 DB cleared"); + } +```` 22. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. ```` @Getter @@ -912,7 +955,7 @@ List cacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.CACHE ```` if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { ```` -37. Add below code snippet to in ResponseBuilderServiceImpl +37. Add below code snippet to in FarmerResponseBuilderServiceImpl ```` @Autowired private FarmerInfoRepository farmerInfoRepository; @@ -964,44 +1007,46 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx ```` 38. Add below snippet in scheduler class if condition ```` - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); - List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() - .map(txnTrackerEntity -> { - try { - return objectMapper.readValue(txnTrackerEntity.getQuery(), QueryDTO.class); - } catch (JsonProcessingException e) { - return null; - } - }).toList(); - List refRecordsStringsList = farmerResponseBuilderService.getRegFarmerRecords(queryDTOList); - - List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( - requestDTO, refRecordsStringsList); - - ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); - - ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); - Map meta= (Map) headerDTO.getMeta().getData(); - meta.put(CoreConstants.DP_ID,dp_id); - requestDTO.getHeader().getMeta().setData(meta); - String responseString = responseBuilderService.buildResponseString("signature", - headerDTO, responseMessageDTO); - responseString = CommonUtils.formatString(responseString); - log.info("on-search response = {}", responseString); - Resource resource = resourceLoader.getResource(farmer_key_path); - String encryptedSalt=""; - InputStream fis = resource.getInputStream(); - G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl , fis , encryptedSalt); - if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { - throw new G2pHttpException(g2pcError); - } else { - txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); - - } + { + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() + .map(txnTrackerEntity -> { + try { + return objectMapper.readValue(txnTrackerEntity.getQuery(), QueryDTO.class); + } catch (JsonProcessingException e) { + return null; + } + }).toList(); + List refRecordsStringsList = farmerResponseBuilderService.getRegFarmerRecords(queryDTOList); + + List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( + requestDTO, refRecordsStringsList); + + ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); + + ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); + Map meta = (Map) headerDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID, dp_id); + requestDTO.getHeader().getMeta().setData(meta); + String responseString = responseBuilderService.buildResponseString("signature", + headerDTO, responseMessageDTO); + responseString = CommonUtils.formatString(responseString); + log.info("on-search response = {}", responseString); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + Resource resource = resourceLoader.getResource(farmer_key_path); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, encryptedSalt); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } ```` 39. Add below the catch statement at last as mentioned in point 33 that try is already written. ```` @@ -1041,6 +1086,979 @@ Implementation explained in below point when it act like data consumer - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/on_search_spec.png) 7. Once responseString gets validated data consumers should update that data in redis cache and send acknowledgement back to the data consumer. Refer below for more understanding. ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/on_search_seqeunce_dia.png) + +# How to create a Data Consumer ? +1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/spring_boot_dc_creation.png) +2. Extract the downloaded jar and open it in IDE. +3. Add below dependencies in tag in pom.xml +```` + + org.springframework.boot + spring-boot-starter + + + org.projectlombok + lombok + true + + + org.postgresql + postgresql + 42.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + g2pc.dc.core.lib + g2pc-dc-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.springdoc + springdoc-openapi-ui + 1.6.15 + + + com.networknt + json-schema-validator + 1.0.82 + + + org.apache.commons + commons-csv + 1.10.0 + + + jakarta.validation + jakarta.validation-api + 2.0.2 + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + jstl + 1.2 + + + org.springframework.boot + spring-boot-devtools + true + +```` +4. Create package structure shown below. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dc-package-structure.png) +5. Add .p12 files for search received from dp and on-search +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/.p12-dc.png) +5. In the config package , create the ObjectMapperConfig.java class. This class is used to avoid ambiguity between parent class and child class of Header. +```` +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +@Configuration +public class ObjectMapperConfig { + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class); + return objectMapper; + } +} +```` +6. Take reference of below application.yml , create application.yml for particular dc with the help of details mentioned after .yml file. +```` +spring: + mvc: + view: + prefix: /WEB-INF/jsp/ + suffix: .jsp + + pathmatch: + matching-strategy: ANT_PATH_MATCHER + + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW + + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration + + devtools: + restart: + additional-paths: src/main/webapp + exclude: static/**,public/** + +server: + port: 8000 + error: + include-message: always + +spring.data.redis: + repositories.enabled: false + host: 3.109.26.38 + password: cdpi@99221 + port: 6379 + + +keycloak: + from_dp: + farmer: + url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" + clientId: "dp-farmer-client" + clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" + mobile: + url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" + clientId: "dp-mobile-client" + clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" + dc: + url: https://g2pc-dc-lab.cdpi.dev/auth + username: admin + password: cdpi@9922 + master: + url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients + clientId: admin-cli + clientSecret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs + client: + url: https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token + realm: data-consumer + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + +crypto: + to_dp_farmer: + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" + to_dp_mobile: + support_encryption: true + support_signature: true + password: "mobile_search" + key_path: "classpath:mobile_search.p12" + from_dp_farmer: + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER + from_dp_mobile: + support_encryption: true + support_signature: true + password: "mobile_on_search" + key_path: "classpath:mobile_on_search.p12" + id: MOBILE + +registry: + api_urls: + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + +dashboard: + left_panel_url: "http://3.109.26.38:3005/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + right_panel_url: "http://3.109.26.38:3005/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + bottom_panel_url: "http://3.109.26.38:3005/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + post_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" + clear_dc_db_endpoint_url: "http://localhost:8000/private/api/v1/registry/clear-db" + clear_dp1_db_endpoint_url: "http://localhost:9001/private/api/v1/registry/clear-db" + clear_dp2_db_endpoint_url: "http://localhost:9002/private/api/v1/registry/clear-db" +```` + +7. Add below attributes as per your requirement - + 1. Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. + 2. spring.data.redis.host -> add redis host + 3. spring.data.redis.password -> password of dp redis port + 4. spring.data.redis.port -> port assigned for redis of particular dp + 5. keycloak.from_dp.{dp-name}.url -> url of token creation of particular dp , replace realm name with respective dp. + 6. keycloak.from_dp.{dp-name}.clientId -> client id of particular dp client , check in below image. + 7. keycloak.from_dp.{dp-name}.clientSecret -> client secret of particular dp client , check in below image. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-dp-client-secret.png) + 8. keycloak.dc.url -> hosting url of dc + 9. keycloak.dc.username -> authentication username of hosting url of dc + 10. keycloak.dc.password -> authentication password of hosting url of dc + 11. keycloak.dc.master.url -> token endpoint url of master realm of data-consumer. + 12. keycloak.dc.master.getClientUrl -> get client by realm id endpoint of particular dc. replace host and realm id for particular data consumer + 13. keycloak.dc.master.clientId -> admin cli client id of master realm of dc + 14. keycloak.dc.master.clientSecret -> admin cli client secret of master realm of dc + 15. keycloak.dc.client.url -> token endpoint url of data-consumer realm of data-consumer. + 16. keycloak.dc.client.realm -> dc realm name + 17. keycloak.dc.client.clientId -> dc client id + 18. keycloak.dc.client.clientSecret -> dc client secret + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-dc-client.png) + 19. crypto.to_dp_{dp-name}.support_encryption -> encryption flag of particular dp of search endpoint + 20. crypto.to_dp_{dp-name}.support_signature -> signature flag of particular dp of search endpoint + 21. crypto.to_dp_{dp-name}.password -> password of .p12 file particular dp of search endpoint + 22. crypto.to_dp_{dp-name}.key_path -> key path of .p12 file particular dp of search endpoint + 23. crypto.from_dp_{dp-name}.support_encryption -> encryption flag of particular dp of on-search endpoint + 24. crypto.from_dp_{dp-name}.support_signature -> signature flag of particular dp of on-search endpoint + 25. crypto.from_dp_{dp-name}.password -> password of .p12 file particular dp of on-search endpoint + 26. crypto.from_dp_{dp-name}.key_path -> key path of .p12 file particular dp of on-search endpoint + 27. crypto.from_dp_{dp-name}.id -> id flag for dp + 28. registry.api_urls.{dp-name}_search_api -> search end point of dp + 29. dashboard.clear_dc_db_endpoint_url -> clear db endpoint of dc + 30. dashboard.clear_dp_db_endpoint_url -> clear db endpoints of multiple dps +8. Add RegistryConfig.java class in config package +```` +@Service +@Slf4j +public class RegistryConfig {} +```` +9. Add below values which mentioned in application.yml for particular dp or multiple dps +```` + @Value("${registry.api_urls.farmer_search_api}") + private String farmerSearchURL; + + @Value("${dashboard.clear_dp1_db_endpoint_url}") + private String farmerClearDbURL; + + @Value("${keycloak.from_dp.farmer.clientId}") + private String farmerClientId; + + @Value("${keycloak.from_dp.farmer.clientSecret}") + private String farmerClientSecret; + + @Value("${keycloak.from_dp.farmer.url}") + private String keycloakFarmerTokenUrl; + + @Value("${crypto.to_dp_farmer.support_encryption}") + private boolean isFarmerEncrypt; + + @Value("${crypto.to_dp_farmer.support_signature}") + private boolean isFarmerSign; + + @Value("${crypto.to_dp_farmer.key_path}") + private String farmerKeyPath; + + @Value("${crypto.to_dp_farmer.password}") + private String farmerKeyPassword; + +```` +10. Add below method getFarmerRegistryMap() of particular dp and create same methods for multiple dps. +```` + private Map getFarmerRegistryMap() { + Map farmerRegistryMap = new HashMap<>(); + farmerRegistryMap.put(CoreConstants.QUERY_NAME, "paid_farmer"); + farmerRegistryMap.put(CoreConstants.REG_TYPE, "ns:FARMER_REGISTRY"); + farmerRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); + farmerRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); + farmerRegistryMap.put(CoreConstants.SORT_ATTRIBUTE, "farmer_id"); + farmerRegistryMap.put(CoreConstants.SORT_ORDER, SortOrderEnum.ASC.toValue()); + farmerRegistryMap.put(CoreConstants.PAGE_NUMBER, "1"); + farmerRegistryMap.put(CoreConstants.PAGE_SIZE, "10"); + farmerRegistryMap.put(CoreConstants.KEYCLOAK_URL, keycloakFarmerTokenUrl); + farmerRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_ID, farmerClientId); + farmerRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_SECRET, farmerClientSecret); + farmerRegistryMap.put(CoreConstants.SUPPORT_ENCRYPTION, "" + isFarmerEncrypt); + farmerRegistryMap.put(CoreConstants.SUPPORT_SIGNATURE, "" + isFarmerSign); + farmerRegistryMap.put(CoreConstants.KEY_PATH, farmerKeyPath); + farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, farmerKeyPassword); + farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchURL); + farmerRegistryMap.put(CoreConstants.DP_CLEAR_DB_URL, farmerClearDbURL); + return farmerRegistryMap; + } +```` +11. Add below 2 methods to create map of particular dp. + - In getQueryParamsConfig() create map for particular dp for specific queryparams declared in specification and add it in queryParam map. + - In getRegistrySpecificConfig() in this method particular dp's map creation method will call and map will created with particular dp key and returned. +```` + public Map getQueryParamsConfig() { + Map queryParamsConfig = new HashMap<>(); + + Map farmerRegistryMap = new HashMap<>(); + farmerRegistryMap.put("farmer_id", ""); + farmerRegistryMap.put("season", ""); + + queryParamsConfig.put(Constants.FARMER_REGISTRY, farmerRegistryMap); + return queryParamsConfig; + } + + /** + * Map to represent which common values to be used to generate request for a registry + * + * @return Map to represent registry specific config values + */ + public Map getRegistrySpecificConfig() { + Map queryParamsConfig = new HashMap<>(); + + Map farmerRegistryMap = getFarmerRegistryMap(); + + queryParamsConfig.put(Constants.FARMER_REGISTRY, farmerRegistryMap); + return queryParamsConfig; + } +```` + +12. Create DcController in controller.rest package +```` +@RestController +@Slf4j +@RequestMapping(produces = "application/json") +@Tag(name = "Data Consumer", description = "DC APIs") +public class DcController {} +```` +13. Create below endpoint for triggering dc communication using only one data. +```` + @Operation(summary = "Receive consumer search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/public/api/v1/consumer/search/payload") + public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { +```` +14. Add below code snippet to request from payload +```` + log.info("Payload received from postman"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(payloadMap)) { + acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); + } + return acknowledgementDTO; +```` +15. To run above endpoint refer below curl. +```` +curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ +--header 'Content-Type: application/json' \ +--data '{ + "farmer_id" : "F-1", + "farmer_name" : "Farmer-1", + "mobile_number" : "9767670153", + "season" : "2023-xyz" + +}' + +```` +16. Create below end point for triggering dc communication for multiple data using csv file +```` + @Operation(summary = "Receive consumer search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/consumer/search/csv") + public AcknowledgementDTO createSearchRequestsFromCsv(@RequestPart(value = "file") MultipartFile payloadFile) throws Exception { + log.info("Payload received from csv file"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(payloadFile)) { + acknowledgementDTO = dcRequestBuilderService.generatePayloadFromCsv(payloadFile); + } + return acknowledgementDTO; + } +```` +17. To run above endpoint refer below curl and create one payload.csv with multiple data. +```` +curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ +--form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' +```` +18. Add below exception handling in the DC controller. +```` +@ExceptionHandler(value = G2pcValidationException.class) +@ResponseStatus(HttpStatus.BAD_REQUEST) +public ValidationErrorResponse handleValidationException(G2pcValidationException ex) { + return new ValidationErrorResponse(ex.getG2PcErrorList()); +} + +@ExceptionHandler(value = G2pHttpException.class) +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) { + return new ErrorResponse(ex.getG2PcError()); +} +```` +19. Create RegResponseDTO for particular data provider in dto.(dp-name) package. Shown below +```` +package g2pc.ref.dc.client.dto.farmer.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class RegRecordFarmerDTO { + + @JsonProperty("farmer_id") + private String farmerId; + + @JsonProperty("farmer_name") + private String farmerName; + + @JsonProperty("season") + private String season; + + @JsonProperty("payment_status") + private String paymentStatus; + + @JsonProperty("payment_date") + private String paymentDate; + + @JsonProperty("payment_amount") + private Double paymentAmount; +} +```` +20. Declare below interface DcRequestBuilderService to generateRequest for single data and multiple data in csv file. +```` +public interface DcRequestBuilderService { + + AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception; + + AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception; +} +```` +21. Create DcRequestBuilderServiceImpl.java class and implement it from DcRequestBuilderService interface. +```` +@Service +@Slf4j +public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { +```` +22. Add below autowired beans in above class. +```` + @Autowired + private RequestBuilderService requestBuilderService; + + @Autowired + RegistryConfig registryConfig; + + @Autowired + TxnTrackerService txnTrackerService; + + @Autowired + private ResourceLoader resourceLoader; +```` +23. Override below method generateRequest() from interface. +```` + public AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception { + Map g2pcErrorMap = new HashMap<>(); + + List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + List> queryMapFilteredList = queryMapList.stream() + .map(map -> map.entrySet().stream() + .filter(entry -> entry.getKey().equals(configEntryMap.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))).toList(); + + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + List searchCriteriaDTOList = new ArrayList<>(); + for (Map queryParamsMap : queryMapFilteredList) { + SearchCriteriaDTO searchCriteriaDTO = requestBuilderService.getSearchCriteriaDTO(queryParamsMap, registrySpecificConfigMap); + searchCriteriaDTOList.add(searchCriteriaDTO); + } + String transactionId = CommonUtils.generateUniqueId("T"); + String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId); + try { + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = requestBuilderService.sendRequest(requestString, + registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString()); + g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); + log.info("DP_SEARCH_URL = {}", registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); + + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); + txnTrackerService.saveRequestTransaction(requestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); + txnTrackerService.saveRequestInDB(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } catch (Exception e) { + log.error("Exception in generateRequest : ", e); + } + } + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(g2pcErrorMap); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + return acknowledgementDTO; + } +```` +24. To generate request from csv file overwrite below method and one private method to iterate csv file. +```` + @Override + public AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { + Reader reader = new BufferedReader(new InputStreamReader(payloadFile.getInputStream())); + CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); + List> payloadMapList = getPayloadMapList(csvParser); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(payloadMapList)) { + acknowledgementDTO = generateRequest(payloadMapList); + } + return acknowledgementDTO; + } + + private static List> getPayloadMapList(CSVParser csvParser) { + List csvRecordList = csvParser.getRecords(); + CSVRecord headerRecord = csvRecordList.get(0); + List headerList = new ArrayList<>(); + for (int i = 0; i < headerRecord.size(); i++) { + headerList.add(headerRecord.get(i)); + } + List> payloadMapList = new ArrayList<>(); + for (int i = 1; i < csvRecordList.size(); i++) { + CSVRecord csvRecord = csvRecordList.get(i); + Map payloadMap = new HashMap<>(); + for (int j = 0; j < headerRecord.size(); j++) { + payloadMap.put(headerList.get(j), csvRecord.get(j)); + } + payloadMapList.add(payloadMap); + } + return payloadMapList; + } +```` +25. Create on-search endpoint , refer below snippet , there are methods called in this methods refer code after this point. +```` + @Operation(summary = "Listen to registry response") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.ON_SEARCH_RESPONSE_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/on-search") + public AcknowledgementDTO handleOnSearchResponse(@RequestBody String responseString) throws Exception { + commonUtils.handleToken(); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); + ResponseMessageDTO messageDTO; + Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); + messageDTO = dcValidationService.signatureValidation(metaData, responseDTO); + responseDTO.setMessage(messageDTO); + try { + dcValidationService.validateResponseDto(responseDTO); + if (ObjectUtils.isNotEmpty(responseDTO)) { + acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO); + } + } catch (JsonProcessingException | IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + return acknowledgementDTO; + } +```` +26. Create DcCommonUtil.java in util package and create handle token method to handle and validate token. +```` +package g2pc.ref.dc.client.utils; +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pTokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +@Service +public class DcCommonUtils { +} + +```` +26. Add below values from application.yml to validate token +```` + @Autowired + G2pTokenService g2pTokenService; + + @Value("${keycloak.dc.client.realm}") + private String keycloakRealm; + + @Value("${keycloak.dc.client.url}") + private String keycloakURL; + + @Value("${keycloak.dc.master.url}") + private String masterUrl; + + @Value("${keycloak.dc.master.getClientUrl}") + private String getClientUrl; + + @Value("${keycloak.dc.client.clientId}") + private String dcClientId; + + @Value("${keycloak.dc.client.clientSecret}") + private String dcClientSecret; + + @Value("${keycloak.dc.master.clientId}") + private String masterClientId; + + @Value("${keycloak.dc.master.clientSecret}") + private String masterClientSecret; + + @Value("${keycloak.dc.username}") + private String adminUsername; + + @Value("${keycloak.dc.password}") + private String adminPassword; + + + +```` +27. Add below method in DcCommonUtils.java to handle and validate token. +```` + public void handleToken() throws G2pHttpException, JsonProcessingException { + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspect = keycloakURL + "/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, dcClientId, dcClientSecret); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, adminUsername, adminPassword)) { + + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + } + } +```` +28. Create DcValidationService interface with 3 methods , validateRegRecords() this method is dc specific , to validate query params. +```` +@Service +public interface DcValidationService { + + public void validateResponseDto(ResponseDTO responseDTO) throws Exception; + + public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException; + + ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception; +} +```` +29. Create DcValidationServiceImpl class to implement DcValidationService interface and override method. +```` +@Service +@Slf4j +public class DcValidationServiceImpl implements DcValidationService { +```` +30. Add below autowired beans in DcValidationServiceImpl. Add below values for all required dps. +```` + @Autowired + ResponseHandlerService responseHandlerService; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + + @Autowired + private AsymmetricSignatureService asymmetricSignatureService; + + @Autowired + private ResourceLoader resourceLoader; + + @Value("${crypto.from_dp_farmer.support_encryption}") + private boolean isFarmerEncrypt; + + @Value("${crypto.from_dp_farmer.support_signature}") + private boolean isFarmerSign; + + @Value("${crypto.from_dp_farmer.password}") + private String farmerp12Password; + + @Value("${crypto.from_dp_farmer.key_path}") + private String farmerKeyPath; + + @Value("${crypto.from_dp_farmer.id}") + private String farmerID; +```` +31. Override below signatureValidation() method and add required if conditions for required dps. +```` + @Override + public ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception { + + + String p12Password =""; + boolean isEncrypt = false; + boolean isSign=false; + String keyPath=""; + if(metaData.get(CoreConstants.DP_ID).equals(farmerID)){ + p12Password = farmerp12Password; + isEncrypt = isFarmerEncrypt; + isSign = isFarmerSign; + keyPath = farmerKeyPath; + } else if(metaData.get(CoreConstants.DP_ID).equals(mobileID)){ + p12Password = mobilep12Password; + isEncrypt=isMobileEncrypt; + isSign = isMobileSign; + keyPath = mobileKeyPath; + } + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); + ObjectMapper objectMapper = new ObjectMapper(); + ResponseMessageDTO messageDTO; + + + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(keyPath); + InputStream fis = resource.getInputStream(); + + if(isEncrypt){ + if(!responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + String responseSignature = responseDTO.getSignature(); + String messageString = responseDTO.getMessage().toString(); + String data = responseHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(responseDTO.getHeader().getIsMsgEncrypted()){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + String responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + String responseSignature = responseDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = responseHeaderString+messageString; + log.info("Signature ->"+responseSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = responseDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(),"Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(ResponseMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(responseDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + } + } + return messageDTO; + } +```` +32. Override validateResponse() and validateRegRecord() method , in this for another dp validateRegRecord Method will be different as per query params. +```` + @Override + public void validateResponseDto(ResponseDTO responseDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(responseDTO.getHeader()); + ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). + readValue(headerString); + responseHandlerService.validateResponseHeader(headerDTO); + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + validateRegRecords(messageDTO); + responseHandlerService.validateResponseMessage(messageDTO); + } + + /** + * Validate reg records. + * + * @param messageDTO the message dto + * @throws G2pcValidationException the g 2 pc validation exception + * @throws JsonProcessingException the json processing exception + */ + @Override + public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException { + + ObjectMapper objectMapper = new ObjectMapper(); + List searchResponseList = messageDTO.getSearchResponse(); + for(SearchResponseDTO searchResponseDTO : searchResponseList){ + DataDTO dataDTO = searchResponseDTO.getData(); + String regRecordString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(dataDTO.getRegRecords()); + log.info("regRecordString -> " + regRecordString); + if(!regRecordString.equals("null")){ + InputStream schemaStream; + if(dataDTO.getRegType().toString().equals("ns:MOBILE_REGISTRY")){ + schemaStream = DcValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/RegRecordMobileSchema.json"); + } else { + schemaStream = DcValidationServiceImpl.class.getClassLoader() + .getResourceAsStream("schema/RegRecordFarmerSchema.json"); + } + JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); + JsonSchema schemaRegRecord = null; + if (schemaStream != null) { + schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); + List errorCombinedMessage = new ArrayList<>(); + for (ValidationMessage error : errorMessage) { + log.info("Validation errors in Reg records" + error); + errorCombinedMessage.add(new G2pcError("", error.getMessage())); + } + if (errorMessage.size() > 0) { + throw new G2pcValidationException(errorCombinedMessage); + } + } + } + + } +```` +33. Add schema to validate regRecord / respective query params , refer below schema +```` +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties": { + "farmer_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "farmer_name": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "season": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "payment_status": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "payment_date": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "payment_amount": { + "type": "number" + } + } , + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} +```` +34. Create DcResponseHandlerService interface to handle the response +```` +public interface DcResponseHandlerService { + + AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException; +} +```` +34. Create DcResponseHandlerServiceImpl class which implements DcResponseHandlerService interface +```` +@Service +@Slf4j +public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { + + @Autowired + private TxnTrackerService txnTrackerService; + + @Override + public AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + ObjectMapper objectMapper = new ObjectMapper(); + + txnTrackerService.updateTransactionDbAndCache(responseDTO); + log.info("on-search response received from registry : {}", objectMapper.writeValueAsString(responseDTO)); + acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED.toString()); + acknowledgementDTO.setStatus(Constants.COMPLETED); + return acknowledgementDTO; + } +} +```` +35. diff --git a/docs/src/images/.p12-dc.png b/docs/src/images/.p12-dc.png new file mode 100644 index 0000000000000000000000000000000000000000..ec5e767ffdcd1946a91d027ea94c94b48a80bc9d GIT binary patch literal 34127 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

54@DBYiL_#zBlqBY1w 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/filezilla.png b/docs/src/images/filezilla.png new file mode 100644 index 0000000000000000000000000000000000000000..bf990794438400a1e8d5f83f6d824d172f9a3596 GIT binary patch literal 172691 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 !OBH|x6VPpsLtFw(4_}<`qGWMPeh{aIl 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�JYJ{ZA*sp-8-E?U}d!3Em)bEMD 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 z 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

B83dqAD;zQ`784YJ2KYluGQ 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 Som2P?Oys4NrD`NhL;f#7p|xrN literal 0 HcmV?d00001 From 68b41164ab1b25f8dd8388c144a1e8ef04900693 Mon Sep 17 00:00:00 2001 From: ttpl-rt-119 Date: Mon, 22 Jan 2024 19:09:46 +0530 Subject: [PATCH 36/53] Updated README.md file with /status and /on-status , updated images url --- docs/README.md | 649 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 561 insertions(+), 88 deletions(-) diff --git a/docs/README.md b/docs/README.md index de7db43..3d922f3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -81,7 +81,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. - Implementation explained in below point when it act like data consumer - 1. When it acts like a consumer , it needs to define a scheduler. Scheduler is nothing but a framework that allows you to schedule and execute tasks at specific intervals or times. 2. In this scheduler , dp will check whether there is any data stored in pending status with a particular cache key corresponding to that data provider. - 3. If it gets data it will build the response data and the call /on-search endpoint is defined in the data consumer . Please refer to Image 2 for the same. + 3. If it gets data it will build the response data and the call /on-search endpoint is defined in the data consumer . 4. Refer below for understanding of flow from dp to parent libraries. ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-scheduler-seq-diagram.png) @@ -267,7 +267,7 @@ public class DpCommonUtils { } } ```` -13. Add below autowired dependent bean and values configured with application.yml to authenticate user. +13. Add below autowired dependent bean and values configured with application.yml to authenticate user in DpCommonUtils. ```` @Value("${keycloak.dp.client.realm}") private String keycloakRealm; @@ -323,7 +323,7 @@ public class DpCommonUtils { @Value("${sftp.dc.remote.outbound_directory}") private String sftpDcRemoteOutboundDirectory; ```` -14. Create handleToken() method to introspect the token whether it is from valid keycloak dp or not in handleToken() method and validateToken using g2pc-core predefined methods. +14. Create handleToken() method to introspect the token whether it is from valid keycloak dp or not in handleToken() method and validateToken using g2pc-core predefined methods in DpCommonUtils. ```` public void handleToken() throws G2pHttpException, JsonProcessingException { @@ -529,7 +529,7 @@ sftp: public AcknowledgementDTO handleRequest(@RequestBody String requestString) throws Exception { ```` -21. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO. +21. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO in handleRequest(). ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -540,13 +540,13 @@ RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). readValue(requestString); RequestMessageDTO messageDTO = null; ```` -22. Add below code snippet to validate signature and encryption. +22. Add below code snippet handleRequest() to validate signature and encryption. ```` Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); requestDTO.setMessage(messageDTO); ```` -23. Add below code snippet to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. +23. Add below code snippet in handleRequest() to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. ```` String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { @@ -608,8 +608,11 @@ import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.dto.SftpDpData; +import g2pc.ref.farmer.regsvc.service.DpSftpPushUpdateService; import g2pc.ref.farmer.regsvc.service.FarmerValidationService; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @@ -619,7 +622,6 @@ import org.springframework.messaging.Message; import org.springframework.web.server.ResponseStatusException; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -640,57 +642,53 @@ public class DcSftpListener { @Autowired FarmerValidationService farmerValidationService; + @Autowired + private DpSftpPushUpdateService dpSftpPushUpdateService; + @SuppressWarnings("unchecked") @ServiceActivator(inputChannel = "sftpInbound") public void handleMessageInbound(Message message) { try { File file = message.getPayload(); - log.info("Received Message from inbound directory: {}", file.getName()); - String requestString = new String(Files.readAllBytes(file.toPath())); - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). - readValue(requestString); - RequestMessageDTO messageDTO; - - Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); - - messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); - requestDTO.setMessage(messageDTO); - String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); - try { - farmerValidationService.validateRequestDTO(requestDTO); - requestHandlerService.buildCacheRequest( - objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_SFTP); - } catch (G2pcValidationException e) { - throw new G2pcValidationException(e.getG2PcErrorList()); - } catch (JsonProcessingException e) { - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); - } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + log.info("Received Message from inbound directory of dp-1: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file) && file.getName().contains(".json")) { + String requestString = new String(Files.readAllBytes(file.toPath())); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO; + + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + + messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); + requestDTO.setMessage(messageDTO); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + try { + farmerValidationService.validateRequestDTO(requestDTO); + requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_SFTP); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } } Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); } catch (Exception e) { log.error("Error: ", e); } } - - @ServiceActivator(inputChannel = "sftpOutbound") - public void handleMessageOutbound(Message message) throws IOException { - File file = message.getPayload(); - log.info("Received Message from outbound directory: {}", file.getName()); - Files.deleteIfExists(Path.of(sftpLocalDirectoryOutbound + "/" + file.getName())); - } - @ServiceActivator(inputChannel = "errorChannel") public void handleError(Message message) { Throwable error = (Throwable) message.getPayload(); log.error("Handling ERROR: {}", error.getMessage()); } } - ```` 27. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. ```` @@ -803,7 +801,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque ```` 30. Define below autowired beans and configurations in ValidationServiceImpl. ```` -@Autowired + @Autowired RequestHandlerService requestHandlerService; @Value("${crypto.from_dc.support_encryption}") @@ -1002,7 +1000,7 @@ Define below method in Scheduler. public void responseScheduler() throws IOException { ```` 33. Define try catch in the same method. -34. Add below snippet in method. +34. Add below snippet in method responseScheduler(). ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); @@ -1011,11 +1009,11 @@ objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); ```` List cacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); ```` -36. Check whether in list the status in PNDG or not +36. Check whether in list the status in PNDG or not. ```` if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { ```` -37. Add below code snippet to in FarmerResponseBuilderServiceImpl +37. Add below code snippet to in FarmerResponseBuilderServiceImpl. ```` @Autowired private FarmerInfoRepository farmerInfoRepository; @@ -1065,7 +1063,7 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx return regFarmerRecordsList; ```` -38. Add below snippet in scheduler class if condition +38. Add below snippet in scheduler class method responseScheduler() if condition. ```` { RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); @@ -1106,6 +1104,24 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx } else { txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); } + List statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); + for (String cacheKey : statusCacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + G2pcError g2pcError = responseBuilderService.buildOnStatusScheduler(cacheDTO); + + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } + } + } ```` 39. Add below the catch statement at last as mentioned in point 33 that try is already written. @@ -1169,7 +1185,14 @@ dpCommonUtils.handleToken(); } ```` -44. Add below overloaded method in FarmerValidationService +44. Add below endpoint in RegistryController.java. +```` + @GetMapping(value = "/dashboard/sftp/dp1/data", produces = "text/event-stream") + public SseEmitter sseEmitterFirstPanel() { + return dpSftpPushUpdateService.register(); + } +```` +45. Add below overloaded method in FarmerValidationService ```` StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception ; ```` @@ -1299,7 +1322,7 @@ void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, 3. Get request data for particular cache key and convert it into cacheDTO. 4. Check if status is pending for that data. 5. Get StatusRequestDto from cacheDto and statusRequestMessageDTO from StatusRequestDto. - 6. Fetch the msgTrackerEntity from db using statusRequestDto and build responseHeaderDto + 6. Fetch the msgTrackerEntity from db using statusRequestDto and build responseHeaderDto. 7. Build StatusResponseMessageDto using StatusRequestMessageDto. 8. Build StatusResponseString , create resource using farmer key path and send response to dc. ```` @@ -1338,6 +1361,13 @@ for (String cacheKey: statusCacheKeysList) { ```` 49. Create DpDashboardController for creating dashboard endpoints for Grafana. ```` +package g2pc.ref.farmer.regsvc.controller.rest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + @Controller public class DpDashboardController { @@ -1349,9 +1379,119 @@ public class DpDashboardController { model.addAttribute("dp_dashboard_url", dpDashboardUrl); return "dashboard"; } + + } + +```` +50. Add CorsConfig.java in config folder, refer below code. ```` -50. +package g2pc.ref.farmer.regsvc.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Value("${dashboard.cors_origin_url}") + private String corsOriginUrl; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins(corsOriginUrl) + .allowedMethods("*") + .allowedHeaders("*"); + } +} +```` +51. Add below dto SftpDpData in DP. +```` +package g2pc.ref.farmer.regsvc.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SftpDpData { + + private String dpType; + private String messageTs; + private String transactionId; + private String fileName; +} + +```` +52. Add below interface in service package +```` +package g2pc.ref.farmer.regsvc.service; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface DpSftpPushUpdateService { + + SseEmitter register(); + + void pushUpdate(Object update); +} +```` +53. Implement service in below class. +```` +package g2pc.ref.farmer.regsvc.serviceimpl; + +import g2pc.ref.farmer.regsvc.dto.SftpDpData; +import g2pc.ref.farmer.regsvc.service.DpSftpPushUpdateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +public class DpSftpPushUpdateServiceImpl implements DpSftpPushUpdateService { + private final List emitters = new ArrayList<>(); + public SseEmitter register() { + int minutes = 15; + long timeout = (long) minutes * 60000; + SseEmitter emitter = new SseEmitter(timeout); + this.emitters.add(emitter); + emitter.onCompletion(() -> this.emitters.remove(emitter)); + emitter.onTimeout(() -> this.emitters.remove(emitter)); + log.info("SSE emitter registered" + emitter); + return emitter; + } + public void pushUpdate(Object update) { + List deadEmitters = new ArrayList<>(); + this.emitters.forEach(emitter -> { + try { + emitter.send(update); + } catch (IOException e) { + deadEmitters.add(emitter); + } + }); + this.emitters.removeAll(deadEmitters); + } + public SftpDpData getSftpDpData(String dpType, String messageTs, String transactionId, String filename){ + SftpDpData sftpDpData = new SftpDpData(); + sftpDpData.setDpType(dpType); + sftpDpData.setMessageTs(messageTs); + sftpDpData.setTransactionId(transactionId); + sftpDpData.setFileName(filename); + return sftpDpData; + } +} + +```` +54. # 6. Data Consumer (DC) Implementation - In DC implementation , as explained in Overview of libraries , dependency of G2pc-dc-core-lib needs to be added. @@ -1618,38 +1758,108 @@ public class RegistryConfig {} ```` 9. Add below values which mentioned in application.yml for particular dp or multiple dps ```` + @Value("${registry.api_urls.farmer_search_api}") private String farmerSearchURL; - + + @Value("${registry.api_urls.mobile_search_api}") + private String mobileSearchURL; + @Value("${dashboard.clear_dp1_db_endpoint_url}") private String farmerClearDbURL; - + + @Value("${dashboard.clear_dp2_db_endpoint_url}") + private String mobileClearDbURL; + @Value("${keycloak.from_dp.farmer.clientId}") private String farmerClientId; @Value("${keycloak.from_dp.farmer.clientSecret}") private String farmerClientSecret; - + @Value("${keycloak.from_dp.farmer.url}") private String keycloakFarmerTokenUrl; - + + @Value("${keycloak.from_dp.mobile.url}") + private String keycloakMobileTokenUrl; + + @Value("${keycloak.from_dp.mobile.clientId}") + private String mobileClientId; + + @Value("${keycloak.from_dp.mobile.clientSecret}") + private String mobileClientSecret; + @Value("${crypto.to_dp_farmer.support_encryption}") private boolean isFarmerEncrypt; @Value("${crypto.to_dp_farmer.support_signature}") private boolean isFarmerSign; - + + @Value("${crypto.to_dp_mobile.support_encryption}") + private boolean isMobileEncrypt; + + @Value("${crypto.to_dp_mobile.support_signature}") + private boolean isMobileSign; + + @Value("${crypto.to_dp_mobile.key_path}") + private String mobileKeyPath; + @Value("${crypto.to_dp_farmer.key_path}") private String farmerKeyPath; @Value("${crypto.to_dp_farmer.password}") private String farmerKeyPassword; + @Value("${crypto.to_dp_mobile.password}") + private String mobileKeyPassword; + + @Value("${sftp.dp1.host}") + private String sftpDp1Host; + + @Value("${sftp.dp1.port}") + private int sftpDp1Port; + + @Value("${sftp.dp1.user}") + private String sftpDp1User; + + @Value("${sftp.dp1.password}") + private String sftpDp1Password; + + @Value("${sftp.dp1.remote.inbound_directory}") + private String sftpDp1RemoteInboundDirectory; + + @Value("${sftp.dp2.host}") + private String sftpDp2Host; + + @Value("${sftp.dp2.port}") + private int sftpDp2Port; + + @Value("${sftp.dp2.user}") + private String sftpDp2User; + + @Value("${sftp.dp2.password}") + private String sftpDp2Password; + + @Value("${sftp.dp2.remote.inbound_directory}") + private String sftpDp2RemoteInboundDirectory; + + @Value("${crypto.sample.password}") + private String dummyKeyPassword; + + @Value("${crypto.sample.key.path}") + private String dummyKeyPath; + + @Value("${registry.api_urls.farmer_status_api}") + private String farmerStatusUrl; + + @Value("${registry.api_urls.mobile_status_api}") + private String mobileStatusUrl; + ```` 10. Add below method getFarmerRegistryMap() of particular dp and create same methods for multiple dps. ```` private Map getFarmerRegistryMap() { - Map farmerRegistryMap = new HashMap<>(); + Map farmerRegistryMap = new HashMap<>(); farmerRegistryMap.put(CoreConstants.QUERY_NAME, "paid_farmer"); farmerRegistryMap.put(CoreConstants.REG_TYPE, "ns:FARMER_REGISTRY"); farmerRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); @@ -1663,10 +1873,24 @@ public class RegistryConfig {} farmerRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_SECRET, farmerClientSecret); farmerRegistryMap.put(CoreConstants.SUPPORT_ENCRYPTION, "" + isFarmerEncrypt); farmerRegistryMap.put(CoreConstants.SUPPORT_SIGNATURE, "" + isFarmerSign); - farmerRegistryMap.put(CoreConstants.KEY_PATH, farmerKeyPath); - farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, farmerKeyPassword); + if(isSignEncrypt.equals("2")){ + farmerRegistryMap.put(CoreConstants.KEY_PATH, dummyKeyPath); + farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, dummyKeyPassword); + } else { + farmerRegistryMap.put(CoreConstants.KEY_PATH, farmerKeyPath); + farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, farmerKeyPassword); + } farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchURL); farmerRegistryMap.put(CoreConstants.DP_CLEAR_DB_URL, farmerClearDbURL); + farmerRegistryMap.put(SftpConstants.SFTP_HOST, sftpDp1Host); + farmerRegistryMap.put(SftpConstants.SFTP_PORT, String.valueOf(sftpDp1Port)); + farmerRegistryMap.put(SftpConstants.SFTP_USER, sftpDp1User); + farmerRegistryMap.put(SftpConstants.SFTP_PASSWORD, sftpDp1Password); + farmerRegistryMap.put(SftpConstants.SFTP_SESSION_CONFIG, "no"); + farmerRegistryMap.put(SftpConstants.SFTP_ALLOW_UNKNOWN_KEYS, String.valueOf(true)); + farmerRegistryMap.put(SftpConstants.SFTP_REMOTE_INBOUND_DIRECTORY, sftpDp1RemoteInboundDirectory); + farmerRegistryMap.put(CoreConstants.DP_STATUS_URL , farmerStatusUrl); + return farmerRegistryMap; } ```` @@ -1739,7 +1963,7 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ }' ```` -16. Create below entry point for triggering dc communication for multiple data using csv file +16. Create below entry point for triggering dc communication for multiple data using csv file. ```` @Operation(summary = "Receive consumer search request") @ApiResponses(value = { @@ -1885,21 +2109,16 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { 12. Save initial transaction and requestString in redis. 13. Save data in db. ```` - @SuppressWarnings("unchecked") - @Override - public AcknowledgementDTO generateRequest(List> payloadMapList, String protocol) throws Exception { + public AcknowledgementDTO generateRequest(List> payloadMapList, String protocol, + String isSignEncrypt, String payloadFilename, String inboundFilename) throws Exception { Map g2pcErrorMap = new HashMap<>(); - List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); - - for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { - + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig(isSignEncrypt).entrySet()) { List> queryMapFilteredList = queryMapList.stream() .map(map -> map.entrySet().stream() .filter(entry -> entry.getKey().equals(configEntryMap.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))).toList(); - - Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig(isSignEncrypt).get(configEntryMap.getKey()); List searchCriteriaDTOList = new ArrayList<>(); for (Map queryParamsMap : queryMapFilteredList) { SearchCriteriaDTO searchCriteriaDTO = requestBuilderService.getSearchCriteriaDTO(queryParamsMap, registrySpecificConfigMap); @@ -1907,12 +2126,22 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { } String transactionId = CommonUtils.generateUniqueId("T"); String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId, ActionsENUM.SEARCH); + String encryptedSalt = ""; + G2pcError g2pcError = new G2pcError(); + switch (isSignEncrypt) { + case "0": + break; + case "1": + encryptedSalt = "salt"; + case "2": + break; + } try { if (protocol.equals(CoreConstants.SEND_PROTOCOL_HTTPS)) { Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); - String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); - G2pcError g2pcError = requestBuilderService.sendRequest(requestString, + g2pcError = requestBuilderService.sendRequest(requestString, registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), @@ -1933,26 +2162,30 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { sftpServerConfigDTO.setRemoteInboundDirectory(registrySpecificConfigMap.get(SftpConstants.SFTP_REMOTE_INBOUND_DIRECTORY).toString()); Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); - String encryptedSalt = ""; InputStream fis = resource.getInputStream(); - G2pcError g2pcError = requestBuilderService.sendRequestSftp(requestString, + inboundFilename = UUID.randomUUID() + ".json"; + g2pcError = requestBuilderService.sendRequestSftp(requestString, Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), fis, encryptedSalt, registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), CoreConstants.SEARCH_TXN_TYPE, - sftpServerConfigDTO); - log.info("g2pcError = {}", g2pcError); + sftpServerConfigDTO, inboundFilename); g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); - - log.info("Uploaded to inbound of : {}", registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + if (g2pcError != null && g2pcError.getCode().contains("err")) { + log.info("Uploaded failed for : {}", registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + throw new Exception("Uploaded failed for : " + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } else { + log.info("Uploaded to inbound of : {}", registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } } - txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue(),protocol); + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue(), protocol); txnTrackerService.saveRequestTransaction(requestString, - registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId,protocol); + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId, protocol); txnTrackerService.saveRequestInDB(requestString, - registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), protocol); + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), protocol, g2pcError, + payloadFilename, inboundFilename); } catch (Exception e) { - log.error(Constants.GENERATE_REQUEST_ERROR_MESSAGE, e); + log.error(Constants.GENERATE_REQUEST_ERROR_MESSAGE + ": {}", e.getMessage()); } } AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); @@ -2068,11 +2301,10 @@ public class DcSftpListener { log.error("Handling ERROR: {}", error.getMessage()); } } - ```` -25. Create on-search endpoint , refer below snippet , there are methods called in this methods refer code after this point. +25. Create on-search endpoint , refer below snippet ,there are methods called in this methods refer code after this point. ```` - @Operation(summary = "Listen to registry response") + @Operation(summary = "Listen to registry response") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = Constants.ON_SEARCH_RESPONSE_RECEIVED), @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), @@ -2188,6 +2420,20 @@ public class DcCommonUtils { sftpServerConfigDTO.setLocalOutboundDirectory(sftpDcLocalOutboundDirectory); return sftpServerConfigDTO; } + public void deleteFolder(Path path) { + try { + if (Files.isRegularFile(path)) { + Files.delete(path); + return; + } + try (Stream paths = Files.walk(path)) { + paths.filter(p -> p.compareTo(path) != 0).forEach(p -> deleteFolder(p)); // delete all the children folders or files; + Files.delete(path); // delete the folder itself; + } + } catch (IOException ignored) { + ignored.printStackTrace(); + } + } ```` 28. Create DcValidationService interface with 3 methods , validateRegRecords() this method is dc specific , to validate query params. ```` @@ -2685,7 +2931,7 @@ public class DcDashboardController { ```` 43. Add below configuration values which added in application.yml ```` - @Value("${dashboard.left_panel_url}") + @Value("${dashboard.left_panel_url}") private String leftPanelUrl; @Value("${dashboard.right_panel_url}") @@ -2694,8 +2940,8 @@ public class DcDashboardController { @Value("${dashboard.bottom_panel_url}") private String bottomPanelUrl; - @Value("${dashboard.post_endpoint_url}") - private String postEndpointUrl; + @Value("${dashboard.post_https_endpoint_url}") + private String postHttpsEndpointUrl; @Value("${dashboard.clear_dc_db_endpoint_url}") private String clearDcDbEndpointUrl; @@ -2709,6 +2955,33 @@ public class DcDashboardController { @Value("${keycloak.dc.client.clientSecret}") private String dcClientSecret; + @Value("${dashboard.left_panel_data_endpoint_url}") + private String leftPanelDataEndpointUrl; + + @Value("${dashboard.sftp_post_endpoint_url}") + private String sftpPostEndpointUrl; + + @Value("${dashboard.sftp_dc_data_endpoint_url}") + private String sftpDcDataEndpointUrl; + + @Value("${dashboard.sftp_dp1_data_endpoint_url}") + private String sftpDp1DataEndpointUrl; + + @Value("${dashboard.sftp_dp2_data_endpoint_url}") + private String sftpDp2DataEndpointUrl; + + @Value("${dashboard.dc_status_endpoint_url}") + private String dcStatusEndpointUrl; + + @Value("${dashboard.sftp_left_panel_url}") + private String sftpLeftPanelUrl; + + @Value("${dashboard.sftp_right_panel_url}") + private String sftpRightPanelUrl; + + @Value("${dashboard.sftp_bottom_panel_url}") + private String sftpBottomPanelUrl; + @Autowired private RequestBuilderService requestBuilderService; ```` @@ -2726,9 +2999,32 @@ public class DcDashboardController { model.addAttribute("jwtToken", jwtToken); return "dashboard"; } + @GetMapping("/dashboard/sftp") + public String showDashboardSftpPage(Model model) throws IOException, ParseException { + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + model.addAttribute("sftp_post_endpoint_url", sftpPostEndpointUrl); + model.addAttribute("sftp_left_panel_url", sftpLeftPanelUrl); + model.addAttribute("sftp_right_panel_url", sftpRightPanelUrl); + model.addAttribute("sftp_bottom_panel_url", sftpBottomPanelUrl); + model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); + model.addAttribute("jwtToken", jwtToken); + return "dashboardSftp"; + } + + @GetMapping("/dashboard/sftp/sse") + public String showDashboardSftpPageSse(Model model) throws IOException, ParseException { + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + model.addAttribute("sftp_post_endpoint_url", sftpPostEndpointUrl); + model.addAttribute("sftp_dc_data_endpoint_url", sftpDcDataEndpointUrl); + model.addAttribute("sftp_dp1_data_endpoint_url", sftpDp1DataEndpointUrl); + model.addAttribute("sftp_dp2_data_endpoint_url", sftpDp2DataEndpointUrl); + model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); + model.addAttribute("jwtToken", jwtToken); + return "dashboardSftpSse"; + } ```` -45. Create method createStatusRequestSftp() for creating endpoint to listen to CSV file payload to handle using SFTP. +45. Create method createStatusRequestSftp() for creating endpoint to listen to CSV file payload to handle using SFTP in DcController. ```` @Operation(summary = "Listen to CSV file payload to handle using SFTP") @ApiResponses(value = { @@ -2771,7 +3067,103 @@ public class DcDashboardController { curl --location 'localhost:8000/public/api/v1/consumer/search/sftp/csv' \ --form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' ```` -48. +48. Add below 2 methods in DcController. +```` + @GetMapping("/dashboard/leftPanel/data") + public List fetchLeftPanelData() { + List leftPanelDataDTOList = new ArrayList<>(); + Optional> optionalList = + responseTrackerRepository.findAllByAction("search"); + if (optionalList.isEmpty()) { + return leftPanelDataDTOList; + } + List responseTrackerEntityList = optionalList.get(); + for (ResponseTrackerEntity responseTrackerEntity : responseTrackerEntityList) { + HttpsLeftPanelDataDTO leftPanelDataDTO = new HttpsLeftPanelDataDTO(); + leftPanelDataDTO.setMessageTs(responseTrackerEntity.getMessageTs()); + leftPanelDataDTO.setTransactionId(responseTrackerEntity.getTransactionId()); + leftPanelDataDTO.setStatus(responseTrackerEntity.getStatus()); + leftPanelDataDTOList.add(leftPanelDataDTO); + } + return leftPanelDataDTOList; + } + + @GetMapping(value = "/dashboard/sftp/dc/data", produces = "text/event-stream") + public SseEmitter sseEmitterFirstPanel() { + return dcSftpPushUpdateService.register(); + } +```` +49. Add interface DcSftpPushUpdateService in service class. +```` +package g2pc.ref.dc.client.service; + +public interface DcSftpPushUpdateService { + +} +```` +50. Implement DcSftpPushUpdateServiceImpl from DcSftpPushUpdateService , refer below code. +```` +@Service +@Slf4j +public class DcSftpPushUpdateServiceImpl implements DcSftpPushUpdateService { + private final List emitters = new ArrayList<>(); + public SseEmitter register() { + int minutes = 15; + long timeout = (long) minutes * 60000; + SseEmitter emitter = new SseEmitter(timeout); + this.emitters.add(emitter); + emitter.onCompletion(() -> this.emitters.remove(emitter)); + emitter.onTimeout(() -> this.emitters.remove(emitter)); + log.info("SSE emitter registered" + emitter); + return emitter; + } + public void pushUpdate(Object update) { + List deadEmitters = new ArrayList<>(); + this.emitters.forEach(emitter -> { + try { + emitter.send(update); + } catch (IOException e) { + deadEmitters.add(emitter); + } + }); + this.emitters.removeAll(deadEmitters); + } + public SftpDcData buildSftpDcData(String transactionId, String filename) { + SftpDcData sftpDcData = new SftpDcData(); + sftpDcData.setMessageTs(CommonUtils.getCurrentTimeStamp()); + sftpDcData.setTransactionId(transactionId); + sftpDcData.setFileName(filename); + sftpDcData.setSftpDirectoryType("INBOUND"); + return sftpDcData; + } +} +```` +51. Add dto HttpsLeftPanelDataDTO in package g2pc.ref.dc.client.dto.dashboard +```` +@Data +@AllArgsConstructor +@NoArgsConstructor +public class HttpsLeftPanelDataDTO { + + private String messageTs; + private String transactionId; + private String status; +} +```` +52. Add SftpDcData dto in package g2pc.ref.dc.client.dto.dashboard +```` +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SftpDcData { + + private String messageTs; + private String transactionId; + private String fileName; + private String sftpDirectoryType; +} +```` +53. # 8. Keycloak configuration ### Steps for DC and DP - @@ -2838,9 +3230,90 @@ services: 3. Create folder structure shown below. ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/filezila-folder.png) 4. -3. +# Sunbird Rc Integration +### Below are the steps to install sunbird rc in local host. +1. Take registries.zip from this path. +2. Open the terminal in the folder where docker-compose.yml is there. +3. Navigate to the directory containing the Sunbird-RC registries code: +```` +cd ~/Documents/CDPI/Sunbird-RC/registries +```` +4. Recreate and start the Elasticsearch container: +```` +sudo docker-compose up -d --no-deps --force-recreate es +```` +This command rebuilds and recreates the Elasticsearch container (g2pc-es-1), excluding its dependencies. +It ensures that the container starts with a fresh configuration. +5. Recreate and start the database container: +```` +sudo docker-compose up -d --no-deps --force-recreate db +```` +This command rebuilds and recreates the database container (g2pc-db-1), excluding its dependencies. +It ensures that the container starts with a fresh configuration. +6. Recreate and start the Sunbird-RC registry container: +```` +sudo docker-compose up -d --no-deps --force-recreate rg +```` +This command rebuilds and recreates the Sunbird-RC registry container (g2pc-rg-1), excluding its dependencies. +It ensures that the container starts with a fresh configuration. +7. Check the running containers: +```` +sudo docker ps +```` +Verify that the containers (g2pc-db-1, g2pc-es-1, and g2pc-rg-1) are up and running. +8. Adjust paths and container names accordingly based on your specific setup and configurations. +9. These commands use Docker Compose to manage and orchestrate the containers. +10. The --no-deps flag ensures that only the specified service is recreated without starting its dependencies. +11. The --force-recreate flag ensures the recreation of the container even if it is already running. +12. Create schema in config/schemas folder. Refer below schema script. Create schemas as per your requirement +```` +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "title": "Farmer", + "properties": { + "Farmer": { + "$ref": "#/definitions/Farmer" + } + }, + "required": [ + "Farmer" + ], + "definitions": { + "Farmer": { + "$id": "#/properties/Farmer", + "type": "object", + "title": "The Farmer Schema", + "properties": { + "id": { + "type": "string" + }, + "farmer_id": { + "type": "string" + }, + "farmer_name": { + "type": "string" + }, + "season": { + "type": "string" + }, + "payment_status": { + "type": "string" + }, + "payment_date": { + "type": "string" + }, + "payment_amount": { + "type": "string" + } + } + } + } +} + +```` From fda5e363fef528c76d24cafb26cf8aa71dd79e32 Mon Sep 17 00:00:00 2001 From: ttpl-rt-119 Date: Tue, 30 Jan 2024 19:55:05 +0530 Subject: [PATCH 37/53] Updated README.md file with sunbird rc integration changes --- docs/README.md | 708 ++++++++++++++++++++++--------------------------- 1 file changed, 317 insertions(+), 391 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3d922f3..ba7eab4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -247,120 +247,6 @@ public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderSe public class FarmerValidationServiceImpl implements FarmerValidationService { } ```` -12. Create DpCommonUtils for handling token and sftp request. -```` -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.enums.ExceptionsENUM; -import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; -import g2pc.core.lib.security.BearerTokenUtil; -import g2pc.core.lib.security.service.G2pTokenService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class DpCommonUtils { - } -} -```` -13. Add below autowired dependent bean and values configured with application.yml to authenticate user in DpCommonUtils. -```` - @Value("${keycloak.dp.client.realm}") - private String keycloakRealm; - - @Value("${keycloak.dp.master.getClientUrl}") - private String getClientUrl; - - @Value("${crypto.to_dc.support_encryption}") - private boolean isEncrypt; - - @Value("${crypto.to_dc.support_signature}") - private boolean isSign; - - @Value("${keycloak.dp.client.url}") - private String keycloakURL; - - @Value("${keycloak.dp.client.clientId}") - private String keycloakClientId; - - @Value("${keycloak.dp.client.clientSecret}") - private String keycloakClientSecret; - - @Value("${keycloak.dp.master.url}") - private String masterUrl; - - @Value("${keycloak.dp.master.clientId}") - private String masterClientId; - - @Value("${keycloak.dp.master.clientSecret}") - private String masterClientSecret; - - @Value("${keycloak.dp.username}") - private String adminUsername; - - @Value("${keycloak.dp.password}") - private String adminPassword; - - @Autowired - G2pTokenService g2pTokenService; - - @Value("${sftp.dc.host}") - private String sftpDcHost; - - @Value("${sftp.dc.port}") - private int sftpDcPort; - - @Value("${sftp.dc.user}") - private String sftpDcUser; - - @Value("${sftp.dc.password}") - private String sftpDcPassword; - - @Value("${sftp.dc.remote.outbound_directory}") - private String sftpDcRemoteOutboundDirectory; -```` -14. Create handleToken() method to introspect the token whether it is from valid keycloak dp or not in handleToken() method and validateToken using g2pc-core predefined methods in DpCommonUtils. -```` - - public void handleToken() throws G2pHttpException, JsonProcessingException { - log.info("Is encrypted ? -> " + isEncrypt); - log.info("Is signed ? -> " + isSign); - String token = BearerTokenUtil.getBearerTokenHeader(); - String introspectUrl = keycloakURL + "/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspectUrl, token, - keycloakClientId, keycloakClientSecret); - log.info("Introspect response -> " + introspectResponse.getStatusCode()); - log.info("Introspect response body -> " + introspectResponse.getBody()); - if (introspectResponse.getStatusCode().value() == 401) { - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); - } - - if (!g2pTokenService.validateToken(masterUrl, getClientUrl, - g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, - adminUsername, adminPassword)) { - - throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); - } - -```` -15. Add getSftpConfigForDp() in DpCommonUtils to fetch sftp configurations. -```` - public SftpServerConfigDTO getSftpConfigForDp() { - SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); - sftpServerConfigDTO.setHost(sftpDcHost); - sftpServerConfigDTO.setPort(sftpDcPort); - sftpServerConfigDTO.setUser(sftpDcUser); - sftpServerConfigDTO.setPassword(sftpDcPassword); - sftpServerConfigDTO.setAllowUnknownKeys(true); - sftpServerConfigDTO.setStrictHostKeyChecking("no"); - sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); - return sftpServerConfigDTO; - } -```` 17. Add below autowired dependencies in the RegistryController class. ```` @Autowired @@ -374,6 +260,13 @@ public class DpCommonUtils { @Autowired private MsgTrackerRepository msgTrackerRepository; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private DpSftpPushUpdateService dpSftpPushUpdateService; + ```` 18. Add below application.yml and update as per below instructions. ```` @@ -516,6 +409,13 @@ sftp: password: {password of sftp connection} remote: outbound_directory: {path mentioned in sftp client for outbound} + +sunbird: + enabled: true + elasticsearch: + host: {domain of elasticsearch like if running on local env i.e localhost} + port: {port mention in docker-compose for elastic search} + scheme: http ```` 20. Define below endpoint in RegistryController. ```` @@ -589,8 +489,14 @@ public ErrorResponse handleG2pHttpStatusException( ```` @GetMapping("/public/api/v1/registry/clear-db") public void clearDb() throws G2pHttpException, IOException { + dpCommonUtils.handleToken(); msgTrackerRepository.deleteAll(); log.info("DP-1 DB cleared"); + Set keys = redisTemplate.keys("*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + log.info("DP-1 Redis cache cleared"); } ```` 26. Create below class DcSftpListener for handing sftp request. @@ -951,12 +857,6 @@ requestHandlerService.validateRequestMessage(messageDTO); ```` 32. In Scheduler class define below autowired bean. these beans are from dp-core library and also custom created in dp. ```` - @Value("${client.api_urls.client_search_api}") - String onSearchURL; - - @Autowired - private RequestHandlerService requestHandlerService; - @Autowired private ResponseBuilderService responseBuilderService; @@ -966,32 +866,8 @@ requestHandlerService.validateRequestMessage(messageDTO); @Autowired private TxnTrackerRedisService txnTrackerRedisService; - @Autowired - private MsgTrackerRepository msgTrackerRepository; - - @Autowired - private TxnTrackerRepository txnTrackerRepository; - @Autowired private TxnTrackerDbService txnTrackerDbService; - - @Autowired - private ResourceLoader resourceLoader; - - @Value("${keycloak.from-dc.client-id}") - private String dcClientId; - - @Value("${keycloak.from-dc.client-secret}") - private String dcClientSecret; - - @Value("${keycloak.from-dc.url}") - private String keyClockClientTokenUrl; - - @Value("${crypto.to_dc.id}") - private String dp_id; - - @Value("${crypto.to_dc.key.path}") - private String farmer_key_path; ```` Define below method in Scheduler. ```` @@ -1066,11 +942,9 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx 38. Add below snippet in scheduler class method responseScheduler() if condition. ```` { + String protocol = cacheDTO.getProtocol(); RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO, protocol); List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() .map(txnTrackerEntity -> { try { @@ -1080,47 +954,13 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx } }).toList(); List refRecordsStringsList = farmerResponseBuilderService.getRegFarmerRecords(queryDTOList); - - List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( - requestDTO, refRecordsStringsList); - - ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); - - ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); - Map meta = (Map) headerDTO.getMeta().getData(); - meta.put(CoreConstants.DP_ID, dp_id); - requestDTO.getHeader().getMeta().setData(meta); - String responseString = responseBuilderService.buildResponseString("signature", - headerDTO, responseMessageDTO); - responseString = CommonUtils.formatString(responseString); - log.info("on-search response = {}", responseString); - txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); - Resource resource = resourceLoader.getResource(farmer_key_path); - String encryptedSalt = ""; - InputStream fis = resource.getInputStream(); - G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, encryptedSalt); - if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { - throw new G2pHttpException(g2pcError); - } else { - txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); - } - List statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); - for (String cacheKey : statusCacheKeysList) { - String requestData = txnTrackerRedisService.getRequestData(cacheKey); - CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); - if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { - StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); - StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); - G2pcError g2pcError = responseBuilderService.buildOnStatusScheduler(cacheDTO); - + G2pcError g2pcError = responseBuilderService.buildOnSearchScheduler(refRecordsStringsList , cacheDTO); + log.info("on-search database updation response from sunbird - "+g2pcError.getCode()); if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { throw new G2pHttpException(g2pcError); } else { - txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); } - } - } } ```` @@ -1326,38 +1166,23 @@ void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, 7. Build StatusResponseMessageDto using StatusRequestMessageDto. 8. Build StatusResponseString , create resource using farmer key path and send response to dc. ```` -List < String > statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); -for (String cacheKey: statusCacheKeysList) { - String requestData = txnTrackerRedisService.getRequestData(cacheKey); - CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); - if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { - StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); - StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveStatusRequestDetails(statusRequestDTO); - ResponseHeaderDTO responseHeaderDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); - - StatusResponseMessageDTO statusResponseMessageDTO = responseBuilderService.buildStatusResponseMessage(statusRequestMessageDTO); - - Map < String, Object > meta = (Map < String, Object > ) responseHeaderDTO.getMeta().getData(); - meta.put(CoreConstants.DP_ID, dp_id); - statusRequestDTO.getHeader().getMeta().setData(meta); - - String statusResponseString = responseBuilderService.buildStatusResponseString("signature", responseHeaderDTO, statusResponseMessageDTO); - statusResponseString = CommonUtils.formatString(statusResponseString); - log.info("on-status response = {}", statusResponseString); - Resource resource = resourceLoader.getResource(farmer_key_path); - String encryptedSalt = ""; - InputStream fis = resource.getInputStream(); - G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(statusResponseString, onStatusURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, encryptedSalt, CoreConstants.DP_STATUS_URL); - if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { - throw new G2pHttpException(g2pcError); - } else { - txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); - txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); - } - } -} + List statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); + for (String cacheKey : statusCacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + G2pcError g2pcError = responseBuilderService.buildOnStatusScheduler(cacheDTO); + log.info("on-status database updation response from sunbird - "+g2pcError.getCode()); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } + } ```` 49. Create DpDashboardController for creating dashboard endpoints for Grafana. ```` @@ -1379,8 +1204,6 @@ public class DpDashboardController { model.addAttribute("dp_dashboard_url", dpDashboardUrl); return "dashboard"; } - - } ```` @@ -1491,7 +1314,6 @@ public class DpSftpPushUpdateServiceImpl implements DpSftpPushUpdateService { } ```` -54. # 6. Data Consumer (DC) Implementation - In DC implementation , as explained in Overview of libraries , dependency of G2pc-dc-core-lib needs to be added. @@ -1519,7 +1341,7 @@ Implementation explained in below point when it act like data consumer - 2. Extract the downloaded jar and open it in IDE. 3. Add below dependencies in tag in pom.xml ```` - + org.springframework.boot spring-boot-starter @@ -1582,6 +1404,16 @@ Implementation explained in below point when it act like data consumer - spring-boot-devtools true + + javax.servlet.jsp.jstl + javax.servlet.jsp.jstl-api + 1.2.1 + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.10.2 + ```` 4. Create package structure shown below. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dc-package-structure.png) @@ -1722,7 +1554,16 @@ dashboard: post_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/csv" clear_dc_db_endpoint_url: "http://{host}:{port of dc}/private/api/v1/registry/clear-db" clear_dp1_db_endpoint_url: "http://{host}:{port of dp1}/private/api/v1/registry/clear-db" - + # Add all respective clear-db endpoint of all dp example is above. + left_panel_data_endpoint_url: "http://{host}:{port of dc}/dashboard/leftPanel/data" + sftp_post_endpoint_url: "http://{host}:{port of dc}/public/api/v1/consumer/search/sftp/csv" + sftp_dc_data_endpoint_url: "http://{host}:{port of dc}/dashboard/sftp/dc/data" + sftp_dp1_data_endpoint_url: "http://{host}:{port of dp1}/dashboard/sftp/dp1/data" + # Add all respective sftp_dp1_data_endpoint_url endpoint of all dp example is above. + dc_status_endpoint_url: "http://{host}:{port of dc}/private/api/v1/consumer/status/payload?transactionType=search&transactionId=" + sftp_left_panel_url: "https://{domain of dc}/grafana/d-solo/aa62b4d5-f0c6-4c5d-97eb-753343c89a32/sftp-left-panel-data?orgId=1&refresh=5s&from=1705573550702&to=1705595150703&panelId=1" + sftp_right_panel_url: "https://{domain of dc}/grafana/d-solo/c319354b-d0a9-4541-ae9f-d052e31fa275/sftp-right-panel-data?orgId=1&refresh=5s&from=1705574488336&to=1705596088337&panelId=1" + sftp_bottom_panel_url: "https://{domain of dc}/grafana/d-solo/c63fe588-c69c-4918-bb96-97fba722afc8/sftp-bottom-panel-data?orgId=1&refresh=5s&from=1705574366440&to=1705595966440&panelId=1" # sftp connection configurations. sftp: listener: @@ -1749,6 +1590,15 @@ sftp: remote: # path of sftp client for inbound e.g. /inbound inbound_directory: {path mentioned in sftp client for inbound} +sunbird: + save: + response_data: http://{host}:{port of sunbird}/api/v1/Response_Data + response_tracker: http://{host}:{port of sunbird}/api/v1/Response_Tracker + enabled: true + elasticsearch: + host: {domain of elasticsearch like if running on local env i.e localhost} + port: {port mention in docker-compose for elastic search} + scheme: http ```` 8. Add RegistryConfig.java class in config package ```` @@ -1945,7 +1795,8 @@ public class DcController {} log.info("Payload received from postman"); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); if (ObjectUtils.isNotEmpty(payloadMap)) { - acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); + acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap), + CoreConstants.SEND_PROTOCOL_HTTPS, "", "",""); } return acknowledgementDTO; } @@ -1973,14 +1824,19 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/consumer/search/csv") public AcknowledgementDTO createSearchRequestsFromCsv(@RequestPart(value = "file") MultipartFile payloadFile) throws Exception { - log.info("Payload received from csv file"); + log.info("Payload received from csv file"); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); if (ObjectUtils.isNotEmpty(payloadFile)) { Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), "payload.csv"); + if (Files.exists(tempFile)) { + commonUtils.deleteFolder(tempFile); + } Files.createFile(tempFile); payloadFile.transferTo(tempFile.toFile()); acknowledgementDTO = dcRequestBuilderService.generateRequest( - requestBuilderService.generatePayloadFromCsv(tempFile.toFile()), CoreConstants.SEND_PROTOCOL_HTTPS); + requestBuilderService.generatePayloadFromCsv(tempFile.toFile()), CoreConstants.SEND_PROTOCOL_HTTPS, + dcRequestBuilderService.demoTestEncryptionSignature(tempFile.toFile()), + payloadFile.getName(), ""); Files.delete(tempFile); } return acknowledgementDTO; @@ -1995,13 +1851,13 @@ curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ ```` @GetMapping("/public/api/v1/registry/clear-db") public void clearDb() throws G2pHttpException, IOException { - commonUtils.handleToken(); + commonUtils.handleToken(); responseDataRepository.deleteAll(); log.info("DC DB cleared"); - for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig("").entrySet()) { try { - Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig("").get(configEntryMap.getKey()); String jwtToken = requestBuilderService.getValidatedToken(registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString()); @@ -2016,6 +1872,11 @@ curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ log.error("Exception in clearDb: ", e); } } + Set keys = redisTemplate.keys("*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + log.info("DC Redis cache cleared"); } ```` 19. Add below exception handling in the DC controller. @@ -2069,9 +1930,10 @@ public class RegRecordFarmerDTO { ```` public interface DcRequestBuilderService { - AcknowledgementDTO generateRequest(List> payloadMapList, String protocol) throws Exception; + AcknowledgementDTO generateRequest(List> payloadMapList, String protocol, + String isSignEncrypt, String payloadFilename, String inboundFilename) throws Exception; - AcknowledgementDTO generateStatusRequest(String transactionID, String transactionType,String protocol) throws Exception; + AcknowledgementDTO generateStatusRequest(String transactionID, String transactionType, String protocol) throws Exception; } ```` 21. Create DcRequestBuilderServiceImpl.java class and implement it from DcRequestBuilderService interface. @@ -2093,6 +1955,12 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { @Autowired private ResourceLoader resourceLoader; + + @Autowired + ResponseTrackerRepository responseTrackerRepository; + + @Autowired + private ElasticsearchService elasticsearchService; ```` 23. Override below method generateRequest() from interface. 1. Fetch queryMapList using payload list provided and registryconfig. @@ -2198,21 +2066,22 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { 1. Values added in this class are inbound and outbound file path mentioned in application.yml. 2. handleMessageInbound() method written to handle inbound message. ```` -package g2pc.ref.dc.client.controller.sftp; +package g2pc.ref.farmer.regsvc.controller.sftp; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.constants.CoreConstants; -import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; -import g2pc.core.lib.dto.search.message.response.ResponseDTO; -import g2pc.dc.core.lib.service.RequestBuilderService; -import g2pc.ref.dc.client.service.DcRequestBuilderService; -import g2pc.ref.dc.client.service.DcResponseHandlerService; -import g2pc.ref.dc.client.service.DcValidationService; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.dto.SftpDpData; +import g2pc.ref.farmer.regsvc.service.DpSftpPushUpdateService; +import g2pc.ref.farmer.regsvc.service.FarmerValidationService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -2235,30 +2104,45 @@ public class DcSftpListener { @Value("${sftp.listener.local.inbound_directory}") private String sftpLocalDirectoryInbound; - @Value("${sftp.listener.local.outbound_directory}") - private String sftpLocalDirectoryOutbound; @Autowired - private DcRequestBuilderService dcRequestBuilderService; - - @Autowired - private RequestBuilderService requestBuilderService; - - @Autowired - private DcResponseHandlerService dcResponseHandlerService; + private RequestHandlerService requestHandlerService; @Autowired - DcValidationService dcValidationService; + FarmerValidationService farmerValidationService; + @SuppressWarnings("unchecked") @ServiceActivator(inputChannel = "sftpInbound") public void handleMessageInbound(Message message) { try { File file = message.getPayload(); - log.info("Received Message from inbound directory: {}", file.getName()); - if (ObjectUtils.isNotEmpty(file)) { - AcknowledgementDTO acknowledgementDTO = dcRequestBuilderService.generateRequest( - requestBuilderService.generatePayloadFromCsv(file), CoreConstants.SEND_PROTOCOL_SFTP); - log.info("AcknowledgementDTO: {}", acknowledgementDTO); + log.info("Received Message from inbound directory of dp-1: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file) && file.getName().contains(".json")) { + String requestString = new String(Files.readAllBytes(file.toPath())); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO; + + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + + messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); + requestDTO.setMessage(messageDTO); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + try { + farmerValidationService.validateRequestDTO(requestDTO); + requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_SFTP); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } } Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); } catch (Exception e) { @@ -2266,41 +2150,13 @@ public class DcSftpListener { } } - @SuppressWarnings("unchecked") - @ServiceActivator(inputChannel = "sftpOutbound") - public void handleMessageOutbound(Message message) throws Exception { - File file = message.getPayload(); - log.info("Received Message from outbound directory: {}", file.getName()); - String responseString = new String(Files.readAllBytes(file.toPath())); - - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). - readValue(responseString); - ResponseMessageDTO messageDTO; - Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); - messageDTO = dcValidationService.signatureValidation(metaData, responseDTO); - responseDTO.setMessage(messageDTO); - try { - dcValidationService.validateResponseDto(responseDTO); - if (ObjectUtils.isNotEmpty(responseDTO)) { - dcResponseHandlerService.getResponse(responseDTO); - } - log.info("on search response handled successfully"); - } catch (JsonProcessingException | IllegalArgumentException e) { - log.info("on search response handled error : ", e); - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); - } - Files.deleteIfExists(Path.of(sftpLocalDirectoryOutbound + "/" + file.getName())); - } - @ServiceActivator(inputChannel = "errorChannel") public void handleError(Message message) { Throwable error = (Throwable) message.getPayload(); log.error("Handling ERROR: {}", error.getMessage()); } } + ```` 25. Create on-search endpoint , refer below snippet ,there are methods called in this methods refer code after this point. ```` @@ -2388,7 +2244,29 @@ public class DcCommonUtils { @Value("${keycloak.dc.password}") private String adminPassword; - + @Value("${sftp.listener.host}") + private String sftpDcHost; + + @Value("${sftp.listener.port}") + private int sftpDcPort; + + @Value("${sftp.listener.user}") + private String sftpDcUser; + + @Value("${sftp.listener.password}") + private String sftpDcPassword; + + @Value("${sftp.listener.remote.inbound_directory}") + private String sftpDcRemoteInboundDirectory; + + @Value("${sftp.listener.remote.outbound_directory}") + private String sftpDcRemoteOutboundDirectory; + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpDcLocalInboundDirectory; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpDcLocalOutboundDirectory; ```` 27. Add below methods in DcCommonUtils.java to handle and validate token and to get sftp configuration for dc. @@ -2400,8 +2278,8 @@ public class DcCommonUtils { if (introspectResponse.getStatusCode().value() == 401) { throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); } - if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, adminUsername, adminPassword)) { - + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), + masterClientId, masterClientSecret, adminUsername, adminPassword)) { throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); } } @@ -2442,9 +2320,11 @@ public interface DcValidationService { public void validateResponseDto(ResponseDTO responseDTO) throws Exception; - public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException; - ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception; + + StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception; + + void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException; } ```` 29. Create DcValidationServiceImpl class to implement DcValidationService interface and override method. @@ -2640,13 +2520,23 @@ public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { @Override public AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException { - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); ObjectMapper objectMapper = new ObjectMapper(); - txnTrackerService.updateTransactionDbAndCache(responseDTO); + G2pcError g2pcError = txnTrackerService.updateTransactionDbAndCache(responseDTO, outboundFilename); log.info("on-search response received from registry : {}", objectMapper.writeValueAsString(responseDTO)); - acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED.toString()); - acknowledgementDTO.setStatus(Constants.COMPLETED); + log.info("on-search database updation response from sunbird - "+g2pcError.getCode()); + if (g2pcError.getCode().equals(HttpStatus.OK.toString())){ + acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED.toString()); + acknowledgementDTO.setStatus(Constants.COMPLETED); + + } else { + acknowledgementDTO.setMessage(Constants.INVALID_RESPONSE.toString()); + acknowledgementDTO.setStatus(Constants.PENDING); + throw new G2pHttpException(g2pcError); + + } + return acknowledgementDTO; } } @@ -2693,40 +2583,52 @@ curl --location \ ```` @Override public AcknowledgementDTO generateStatusRequest(String transactionID,String transactionType) throws Exception { - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - String statusRequestTransactionId= CommonUtils.generateUniqueId("T"); - Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionID); - ResponseTrackerEntity responseTrackerEntity = responseTrackerEntityOptional.get(); - String registryType = responseTrackerEntity.getRegistryType().substring(3).toLowerCase(); - Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(registryType); - TxnStatusRequestDTO txnStatusRequestDTO = requestBuilderService.buildTransactionRequest(transactionID,transactionType); - String statusRequestString = requestBuilderService.buildStatusRequest(txnStatusRequestDTO,statusRequestTransactionId , ActionsENUM.STATUS); - G2pcError g2pcError = null; - try { - Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); - String encryptedSalt = ""; - InputStream fis = resource.getInputStream(); - g2pcError = requestBuilderService.sendRequest(statusRequestString, - registrySpecificConfigMap.get(CoreConstants.DP_STATUS_URL).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), - Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), - Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), - fis, encryptedSalt, - registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(),CoreConstants.DP_STATUS_URL); - log.info(""+g2pcError); - } catch (Exception e) { - log.error("Exception in generateRequest : ", e); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + String statusRequestTransactionId = CommonUtils.generateUniqueId("T"); + ObjectMapper objectMapper = new ObjectMapper(); + String encryptedSalt = ""; + Map fieldValues = new HashMap<>(); + fieldValues.put("transaction_id.keyword",transactionID); + SearchResponse responseTrackerSearchResponse = elasticsearchService.exactSearch("response_tracker", fieldValues); + if (responseTrackerSearchResponse.getHits().getHits().length > 0) { + + String responseTrackerDtoString = responseTrackerSearchResponse.getHits().getHits()[0].getSourceAsString(); + ResponseTrackerDto responseTrackerDto = objectMapper.readerFor(ResponseTrackerDto.class). + readValue(responseTrackerDtoString); + String registryType = responseTrackerDto.getRegistryType().substring(3).toLowerCase(); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig("").get(registryType); + TxnStatusRequestDTO txnStatusRequestDTO = requestBuilderService.buildTransactionRequest(transactionID, transactionType); + String statusRequestString = requestBuilderService.buildStatusRequest(txnStatusRequestDTO, statusRequestTransactionId, ActionsENUM.STATUS); + G2pcError g2pcError = null; + try { + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + InputStream fis = resource.getInputStream(); + g2pcError = requestBuilderService.sendRequest(statusRequestString, + registrySpecificConfigMap.get(CoreConstants.DP_STATUS_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), "status"); + log.info("" + g2pcError); + } catch (Exception e) { + log.error(Constants.GENERATE_REQUEST_ERROR_MESSAGE, e); + } + txnTrackerService.saveInitialStatusTransaction(transactionType, statusRequestTransactionId, HeaderStatusENUM.RCVD.toValue(), protocol); + txnTrackerService.saveRequestTransaction(statusRequestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), statusRequestTransactionId, protocol); + txnTrackerService.saveRequestInStatusDB(statusRequestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + acknowledgementDTO.setMessage(g2pcError); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + } else { + G2pcError g2pcError = new G2pcError(); + g2pcError.setCode(ExceptionsENUM.ERROR_REQUEST_NOT_FOUND.toValue()); + g2pcError.setMessage("Data for transaction id " + transactionID + "is not found"); + acknowledgementDTO.setMessage(g2pcError); } - txnTrackerService.saveInitialStatusTransaction(transactionType , statusRequestTransactionId, HeaderStatusENUM.RCVD.toValue()); - txnTrackerService.saveRequestTransaction(statusRequestString, - registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), statusRequestTransactionId); - txnTrackerService.saveRequestInStatusDB(statusRequestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); - acknowledgementDTO.setMessage(g2pcError); - acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); - - return acknowledgementDTO; + return acknowledgementDTO; } ```` 38. Define the below method in DcController to create /on-status endpoint which will get call from dp side. @@ -2746,7 +2648,7 @@ curl --location \ @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/on-status") public AcknowledgementDTO handleOnStatusResponse(@RequestBody String responseString) throws Exception { - commonUtils.handleToken(); + commonUtils.handleToken(); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -2915,10 +2817,19 @@ AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); ObjectMapper objectMapper = new ObjectMapper(); - txnTrackerService.updateStatusTransactionDbAndCache(statusResponseDTO); + G2pcError g2pcError = txnTrackerService.updateStatusTransactionDbAndCache(statusResponseDTO); log.info("on-status response received from registry : {}", objectMapper.writeValueAsString(statusResponseDTO)); - acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED.toString()); - acknowledgementDTO.setStatus(Constants.COMPLETED); + log.info("on-status database updation response from sunbird - "+g2pcError.getCode()); + if (g2pcError.getCode().equals(HttpStatus.OK.toString())){ + acknowledgementDTO.setMessage(Constants.ON_STATUS_RESPONSE_RECEIVED.toString()); + acknowledgementDTO.setStatus(Constants.COMPLETED); + + } else { + acknowledgementDTO.setMessage(Constants.INVALID_RESPONSE.toString()); + acknowledgementDTO.setStatus(Constants.PENDING); + throw new G2pHttpException(g2pcError); + + } return acknowledgementDTO; } ```` @@ -2989,15 +2900,16 @@ public class DcDashboardController { ```` @GetMapping("/dashboard") public String showDashboardPage(Model model) throws IOException, ParseException { - String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); - + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); model.addAttribute("left_panel_url", leftPanelUrl); model.addAttribute("right_panel_url", rightPanelUrl); model.addAttribute("bottom_panel_url", bottomPanelUrl); - model.addAttribute("post_endpoint_url", postEndpointUrl); + model.addAttribute("post_https_endpoint_url", postHttpsEndpointUrl); model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); model.addAttribute("jwtToken", jwtToken); - return "dashboard"; + model.addAttribute("left_panel_data_endpoint_url", leftPanelDataEndpointUrl); + model.addAttribute("dc_status_endpoint_url", dcStatusEndpointUrl); + return "dashboardHttps"; } @GetMapping("/dashboard/sftp") public String showDashboardSftpPage(Model model) throws IOException, ParseException { @@ -3098,6 +3010,10 @@ curl --location 'localhost:8000/public/api/v1/consumer/search/sftp/csv' \ package g2pc.ref.dc.client.service; public interface DcSftpPushUpdateService { + SseEmitter register(); + + void pushUpdate(Object update); + } ```` @@ -3229,91 +3145,101 @@ services: ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/filezilla-site-create.png) 3. Create folder structure shown below. ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/filezila-folder.png) - 4. + # Sunbird Rc Integration ### Below are the steps to install sunbird rc in local host. -1. Take registries.zip from this path. -2. Open the terminal in the folder where docker-compose.yml is there. -3. Navigate to the directory containing the Sunbird-RC registries code: +1. Refer below docker-compose.yml file. Change password and host name ad per instruction given in below file. +```` +version: '2.4' + +services: + db: + image: postgres + container_name: {Add container name of db} + restart: unless-stopped + ports: + - '{Add port}:5432' + environment: + - POSTGRES_DB={Add DB name} + - POSTGRES_USER={Add user name} + - POSTGRES_PASSWORD={Add password} + es: + image: docker.elastic.co/elasticsearch/elasticsearch:7.10.1 + container_name: {Add container name of es} + restart: unless-stopped + environment: + - discovery.type=single-node + - 'ES_JAVA_OPTS=-Xms512m -Xmx512m' + healthcheck: + test: ['CMD', 'curl', '-f', 'localhost:9200/_cluster/health'] + interval: 30s + timeout: 10s + retries: 4 + ports: + - '{Add port}:9200' + - '{Add port}:9300' + + rg: + image: dockerhub/sunbird-rc-core:v0.0.8 + container_name: {Add container name of sunbird rc} + restart: unless-stopped + volumes: + - /home/{Add machine hostname}/sunbirdrc/config/schemas:/home/sunbirdrc/config/public/_schemas + environment: + - connectionInfo_uri=jdbc:postgresql://db:5432/registry + - connectionInfo_username={Add username of connection of db} + - connectionInfo_password={Add password of connection of db} + - elastic_search_connection_url=es:{Add port like 9201} + - search_provider=dev.sunbirdrc.registry.service.ElasticSearchService + - search_providerName=dev.sunbirdrc.registry.service.ElasticSearchService + - signature_enabled=false + - authentication_enabled=false + ports: + - '{Add port to run registry like 8080,8081}:8081' + depends_on: + - db + - es +```` +2. Create folder structure mentioned below. ```` -cd ~/Documents/CDPI/Sunbird-RC/registries +/home/{Add machine hostname}/sunbirdrc/config/schemas ```` -4. Recreate and start the Elasticsearch container: +3. +4. Open the terminal in the folder where docker-compose.yml is there. +5. Recreate and start the Elasticsearch container: ```` sudo docker-compose up -d --no-deps --force-recreate es ```` This command rebuilds and recreates the Elasticsearch container (g2pc-es-1), excluding its dependencies. It ensures that the container starts with a fresh configuration. -5. Recreate and start the database container: +6. create and start the database container: ```` sudo docker-compose up -d --no-deps --force-recreate db ```` This command rebuilds and recreates the database container (g2pc-db-1), excluding its dependencies. It ensures that the container starts with a fresh configuration. -6. Recreate and start the Sunbird-RC registry container: +7. Recreate and start the Sunbird-RC registry container: ```` sudo docker-compose up -d --no-deps --force-recreate rg ```` This command rebuilds and recreates the Sunbird-RC registry container (g2pc-rg-1), excluding its dependencies. It ensures that the container starts with a fresh configuration. -7. Check the running containers: +8. Check the running containers: ```` sudo docker ps ```` -Verify that the containers (g2pc-db-1, g2pc-es-1, and g2pc-rg-1) are up and running. -8. Adjust paths and container names accordingly based on your specific setup and configurations. -9. These commands use Docker Compose to manage and orchestrate the containers. -10. The --no-deps flag ensures that only the specified service is recreated without starting its dependencies. -11. The --force-recreate flag ensures the recreation of the container even if it is already running. -12. Create schema in config/schemas folder. Refer below schema script. Create schemas as per your requirement -```` -{ - "$schema": "http://json-schema.org/draft-07/schema", - "type": "object", - "title": "Farmer", - "properties": { - "Farmer": { - "$ref": "#/definitions/Farmer" - } - }, - "required": [ - "Farmer" - ], - "definitions": { - "Farmer": { - "$id": "#/properties/Farmer", - "type": "object", - "title": "The Farmer Schema", - "properties": { - "id": { - "type": "string" - }, - "farmer_id": { - "type": "string" - }, - "farmer_name": { - "type": "string" - }, - "season": { - "type": "string" - }, - "payment_status": { - "type": "string" - }, - "payment_date": { - "type": "string" - }, - "payment_amount": { - "type": "string" - } - } - } - } -} - +Verify that the containers are up and running. +9. Adjust paths and container names accordingly based on your specific setup and configurations. +10. These commands use Docker Compose to manage and orchestrate the containers. +11. The --no-deps flag ensures that only the specified service is recreated without starting its dependencies. +12. The --force-recreate flag ensures the recreation of the container even if it is already running. +13. Use below command to restart the registry. +```` + sudo docker restart {container-name} ```` +14. From b9590a1749424f677f9c7b7ffdd23aeea8c511ba Mon Sep 17 00:00:00 2001 From: ttpl-rt-119 Date: Thu, 8 Feb 2024 10:39:43 +0530 Subject: [PATCH 38/53] numbering updated --- docs/README.md | 161 ++++++++++++++++++++++++------------------------- 1 file changed, 79 insertions(+), 82 deletions(-) diff --git a/docs/README.md b/docs/README.md index ba7eab4..2990876 100644 --- a/docs/README.md +++ b/docs/README.md @@ -247,7 +247,7 @@ public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderSe public class FarmerValidationServiceImpl implements FarmerValidationService { } ```` -17. Add below autowired dependencies in the RegistryController class. +12. Add below autowired dependencies in the RegistryController class. ```` @Autowired private RequestHandlerService requestHandlerService; @@ -268,7 +268,7 @@ public class FarmerValidationServiceImpl implements FarmerValidationService { private DpSftpPushUpdateService dpSftpPushUpdateService; ```` -18. Add below application.yml and update as per below instructions. +13. Add below application.yml and update as per below instructions. ```` spring: mvc: @@ -417,7 +417,7 @@ sunbird: port: {port mention in docker-compose for elastic search} scheme: http ```` -20. Define below endpoint in RegistryController. +14. Define below endpoint in RegistryController. ```` @Operation(summary = "Receive search request") @ApiResponses(value = { @@ -429,7 +429,7 @@ sunbird: public AcknowledgementDTO handleRequest(@RequestBody String requestString) throws Exception { ```` -21. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO in handleRequest(). +15. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO in handleRequest(). ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -440,13 +440,13 @@ RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). readValue(requestString); RequestMessageDTO messageDTO = null; ```` -22. Add below code snippet handleRequest() to validate signature and encryption. +16. Add below code snippet handleRequest() to validate signature and encryption. ```` Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); requestDTO.setMessage(messageDTO); ```` -23. Add below code snippet in handleRequest() to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. +17. Add below code snippet in handleRequest() to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. ```` String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { @@ -464,7 +464,7 @@ catch (Exception e){ } ```` -24. Add below 2 methods Custom Exception handling using spring boot annotations in RegistryController. +18. Add below 2 methods Custom Exception handling using spring boot annotations in RegistryController. ```` @ExceptionHandler(value = G2pcValidationException.class) @@ -485,7 +485,7 @@ public ErrorResponse handleG2pHttpStatusException( } ```` -25. Create below endpoint for clearing the db. +19. Create below endpoint for clearing the db. ```` @GetMapping("/public/api/v1/registry/clear-db") public void clearDb() throws G2pHttpException, IOException { @@ -499,7 +499,7 @@ public ErrorResponse handleG2pHttpStatusException( log.info("DP-1 Redis cache cleared"); } ```` -26. Create below class DcSftpListener for handing sftp request. +20. Create below class DcSftpListener for handing sftp request. ```` package g2pc.ref.farmer.regsvc.controller.sftp; @@ -596,7 +596,7 @@ public class DcSftpListener { } } ```` -27. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. +21. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. ```` @Getter @Setter @@ -626,7 +626,7 @@ public class QueryParamsFarmerDTO { } ```` -27. Create regRecordDTO for data provider as per on search endpoint requirement in dto.response package shown below. +22. Create regRecordDTO for data provider as per on search endpoint requirement in dto.response package shown below. ```` @Getter @Setter @@ -666,7 +666,7 @@ public class RegRecordFarmerDTO { } ```` -28. To get data provider information create a data-provider info table in db , entity and repository as shown below. +23. To get data provider information create a data-provider info table in db , entity and repository as shown below. ```` @Builder @Data @@ -693,19 +693,19 @@ public class FarmerInfoEntity { private Double paymentAmount; ```` -Write your respective method using data jpa concept. +24. Write your respective method using data jpa concept. ```` @Repository public interface FarmerInfoRepository extends JpaRepository { Optional findBySeasonAndFarmerId(String season, String farmerId); } ```` -29. Define below method in ValidationServiceImpl as it has implemented from ValidationService interface. +25. Define below method in ValidationServiceImpl as it has implemented from ValidationService interface. ```` @Override public RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO) throws Exception { ```` -30. Define below autowired beans and configurations in ValidationServiceImpl. +26. Define below autowired beans and configurations in ValidationServiceImpl. ```` @Autowired RequestHandlerService requestHandlerService; @@ -731,7 +731,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque @Value("${crypto.from_dc.key.path}") private String farmer_key_path; ```` -31. Add below code snippet in signatureValidation method. This is the validation for signature and encryption to check whether this is transferred correctly. +27. Add below code snippet in signatureValidation method. This is the validation for signature and encryption to check whether this is transferred correctly. 1. Check isSign flag , if yes check metadata given from controller is true or false , if not true throw error that configurations are not valid. 2. Take farmerKeyPath for dp .p12 file. 3. Check isEncrypt flag , if true check isMsgEncrypt flag from header is true or false , if not true throw error that configurations are not valid. @@ -835,12 +835,12 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque return messageDTO; ```` -Implement below method from ValidationService in ValidationServiceImpl. +28. Implement below method from ValidationService in ValidationServiceImpl. ```` @Override public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, IOException { ```` -Change QueryFarmerDTO , QueryParamsFarmerDTO , with respective data provider DTOs. In this method , validations of message and header methods are called from parent dp-core. +29. Change QueryFarmerDTO , QueryParamsFarmerDTO , with respective data provider DTOs. In this method , validations of message and header methods are called from parent dp-core. ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(QueryDTO.class, @@ -855,7 +855,7 @@ RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). requestHandlerService.validateRequestHeader(headerDTO); requestHandlerService.validateRequestMessage(messageDTO); ```` -32. In Scheduler class define below autowired bean. these beans are from dp-core library and also custom created in dp. +30. In Scheduler class define below autowired bean. these beans are from dp-core library and also custom created in dp. ```` @Autowired private ResponseBuilderService responseBuilderService; @@ -875,21 +875,21 @@ Define below method in Scheduler. @Transactional public void responseScheduler() throws IOException { ```` -33. Define try catch in the same method. -34. Add below snippet in method responseScheduler(). +31. Define try catch in the same method. +32. Add below snippet in method responseScheduler(). ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); ```` -35. Call method from txnTrackerRedisService to get cache list. +33. Call method from txnTrackerRedisService to get cache list. ```` List cacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.CACHE_KEY_SEARCH_STRING); ```` -36. Check whether in list the status in PNDG or not. +34. Check whether in list the status in PNDG or not. ```` if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { ```` -37. Add below code snippet to in FarmerResponseBuilderServiceImpl. +35. Add below code snippet to in FarmerResponseBuilderServiceImpl. ```` @Autowired private FarmerInfoRepository farmerInfoRepository; @@ -939,7 +939,7 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx return regFarmerRecordsList; ```` -38. Add below snippet in scheduler class method responseScheduler() if condition. +36. Add below snippet in scheduler class method responseScheduler() if condition. ```` { String protocol = cacheDTO.getProtocol(); @@ -964,7 +964,7 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx } ```` -39. Add below the catch statement at last as mentioned in point 33 that try is already written. +37. Add below the catch statement at last as mentioned in point 33 that try is already written. ```` catch ( G2pHttpException e){ log.error("Exception thrown from on-search endpoint"+ e.getG2PcError().getMessage()); @@ -974,14 +974,14 @@ catch (Exception ex) { } ```` -40. This scheduler will run every 1 min. But to test this code write a test case in Test class. +38. This scheduler will run every 1 min. But to test this code write a test case in Test class. ```` @Test void testResponseScheduler() throws IOException { scheduler.responseScheduler(); } ```` -41. Create new method in RegistryController for /status +39. Create new method in RegistryController for /status ```` @Operation(summary = "Receive status request") @ApiResponses(value = { @@ -993,11 +993,11 @@ void testResponseScheduler() throws IOException { public AcknowledgementDTO handleStatusRequest(@RequestBody String requestString) throws Exception { } ```` -42. Add below code for authenticating user +40. Add below code for authenticating user ```` dpCommonUtils.handleToken(); ```` -43. Add below code for validating the statusRequest and updating cache. +41. Add below code for validating the statusRequest and updating cache. ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); @@ -1025,18 +1025,18 @@ dpCommonUtils.handleToken(); } ```` -44. Add below endpoint in RegistryController.java. +42. Add below endpoint in RegistryController.java. ```` @GetMapping(value = "/dashboard/sftp/dp1/data", produces = "text/event-stream") public SseEmitter sseEmitterFirstPanel() { return dpSftpPushUpdateService.register(); } ```` -45. Add below overloaded method in FarmerValidationService +43. Add below overloaded method in FarmerValidationService ```` StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception ; ```` -45. Override above method in FarmerValidationServiceImpl. +44. Override above method in FarmerValidationServiceImpl. ```` @Override public StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception { @@ -1136,11 +1136,11 @@ StatusRequestMessageDTO signatureValidation(Map metaData, Status return messageDTO; } ```` -46. Add below method to validated statusRequestMessage in FarmerValidationService. +45. Add below method to validated statusRequestMessage in FarmerValidationService. ```` void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, G2pcValidationException; ```` -47. Override above method in FarmerValidationServiceImpl +46. Override above method in FarmerValidationServiceImpl ```` @Override public void validateStatusRequestDTO(StatusRequestDTO statusRequestDTO) throws IOException, G2pcValidationException { @@ -1156,7 +1156,7 @@ void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, requestHandlerService.validateStatusRequestMessage(statusRequestMessageDTO); } ```` -48. To call on-status endpoint from dc add below snippet in scheduler class in try block. Refer application zip. +47. To call on-status endpoint from dc add below snippet in scheduler class in try block. Refer application zip. 1. Get cache key from redis stored with start of key "status-request-farmer*". 2. Iterate list of cache. 3. Get request data for particular cache key and convert it into cacheDTO. @@ -1184,7 +1184,7 @@ void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, } } ```` -49. Create DpDashboardController for creating dashboard endpoints for Grafana. +48. Create DpDashboardController for creating dashboard endpoints for Grafana. ```` package g2pc.ref.farmer.regsvc.controller.rest; @@ -1207,7 +1207,7 @@ public class DpDashboardController { } ```` -50. Add CorsConfig.java in config folder, refer below code. +49. Add CorsConfig.java in config folder, refer below code. ```` package g2pc.ref.farmer.regsvc.config; @@ -1232,7 +1232,7 @@ public class CorsConfig implements WebMvcConfigurer { } } ```` -51. Add below dto SftpDpData in DP. +50. Add below dto SftpDpData in DP. ```` package g2pc.ref.farmer.regsvc.dto; @@ -1252,7 +1252,7 @@ public class SftpDpData { } ```` -52. Add below interface in service package +51. Add below interface in service package ```` package g2pc.ref.farmer.regsvc.service; @@ -1265,7 +1265,7 @@ public interface DpSftpPushUpdateService { void pushUpdate(Object update); } ```` -53. Implement service in below class. +52. Implement service in below class. ```` package g2pc.ref.farmer.regsvc.serviceimpl; @@ -1419,7 +1419,7 @@ Implementation explained in below point when it act like data consumer - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dc-package-structure.png) 5. Add .p12 files for search received from dp and on-search ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/.p12-dc.png) -5. In the config package , create the ObjectMapperConfig.java class. This class is used to avoid ambiguity between parent class and child class of Header. +6. In the config package , create the ObjectMapperConfig.java class. This class is used to avoid ambiguity between parent class and child class of Header. ```` import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; @@ -1437,7 +1437,7 @@ public class ObjectMapperConfig { } } ```` -6. Take reference of below application.yml , create application.yml for particular dc with the help of details mentioned after .yml file. +7. Take reference of below application.yml , create application.yml for particular dc with the help of details mentioned after .yml file. ```` spring: mvc: @@ -1801,7 +1801,7 @@ public class DcController {} return acknowledgementDTO; } ```` -15. To run above entrypoint refer below curl. +14. To run above entrypoint refer below curl. ```` curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ --header 'Content-Type: application/json' \ @@ -1814,7 +1814,7 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ }' ```` -16. Create below entry point for triggering dc communication for multiple data using csv file. +15. Create below entry point for triggering dc communication for multiple data using csv file. ```` @Operation(summary = "Receive consumer search request") @ApiResponses(value = { @@ -1842,12 +1842,12 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ return acknowledgementDTO; } ```` -17. To run above entrypoint refer below curl and create one payload.csv with multiple data. +16. To run above entrypoint refer below curl and create one payload.csv with multiple data. ```` curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ --form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' ```` -18. Create below endpoint for clearing db in dc. +17. Create below endpoint for clearing db in dc. ```` @GetMapping("/public/api/v1/registry/clear-db") public void clearDb() throws G2pHttpException, IOException { @@ -1879,7 +1879,7 @@ curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ log.info("DC Redis cache cleared"); } ```` -19. Add below exception handling in the DC controller. +18. Add below exception handling in the DC controller. ```` @ExceptionHandler(value = G2pcValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @@ -2209,7 +2209,7 @@ public class DcCommonUtils { } ```` -26. Add below values from application.yml to validate token +27. Add below values from application.yml to validate token ```` @Autowired G2pTokenService g2pTokenService; @@ -2269,7 +2269,7 @@ public class DcCommonUtils { private String sftpDcLocalOutboundDirectory; ```` -27. Add below methods in DcCommonUtils.java to handle and validate token and to get sftp configuration for dc. +28. Add below methods in DcCommonUtils.java to handle and validate token and to get sftp configuration for dc. ```` public void handleToken() throws G2pHttpException, JsonProcessingException { String token = BearerTokenUtil.getBearerTokenHeader(); @@ -2313,7 +2313,7 @@ public class DcCommonUtils { } } ```` -28. Create DcValidationService interface with 3 methods , validateRegRecords() this method is dc specific , to validate query params. +29. Create DcValidationService interface with 3 methods , validateRegRecords() this method is dc specific , to validate query params. ```` @Service public interface DcValidationService { @@ -2327,13 +2327,13 @@ public interface DcValidationService { void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException; } ```` -29. Create DcValidationServiceImpl class to implement DcValidationService interface and override method. +30. Create DcValidationServiceImpl class to implement DcValidationService interface and override method. ```` @Service @Slf4j public class DcValidationServiceImpl implements DcValidationService { ```` -30. Add below autowired beans in DcValidationServiceImpl. Add below values for all required dps. +31. Add below autowired beans in DcValidationServiceImpl. Add below values for all required dps. ```` @Autowired ResponseHandlerService responseHandlerService; @@ -2363,7 +2363,7 @@ public class DcValidationServiceImpl implements DcValidationService { @Value("${crypto.from_dp_farmer.id}") private String farmerID; ```` -31. Override below signatureValidation() method and add required if conditions for required dps. +32. Override below signatureValidation() method and add required if conditions for required dps. 1. Check get string from CoreConstants.DP_ID for respective farmer ids , and add respective attributes. 2. Check isSign flag , if yes check metadata given from controller is true or false , if not true throw error that configurations are not valid. 2. Take farmerKeyPath for dp .p12 file. @@ -2486,7 +2486,7 @@ public class DcValidationServiceImpl implements DcValidationService { return messageDTO; } ```` -32. Override validateResponse() to validate ResponseDTO +33. Override validateResponse() to validate ResponseDTO ```` @Override public void validateResponseDto(ResponseDTO responseDTO) throws Exception { @@ -2502,14 +2502,14 @@ public class DcValidationServiceImpl implements DcValidationService { responseHandlerService.validateResponseMessage(messageDTO); } ```` -33. Create DcResponseHandlerService interface to handle the response +34. Create DcResponseHandlerService interface to handle the response ```` public interface DcResponseHandlerService { AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException; } ```` -34. Create DcResponseHandlerServiceImpl class which implements DcResponseHandlerService interface +35. Create DcResponseHandlerServiceImpl class which implements DcResponseHandlerService interface ```` @Service @Slf4j @@ -2541,7 +2541,7 @@ public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { } } ```` -35. Create new entrypoint method for /status. +36. Create new entrypoint method for /status. ```` @Operation(summary = "Receive consumer search request") @ApiResponses(value = { @@ -2559,17 +2559,17 @@ public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { return acknowledgementDTO; } ```` -Refer below curl to execute above entrypoint. In which you can see we are sending transactionId for which we wanted to know status and action/transactionType for which we are searching status. +37. Refer below curl to execute above entrypoint. In which you can see we are sending transactionId for which we wanted to know status and action/transactionType for which we are searching status. ```` curl --location \ --request \ POST 'localhost:8000/private/api/v1/consumer/status/payload?transactionId=T757-5372-9253-9725-4673&transactionType=search' ```` -36. Declare below method in DcRequestBuilderService interface to generate request for /status endpoint. +38. Declare below method in DcRequestBuilderService interface to generate request for /status endpoint. ```` AcknowledgementDTO generateStatusRequest(String transactionID,String transactionType) throws Exception; ```` -37. Override the above method in DcRequestBuilderServiceImpl class. +39. Override the above method in DcRequestBuilderServiceImpl class. 1. Generate unique transaction id for status transaction. 2. Find registryType from response_tracker and response using transactionId given from postman. 3. Fetch registry specific map from registryConfig class , as it returns values specified for registry like url and credentials. @@ -2631,7 +2631,7 @@ curl --location \ return acknowledgementDTO; } ```` -38. Define the below method in DcController to create /on-status endpoint which will get call from dp side. +40. Define the below method in DcController to create /on-status endpoint which will get call from dp side. 1. call commonUtils.handleToken() method to authenticate user. 2. Declare objectMapper and add neccessary dependencies. 3. Get statusResponseDto by converting string to object using objectMapper. @@ -2670,13 +2670,13 @@ curl --location \ return acknowledgementDTO; } ```` -39. Add below 2 methods in DcValidationService for signatureValidation of statusResponseMessageDto and validate statusResponseDto. +41. Add below 2 methods in DcValidationService for signatureValidation of statusResponseMessageDto and validate statusResponseDto. ```` StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception; void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException; ```` -40. Override signatureValidation() method in DcValidationServiveImpl. +42. Override signatureValidation() method in DcValidationServiveImpl. ```` @Override public StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception { @@ -2790,7 +2790,7 @@ curl --location \ return messageDTO; } ```` -41. Override validateStatusResponseDTO() method in DcValidationServiceImpl. +43. Override validateStatusResponseDTO() method in DcValidationServiceImpl. ```` @Override public void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException { @@ -2805,12 +2805,12 @@ curl --location \ responseHandlerService.validateStatusResponseMessage(statusResponseMessageDTO); } ```` -42. Add below method in DcResponseHandlerService interface. +44. Add below method in DcResponseHandlerService interface. ```` AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws JsonProcessingException; ```` -43. Override above method in DcResponseHandlerServiceImpl class. +45. Override above method in DcResponseHandlerServiceImpl class. ```` @Override public AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws JsonProcessingException { @@ -2833,14 +2833,14 @@ AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws return acknowledgementDTO; } ```` -43. Create DcDashboardController.java for creating endpoints for dashboard of Grafana. +46. Create DcDashboardController.java for creating endpoints for dashboard of Grafana. ```` @Controller @Slf4j public class DcDashboardController { } ```` -43. Add below configuration values which added in application.yml +47. Add below configuration values which added in application.yml ```` @Value("${dashboard.left_panel_url}") private String leftPanelUrl; @@ -2896,7 +2896,7 @@ public class DcDashboardController { @Autowired private RequestBuilderService requestBuilderService; ```` -44. Create below endpoint method +48. Create below endpoint method ```` @GetMapping("/dashboard") public String showDashboardPage(Model model) throws IOException, ParseException { @@ -2936,7 +2936,7 @@ public class DcDashboardController { } ```` -45. Create method createStatusRequestSftp() for creating endpoint to listen to CSV file payload to handle using SFTP in DcController. +49. Create method createStatusRequestSftp() for creating endpoint to listen to CSV file payload to handle using SFTP in DcController. ```` @Operation(summary = "Listen to CSV file payload to handle using SFTP") @ApiResponses(value = { @@ -2974,12 +2974,12 @@ public class DcDashboardController { return acknowledgementDTO; } ```` -47. Import below curl to run the endpoint and add payload.csv +50. Import below curl to run the endpoint and add payload.csv ```` curl --location 'localhost:8000/public/api/v1/consumer/search/sftp/csv' \ --form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' ```` -48. Add below 2 methods in DcController. +51. Add below 2 methods in DcController. ```` @GetMapping("/dashboard/leftPanel/data") public List fetchLeftPanelData() { @@ -3005,7 +3005,7 @@ curl --location 'localhost:8000/public/api/v1/consumer/search/sftp/csv' \ return dcSftpPushUpdateService.register(); } ```` -49. Add interface DcSftpPushUpdateService in service class. +52. Add interface DcSftpPushUpdateService in service class. ```` package g2pc.ref.dc.client.service; @@ -3017,7 +3017,7 @@ public interface DcSftpPushUpdateService { } ```` -50. Implement DcSftpPushUpdateServiceImpl from DcSftpPushUpdateService , refer below code. +53. Implement DcSftpPushUpdateServiceImpl from DcSftpPushUpdateService , refer below code. ```` @Service @Slf4j @@ -3054,7 +3054,7 @@ public class DcSftpPushUpdateServiceImpl implements DcSftpPushUpdateService { } } ```` -51. Add dto HttpsLeftPanelDataDTO in package g2pc.ref.dc.client.dto.dashboard +54. Add dto HttpsLeftPanelDataDTO in package g2pc.ref.dc.client.dto.dashboard ```` @Data @AllArgsConstructor @@ -3066,7 +3066,7 @@ public class HttpsLeftPanelDataDTO { private String status; } ```` -52. Add SftpDcData dto in package g2pc.ref.dc.client.dto.dashboard +55. Add SftpDcData dto in package g2pc.ref.dc.client.dto.dashboard ```` @Data @AllArgsConstructor @@ -3078,8 +3078,7 @@ public class SftpDcData { private String fileName; private String sftpDirectoryType; } -```` -53. +```` # 8. Keycloak configuration ### Steps for DC and DP - @@ -3226,11 +3225,10 @@ sudo docker-compose up -d --no-deps --force-recreate rg ```` This command rebuilds and recreates the Sunbird-RC registry container (g2pc-rg-1), excluding its dependencies. It ensures that the container starts with a fresh configuration. -8. Check the running containers: +8. Check the running containers and Verify that the containers are up and running. ```` sudo docker ps ```` -Verify that the containers are up and running. 9. Adjust paths and container names accordingly based on your specific setup and configurations. 10. These commands use Docker Compose to manage and orchestrate the containers. 11. The --no-deps flag ensures that only the specified service is recreated without starting its dependencies. @@ -3239,7 +3237,6 @@ Verify that the containers are up and running. ```` sudo docker restart {container-name} ```` -14. From a176c37a1c09de66a33aa57098350142ff33e760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:34:26 +0530 Subject: [PATCH 39/53] g2pc-core-lib changes. --- g2pc-core-lib/pom.xml | 25 +++- .../core/lib/config/ElasticsearchConfig.java | 30 +++++ .../java/g2pc/core/lib/config/SftpConfig.java | 61 +++++++++ .../core/lib/constants/CoreConstants.java | 43 +++++++ .../core/lib/constants/SftpConstants.java | 37 ++++++ .../lib/dto/common/AcknowledgementDTO.java | 2 +- .../core/lib/dto/common/cache/CacheDTO.java | 3 + .../dto/common/security/G2pTokenResponse.java | 6 +- .../dto/common/security/TokenExpiryDto.java | 2 +- .../message/request/AuthorizeDTO.java | 2 +- .../message/request/ConsentDTO.java | 2 +- .../message/request/QueryDTO.java | 2 +- .../message/request/RequestDTO.java | 2 +- .../message/request/RequestMessageDTO.java | 2 +- .../message/request/RequestPaginationDTO.java | 2 +- .../message/request}/ResponseMessageDTO.java | 3 +- .../message/request/SearchCriteriaDTO.java | 2 +- .../message/request/SearchRequestDTO.java | 2 +- .../message/request/SortDTO.java | 3 +- .../message/response/DataDTO.java | 2 +- .../message/response/ResponseDTO.java | 2 +- .../response/ResponsePaginationDTO.java | 2 +- .../message/response/SearchResponseDTO.java | 2 +- .../lib/dto/sftp/SftpServerConfigDTO.java | 34 +++++ .../message/request/StatusRequestDTO.java | 22 ++++ .../request/StatusRequestMessageDTO.java | 20 +++ .../message/request/TxnStatusRequestDTO.java | 27 ++++ .../message/response/StatusResponseDTO.java | 23 ++++ .../response/StatusResponseMessageDTO.java | 25 ++++ .../response/TxnStatusResponseDTO.java | 23 ++++ .../java/g2pc/core/lib/enums/ActionsENUM.java | 6 +- .../core/lib/enums/AttributeTypeEnum.java | 52 ++++++++ .../g2pc/core/lib/enums/ExceptionsENUM.java | 6 +- .../lib/enums/StatusTransactionTypeEnum.java | 54 ++++++++ .../g2pc/core/lib/exceptions/G2pcError.java | 2 + .../exceptions/G2pcValidationException.java | 2 - .../lib/security/G2pTokenVerification.java | 10 +- .../security/service/G2pcUtilityClass.java | 16 +++ .../serviceImpl/G2pEncryptDecryptImpl.java | 8 +- .../serviceImpl/G2pTokenServiceImpl.java | 117 +++++++++--------- .../lib/security/serviceImpl/UtlitiyImpl.java | 103 +++++++++++++++ .../lib/service/ElasticsearchService.java | 13 ++ .../core/lib/service/SftpHandlerService.java | 20 +++ .../serviceimpl/ElasticsearchServiceImpl.java | 52 ++++++++ .../serviceimpl/SftpHandlerServiceImpl.java | 62 ++++++++++ .../java/g2pc/core/lib/utils/CommonUtils.java | 18 +++ .../schema/ResponseMessageSchema.json | 2 +- .../schema/StatusRequestMessageSchema.json | 52 ++++++++ .../schema/StatusResponseMessageSchema.json | 49 ++++++++ 49 files changed, 963 insertions(+), 94 deletions(-) create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/config/ElasticsearchConfig.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/config/SftpConfig.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/constants/SftpConstants.java rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/AuthorizeDTO.java (84%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/ConsentDTO.java (84%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/QueryDTO.java (84%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/RequestDTO.java (89%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/RequestMessageDTO.java (89%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/RequestPaginationDTO.java (87%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common/message/response => search/message/request}/ResponseMessageDTO.java (85%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/SearchCriteriaDTO.java (93%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/SearchRequestDTO.java (92%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/request/SortDTO.java (77%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/response/DataDTO.java (90%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/response/ResponseDTO.java (89%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/response/ResponsePaginationDTO.java (89%) rename g2pc-core-lib/src/main/java/g2pc/core/lib/dto/{common => search}/message/response/SearchResponseDTO.java (93%) create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/sftp/SftpServerConfigDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestMessageDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/TxnStatusRequestDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseMessageDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/TxnStatusResponseDTO.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AttributeTypeEnum.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/enums/StatusTransactionTypeEnum.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pcUtilityClass.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/UtlitiyImpl.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/service/ElasticsearchService.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/service/SftpHandlerService.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/ElasticsearchServiceImpl.java create mode 100644 g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/SftpHandlerServiceImpl.java create mode 100644 g2pc-core-lib/src/main/resources/schema/StatusRequestMessageSchema.json create mode 100644 g2pc-core-lib/src/main/resources/schema/StatusResponseMessageSchema.json diff --git a/g2pc-core-lib/pom.xml b/g2pc-core-lib/pom.xml index 178a958..64bb9c4 100644 --- a/g2pc-core-lib/pom.xml +++ b/g2pc-core-lib/pom.xml @@ -64,7 +64,7 @@ jakarta.validation jakarta.validation-api - 2.0.2 + 3.0.2 org.springframework.security @@ -91,5 +91,28 @@ commons-lang3 3.14.0 + + com.jcraft + jsch + 0.1.55 + + + org.springframework.integration + spring-integration-sftp + + + org.springframework.integration + spring-integration-core + + + org.apache.commons + commons-csv + 1.10.0 + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.10.2 + diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/ElasticsearchConfig.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/ElasticsearchConfig.java new file mode 100644 index 0000000..d47b6c3 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/ElasticsearchConfig.java @@ -0,0 +1,30 @@ +package g2pc.core.lib.config; + +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestHighLevelClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ElasticsearchConfig { + + @Value("${sunbird.elasticsearch.host}") + private String elasticsearchHost; + + @Value("${sunbird.elasticsearch.port}") + private int elasticsearchPort; + + @Value("${sunbird.elasticsearch.scheme}") + private String elasticsearchScheme; + + @Bean + public RestHighLevelClient client() { + return new RestHighLevelClient( + RestClient.builder( + new HttpHost(elasticsearchHost, elasticsearchPort, elasticsearchScheme) + ) + ); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/SftpConfig.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/SftpConfig.java new file mode 100644 index 0000000..ad69f8b --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/SftpConfig.java @@ -0,0 +1,61 @@ +package g2pc.core.lib.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.InboundChannelAdapter; +import org.springframework.integration.annotation.Poller; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.file.FileReadingMessageSource; +import org.springframework.integration.file.filters.AcceptOnceFileListFilter; +import org.springframework.messaging.MessageChannel; + +import java.io.File; + +@Configuration +@Slf4j +public class SftpConfig { + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpLocalDirectoryInbound; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpLocalDirectoryOutbound; + + @Bean + public MessageChannel sftpInbound() { + return new DirectChannel(); + } + + @Bean + public MessageChannel sftpOutbound() { + return new DirectChannel(); + } + + @Bean + public MessageChannel errorChannel() { + return new DirectChannel(); + } + + @Bean + @InboundChannelAdapter(channel = "sftpInbound", poller = @Poller(fixedDelay = "5000")) + public MessageSource fileReadingMessageSourceInbound() { + FileReadingMessageSource source = new FileReadingMessageSource(); + source.setDirectory(new File(sftpLocalDirectoryInbound)); + source.setAutoCreateDirectory(true); + source.setFilter(new AcceptOnceFileListFilter<>()); + return source; + } + + @Bean + @InboundChannelAdapter(channel = "sftpOutbound", poller = @Poller(fixedDelay = "5000")) + public MessageSource fileReadingMessageSourceOutbound() { + FileReadingMessageSource source = new FileReadingMessageSource(); + source.setDirectory(new File(sftpLocalDirectoryOutbound)); + source.setAutoCreateDirectory(true); + source.setFilter(new AcceptOnceFileListFilter<>()); + return source; + } +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java index 6de59f7..af6a406 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java @@ -11,6 +11,8 @@ private CoreConstants() { public static final String DP_SEARCH_URL = "dp_search_url"; + public static final String DP_STATUS_URL = "dp_status_url"; + public static final String REG_TYPE = "reg_type"; public static final String REG_SUB_TYPE = "reg_sub_type"; @@ -46,4 +48,45 @@ private CoreConstants() { public static final String KEY_PASSWORD = "key_password"; public static final String DP_CLEAR_DB_URL = "dp_clear_db_url"; + + public static final String SEND_PROTOCOL_HTTPS = "https"; + + public static final String SEND_PROTOCOL_SFTP = "sftp"; + + public static final String SEARCH_TXN_TYPE = "search"; + + public static final String STATUS_TXN_TYPE = "status"; + + public static final String CONTENT_TYPE = "Content-Type"; + + public static final String GRANT_TYPE ="grant_type"; + + public static final String CLIENT_ID = "client_id"; + + public static final String CLIENT_SECRET= "client_secret"; + + public static final String USERNAME = "username"; + + public static final String PASSWORD = "password"; + + public static final String ACCESS_TOKEN = "access_token"; + + public static final String AUTHORIZATION = "Authorization"; + + public static final String TOKEN_TYPE = "token_type"; + + public static final String EXPIRES_IN = "expires_in"; + + public static final String RESPONSE_HEADER = "response_header"; + + public static final String SEARCH_RESPONSE = "search_response"; + + public static final String STATUS_RESPONSE = "status_response"; + + + public static final String REQUEST_HEADER = "request_header"; + + public static final String SEARCH_REQUEST = "search_request"; + + public static final String STATUS_REQUEST = "status_request"; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/SftpConstants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/SftpConstants.java new file mode 100644 index 0000000..dc25bca --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/SftpConstants.java @@ -0,0 +1,37 @@ +package g2pc.core.lib.constants; + +public class SftpConstants { + + private SftpConstants() { + } + + public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking"; + + public static final String UPLOAD_ERROR_MESSAGE = "Error while uploading file to SFTP server"; + + public static final String UPLOAD_SUCCESS_MESSAGE = "File uploaded successfully to SFTP server"; + + public static final String SFTP_HOST = "sftp_host"; + + public static final String SFTP_PORT = "sftp_port"; + + public static final String SFTP_USER = "sftp_user"; + + public static final String SFTP_PASSWORD = "sftp_password"; + + public static final String SFTP_ALLOW_UNKNOWN_KEYS = "sftp_allow_unknown_keys"; + + public static final String SFTP_SESSION_CONFIG = "sftp_session_config"; + + public static final String SFTP_SESSION_CHANNEL = "sftp_session_channel"; + + public static final String SFTP_REMOTE_INBOUND_DIRECTORY = "sftp_remote_inbound_directory"; + + public static final String SFTP_REMOTE_OUTBOUND_DIRECTORY = "sftp_remote_outbound_directory"; + + public static final String SFTP_LOCAL_INBOUND_DIRECTORY = "sftp_local_inbound_directory"; + + public static final String SFTP_LOCAL_OUTBOUND_DIRECTORY = "sftp_local_outbound_directory"; + + public static final String SFTP = "sftp"; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java index f8ebc6e..0a9d2a4 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/AcknowledgementDTO.java @@ -11,7 +11,7 @@ public class AcknowledgementDTO { @JsonProperty("message") - private String message; + private Object message; @JsonProperty("status") private String status; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java index 2542fd4..089df30 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/cache/CacheDTO.java @@ -16,6 +16,9 @@ public class CacheDTO { @JsonProperty("status") private String status; + @JsonProperty("protocol") + private String protocol; + @JsonProperty("created_date") private String createdDate; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java index 2c811eb..77f67d9 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/G2pTokenResponse.java @@ -11,7 +11,7 @@ @Setter public class G2pTokenResponse { - private String access_token; - private String token_type; - private String expires_in; + private String accessToken; + private String tokenType; + private String expiresIn; } \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java index 4d4a1d1..df716e6 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/security/TokenExpiryDto.java @@ -14,7 +14,7 @@ public class TokenExpiryDto { private String token; - private String expires_in; + private String expiresIn; private Timestamp dateSaved; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/AuthorizeDTO.java similarity index 84% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/AuthorizeDTO.java index 3cc5443..1f12617 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/AuthorizeDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/AuthorizeDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import g2pc.core.lib.dto.common.PurposeDTO; import lombok.AllArgsConstructor; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/ConsentDTO.java similarity index 84% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/ConsentDTO.java index 35b1d7f..5340405 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/ConsentDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/ConsentDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import g2pc.core.lib.dto.common.PurposeDTO; import lombok.AllArgsConstructor; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/QueryDTO.java similarity index 84% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/QueryDTO.java index 2accfaf..ef777e0 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/QueryDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/QueryDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestDTO.java similarity index 89% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestDTO.java index 7ebb2cb..0b8abc7 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonProperty; import g2pc.core.lib.dto.common.header.HeaderDTO; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestMessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestMessageDTO.java similarity index 89% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestMessageDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestMessageDTO.java index 26992a0..9fda76e 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestMessageDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestMessageDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestPaginationDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestPaginationDTO.java similarity index 87% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestPaginationDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestPaginationDTO.java index 5514353..324f32a 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/RequestPaginationDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/RequestPaginationDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseMessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/ResponseMessageDTO.java similarity index 85% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseMessageDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/ResponseMessageDTO.java index 398d028..536af20 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseMessageDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/ResponseMessageDTO.java @@ -1,8 +1,9 @@ -package g2pc.core.lib.dto.common.message.response; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SearchCriteriaDTO.java similarity index 93% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SearchCriteriaDTO.java index 84beddf..ace88eb 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchCriteriaDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SearchCriteriaDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SearchRequestDTO.java similarity index 92% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SearchRequestDTO.java index 4902837..94603d0 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SearchRequestDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SearchRequestDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SortDTO.java similarity index 77% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SortDTO.java index 12d19ca..ed0ba39 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/request/SortDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/request/SortDTO.java @@ -1,6 +1,5 @@ -package g2pc.core.lib.dto.common.message.request; +package g2pc.core.lib.dto.search.message.request; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/DataDTO.java similarity index 90% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/DataDTO.java index d55f271..bd6974f 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/DataDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/DataDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.response; +package g2pc.core.lib.dto.search.message.response; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/ResponseDTO.java similarity index 89% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/ResponseDTO.java index 3c7f3bb..0a6187a 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponseDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/ResponseDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.response; +package g2pc.core.lib.dto.search.message.response; import com.fasterxml.jackson.annotation.JsonProperty; import g2pc.core.lib.dto.common.header.HeaderDTO; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponsePaginationDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/ResponsePaginationDTO.java similarity index 89% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponsePaginationDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/ResponsePaginationDTO.java index 186f982..6df0a70 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/ResponsePaginationDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/ResponsePaginationDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.response; +package g2pc.core.lib.dto.search.message.response; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/SearchResponseDTO.java similarity index 93% rename from g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java rename to g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/SearchResponseDTO.java index cdf9bc3..c814e24 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/common/message/response/SearchResponseDTO.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/search/message/response/SearchResponseDTO.java @@ -1,4 +1,4 @@ -package g2pc.core.lib.dto.common.message.response; +package g2pc.core.lib.dto.search.message.response; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/sftp/SftpServerConfigDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/sftp/SftpServerConfigDTO.java new file mode 100644 index 0000000..2e1edee --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/sftp/SftpServerConfigDTO.java @@ -0,0 +1,34 @@ +package g2pc.core.lib.dto.sftp; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SftpServerConfigDTO { + + private String host; + + private int port; + + private String user; + + private String password; + + private Boolean allowUnknownKeys; + + private String strictHostKeyChecking; + + private String sessionChannel = "sftp"; + + private String remoteInboundDirectory; + + private String remoteOutboundDirectory; + + private String localInboundDirectory; + + private String localOutboundDirectory; +} + diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestDTO.java new file mode 100644 index 0000000..18c506a --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestDTO.java @@ -0,0 +1,22 @@ +package g2pc.core.lib.dto.status.message.request; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StatusRequestDTO { + + @JsonProperty("signature") + private String signature; + + @JsonProperty("header") + private HeaderDTO header; + + @JsonProperty("message") + private Object message; +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestMessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestMessageDTO.java new file mode 100644 index 0000000..e703e31 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/StatusRequestMessageDTO.java @@ -0,0 +1,20 @@ +package g2pc.core.lib.dto.status.message.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class StatusRequestMessageDTO { + + @JsonProperty("transaction_id") + private String transactionId; + + @JsonProperty("txnstatus_request") + private TxnStatusRequestDTO txnStatusRequest; +} \ No newline at end of file diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/TxnStatusRequestDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/TxnStatusRequestDTO.java new file mode 100644 index 0000000..45c3145 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/request/TxnStatusRequestDTO.java @@ -0,0 +1,27 @@ +package g2pc.core.lib.dto.status.message.request; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TxnStatusRequestDTO { + + @JsonProperty("txn_type") + private String txnType; + + @JsonProperty("attribute_type") + private String attributeType; + + @JsonProperty("attribute_value") + private Object attributeValue; + + @JsonProperty("locale") + private String locale; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseDTO.java new file mode 100644 index 0000000..4dee4f9 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseDTO.java @@ -0,0 +1,23 @@ +package g2pc.core.lib.dto.status.message.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StatusResponseDTO { + + @JsonProperty("signature") + private String signature; + + @JsonProperty("header") + private HeaderDTO header; + + @JsonProperty("message") + private Object message; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseMessageDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseMessageDTO.java new file mode 100644 index 0000000..36e5b58 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/StatusResponseMessageDTO.java @@ -0,0 +1,25 @@ +package g2pc.core.lib.dto.status.message.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class StatusResponseMessageDTO { + + @JsonProperty("transaction_id") + private String transactionId; + + @JsonProperty("correlation_id") + private String correlationId; + + @JsonProperty("txnstatus_response") + private TxnStatusResponseDTO txnStatusResponse; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/TxnStatusResponseDTO.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/TxnStatusResponseDTO.java new file mode 100644 index 0000000..c6a2eff --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/dto/status/message/response/TxnStatusResponseDTO.java @@ -0,0 +1,23 @@ +package g2pc.core.lib.dto.status.message.response; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TxnStatusResponseDTO { + + + @JsonProperty("txn_type") + private String txnType; + + @JsonProperty("txn_status") + private Object txnStatus; + +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java index 8715201..8106ab4 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ActionsENUM.java @@ -6,12 +6,14 @@ public enum ActionsENUM { - SEARCH, ON_SEARCH; + SEARCH, ON_SEARCH , STATUS , ON_STATUS; public String toValue() { switch (this) { case SEARCH: return "search"; case ON_SEARCH: return "on-search"; + case STATUS: return "status"; + case ON_STATUS: return "on-status"; } return null; } @@ -21,6 +23,8 @@ public static ActionsENUM forValue(String value) throws IOException { switch (value.toLowerCase()) { case "search": return SEARCH; case "on-search": return ON_SEARCH; + case "status" : return STATUS; + case "on-status" : return ON_STATUS; } } throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AttributeTypeEnum.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AttributeTypeEnum.java new file mode 100644 index 0000000..5ab2074 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/AttributeTypeEnum.java @@ -0,0 +1,52 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.CoreConstants; + +import java.io.IOException; + +public enum AttributeTypeEnum { + + /** + * "transaction_id" "reference_id_list" "correlation_id" "subscription_code_list" + */ + + + TRANSACTION_ID , + + REFERENCE_ID_LIST, + + CORRELATION_ID , + + SUBSCRIPTION_CODE_LIST; + + + public String toValue() { + switch (this) { + case TRANSACTION_ID: + return "transaction_id"; + case REFERENCE_ID_LIST: + return "reference_id_list"; + case CORRELATION_ID: + return "correlation_id"; + case SUBSCRIPTION_CODE_LIST: + return "subscription_code_list"; + } + return null; + } + + public static AttributeTypeEnum forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "transaction_id": + return TRANSACTION_ID; + case "reference_id_list": + return REFERENCE_ID_LIST; + case "correlation_id": + return CORRELATION_ID; + case "subscription_code_list": + return SUBSCRIPTION_CODE_LIST; + } + } + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java index 0ef0f34..ea36d83 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/ExceptionsENUM.java @@ -11,7 +11,9 @@ public enum ExceptionsENUM { ERROR_ENCRYPTION_INVALID , ERROR_USER_UNAUTHORIZED , ERROR_BAD_REQUEST , - ERROR_SERVICE_UNAVAILABLE; + ERROR_SERVICE_UNAVAILABLE , + + ERROR_REQUEST_NOT_FOUND; public String toValue() { switch (this) { @@ -21,6 +23,7 @@ public String toValue() { case ERROR_USER_UNAUTHORIZED: return "err.request.unauthorized"; case ERROR_BAD_REQUEST: return "err.request.bad"; case ERROR_SERVICE_UNAVAILABLE: return "err.service.unavailable"; + case ERROR_REQUEST_NOT_FOUND: return "err.request.not_found"; } return null; @@ -35,6 +38,7 @@ public static ExceptionsENUM forValue(String value) throws IOException { case "err.request.unauthorized" :return ERROR_USER_UNAUTHORIZED; case "err.request.bad" :return ERROR_BAD_REQUEST; case "err.service.unavailable" : return ERROR_SERVICE_UNAVAILABLE; + case "err.request.not_found" : return ERROR_REQUEST_NOT_FOUND ; } } throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/StatusTransactionTypeEnum.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/StatusTransactionTypeEnum.java new file mode 100644 index 0000000..6bf3a7f --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/enums/StatusTransactionTypeEnum.java @@ -0,0 +1,54 @@ +package g2pc.core.lib.enums; + +import g2pc.core.lib.constants.CoreConstants; + +import java.io.IOException; + +public enum StatusTransactionTypeEnum { + + + /** + * "search" "subscribe" "unsubscribe" + */ + + SEARCH , SUBSCRIBE , UNSUBSCRIBE , ON_SEARCH , ON_SUBSCRIBE , ON_UNSUBSCRIBE; + + + public String toValue() { + switch (this) { + case SEARCH: + return "search"; + case SUBSCRIBE: + return "subscribe"; + case UNSUBSCRIBE: + return "unsubscribe"; + case ON_SEARCH: + return "on-search"; + case ON_SUBSCRIBE: + return "on-subscribe"; + case ON_UNSUBSCRIBE: + return "on-unsubscribe"; + } + return null; + } + + public static StatusTransactionTypeEnum forValue(String value) throws IOException { + if (null != value) { + switch (value.toLowerCase()) { + case "search": + return SEARCH; + case "subscribe": + return SUBSCRIBE; + case "unsubscribe": + return UNSUBSCRIBE; + case "on-search": + return ON_SEARCH; + case "on-subscribe": + return ON_SUBSCRIBE; + case "on-unsubscribe": + return ON_UNSUBSCRIBE; + } + } + throw new IOException(CoreConstants.CANNOT_DESERIALIZE_TYPE); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java index bfa9f32..7840919 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcError.java @@ -1,6 +1,7 @@ package g2pc.core.lib.exceptions; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.*; /** @@ -11,6 +12,7 @@ @NoArgsConstructor @Getter @Setter +@JsonIgnoreProperties(ignoreUnknown = true) public class G2pcError { private String code ; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java index 5dd02d2..7c0e2e0 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/exceptions/G2pcValidationException.java @@ -15,8 +15,6 @@ @Setter public class G2pcValidationException extends Exception{ - - private List g2PcErrorList; /** diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java index 8b73203..e44e6f9 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/G2pTokenVerification.java @@ -26,11 +26,11 @@ public class G2pTokenVerification extends OncePerRequestFilter { /** * Method to validate token - * @param httpRequest - * @param httpResponse - * @param filterChain - * @throws ServletException - * @throws IOException + * @param httpRequest httpRequest + * @param httpResponse httpResponse + * @param filterChain filterChain + * @throws ServletException ServletException + * @throws IOException IOException */ @Override protected void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws ServletException, IOException diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pcUtilityClass.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pcUtilityClass.java new file mode 100644 index 0000000..c772739 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/G2pcUtilityClass.java @@ -0,0 +1,16 @@ +package g2pc.core.lib.security.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.search.message.request.*; +import g2pc.core.lib.exceptions.G2pcValidationException; + +import java.util.List; +import java.util.Map; + +public interface G2pcUtilityClass { + + SearchCriteriaDTO getSearchCriteriaDTO(Map queryParamsMap, Map registrySpecificConfigMap, + List sortDTOList, RequestPaginationDTO paginationDTO, ConsentDTO consentDTO, AuthorizeDTO authorizeDTO) ; + + public void validateResponse(String inputString , String inputType) throws G2pcValidationException, JsonProcessingException; + } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java index 2d08ad2..e60bb59 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pEncryptDecryptImpl.java @@ -34,12 +34,12 @@ public class G2pEncryptDecryptImpl implements G2pEncryptDecrypt { public String g2pEncrypt(String data, String key) throws Exception { IvParameterSpec ivParameterSpec = RandomIVGenerator.generateIv(); - byte[] keyBytes = key.getBytes("UTF-8"); + byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); SecretKey secretKey = new SecretKeySpec(keyBytes, AlgorithmENUM.AES.toValue()); Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec); - byte[] encryptedData = aesCipher.doFinal(data.getBytes("UTF-8")); + byte[] encryptedData = aesCipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); String ivStr = Base64.getEncoder().encodeToString(ivParameterSpec.getIV()); String encryptedDataStr = Base64.getEncoder().encodeToString(encryptedData); @@ -67,14 +67,14 @@ public String g2pDecrypt(String encryptedData, String key) throws Exception { byte[] ivBytes = Base64.getDecoder().decode(ivStr); byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedDataStr); - byte[] keyBytes = key.getBytes("UTF-8"); + byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); SecretKey secretKey = new SecretKeySpec(keyBytes, AlgorithmENUM.AES.toValue()); Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); aesCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ivBytes)); byte[] decryptedDataBytes = aesCipher.doFinal(encryptedDataBytes); - return new String(decryptedDataBytes, "UTF-8"); + return new String(decryptedDataBytes, StandardCharsets.UTF_8); } /** diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java index 16b717e..c03a9ee 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.security.G2pTokenResponse; import g2pc.core.lib.dto.common.security.TokenExpiryDto; import g2pc.core.lib.security.service.G2pTokenService; @@ -43,9 +44,9 @@ public class G2pTokenServiceImpl implements G2pTokenService { * @param URL keycloak url * @param clientId clientID * @param clientSecret clientSecret - * @return - * @throws IOException - * @throws UnirestException + * @return G2pTokenResponse + * @throws IOException might be thrown + * @throws UnirestException might be thrown */ @Override public G2pTokenResponse getToken(String URL, String clientId, String clientSecret) throws IOException, UnirestException { @@ -54,31 +55,31 @@ public G2pTokenResponse getToken(String URL, String clientId, String clientSecre ObjectMapper objectMapper = new ObjectMapper(); // Make an HTTP POST request using Unirest HttpResponse response = Unirest.post(URL) - .header("Content-Type", "application/x-www-form-urlencoded") - .field("grant_type", grantType) - .field("client_id", clientId) - .field("client_secret", clientSecret) + .header(CoreConstants.CONTENT_TYPE, "application/x-www-form-urlencoded") + .field(CoreConstants.GRANT_TYPE, grantType) + .field(CoreConstants.CLIENT_ID, clientId) + .field(CoreConstants.CLIENT_SECRET, clientSecret) .asJson(); Map body = objectMapper.readValue(response.getBody().toString(), new TypeReference>() { }); G2pTokenResponse tokenResponse = new G2pTokenResponse(); - tokenResponse.setAccess_token(body.get("access_token").toString()); - tokenResponse.setToken_type(body.get("token_type").toString()); - tokenResponse.setExpires_in(body.get("expires_in").toString()); + tokenResponse.setAccessToken(body.get(CoreConstants.ACCESS_TOKEN).toString()); + tokenResponse.setTokenType(body.get(CoreConstants.TOKEN_TYPE).toString()); + tokenResponse.setExpiresIn(body.get(CoreConstants.EXPIRES_IN).toString()); return tokenResponse; } /** * Method to create tokenExpiryDto - * @param g2pTokenResponse - * @return + * @param g2pTokenResponse to create token expiry dto + * @return tokenExpiryDto */ @Override public TokenExpiryDto createTokenExpiryDto(G2pTokenResponse g2pTokenResponse) { TokenExpiryDto tokenExpiryDto = new TokenExpiryDto(); - tokenExpiryDto.setToken(g2pTokenResponse.getAccess_token()); - tokenExpiryDto.setExpires_in(g2pTokenResponse.getExpires_in()); + tokenExpiryDto.setToken(g2pTokenResponse.getAccessToken()); + tokenExpiryDto.setExpiresIn(g2pTokenResponse.getExpiresIn()); Timestamp currentTimeStamp = new Timestamp(System.currentTimeMillis()); tokenExpiryDto.setDateSaved(currentTimeStamp); return tokenExpiryDto; @@ -86,14 +87,13 @@ public TokenExpiryDto createTokenExpiryDto(G2pTokenResponse g2pTokenResponse) { /** * Method to check whether token is expired or not by calculations - * @param tokenExpiryDto - * @return - * @throws ParseException + * @param tokenExpiryDto to check token expired + * @return result + * @throws ParseException might be thrown */ @Override public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseException { if (tokenExpiryDto != null) { - String accessToken = tokenExpiryDto.getToken(); String lastSaved = tokenExpiryDto.getDateSaved().toString(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); Date parsedDate = sdf.parse(lastSaved); @@ -102,7 +102,7 @@ public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseExcepti long milliseconds = currentTimeStamp.getTime() - lastTimeStamp.getTime(); int seconds = (int) milliseconds / 1000; int minutes = seconds / 60; - int expiry = Integer.parseInt(tokenExpiryDto.getExpires_in()) / 60; + int expiry = Integer.parseInt(tokenExpiryDto.getExpiresIn()) / 60; return minutes > expiry; } @@ -111,10 +111,10 @@ public Boolean isTokenExpired(TokenExpiryDto tokenExpiryDto) throws ParseExcepti /** * Method to return clients present in realm of keycloak to validate token - * @param masterAdminUrl - * @param getClientUrl - * @return - * @throws JsonProcessingException + * @param masterAdminUrl master admin url + * @param getClientUrl get client url + * @return clients + * @throws JsonProcessingException might be thrown */ @Override public ArrayList> getClientByRealm(String masterAdminUrl, String getClientUrl , String adminClientId , String adminClientSecret @@ -123,35 +123,33 @@ public ArrayList> getClientByRealm(String masterAdminUrl, St ObjectMapper objectMapper = new ObjectMapper(); ArrayList> responseMap = new ArrayList<>(); HttpResponse response = Unirest.post(masterAdminUrl) - .header("Content-Type", "application/x-www-form-urlencoded") - .field("grant_type", grantType) - .field("client_id", adminClientId) - .field("client_secret", adminClientSecret) - .field("username",username) - .field("password",password) + .header(CoreConstants.CONTENT_TYPE, "application/x-www-form-urlencoded") + .field(CoreConstants.GRANT_TYPE, grantType) + .field(CoreConstants.CLIENT_ID, adminClientId) + .field(CoreConstants.CLIENT_SECRET, adminClientSecret) + .field( CoreConstants.USERNAME,username) + .field(CoreConstants.PASSWORD,password) .asJson(); Map responseBody = objectMapper.readValue(response.getBody().toString(), new TypeReference>() {}); - if(responseBody.get("access_token")!=null){ - String token = responseBody.get("access_token").toString(); + if(responseBody.get(CoreConstants.ACCESS_TOKEN)!=null){ + String token = responseBody.get(CoreConstants.ACCESS_TOKEN).toString(); HttpResponse clientResponse = g2pUnirestHelper.g2pGet(getClientUrl) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + token) + .header(CoreConstants.CONTENT_TYPE, "application/json") + .header(CoreConstants.AUTHORIZATION, "Bearer " + token) .asString(); responseMap = objectMapper.readValue(clientResponse.getBody(), ArrayList.class); - } - - return responseMap; + return responseMap; } /** * Method to validate the token whether its present in client list of respective realm - * @param masterAdminUrl - * @param getClientUrl - * @param clientId - * @return - * @throws JsonProcessingException + * @param masterAdminUrl keycloak master url + * @param getClientUrl keycloak client url + * @param clientId client id for validate + * @return result + * @throws JsonProcessingException might be thrown */ @Override public boolean validateToken(String masterAdminUrl, String getClientUrl , String clientId , @@ -171,16 +169,16 @@ public boolean validateToken(String masterAdminUrl, String getClientUrl , String /** * Method to decode the token - * @param jwtToken - * @return - * @throws JsonProcessingException + * @param jwtToken token to decode + * @return clientId + * @throws JsonProcessingException might be thrown */ @Override public String decodeToken(String jwtToken) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - String[] split_string = jwtToken.split("\\."); - String base64EncodedPayload = split_string[1]; + String[] splitString = jwtToken.split("\\."); + String base64EncodedPayload = splitString[1]; org.apache.commons.codec.binary.Base64 base64Url = new Base64(true); String body = new String(base64Url.decode(base64EncodedPayload)); HashMap payLoad = objectMapper.readValue(body, HashMap.class); @@ -189,27 +187,28 @@ public String decodeToken(String jwtToken) throws JsonProcessingException { /** * Method to do introspect of token using keycloak api - * @param url - * @param token - * @param clientId - * @param clientSecret - * @return - * @throws UnirestException + * @param url url for keycloak + * @param token token to verify + * @param clientId clientId to verify + * @param clientSecret clientSecret to verify + * @return result + * @throws UnirestException might be thrown */ @Override public ResponseEntity getInterSpectResponse(String url, String token, String clientId, String clientSecret) throws UnirestException { - ObjectMapper objectMapper = new ObjectMapper(); HttpResponse introResponse = Unirest.post(url) - .header("Content-Type", "application/x-www-form-urlencoded") + .header(CoreConstants.CONTENT_TYPE, "application/x-www-form-urlencoded") .field("token", token) - .field("client_id", clientId) - .field("client_secret", clientSecret) + .field(CoreConstants.CLIENT_ID, clientId) + .field(CoreConstants.CLIENT_SECRET, clientSecret) .asString(); JSONObject json = new JSONObject(introResponse.getBody()); - String isValid = json.getString("active"); - if (isValid.equals("true")) { - return ResponseEntity.status(HttpStatus.OK).body("Token is valid"); + if(json!=null && !json.getString("active").isEmpty()) { + String isValid = json.getString("active"); + if (isValid.equals("true")) { + return ResponseEntity.status(HttpStatus.OK).body("Token is valid"); + } } return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Token is not valid"); } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/UtlitiyImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/UtlitiyImpl.java new file mode 100644 index 0000000..9dd7a25 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/UtlitiyImpl.java @@ -0,0 +1,103 @@ +package g2pc.core.lib.security.serviceImpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.dto.search.message.request.*; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.core.lib.security.service.G2pcUtilityClass; +import g2pc.core.lib.utils.CommonUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Slf4j +@Service +public class UtlitiyImpl implements G2pcUtilityClass { + + + @Autowired + private CommonUtils commonUtils; + @Override + public SearchCriteriaDTO getSearchCriteriaDTO(Map queryParamsMap, Map registrySpecificConfigMap, List sortDTOList, RequestPaginationDTO paginationDTO, ConsentDTO consentDTO, AuthorizeDTO authorizeDTO) { + SearchCriteriaDTO searchCriteriaDTO = new SearchCriteriaDTO(); + searchCriteriaDTO.setVersion("1.0.0"); + searchCriteriaDTO.setRegType(registrySpecificConfigMap.get("reg_type").toString()); + searchCriteriaDTO.setRegSubType(registrySpecificConfigMap.get("reg_sub_type").toString()); + searchCriteriaDTO.setQueryType(registrySpecificConfigMap.get("query_type").toString()); + + QueryDTO queryDTO = new QueryDTO(); + queryDTO.setQueryName(registrySpecificConfigMap.get("query_name").toString()); + queryDTO.setQueryParams(queryParamsMap.values().iterator().hasNext() ? queryParamsMap.values().iterator().next() : ""); + + searchCriteriaDTO.setQuery(queryDTO); + searchCriteriaDTO.setSort(sortDTOList); + searchCriteriaDTO.setPagination(paginationDTO); + searchCriteriaDTO.setConsent(consentDTO); + searchCriteriaDTO.setAuthorize(authorizeDTO); + return searchCriteriaDTO; + } + + /** + * + * @param inputString inputString to validate + * @param inputType type of input + * @throws G2pcValidationException might be thrown + * @throws JsonProcessingException might be thrown + */ + @Override + public void validateResponse(String inputString, String inputType) throws G2pcValidationException, JsonProcessingException { + + ObjectMapper objectMapper = new ObjectMapper(); + InputStream schemaStream = getJsonInputStream(inputType); + JsonNode jsonNodeMessage = objectMapper.readTree(inputString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorCombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorCombinedMessage.add(new G2pcError("",error.getMessage())); + } + if (!errorMessage.isEmpty()){ + throw new G2pcValidationException(errorCombinedMessage); + } + } + + /** + * + * @param inputType type of input stream + * @return + */ + private InputStream getJsonInputStream(String inputType) { + if(inputType.equals(CoreConstants.RESPONSE_HEADER)){ + return commonUtils.getResponseHeaderString(); + } else if (inputType.equals(CoreConstants.SEARCH_RESPONSE)){ + return commonUtils.getResponseMessageString(); + } else if (inputType.equals(CoreConstants.STATUS_RESPONSE)){ + return commonUtils.getStatusResponseMessageString(); + } + else if(inputType.equals(CoreConstants.REQUEST_HEADER)){ + return commonUtils.getRequestHeaderString(); + } else if (inputType.equals(CoreConstants.SEARCH_REQUEST)){ + return commonUtils.getRequestMessageString(); + } else { + return commonUtils.getStatusRequestMessageString(); + } + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/service/ElasticsearchService.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/service/ElasticsearchService.java new file mode 100644 index 0000000..ad4f87d --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/service/ElasticsearchService.java @@ -0,0 +1,13 @@ +package g2pc.core.lib.service; + +import org.elasticsearch.action.search.SearchResponse; + +import java.io.IOException; +import java.util.Map; + +public interface ElasticsearchService { + + SearchResponse exactSearch(String index, Map fieldValues) throws IOException; + + void clearData(String index) throws IOException; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/service/SftpHandlerService.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/service/SftpHandlerService.java new file mode 100644 index 0000000..29ad7e6 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/service/SftpHandlerService.java @@ -0,0 +1,20 @@ +package g2pc.core.lib.service; + +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.SftpException; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer; +import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource; +import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.messaging.MessageChannel; + +import java.io.File; +import java.io.IOException; + +public interface SftpHandlerService { + + Boolean uploadFileToSftp(SftpServerConfigDTO serverConfigDTO, String localFilePath, String remoteDirectory); + + ChannelSftp getJschSession(SftpServerConfigDTO serverConfigDTO) throws IOException; +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/ElasticsearchServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/ElasticsearchServiceImpl.java new file mode 100644 index 0000000..adc1766 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/ElasticsearchServiceImpl.java @@ -0,0 +1,52 @@ +package g2pc.core.lib.serviceimpl; + +import g2pc.core.lib.service.ElasticsearchService; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; + +import org.elasticsearch.index.query.BoolQueryBuilder; + +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.elasticsearch.index.reindex.DeleteByQueryRequest; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.Map; + + +@Service +@Slf4j +public class ElasticsearchServiceImpl implements ElasticsearchService { + + + @Autowired + private RestHighLevelClient client; + + @Override + public SearchResponse exactSearch(String index, Map fieldValues) throws IOException { + SearchRequest searchRequest = new SearchRequest(index); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + for (Map.Entry entry : fieldValues.entrySet()) { + boolQueryBuilder.must(QueryBuilders.termQuery(entry.getKey(), entry.getValue())); + } + searchSourceBuilder.query(boolQueryBuilder); + searchRequest.source(searchSourceBuilder); + log.info("searchRequest: {}", searchSourceBuilder); + return client.search(searchRequest, RequestOptions.DEFAULT); + } + + @Override + public void clearData(String index) throws IOException { + DeleteByQueryRequest request = new DeleteByQueryRequest(index); + request.setQuery(QueryBuilders.matchAllQuery()); + BulkByScrollResponse deleteResponse = client.deleteByQuery(request, RequestOptions.DEFAULT); + log.info("DeleteResponse: {}", deleteResponse.toString()); + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/SftpHandlerServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/SftpHandlerServiceImpl.java new file mode 100644 index 0000000..95de036 --- /dev/null +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/serviceimpl/SftpHandlerServiceImpl.java @@ -0,0 +1,62 @@ +package g2pc.core.lib.serviceimpl; + +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; +import g2pc.core.lib.constants.SftpConstants; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.service.SftpHandlerService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +@Slf4j +public class SftpHandlerServiceImpl implements SftpHandlerService { + + @Override + public Boolean uploadFileToSftp(SftpServerConfigDTO serverConfigDTO, String localFilePath, + String remoteDirectory) { + ChannelSftp sftpChannel; + try { + sftpChannel = getJschSession(serverConfigDTO); + } catch (IOException e) { + return false; + } + try { + sftpChannel.put(localFilePath, remoteDirectory); + return true; + } catch (SftpException e) { + log.error(SftpConstants.UPLOAD_ERROR_MESSAGE, e); + return false; + } finally { + sftpChannel.exit(); + } + } + + @Override + public ChannelSftp getJschSession(SftpServerConfigDTO serverConfigDTO) throws IOException { + JSch jsch = new JSch(); + Session session = null; + ChannelSftp sftpChannel; + try { + session = jsch.getSession(serverConfigDTO.getUser(), + serverConfigDTO.getHost(), + serverConfigDTO.getPort()); + session.setPassword(serverConfigDTO.getPassword()); + session.setConfig(SftpConstants.STRICT_HOST_KEY_CHECKING, serverConfigDTO.getStrictHostKeyChecking()); + session.connect(); + sftpChannel = (ChannelSftp) session.openChannel(serverConfigDTO.getSessionChannel()); + sftpChannel.connect(); + return sftpChannel; + } catch (Exception e) { + log.error(SftpConstants.UPLOAD_ERROR_MESSAGE, e); + if (session != null) { + session.disconnect(); + } + throw new IOException(e); + } + } +} diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java index db62046..5909a06 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/utils/CommonUtils.java @@ -67,6 +67,24 @@ public InputStream getResponseMessageString() { .getResourceAsStream("schema/ResponseMessageSchema.json"); } + /** + * Get status request message string input stream. + * @return + */ + public InputStream getStatusRequestMessageString(){ + return CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/StatusRequestMessageSchema.json"); + } + + /** + * Get status response message string input stream. + * @return + */ + public InputStream getStatusResponseMessageString(){ + return CommonUtils.class.getClassLoader() + .getResourceAsStream("schema/StatusResponseMessageSchema.json"); + } + /** * Generate unique ID * diff --git a/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json b/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json index b0d44b4..aeaa70f 100644 --- a/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json +++ b/g2pc-core-lib/src/main/resources/schema/ResponseMessageSchema.json @@ -44,7 +44,7 @@ "enum": ["rjct.reference_id.invalid", "rjct.reference_id.duplicate", "rjct.timestamp.invalid" ,"rjct.search_criteria.invalid" , "rjct.filter.invalid" ,"rjct.sort.invalid" , - "rjct.pagination.invalid" ,"rjct.search.too_many_records_found" ,"succ"] + "rjct.pagination.invalid" ,"rjct.search.too_many_records_found" ,"succ","record_not_found"] }, "status_reason_message": { "type": "string", diff --git a/g2pc-core-lib/src/main/resources/schema/StatusRequestMessageSchema.json b/g2pc-core-lib/src/main/resources/schema/StatusRequestMessageSchema.json new file mode 100644 index 0000000..5cd76fe --- /dev/null +++ b/g2pc-core-lib/src/main/resources/schema/StatusRequestMessageSchema.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties" : { + "type": { + "type": "string" + }, + "transaction_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "txnstatus_request" :{ + "type" : "object", + "properties" : { + "reference_id": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "txn_type" : { + "type" : [ "string", "null" ], + "enum": ["search","subscribe","unsubscribe"], + "$ref": "#/definitions/nonEmptyString" + }, + "attribute_type" : { + "type" : [ "string", "null" ], + "enum": ["transaction_id","reference_id_list","correlation_id","subscription_code_list"], + "$ref": "#/definitions/nonEmptyString" + }, + "attribute_value" : { + "type": ["object","string"], + "$ref": "#/definitions/nonEmptyString" + } + } + } + } , + "required": [ + "transaction_id" , "txnstatus_request" + ], + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + diff --git a/g2pc-core-lib/src/main/resources/schema/StatusResponseMessageSchema.json b/g2pc-core-lib/src/main/resources/schema/StatusResponseMessageSchema.json new file mode 100644 index 0000000..0b9b193 --- /dev/null +++ b/g2pc-core-lib/src/main/resources/schema/StatusResponseMessageSchema.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft-04/schema#", + "$id": "https://example.com/message.schema.json", + "title": "Message schema", + "description": "", + "additionalProperties": false, + "type": "object", + "properties" : { + "type": { + "type": "string" + }, + "transaction_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "correlation_id": { + "type" : "string", + "minLength": 0, + "maxLength": 99, + "$ref": "#/definitions/nonEmptyString" + }, + "txnstatus_response" : { + "type" : "object", + "properties" : { + "txn_type": { + "type": "string", + "$ref": "#/definitions/nonEmptyString" + }, + "txn_status": { + "type": "object" + } + } + } + + + }, + + "definitions": { + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} + + + From 84f38570b01a25fcbd7ef8ec1d2456d5d20b8da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:41:31 +0530 Subject: [PATCH 40/53] g2pc-dc-core-lib changes. --- g2pc-dc-core-lib/pom.xml | 5 + .../dc/core/lib/constants/DcConstants.java | 8 +- .../g2pc/dc/core/lib/dto/ResponseDataDto.java | 80 +++ .../dc/core/lib/dto/ResponseTrackerDto.java | 103 ++++ .../core/lib/entity/ResponseDataEntity.java | 10 +- .../lib/entity/ResponseTrackerEntity.java | 13 +- .../repository/ResponseDataRepository.java | 3 + .../repository/ResponseTrackerRepository.java | 3 + .../lib/service/RequestBuilderService.java | 38 +- .../lib/service/ResponseHandlerService.java | 21 - .../core/lib/service/TxnTrackerService.java | 36 +- .../RequestBuilderServiceImpl.java | 363 +++++++++---- .../ResponseHandlerServiceImpl.java | 67 +-- .../serviceimpl/TxnTrackerServiceImpl.java | 487 +++++++++++++++--- .../src/main/resources/application.yml | 40 +- .../G2pcDcCoreLibraryApplicationTests.java | 4 - 16 files changed, 983 insertions(+), 298 deletions(-) create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseDataDto.java create mode 100644 g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseTrackerDto.java diff --git a/g2pc-dc-core-lib/pom.xml b/g2pc-dc-core-lib/pom.xml index ac08ca5..3ddc62b 100644 --- a/g2pc-dc-core-lib/pom.xml +++ b/g2pc-dc-core-lib/pom.xml @@ -55,5 +55,10 @@ spring-boot-starter-test test + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.10.2 + diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java index 8e56442..e2fe4e9 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/constants/DcConstants.java @@ -4,8 +4,12 @@ public class DcConstants { private DcConstants() { } + public static final String COMPLETED = "COMPLETED"; - public static final String PENDING = "PENDING"; - public static final String COMPLETED = "COMPLETED"; + public static final String DB_TYPE_POSTGRES = "postgres"; + + public static final String DB_TYPE_REGISTRY = "registry"; + + public static final String OSID = "osid"; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseDataDto.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseDataDto.java new file mode 100644 index 0000000..195d5d2 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseDataDto.java @@ -0,0 +1,80 @@ +package g2pc.dc.core.lib.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.utils.CommonUtils; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResponseDataDto { + + public ResponseDataDto() { + this.registryTransactionsId = ""; + this.referenceId = ""; + this.timestamp = ""; + this.status = ""; + this.statusReasonCode = ""; + this.statusReasonMessage = ""; + this.version = ""; + this.regType = ""; + this.regSubType = ""; + this.regRecordType = ""; + this.regRecords = ""; + this.txnType = ""; + this.attributeType = ""; + this.attributeValue = ""; + this.createdDate = CommonUtils.getCurrentTimeStamp(); + this.lastUpdatedDate = CommonUtils.getCurrentTimeStamp(); + } + + @JsonProperty("registry_transactions_id") + private String registryTransactionsId; + + @JsonProperty("reference_id") + private String referenceId; + + @JsonProperty("timestamp") + private String timestamp; + + @JsonProperty("status") + private String status; + + @JsonProperty("status_reason_code") + private String statusReasonCode; + + @JsonProperty("status_reason_message") + private String statusReasonMessage; + + @JsonProperty("version") + private String version; + + @JsonProperty("reg_type") + private String regType; + + @JsonProperty("reg_sub_type") + private String regSubType; + + @JsonProperty("reg_record_type") + private String regRecordType; + + @JsonProperty("reg_records") + private String regRecords; + + @JsonProperty("txn_type") + private String txnType; + + @JsonProperty("attribute_type") + private String attributeType; + + @JsonProperty("attribute_value") + private String attributeValue; + + @JsonProperty("created_date") + private String createdDate; + + @JsonProperty("last_updated_date") + private String lastUpdatedDate; +} \ No newline at end of file diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseTrackerDto.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseTrackerDto.java new file mode 100644 index 0000000..a235144 --- /dev/null +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/dto/ResponseTrackerDto.java @@ -0,0 +1,103 @@ +package g2pc.dc.core.lib.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import g2pc.core.lib.utils.CommonUtils; +import lombok.*; + +@Data +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) + +public class ResponseTrackerDto { + public ResponseTrackerDto() { + this.version = ""; + this.messageId = ""; + this.messageTs = ""; + this.action = ""; + this.status = ""; + this.statusReasonCode = ""; + this.statusReasonMessage = ""; + this.totalCount = 0; + this.completedCount = 0; + this.senderId = ""; + this.receiverId = ""; + this.isMsgEncrypted = false; + this.meta = ""; + this.transactionId = ""; + this.correlationId = ""; + this.registryType = ""; + this.protocol = ""; + this.payloadFilename = ""; + this.inboundFilename = ""; + this.outboundFilename = ""; + this.createdDate = CommonUtils.getCurrentTimeStamp(); + this.lastUpdatedDate = CommonUtils.getCurrentTimeStamp(); + } + + @JsonProperty( "version") + private String version; + + @ JsonProperty( "message_id") + private String messageId; + + @ JsonProperty( "message_ts") + private String messageTs; + + @ JsonProperty( "action") + private String action; + + @ JsonProperty( "status") + private String status; + + @ JsonProperty( "status_reason_code") + private String statusReasonCode; + + @ JsonProperty( "status_reason_message") + private String statusReasonMessage; + + @ JsonProperty( "total_count") + private Integer totalCount; + + @ JsonProperty( "completed_count") + private Integer completedCount; + + @ JsonProperty( "sender_id") + private String senderId; + + @ JsonProperty( "receiver_id") + private String receiverId; + + @ JsonProperty( "is_msg_encrypted") + private Boolean isMsgEncrypted; + + @ JsonProperty( "meta") + private String meta; + + @ JsonProperty( "transaction_id") + private String transactionId; + + @ JsonProperty( "correlation_id") + private String correlationId; + + @ JsonProperty( "registry_type") + private String registryType; + + @ JsonProperty( "protocol") + private String protocol; + + @ JsonProperty( "payload_filename") + private String payloadFilename; + + @ JsonProperty( "inbound_filename") + private String inboundFilename; + + @ JsonProperty( "outbound_filename") + private String outboundFilename; + + @JsonProperty("created_date") + private String createdDate; + + @JsonProperty("last_updated_date") + private String lastUpdatedDate; +} diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java index 1f5c3e5..1c453ab 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseDataEntity.java @@ -5,7 +5,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; - import java.sql.Timestamp; @Builder @@ -50,6 +49,15 @@ public class ResponseDataEntity { @Column(name = "reg_records") private String regRecords; + @Column(name = "txn_type") + private String txnType; + + @Column(name = "attribute_type") + private String attributeType; + + @Column(name = "attribute_value") + private String attributeValue; + @Column(insertable = false, updatable = false) private Timestamp createdDate; diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java index b18899c..5ac2021 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/entity/ResponseTrackerEntity.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; - import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @@ -67,6 +66,18 @@ public class ResponseTrackerEntity { @Column(name = "registry_type") private String registryType; + @Column(name = "protocol") + private String protocol; + + @Column(name = "payload_filename") + private String payloadFilename; + + @Column(name = "inbound_filename") + private String inboundFilename; + + @Column(name = "outbound_filename") + private String outboundFilename; + @Column(insertable = false, updatable = false) private Timestamp createdDate; diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java index 93e2740..9c6552d 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseDataRepository.java @@ -1,6 +1,7 @@ package g2pc.dc.core.lib.repository; import g2pc.dc.core.lib.entity.ResponseDataEntity; +import g2pc.dc.core.lib.entity.ResponseTrackerEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -8,4 +9,6 @@ public interface ResponseDataRepository extends JpaRepository { Optional findByReferenceId(String referenceId); + + Optional findByResponseTrackerEntity(ResponseTrackerEntity responseTrackerEntity); } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java index 0bfe043..a137d69 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/repository/ResponseTrackerRepository.java @@ -3,9 +3,12 @@ import g2pc.dc.core.lib.entity.ResponseTrackerEntity; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface ResponseTrackerRepository extends JpaRepository { Optional findByTransactionId(String transactionId); + + Optional> findAllByAction(String action); } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java index 46b141c..65f9f9a 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java @@ -3,15 +3,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.SearchCriteriaDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; +import g2pc.core.lib.enums.ActionsENUM; import g2pc.core.lib.exceptions.G2pcError; import kong.unirest.UnirestException; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; - import java.util.List; import java.util.Map; import java.util.Set; @@ -24,20 +28,34 @@ public interface RequestBuilderService { RequestMessageDTO buildMessage(List searchCriteriaDTOList); - HeaderDTO buildHeader() throws JsonProcessingException; + HeaderDTO buildHeader(ActionsENUM txnType) throws JsonProcessingException; - String buildRequest(List searchCriteriaDTOList, String transactionId) throws JsonProcessingException; + String buildRequest(List searchCriteriaDTOList, String transactionId, ActionsENUM txnType) throws JsonProcessingException; - G2pcError sendRequest(String requestString, String uri, String clientId, String clientSecret , - String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign , InputStream fis , String encryptedSalt , String p12Password) throws Exception; + G2pcError sendRequest(String requestString, String uri, String clientId, String clientSecret, + String keyClockClientTokenUrl, boolean isEncrypt, boolean isSign, InputStream fis, + String encryptedSalt, String p12Password, String txnType) throws Exception; - CacheDTO createCache(String data, String status); + CacheDTO createCache(String data, String status, String protocol); void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; - public void saveToken(String cacheKey , TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; + public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException; - public String getValidatedToken(String keyCloakUrl , String clientId , String clientSecret) throws IOException, UnirestException, ParseException; + public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException; + + + TxnStatusRequestDTO buildTransactionRequest(String transactionID, String transactionType); + + String buildStatusRequest(TxnStatusRequestDTO txnStatusRequestDTO, String transactionId, ActionsENUM txnType) throws JsonProcessingException; + + StatusRequestMessageDTO buildStatusRequestMessage(TxnStatusRequestDTO txnStatusRequestDTO); + + List> generatePayloadFromCsv(File payloadFile); + + G2pcError sendRequestSftp(String requestString, boolean isEncrypt, boolean isSign, InputStream fis, + String encryptedSalt, String p12Password, String txnType, + SftpServerConfigDTO sftpServerConfigDTO,String sendFilename) throws Exception; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java index 24b90e8..fab2e80 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/ResponseHandlerService.java @@ -1,8 +1,6 @@ package g2pc.dc.core.lib.service; import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; /** @@ -17,23 +15,4 @@ public interface ResponseHandlerService { * @throws JsonProcessingException the json processing exception */ void updateCache(String cacheKey) throws JsonProcessingException; - - /** - * Validate response header. - * - * @param headerDTO the header dto - * @throws G2pcValidationException the g 2 pc validation exception - * @throws JsonProcessingException the json processing exception - */ - public void validateResponseHeader(ResponseHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException; - - /** - * Validate response message. - * - * @param messageDTO the message dto - * @throws G2pcValidationException the g 2 pc validation exception - * @throws JsonProcessingException the json processing exception - */ - public void validateResponseMessage( ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; - } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java index e2d3d37..95d1de0 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/TxnTrackerService.java @@ -1,33 +1,35 @@ package g2pc.dc.core.lib.service; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.cache.CacheDTO; -import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; -import g2pc.dc.core.lib.entity.ResponseTrackerEntity; -import g2pc.dc.core.lib.repository.ResponseTrackerRepository; -import org.springframework.beans.factory.annotation.Autowired; - -import java.sql.Timestamp; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.exceptions.G2pcError; + +import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.Optional; public interface TxnTrackerService { - void saveInitialTransaction(List> payloadMapList, String transactionId, String status) throws JsonProcessingException; + void saveInitialTransaction(List> payloadMapList, String transactionId, String status, String protocol) throws JsonProcessingException; - void saveRequestTransaction(String requestString, String regType, String transactionId) throws JsonProcessingException; + void saveRequestTransaction(String requestString, String regType, String transactionId, String protocol) throws JsonProcessingException; - CacheDTO createCache(String data, String status); + CacheDTO createCache(String data, String status, String protocol); void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; - void saveRequestInDB(String requestString, String regType) throws JsonProcessingException; + G2pcError saveRequestInDB(String requestString, String regType, String protocol, + G2pcError g2pcError, String payloadFilename, + String inboundFilename, Boolean sunbirdEnabled) throws IOException; + + G2pcError updateTransactionDbAndCache(ResponseDTO responseDTO, String outboundFilename, Boolean sunbirdEnabled) throws IOException; + + void saveInitialStatusTransaction(String txnType, String transactionId, String status, String protocol) throws JsonProcessingException; + + G2pcError saveRequestInStatusDB(String requestString, String regType) throws IOException; + + G2pcError updateStatusTransactionDbAndCache(StatusResponseDTO statusResponseDTO) throws IOException; - void updateTransactionDbAndCache(ResponseDTO responseDTO) throws JsonProcessingException; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java index 851860b..1c2b634 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java @@ -5,36 +5,44 @@ import g2pc.core.lib.config.G2pUnirestHelper; import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; +import g2pc.core.lib.constants.SftpConstants; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.MetaDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.*; import g2pc.core.lib.dto.common.security.G2pTokenResponse; import g2pc.core.lib.dto.common.security.TokenExpiryDto; -import g2pc.core.lib.enums.ActionsENUM; -import g2pc.core.lib.enums.ExceptionsENUM; -import g2pc.core.lib.enums.LocalesENUM; +import g2pc.core.lib.dto.search.message.request.*; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; +import g2pc.core.lib.enums.*; import g2pc.core.lib.exceptionhandler.ErrorResponse; -import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.core.lib.security.service.G2pcUtilityClass; +import g2pc.core.lib.service.SftpHandlerService; import g2pc.core.lib.utils.CommonUtils; import g2pc.dc.core.lib.service.RequestBuilderService; import kong.unirest.HttpResponse; import kong.unirest.UnirestException; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.text.ParseException; @@ -57,6 +65,11 @@ public class RequestBuilderServiceImpl implements RequestBuilderService { @Autowired AsymmetricSignatureService asymmetricSignatureService; + @Autowired + private SftpHandlerService sftpHandlerService; + + @Autowired + G2pcUtilityClass utility; /** @@ -69,14 +82,17 @@ public class RequestBuilderServiceImpl implements RequestBuilderService { @SuppressWarnings("unchecked") @Override public List> createQueryMap(List> payloadMapList, Set> entrySet) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); List> queryList = new ArrayList<>(); for (Map payloadMap : payloadMapList) { Map queryMap = new HashMap<>(); for (Map.Entry entry : entrySet) { Map queryParamsMap = objectMapper.readValue(objectMapper.writeValueAsString(entry.getValue()), HashMap.class); + queryParamsMap.forEach((key, value) -> { if (payloadMap.containsKey(key)) { + queryParamsMap.put(key, payloadMap.get(key)); } }); @@ -105,25 +121,12 @@ public SearchCriteriaDTO getSearchCriteriaDTO(Map queryParamsMap sortDTO.setAttributeName(registrySpecificConfigMap.get("sort_attribute").toString()); sortDTO.setSortOrder(registrySpecificConfigMap.get("sort_order").toString()); sortDTOList.add(sortDTO); + return utility.getSearchCriteriaDTO(queryParamsMap, registrySpecificConfigMap, sortDTOList, paginationDTO, consentDTO, authorizeDTO); - SearchCriteriaDTO searchCriteriaDTO = new SearchCriteriaDTO(); - searchCriteriaDTO.setVersion("1.0.0"); - searchCriteriaDTO.setRegType(registrySpecificConfigMap.get("reg_type").toString()); - searchCriteriaDTO.setRegSubType(registrySpecificConfigMap.get("reg_sub_type").toString()); - searchCriteriaDTO.setQueryType(registrySpecificConfigMap.get("query_type").toString()); - - QueryDTO queryDTO = new QueryDTO(); - queryDTO.setQueryName(registrySpecificConfigMap.get("query_name").toString()); - queryDTO.setQueryParams(queryParamsMap.values().iterator().hasNext() ? queryParamsMap.values().iterator().next() : ""); - - searchCriteriaDTO.setQuery(queryDTO); - searchCriteriaDTO.setSort(sortDTOList); - searchCriteriaDTO.setPagination(paginationDTO); - searchCriteriaDTO.setConsent(consentDTO); - searchCriteriaDTO.setAuthorize(authorizeDTO); - return searchCriteriaDTO; } + + /** * Create message component of request * @@ -141,31 +144,29 @@ public RequestMessageDTO buildMessage(List searchCriteriaDTOL searchRequestDTO.setSearchCriteria(searchCriteriaDTO); searchRequestDTOList.add(searchRequestDTO); } - RequestMessageDTO messageDTO = new RequestMessageDTO(); messageDTO.setTransactionId(CommonUtils.generateUniqueId("T")); messageDTO.setSearchRequest(searchRequestDTOList); - return messageDTO; } /** * Create header component of request - * - * @return HeaderDTO + * @param txnType txnType is used to identify transaction type for header creation + * @return headerDto is returning. */ @Override - public HeaderDTO buildHeader() { + public HeaderDTO buildHeader(ActionsENUM txnType) { RequestHeaderDTO requestHeaderDTO = new RequestHeaderDTO(); requestHeaderDTO.setMessageId(CommonUtils.generateUniqueId("M")); requestHeaderDTO.setMessageTs(CommonUtils.getCurrentTimeStamp()); - requestHeaderDTO.setAction(ActionsENUM.SEARCH.toValue()); + requestHeaderDTO.setAction(txnType.toValue()); requestHeaderDTO.setSenderId("spp.example.org"); requestHeaderDTO.setReceiverId("pymts.example.org"); requestHeaderDTO.setTotalCount(21800); requestHeaderDTO.setIsMsgEncrypted(false); - Map metaMap = new HashMap<>(); + Map metaMap = new HashMap<>(); MetaDTO metaDTO = new MetaDTO(); metaDTO.setData(metaMap); requestHeaderDTO.setMeta(metaDTO); @@ -176,19 +177,16 @@ public HeaderDTO buildHeader() { /** * Create whole request from message and header * - * @param searchCriteriaDTOList required - * @param transactionId required + * @param searchCriteriaDTOList searchCriteriaDTOList to save in messageDto + * @param transactionId transactionId to set in messageDto * @return request */ @Override - public String buildRequest(List searchCriteriaDTOList, String transactionId) throws JsonProcessingException { + public String buildRequest(List searchCriteriaDTOList, String transactionId, ActionsENUM txnType) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - RequestMessageDTO messageDTO = buildMessage(searchCriteriaDTOList); messageDTO.setTransactionId(transactionId); - - HeaderDTO headerDTO = buildHeader(); - + HeaderDTO headerDTO = buildHeader(txnType); RequestDTO requestDTO = new RequestDTO(); requestDTO.setSignature("new signature to be generated for request"); requestDTO.setHeader(headerDTO); @@ -197,17 +195,31 @@ public String buildRequest(List searchCriteriaDTOList, String return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(requestDTO); } + /** + * + * @param requestString requestString which need to be sent + * @param uri uri to which communication need to happen. + * @param clientId keycloak clientId + * @param clientSecret keycloak clientSecret + * @param keyClockClientTokenUrl keycloak ClientTokenUrl + * @param isEncrypt encryption flag + * @param isSign signature flag + * @param fis .p12 file input stream + * @param p12Password p12Password + * @param txnType txnType + * @return G2pcError will return + * @throws Exception Exception might get thrown from this method + */ @Override public G2pcError sendRequest(String requestString, String uri, String clientId, String clientSecret, - String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign , InputStream fis, - String encryptedSalt , String p12Password) throws Exception { - Boolean isStatusOk = true; - log.info("Is encrypted ? -> "+isEncrypt); - log.info("Is signed ? -> "+isSign); + String keyClockClientTokenUrl, boolean isEncrypt, boolean isSign, InputStream fis, + String encryptedSalt, String p12Password, String txnType) throws Exception { + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); ObjectMapper objectMapper = new ObjectMapper(); - requestString = createSignature(isEncrypt ,isSign, requestString , fis , encryptedSalt , p12Password); + requestString = createSignature(isEncrypt, isSign, requestString, fis, encryptedSalt, p12Password, txnType); String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); - log.info("Updated Request -> "+requestString); + log.info("Updated Request -> " + requestString); HttpResponse response = g2pUnirestHelper.g2pPost(uri) .header("Content-Type", "application/json") .header("Authorization", jwtToken) @@ -215,41 +227,53 @@ public G2pcError sendRequest(String requestString, String uri, String clientId, .asString(); G2pcError g2pcError = null; if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { - g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), response.getBody()); + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), response.getBody()); log.info("Exception is thrown by search endpoint", response.getStatus()); - } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). readValue(response.getBody()); - g2pcError = errorResponse.getG2PcError(); + g2pcError = errorResponse.getG2PcError(); } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { - g2pcError = new G2pcError(ExceptionsENUM.ERROR_BAD_REQUEST.toValue(), response.getBody()); + g2pcError = new G2pcError(ExceptionsENUM.ERROR_BAD_REQUEST.toValue(), response.getBody()); } else if (response.getStatus() != HttpStatus.OK.value()) { ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). readValue(response.getBody()); - g2pcError= errorResponse.getG2PcError(); + g2pcError = errorResponse.getG2PcError(); } else if (response.getStatus() == HttpStatus.OK.value()) { - g2pcError = new G2pcError("succ", "request saved in cache"); + g2pcError = new G2pcError(HeaderStatusENUM.SUCC.toValue(), "request saved in cache"); - } - else { - g2pcError = new G2pcError("err.service.unavailable", "Internal service error"); + } else { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), HttpStatus.INTERNAL_SERVER_ERROR.toString()); } log.info("request send response status = {}", response.getStatus()); - return g2pcError; + return g2pcError; } + /** + * + * @param data data to send + * @param status status to set + * @param protocol protocol to set + * @return cacheDto + */ @Override - public CacheDTO createCache(String data, String status) { + public CacheDTO createCache(String data, String status, String protocol) { log.info("Save requests in cache with status pending"); CacheDTO cacheDTO = new CacheDTO(); cacheDTO.setData(data); cacheDTO.setStatus(status); + cacheDTO.setProtocol(protocol); cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); return cacheDTO; } + /** + * + * @param cacheDTO cacheDTO to save + * @param cacheKey cacheKey refer to save cacheDTO + * @throws JsonProcessingException + */ @Override public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { ValueOperations val = redisTemplate.opsForValue(); @@ -259,8 +283,8 @@ public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingE /** * Method to save token in cache * - * @param cacheKey - * @param tokenExpiryDto + * @param cacheKey cacheKey on which tokenExpiryDto is going to store + * @param tokenExpiryDto tokenExpiryDto which is being stored * @throws JsonProcessingException */ @Override @@ -272,9 +296,9 @@ public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws Jso /** * Method to get token from cache * - * @param clientId - * @return - * @throws JsonProcessingException + * @param clientId clientId by which token is going to extract + * @return tokenExpiryDto is going to return + * @throws JsonProcessingException JsonProcessingException is might be thrown from this method */ @Override public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException { @@ -294,87 +318,97 @@ public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingEx /** * Method to get validated token * - * @param keyCloakUrl - * @param clientId - * @param clientSecret - * @return - * @throws IOException - * @throws UnirestException - * @throws ParseException + * @param keyCloakUrl keycloak url + * @param clientId client id for token + * @param clientSecret client secret + * @return string which is token + * @throws IOException which can be thrown + * @throws UnirestException which can be thrown + * @throws ParseException which can be thrown */ @Override public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException { TokenExpiryDto tokenExpiryDto = getTokenFromCache(clientId); String jwtToken = ""; - if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { + if (Boolean.TRUE.equals(g2pTokenService.isTokenExpired(tokenExpiryDto))) { G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); - jwtToken = tokenResponse.getAccess_token(); - saveToken(clientId+"-token", g2pTokenService.createTokenExpiryDto(tokenResponse)); + jwtToken = tokenResponse.getAccessToken(); + saveToken(clientId + "-token", g2pTokenService.createTokenExpiryDto(tokenResponse)); } else { jwtToken = tokenExpiryDto.getToken(); } return jwtToken; } + /** * This method is used to create signature and add appropriate message in request body according to configurations. - * @param isEncrypt flag of encryption - * @param isSign flag of signature + * + * @param isEncrypt flag of encryption + * @param isSign flag of signature * @param requestString created request in string format - * @return + * @return created signature in string format * @throws Exception exception is thrown general because there more than 6 exception being thrown by this method. */ - private String createSignature( boolean isEncrypt , boolean isSign, - String requestString , InputStream fis , String encryptionSalt , String p12Password) throws Exception { + @SuppressWarnings("unchecked") + private String createSignature(boolean isEncrypt, boolean isSign, + String requestString, InputStream fis, String encryptionSalt, + String p12Password, String txnType) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). readValue(requestString); byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); - RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); - RequestHeaderDTO requestHeaderDTO = (RequestHeaderDTO) requestDTO.getHeader(); - String requestHeaderString = objectMapper.writeValueAsString(requestHeaderDTO); - String messageString = objectMapper.writeValueAsString(messageDTO); + String messageString = ""; + if (txnType.equals(CoreConstants.STATUS_TXN_TYPE)) { + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + messageString = objectMapper.writeValueAsString(statusRequestMessageDTO); + } else { + RequestMessageDTO requestMessageDTO = objectMapper.readValue(json, RequestMessageDTO.class); + messageString = objectMapper.writeValueAsString(requestMessageDTO); + } + + String requestHeaderString; String signature = null; - if(isSign){ - if(isEncrypt){ - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - requestDTO.setMessage(encryptionSalt+encryptedMessageString); + if (isSign) { + if (isEncrypt) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + requestDTO.setMessage(encryptionSalt + encryptedMessageString); requestDTO.getHeader().setIsMsgEncrypted(true); - Map meta= (Map) requestDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,true); + Map meta = (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, true); requestDTO.getHeader().getMeta().setData(meta); requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( requestHeaderString+encryptedMessageString , fis , p12Password); + byte[] asymmetricSignature = asymmetricSignatureService.sign(requestHeaderString + encryptedMessageString, fis, p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); - log.info("Encrypted message ->"+encryptedMessageString); - log.info("Hashed Signature ->"+signature); - }else{ + log.info("Encrypted message ->" + encryptedMessageString); + log.info("Hashed Signature ->" + signature); + } else { requestDTO.getHeader().setIsMsgEncrypted(false); - Map meta= (Map) requestDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,true); + Map meta = (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, true); requestDTO.getHeader().getMeta().setData(meta); requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( requestHeaderString+messageString , fis , p12Password); + byte[] asymmetricSignature = asymmetricSignatureService.sign(requestHeaderString + messageString, fis, p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); - log.info("Hashed Signature ->"+signature); + log.info("Hashed Signature ->" + signature); } } else { - if(isEncrypt){ - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - requestDTO.setMessage(encryptionSalt+encryptedMessageString); + if (isEncrypt) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + requestDTO.setMessage(encryptionSalt + encryptedMessageString); requestDTO.getHeader().setIsMsgEncrypted(true); - Map meta= (Map) requestDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,false); + Map meta = (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, false); requestDTO.getHeader().getMeta().setData(meta); - log.info("Encrypted message ->"+encryptedMessageString); - }else{ + log.info("Encrypted message ->" + encryptedMessageString); + } else { requestDTO.getHeader().setIsMsgEncrypted(false); - Map meta= (Map) requestDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,false); + Map meta = (Map) requestDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, false); requestDTO.getHeader().getMeta().setData(meta); } } @@ -382,4 +416,127 @@ private String createSignature( boolean isEncrypt , boolean isSign, requestString = objectMapper.writeValueAsString(requestDTO); return requestString; } + + /** + * + * @param transactionID transactionID used to build transactionRequest of /status + * @param transactionType transaction type for which transaction request is building + * @return txnStatusRequestDTO + */ + @Override + public TxnStatusRequestDTO buildTransactionRequest(String transactionID, String transactionType) { + + TxnStatusRequestDTO txnStatusRequestDTO = new TxnStatusRequestDTO(); + if (transactionType.equals(StatusTransactionTypeEnum.SEARCH.toValue())) { + txnStatusRequestDTO.setAttributeType(AttributeTypeEnum.TRANSACTION_ID.toValue()); + txnStatusRequestDTO.setTxnType(StatusTransactionTypeEnum.SEARCH.toValue()); + txnStatusRequestDTO.setAttributeValue(transactionID); + } + return txnStatusRequestDTO; + } + + /** + * + * @param txnStatusRequestDTO txnStatusRequestDTO to build status request + * @param transactionId transactionId for which request is building + * @param txnType txnType for which request is building + * @return string + * @throws JsonProcessingException exception which is going thrown + */ + @Override + public String buildStatusRequest(TxnStatusRequestDTO txnStatusRequestDTO, String transactionId, ActionsENUM txnType) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + HeaderDTO headerDTO = buildHeader(txnType); + StatusRequestMessageDTO messageDTO = buildStatusRequestMessage(txnStatusRequestDTO); + messageDTO.setTransactionId(transactionId); + StatusRequestDTO statusRequestDTO = new StatusRequestDTO(); + statusRequestDTO.setSignature("new signature to be generated for request"); + statusRequestDTO.setHeader(headerDTO); + statusRequestDTO.setMessage(messageDTO); + + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(statusRequestDTO); + } + + /** + * + * @param txnStatusRequestDTO txnStatusRequestDTO for which status request is creating + * @return StatusRequestMessageDTO + */ + @Override + public StatusRequestMessageDTO buildStatusRequestMessage(TxnStatusRequestDTO txnStatusRequestDTO) { + StatusRequestMessageDTO statusRequestMessageDTO = new StatusRequestMessageDTO(); + statusRequestMessageDTO.setTxnStatusRequest(txnStatusRequestDTO); + return statusRequestMessageDTO; + } + + /** + * Create a payload for request + * @param payloadFile csv file containing query params data + * @return List> of payload + */ + @Override + public List> generatePayloadFromCsv(File payloadFile) { + List> payloadMapList = new ArrayList<>(); + Reader reader = null; + try { + reader = new BufferedReader(new FileReader(payloadFile)); + CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); + List csvRecordList = csvParser.getRecords(); + CSVRecord headerRecord = csvRecordList.get(0); + List headerList = new ArrayList<>(); + for (int i = 0; i < headerRecord.size(); i++) { + headerList.add(headerRecord.get(i)); + } + for (int i = 1; i < csvRecordList.size(); i++) { + CSVRecord csvRecord = csvRecordList.get(i); + Map payloadMap = new HashMap<>(); + for (int j = 0; j < headerRecord.size(); j++) { + payloadMap.put(headerList.get(j), csvRecord.get(j)); + } + payloadMapList.add(payloadMap); + } + } catch (IOException e) { + log.error("Error converting to payload : ", e); + } + return payloadMapList; + } + + /** + * + * @param requestString requestString which need to be sent + * @param isEncrypt encryption flag + * @param isSign signature flag + * @param fis .p12 file input stream + * @param p12Password p12Password + * @param txnType txnType + * @param sftpServerConfigDTO SftpServerConfigDTO + * @return G2pcError will return + * @throws Exception Exception might get thrown from this method + */ + @Override + public G2pcError sendRequestSftp(String requestString, boolean isEncrypt, boolean isSign, + InputStream fis, String encryptedSalt, String p12Password, + String txnType, SftpServerConfigDTO sftpServerConfigDTO,String sendFilename) throws Exception { + + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + requestString = createSignature(isEncrypt, isSign, requestString, fis, encryptedSalt, p12Password, txnType); + log.info("Updated Request -> " + requestString); + + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), sendFilename); + Files.createFile(tempFile); + Files.write(tempFile, requestString.getBytes()); + + Boolean status = sftpHandlerService.uploadFileToSftp(sftpServerConfigDTO, tempFile.toString(), + sftpServerConfigDTO.getRemoteInboundDirectory()); + Files.delete(tempFile); + G2pcError g2pcError; + if (Boolean.FALSE.equals(status)) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), SftpConstants.UPLOAD_ERROR_MESSAGE); + log.error(SftpConstants.UPLOAD_ERROR_MESSAGE); + }else { + g2pcError = new G2pcError(HeaderStatusENUM.SUCC.toValue(), SftpConstants.UPLOAD_SUCCESS_MESSAGE); + } + return g2pcError; + } } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java index 337b95d..7279d1c 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/ResponseHandlerServiceImpl.java @@ -7,9 +7,12 @@ import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.utils.CommonUtils; @@ -26,6 +29,8 @@ import java.util.List; import java.util.Set; +import static org.apache.commons.codec.Resources.getInputStream; + /** * The type Response handler service. */ @@ -33,12 +38,16 @@ @Slf4j public class ResponseHandlerServiceImpl implements ResponseHandlerService { - @Autowired - private CommonUtils commonUtils; + @Autowired private RedisTemplate redisTemplate; + /** + * + * @param cacheKey the cache key to update data + * @throws JsonProcessingException exception might be thrown + */ @Override public void updateCache(String cacheKey) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); @@ -51,58 +60,6 @@ public void updateCache(String cacheKey) throws JsonProcessingException { val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); } - @Override - public void validateResponseHeader(ResponseHeaderDTO responseHeaderDTO) throws G2pcValidationException, JsonProcessingException { - - ObjectMapper objectMapper = new ObjectMapper(); - String headerInfoString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(responseHeaderDTO); - InputStream schemaStream = commonUtils.getResponseHeaderString(); - JsonNode jsonNodeMessage = objectMapper.readTree(headerInfoString); - JsonSchema schemaMessage = null; - if(schemaStream !=null){ - schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaMessage.validate(jsonNodeMessage); - List errorCombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorCombinedMessage.add(new G2pcError("",error.getMessage())); - - } - if (errorMessage.size()>0){ - throw new G2pcValidationException(errorCombinedMessage); - } - - } - - @Override - public void validateResponseMessage(ResponseMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - String messageString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(messageDTO); - log.info("MessageString -> " + messageString); - InputStream schemaStream = commonUtils.getResponseMessageString(); - JsonNode jsonNodeMessage = objectMapper.readTree(messageString); - JsonSchema schemaMessage = null; - if(schemaStream !=null){ - schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaMessage.validate(jsonNodeMessage); - List errorCombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorCombinedMessage.add(new G2pcError("",error.getMessage())); - - } - if (errorMessage.size()>0){ - throw new G2pcValidationException(errorCombinedMessage); - } - } } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java index 0853fa4..9f3ea63 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/TxnTrackerServiceImpl.java @@ -6,26 +6,47 @@ import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; +import g2pc.core.lib.dto.status.message.response.TxnStatusResponseDTO; +import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.service.ElasticsearchService; import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.constants.DcConstants; +import g2pc.dc.core.lib.dto.ResponseDataDto; +import g2pc.dc.core.lib.dto.ResponseTrackerDto; import g2pc.dc.core.lib.entity.ResponseDataEntity; import g2pc.dc.core.lib.entity.ResponseTrackerEntity; import g2pc.dc.core.lib.repository.ResponseDataRepository; import g2pc.dc.core.lib.repository.ResponseTrackerRepository; import g2pc.dc.core.lib.service.TxnTrackerService; +import kong.unirest.HttpResponse; +import kong.unirest.JsonNode; +import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.search.SearchResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.core.type.TypeReference; +import java.io.IOException; import java.sql.Timestamp; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -38,7 +59,7 @@ public class TxnTrackerServiceImpl implements TxnTrackerService { private RedisTemplate redisTemplate; @Autowired - ResponseTrackerRepository responseTrackerRepository; + private ResponseTrackerRepository responseTrackerRepository; @Autowired private ResponseDataRepository responseDataRepository; @@ -46,49 +67,56 @@ public class TxnTrackerServiceImpl implements TxnTrackerService { @Autowired private ResponseHandlerServiceImpl responseHandlerService; + @Autowired + private ElasticsearchService elasticsearchService; + + @Value("${sunbird.api_urls.response_data_api}") + private String responseDataURL; + + @Value("${sunbird.api_urls.response_tracker_api}") + private String responseTrackerURL; + /** * Save initial payload to Redis and create a new transaction in DB table * - * @param payloadMapList required - * @param transactionId required - * @param status required - * + * @param payloadMapList payloadMap list to save in initial transaction + * @param transactionId transactionId to store in initial transaction + * @param status status to store in initial transaction */ @Override - public void saveInitialTransaction(List> payloadMapList, String transactionId, String status) throws JsonProcessingException { + public void saveInitialTransaction(List> payloadMapList, String transactionId, String status, String protocol) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - - CacheDTO cacheDTO = createCache(objectMapper.writeValueAsString(payloadMapList), HeaderStatusENUM.PDNG.toValue()); + CacheDTO cacheDTO = createCache(objectMapper.writeValueAsString(payloadMapList), HeaderStatusENUM.PDNG.toValue(), protocol); saveCache(cacheDTO, "initial-" + transactionId); } /** * Save request transactions to Redis * - * @param requestString required - * @param regType required - * @param transactionId required - * + * @param requestString string to store in cache + * @param regType regType to store in cache + * @param transactionId transactionId to store cache */ @Override - public void saveRequestTransaction(String requestString, String regType, String transactionId) throws JsonProcessingException { - CacheDTO cacheDTO = createCache(requestString, HeaderStatusENUM.PDNG.toValue()); + public void saveRequestTransaction(String requestString, String regType, String transactionId, String protocol) throws JsonProcessingException { + CacheDTO cacheDTO = createCache(requestString, HeaderStatusENUM.PDNG.toValue(), protocol); saveCache(cacheDTO, regType + "-" + transactionId); } /** * Create cache to save in Redis * - * @param data required - * @param status required + * @param data data to store in cache + * @param status status to store in cache * @return CacheDTO */ @Override - public CacheDTO createCache(String data, String status) { + public CacheDTO createCache(String data, String status, String protocol) { log.info("Save requests in cache with status pending"); CacheDTO cacheDTO = new CacheDTO(); cacheDTO.setData(data); cacheDTO.setStatus(status); + cacheDTO.setProtocol(protocol); cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); return cacheDTO; @@ -97,8 +125,8 @@ public CacheDTO createCache(String data, String status) { /** * Save cache in Redis * - * @param cacheDTO required - * @param cacheKey required + * @param cacheDTO cacheDTO + * @param cacheKey cacheKey key for which data stored */ @Override public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { @@ -106,8 +134,16 @@ public void saveCache(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingE val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); } + /** + * @param requestString request string to convert it in statusRequestDto + * @param regType regType to store in db + * @param protocol protocol to save in db + * @throws JsonProcessingException + */ @Override - public void saveRequestInDB(String requestString, String regType) throws JsonProcessingException { + public G2pcError saveRequestInDB(String requestString, String regType, String protocol, + G2pcError g2pcError, String payloadFilename, + String inboundFilename, Boolean sunbirdEnabled) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); @@ -115,77 +151,366 @@ public void saveRequestInDB(String requestString, String regType) throws JsonPr HeaderDTO headerDTO = requestDTO.getHeader(); RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); String transactionId = messageDTO.getTransactionId(); - - Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionId); - if (responseTrackerEntityOptional.isEmpty()) { - ResponseTrackerEntity responseTrackerEntity = new ResponseTrackerEntity(); - responseTrackerEntity.setVersion(headerDTO.getVersion()); - responseTrackerEntity.setMessageId(headerDTO.getMessageId()); - responseTrackerEntity.setMessageTs(headerDTO.getMessageTs()); - responseTrackerEntity.setAction(headerDTO.getAction()); - responseTrackerEntity.setSenderId(headerDTO.getSenderId()); - responseTrackerEntity.setReceiverId(headerDTO.getReceiverId()); - responseTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); - responseTrackerEntity.setTransactionId(transactionId); - responseTrackerEntity.setRegistryType(regType); - - List searchRequestDTOList = messageDTO.getSearchRequest(); - for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { - ResponseDataEntity responseDataEntity = new ResponseDataEntity(); - responseDataEntity.setReferenceId(searchRequestDTO.getReferenceId()); - responseDataEntity.setTimestamp(searchRequestDTO.getTimestamp()); - responseDataEntity.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); - responseDataEntity.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); - responseDataEntity.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); - responseDataEntity.setStatus(HeaderStatusENUM.PDNG.toValue()); - responseDataEntity.setResponseTrackerEntity(responseTrackerEntity); - responseTrackerEntity.getResponseDataEntityList().add(responseDataEntity); + g2pcError = new G2pcError(HttpStatus.OK.toString(), "Successfully stored in db"); + if (Boolean.TRUE.equals(sunbirdEnabled)) { + Map fieldValues = new HashMap<>(); + fieldValues.put("transaction_id.keyword", transactionId); + SearchResponse response = elasticsearchService.exactSearch("response_tracker", fieldValues); + if (response.getHits().getHits().length > 0) { + log.info("response: {}", response.getHits().getHits()[0].getSourceAsString()); + } else { + ResponseTrackerDto responseTrackerDto = new ResponseTrackerDto(); + responseTrackerDto.setVersion(headerDTO.getVersion()); + responseTrackerDto.setMessageId(headerDTO.getMessageId()); + responseTrackerDto.setMessageTs(headerDTO.getMessageTs()); + responseTrackerDto.setAction(headerDTO.getAction()); + responseTrackerDto.setSenderId(headerDTO.getSenderId()); + responseTrackerDto.setReceiverId(headerDTO.getReceiverId()); + responseTrackerDto.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + responseTrackerDto.setTransactionId(transactionId); + responseTrackerDto.setRegistryType(regType); + responseTrackerDto.setProtocol(protocol); + responseTrackerDto.setPayloadFilename(payloadFilename); + responseTrackerDto.setInboundFilename(inboundFilename); + List searchRequestDTOList = messageDTO.getSearchRequest(); + for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { + ResponseDataDto responseDataDto = new ResponseDataDto(); + responseDataDto.setRegistryTransactionsId(transactionId); + responseDataDto.setReferenceId(searchRequestDTO.getReferenceId()); + responseDataDto.setTimestamp(searchRequestDTO.getTimestamp()); + responseDataDto.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); + responseDataDto.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); + responseDataDto.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); + responseDataDto.setStatus(HeaderStatusENUM.PDNG.toValue()); + responseDataDto.setStatusReasonCode(g2pcError.getCode()); + String responseDataString = objectMapper.writeValueAsString(responseDataDto); + HttpResponse responseData = Unirest.post(responseDataURL) + .header("Content-Type", "application/json") + .body(responseDataString) + .asJson(); + log.info("ResponseData entity response-> " + responseData); + if (responseData.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseData.getBody().toString()); + } + } + String responseTrackerString = objectMapper.writeValueAsString(responseTrackerDto); + HttpResponse responseT = Unirest.post(responseTrackerURL) + .header("Content-Type", "application/json") + .body(responseTrackerString) + .asJson(); + if (responseT.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseT.getBody().toString()); + } + log.info("ResponseTracker entity response-> " + response); + } + } else { + try { + Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionId); + if (responseTrackerEntityOptional.isEmpty()) { + ResponseTrackerEntity responseTrackerEntity = new ResponseTrackerEntity(); + responseTrackerEntity.setVersion(headerDTO.getVersion()); + responseTrackerEntity.setMessageId(headerDTO.getMessageId()); + responseTrackerEntity.setMessageTs(headerDTO.getMessageTs()); + responseTrackerEntity.setAction(headerDTO.getAction()); + responseTrackerEntity.setSenderId(headerDTO.getSenderId()); + responseTrackerEntity.setReceiverId(headerDTO.getReceiverId()); + responseTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + responseTrackerEntity.setTransactionId(transactionId); + responseTrackerEntity.setRegistryType(regType); + responseTrackerEntity.setProtocol(protocol); + responseTrackerEntity.setPayloadFilename(payloadFilename); + responseTrackerEntity.setInboundFilename(inboundFilename); + List searchRequestDTOList = messageDTO.getSearchRequest(); + for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { + ResponseDataEntity responseDataEntity = new ResponseDataEntity(); + responseDataEntity.setReferenceId(searchRequestDTO.getReferenceId()); + responseDataEntity.setTimestamp(searchRequestDTO.getTimestamp()); + responseDataEntity.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); + responseDataEntity.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); + responseDataEntity.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); + responseDataEntity.setStatus(HeaderStatusENUM.PDNG.toValue()); + responseDataEntity.setResponseTrackerEntity(responseTrackerEntity); + responseDataEntity.setStatusReasonCode(g2pcError.getCode()); + responseTrackerEntity.getResponseDataEntityList().add(responseDataEntity); + } + responseTrackerRepository.save(responseTrackerEntity); + } + } catch (Exception e) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), e.getMessage()); } - responseTrackerRepository.save(responseTrackerEntity); } + return g2pcError; } + /** + * @param responseDTO responseDTO + * @throws JsonProcessingException jsonProcessingException might be thrown + */ @Override - public void updateTransactionDbAndCache(ResponseDTO responseDTO) throws JsonProcessingException { + public G2pcError updateTransactionDbAndCache(ResponseDTO responseDTO, String outboundFilename, Boolean sunbirdEnabled) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(ResponseHeaderDTO.class, HeaderDTO.class); - + G2pcError g2pcError; ResponseHeaderDTO headerDTO = objectMapper.convertValue(responseDTO.getHeader(), ResponseHeaderDTO.class); ResponseMessageDTO messageDTO = objectMapper.convertValue(responseDTO.getMessage(), ResponseMessageDTO.class); String transactionId = messageDTO.getTransactionId(); List searchResponseDTOList = messageDTO.getSearchResponse(); + if (Boolean.TRUE.equals(sunbirdEnabled)) { + Map fieldValues = new HashMap<>(); + fieldValues.put("transaction_id.keyword", transactionId); + SearchResponse responseTrackerSearchResponse = elasticsearchService.exactSearch("response_tracker", fieldValues); + if (responseTrackerSearchResponse.getHits().getHits().length > 0) { + log.info("response: {}", responseTrackerSearchResponse.getHits().getHits()[0].getSourceAsString()); + String responseTrackerDtoString = responseTrackerSearchResponse.getHits().getHits()[0].getSourceAsString(); + Map resultMap = objectMapper.readValue(responseTrackerDtoString, new TypeReference>() { + }); - Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionId); - if (responseTrackerEntityOptional.isPresent()) { - ResponseTrackerEntity responseTrackerEntity = responseTrackerEntityOptional.get(); - String cacheKey = responseTrackerEntity.getRegistryType() + "-" + transactionId; - - for (SearchResponseDTO searchResponseDTO : searchResponseDTOList) { - Optional entityOptional = responseDataRepository.findByReferenceId(searchResponseDTO.getReferenceId()); - if (entityOptional.isPresent()) { - ResponseDataEntity responseDataEntity = entityOptional.get(); - responseDataEntity.setStatus(searchResponseDTO.getStatus()); - responseDataEntity.setStatusReasonCode(searchResponseDTO.getStatusReasonCode()); - responseDataEntity.setStatusReasonMessage(searchResponseDTO.getStatusReasonMessage()); - responseDataEntity.setRegSubType(searchResponseDTO.getData().getRegSubType()); - responseDataEntity.setRegRecordType(searchResponseDTO.getData().getRegRecordType()); - //TODO: check for object conversion method - responseDataEntity.setRegRecords(objectMapper.writeValueAsString(searchResponseDTO.getData().getRegRecords())); - responseDataEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); - responseDataRepository.save(responseDataEntity); + String osid = resultMap.get(DcConstants.OSID).toString().substring(2); + ResponseTrackerDto responseTrackerDto = objectMapper.readerFor(ResponseTrackerDto.class). + readValue(responseTrackerDtoString); + String cacheKey = responseTrackerDto.getRegistryType() + "-" + transactionId; + for (SearchResponseDTO searchResponseDTO : searchResponseDTOList) { + Map dataFieldValues = new HashMap<>(); + dataFieldValues.put("reference_id.keyword", searchResponseDTO.getReferenceId()); + SearchResponse responseDataSearchResponse = elasticsearchService.exactSearch("response_data", dataFieldValues); + if (responseDataSearchResponse.getHits().getHits().length > 0) { + String responseDataDtoString = responseDataSearchResponse.getHits().getHits()[0].getSourceAsString(); + Map responseDataResultMap = objectMapper.readValue(responseDataDtoString, new TypeReference>() { + }); + String responseDataOsId = responseDataResultMap.get(DcConstants.OSID).toString().substring(2); + ResponseDataDto responseDataDto = objectMapper.readerFor(ResponseDataDto.class). + readValue(responseDataDtoString); + responseDataDto.setStatus(searchResponseDTO.getStatus()); + responseDataDto.setStatusReasonCode(searchResponseDTO.getStatusReasonCode()); + responseDataDto.setStatusReasonMessage(searchResponseDTO.getStatusReasonMessage()); + responseDataDto.setRegSubType(searchResponseDTO.getData().getRegSubType()); + responseDataDto.setRegRecordType(searchResponseDTO.getData().getRegRecordType()); + responseDataDto.setRegRecords(objectMapper.writeValueAsString(searchResponseDTO.getData().getRegRecords())); + responseDataDto.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + String responseDataString = objectMapper.writeValueAsString(responseDataDto); + HttpResponse responseD = Unirest.put(responseDataURL + "/" + responseDataOsId) + .header("Content-Type", "application/json") + .body(responseDataString) + .asJson(); + if (responseD.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseD.getBody().toString()); + return g2pcError; + } + } + } + responseTrackerDto.setStatus(headerDTO.getStatus()); + responseTrackerDto.setStatusReasonCode(headerDTO.getStatusReasonCode()); + responseTrackerDto.setStatusReasonMessage(headerDTO.getStatusReasonMessage()); + responseTrackerDto.setTotalCount(headerDTO.getTotalCount()); + responseTrackerDto.setCompletedCount(headerDTO.getCompletedCount()); + responseTrackerDto.setCorrelationId(messageDTO.getCorrelationId()); + responseTrackerDto.setMeta(objectMapper.writeValueAsString(headerDTO.getMeta())); + responseTrackerDto.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + responseTrackerDto.setOutboundFilename(outboundFilename); + String responseTrackerString = objectMapper.writeValueAsString(responseTrackerDto); + HttpResponse responseT = Unirest.put(responseTrackerURL + "/" + osid) + .header("Content-Type", "application/json") + .body(responseTrackerString) + .asJson(); + if (responseT.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseT.getBody().toString()); + return g2pcError; } + responseHandlerService.updateCache(cacheKey); } - responseTrackerEntity.setStatus(headerDTO.getStatus()); - responseTrackerEntity.setStatusReasonCode(headerDTO.getStatusReasonCode()); - responseTrackerEntity.setStatusReasonMessage(headerDTO.getStatusReasonMessage()); - responseTrackerEntity.setTotalCount(headerDTO.getTotalCount()); - responseTrackerEntity.setCompletedCount(headerDTO.getCompletedCount()); - responseTrackerEntity.setCorrelationId(messageDTO.getCorrelationId()); - responseTrackerEntity.setMeta(objectMapper.writeValueAsString(headerDTO.getMeta())); - responseTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); - responseTrackerRepository.save(responseTrackerEntity); + } else { + Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionId); + if (responseTrackerEntityOptional.isPresent()) { + ResponseTrackerEntity responseTrackerEntity = responseTrackerEntityOptional.get(); + String cacheKey = responseTrackerEntity.getRegistryType() + "-" + transactionId; + for (SearchResponseDTO searchResponseDTO : searchResponseDTOList) { + Optional entityOptional = responseDataRepository.findByReferenceId(searchResponseDTO.getReferenceId()); + if (entityOptional.isPresent()) { + ResponseDataEntity responseDataEntity = entityOptional.get(); + responseDataEntity.setStatus(searchResponseDTO.getStatus()); + responseDataEntity.setStatusReasonCode(searchResponseDTO.getStatusReasonCode()); + responseDataEntity.setStatusReasonMessage(searchResponseDTO.getStatusReasonMessage()); + responseDataEntity.setRegSubType(searchResponseDTO.getData().getRegSubType()); + responseDataEntity.setRegRecordType(searchResponseDTO.getData().getRegRecordType()); + responseDataEntity.setRegRecords(objectMapper.writeValueAsString(searchResponseDTO.getData().getRegRecords())); + responseDataEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + responseDataRepository.save(responseDataEntity); + } + } + responseTrackerEntity.setStatus(headerDTO.getStatus()); + responseTrackerEntity.setStatusReasonCode(headerDTO.getStatusReasonCode()); + responseTrackerEntity.setStatusReasonMessage(headerDTO.getStatusReasonMessage()); + responseTrackerEntity.setTotalCount(headerDTO.getTotalCount()); + responseTrackerEntity.setCompletedCount(headerDTO.getCompletedCount()); + responseTrackerEntity.setCorrelationId(messageDTO.getCorrelationId()); + responseTrackerEntity.setMeta(objectMapper.writeValueAsString(headerDTO.getMeta())); + responseTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + responseTrackerEntity.setOutboundFilename(outboundFilename); + responseTrackerRepository.save(responseTrackerEntity); + responseHandlerService.updateCache(cacheKey); + } + } + return new G2pcError(HttpStatus.OK.toString(), "Successfully stored in db"); + } + + /** + * @param txnType transaction type to store in cache + * @param transactionId to store in cache + * @param status status transactionId to store in cache + * @param protocol protocol to store in cache + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public void saveInitialStatusTransaction(String txnType, String transactionId, String status, String protocol) throws JsonProcessingException { + CacheDTO cacheDTO = createCache(txnType, HeaderStatusENUM.PDNG.toValue(), protocol); + saveCache(cacheDTO, "initial-" + transactionId); + + } + + /** + * @param requestString request string to convert it in statusRequestDto + * @param regType regType to store in db + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public G2pcError saveRequestInStatusDB(String requestString, String regType) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); + + StatusRequestDTO statusRequestDTO = objectMapper.readValue(requestString, StatusRequestDTO.class); + HeaderDTO headerDTO = statusRequestDTO.getHeader(); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + String transactionId = statusRequestMessageDTO.getTransactionId(); + G2pcError g2pcError = new G2pcError(HttpStatus.OK.toString(), "Successfully stored in db"); + Map fieldValues = new HashMap<>(); + fieldValues.put("transaction_id.keyword", transactionId); + SearchResponse response = elasticsearchService.exactSearch("response_tracker", fieldValues); + if (response.getHits().getHits().length > 0) { + g2pcError = new G2pcError(HttpStatus.OK.toString(), "Data is already present in the db for transaction id" + transactionId); + log.info("response: {}", response.getHits().getHits()[0].getSourceAsString()); + } else { + ResponseTrackerDto responseTrackerDto = new ResponseTrackerDto(); + responseTrackerDto.setVersion(headerDTO.getVersion()); + responseTrackerDto.setMessageId(headerDTO.getMessageId()); + responseTrackerDto.setMessageTs(headerDTO.getMessageTs()); + responseTrackerDto.setAction(headerDTO.getAction()); + responseTrackerDto.setSenderId(headerDTO.getSenderId()); + responseTrackerDto.setReceiverId(headerDTO.getReceiverId()); + responseTrackerDto.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + responseTrackerDto.setTransactionId(transactionId); + responseTrackerDto.setRegistryType(regType); + + TxnStatusRequestDTO txnStatusRequestDTO = statusRequestMessageDTO.getTxnStatusRequest(); + ResponseDataDto responseDataDto = new ResponseDataDto(); + responseDataDto.setTimestamp(""); + responseDataDto.setVersion(""); + responseDataDto.setRegType(""); + responseDataDto.setRegSubType(""); + responseDataDto.setTxnType(txnStatusRequestDTO.getTxnType()); + responseDataDto.setAttributeType(txnStatusRequestDTO.getAttributeType()); + responseDataDto.setAttributeValue((String) txnStatusRequestDTO.getAttributeValue()); + responseDataDto.setStatus(HeaderStatusENUM.PDNG.toValue()); + + String responseDataString = objectMapper.writeValueAsString(responseDataDto); + HttpResponse responseD = Unirest.post(responseDataURL) + .header("Content-Type", "application/json") + .body(responseDataString) + .asJson(); + if (responseD.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseD.getBody().toString()); + return g2pcError; + } + String responseTrackerString = objectMapper.writeValueAsString(responseTrackerDto); + HttpResponse responseT = Unirest.post(responseTrackerURL) + .header("Content-Type", "application/json") + .body(responseTrackerString) + .asJson(); + log.info("ResponseTracker entity response-> " + responseT); + if (responseT.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseT.getBody().toString()); + return g2pcError; + } + } + return g2pcError; + } + + /** + * @param statusResponseDTO statusResponseDTO used to update transaction db + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public G2pcError updateStatusTransactionDbAndCache(StatusResponseDTO statusResponseDTO) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(ResponseHeaderDTO.class, HeaderDTO.class); + G2pcError g2pcError; + ResponseHeaderDTO headerDTO = objectMapper.convertValue(statusResponseDTO.getHeader(), ResponseHeaderDTO.class); + StatusResponseMessageDTO statusResponseMessageDTO = objectMapper.convertValue(statusResponseDTO.getMessage(), StatusResponseMessageDTO.class); + String transactionId = statusResponseMessageDTO.getTransactionId(); + TxnStatusResponseDTO txnStatusResponseDTO = statusResponseMessageDTO.getTxnStatusResponse(); + Map fieldValues = new HashMap<>(); + fieldValues.put("transaction_id.keyword", transactionId); + SearchResponse responseTrackerSearchResponse = elasticsearchService.exactSearch("response_tracker", fieldValues); + if (responseTrackerSearchResponse.getHits().getHits().length > 0) { + log.info("response: {}", responseTrackerSearchResponse.getHits().getHits()[0].getSourceAsString()); + String responseTrackerDtoString = responseTrackerSearchResponse.getHits().getHits()[0].getSourceAsString(); + Map resultMap = objectMapper.readValue(responseTrackerDtoString, new TypeReference>() { + }); + + String osid = resultMap.get(DcConstants.OSID).toString().substring(2); + ResponseTrackerDto responseTrackerDto = objectMapper.readerFor(ResponseTrackerDto.class). + readValue(responseTrackerDtoString); + String cacheKey = responseTrackerDto.getRegistryType() + "-" + transactionId; + ResponseMessageDTO responseMessageDTO = objectMapper.convertValue(txnStatusResponseDTO.getTxnStatus(), ResponseMessageDTO.class); + for (SearchResponseDTO searchResponseDTO : responseMessageDTO.getSearchResponse()) { + Map dataFieldValues = new HashMap<>(); + dataFieldValues.put("reference_id.keyword", searchResponseDTO.getReferenceId()); + SearchResponse responseDataSearchResponse = elasticsearchService.exactSearch("response_data", dataFieldValues); + if (responseDataSearchResponse.getHits().getHits().length > 0) { + String responseDataDtoString = responseDataSearchResponse.getHits().getHits()[0].getSourceAsString(); + Map responseDataResultMap = objectMapper.readValue(responseDataDtoString, new TypeReference>() { + }); + String responseDataOsid = responseDataResultMap.get(DcConstants.OSID).toString().substring(2); + ResponseDataDto responseDataDto = objectMapper.readerFor(ResponseDataDto.class). + readValue(responseDataDtoString); + + responseDataDto.setStatus(searchResponseDTO.getStatus()); + responseDataDto.setStatusReasonCode(searchResponseDTO.getStatusReasonCode()); + responseDataDto.setStatusReasonMessage(searchResponseDTO.getStatusReasonMessage()); + responseDataDto.setRegSubType(searchResponseDTO.getData().getRegSubType()); + responseDataDto.setRegRecordType(searchResponseDTO.getData().getRegRecordType()); + responseDataDto.setRegRecords(objectMapper.writeValueAsString(searchResponseDTO.getData().getRegRecords())); + responseDataDto.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + String responseDataString = objectMapper.writeValueAsString(responseDataDto); + HttpResponse responseD = Unirest.put(responseDataURL + "/" + responseDataOsid) + .header("Content-Type", "application/json") + .body(responseDataString) + .asJson(); + if (responseD.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseD.getBody().toString()); + return g2pcError; + } + } + } + responseTrackerDto.setStatus(headerDTO.getStatus()); + responseTrackerDto.setStatusReasonCode(headerDTO.getStatusReasonCode()); + responseTrackerDto.setStatusReasonMessage(headerDTO.getStatusReasonMessage()); + responseTrackerDto.setTotalCount(headerDTO.getTotalCount()); + responseTrackerDto.setCompletedCount(headerDTO.getCompletedCount()); + responseTrackerDto.setCorrelationId(statusResponseMessageDTO.getCorrelationId()); + responseTrackerDto.setMeta(objectMapper.writeValueAsString(headerDTO.getMeta())); + responseTrackerDto.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + String responseTrackerString = objectMapper.writeValueAsString(responseTrackerDto); + HttpResponse responseT = Unirest.put(responseTrackerURL + "/" + osid) + .header("Content-Type", "application/json") + .body(responseTrackerString) + .asJson(); + if (responseT.getStatus() != 200) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), responseT.getBody().toString()); + return g2pcError; + } responseHandlerService.updateCache(cacheKey); } + return new G2pcError(HttpStatus.OK.toString(), "Successfully stored in db"); } } diff --git a/g2pc-dc-core-lib/src/main/resources/application.yml b/g2pc-dc-core-lib/src/main/resources/application.yml index 459d3fb..4d25e7a 100644 --- a/g2pc-dc-core-lib/src/main/resources/application.yml +++ b/g2pc-dc-core-lib/src/main/resources/application.yml @@ -1,13 +1,12 @@ - spring: mvc: pathmatch: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2p?currentSchema=common + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc username: postgres - password: postgres + password: K6tnrCU0wqXOwPW hikari: data-source-properties: stringtype: unspecified @@ -40,6 +39,41 @@ spring.data.redis: password: 123456789 port: 6376 +sftp: + listener: + host: localhost + port: 2224 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + outbound_directory: /outbound + local: + inbound_directory: /home/prihir/g2pc/dc/inbound + outbound_directory: /home/prihir/g2pc/dc/outbound + dp1: + host: localhost + port: 2225 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + dp2: + host: localhost + port: 2226 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound +sunbird: + api_urls: + response_data_api: http://3.109.26.38:8083/api/v1/Response_Data + response_tracker_api: http://3.109.26.38:8083/api/v1/Response_Tracker + enabled: false + elasticsearch: + host: 3.109.26.38 + port: 9200 + scheme: http diff --git a/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java b/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java index 7182128..80d6b83 100644 --- a/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java +++ b/g2pc-dc-core-lib/src/test/java/g2pc/dc/core/lib/G2pcDcCoreLibraryApplicationTests.java @@ -1,9 +1,5 @@ package g2pc.dc.core.lib; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; import g2pc.dc.core.lib.service.RequestBuilderService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; From 7f97b6e9f49b60dba43428a36f2ced5785e5174b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:42:47 +0530 Subject: [PATCH 41/53] g2pc-dp-core-lib changes. --- .../RequestHandlerServiceImpl.java | 106 ++--- .../ResponseBuilderServiceImpl.java | 416 ++++++++++++++---- .../serviceimpl/TxnTrackerDbServiceImpl.java | 124 +++++- .../TxnTrackerRedisServiceImpl.java | 12 +- .../src/main/resources/application.yml | 155 +++++-- 5 files changed, 579 insertions(+), 234 deletions(-) diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java index e8d837d..9b34479 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java @@ -2,19 +2,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; import g2pc.core.lib.enums.HeaderStatusENUM; -import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.utils.CommonUtils; import g2pc.dp.core.lib.constants.DpConstants; import g2pc.dp.core.lib.service.RequestHandlerService; @@ -22,23 +16,12 @@ import g2pc.dp.core.lib.service.TxnTrackerRedisService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import g2pc.core.lib.exceptions.G2pcError; -import java.io.InputStream; -import java.util.*; - -/** - * The type Request handler service. - */ @Service @Slf4j public class RequestHandlerServiceImpl implements RequestHandlerService { - @Autowired - private RedisTemplate redisTemplate; - @Autowired private CommonUtils commonUtils; @@ -50,89 +33,58 @@ public class RequestHandlerServiceImpl implements RequestHandlerService { /** * Build a request to save in Redis cache - * - * @param requestData required - * @param cacheKey required + * @param requestData requestData to be stored in redis cache + * @param cacheKey cacheKey for which data to be stored * @return Acknowledgement */ @Override - public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) throws JsonProcessingException { + public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); log.info("Request saved in cache with status pending"); CacheDTO cacheDTO = new CacheDTO(); cacheDTO.setData(requestData); cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); + cacheDTO.setProtocol(protocol); cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); - txnTrackerDbService.saveRequestDetails(requestDTO); - + txnTrackerDbService.saveRequestDetails(requestDTO, protocol,sunbirdEnabled); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); - return acknowledgementDTO; } - /** - * The Object mapper. - */ - @Override - public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - String headerInfoString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(headerDTO); - InputStream schemaStream = commonUtils.getRequestHeaderString(); - JsonNode jsonNodeMessage = objectMapper.readTree(headerInfoString); - JsonSchema schemaMessage = null; - if(schemaStream !=null){ - schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaMessage.validate(jsonNodeMessage); - List errorcombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorcombinedMessage.add(new G2pcError("",error.getMessage())); - } - if (errorMessage.size()>0){ - throw new G2pcValidationException(errorcombinedMessage); - } - } + /** + * @param statusRequestData request data to store + * @param cacheKey cacheKey for which data storing + * @param protocol protocol to store in cache + * @return AcknowledgementDTO + * @throws JsonProcessingException jsonProcessingException might be thrown + */ @Override - public void validateRequestMessage(RequestMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException { + public AcknowledgementDTO buildCacheStatusRequest(String statusRequestData, String cacheKey, String protocol) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + log.info("Request saved in cache with status pending"); + CacheDTO cacheDTO = new CacheDTO(); + cacheDTO.setData(statusRequestData); + cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); + cacheDTO.setProtocol(protocol); + cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + StatusRequestDTO requestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); + txnTrackerDbService.saveStatusRequestDetails(requestDTO); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); - String messageString = objectMapper - .writerWithDefaultPrettyPrinter() - .writeValueAsString(messageDTO); - log.info("MessageString -> " + messageString); - InputStream schemaStream = commonUtils.getRequestMessageString(); - JsonNode jsonNodeMessage = objectMapper.readTree(messageString); - JsonSchema schemaMessage = null; - if(schemaStream !=null){ - schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaMessage.validate(jsonNodeMessage); - List errorcombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorcombinedMessage.add(new G2pcError("",error.getMessage())); - - } - if (errorMessage.size()>0){ - throw new G2pcValidationException(errorcombinedMessage); - } + return acknowledgementDTO; } } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java index 48f4f7e..6a5c927 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java @@ -5,29 +5,53 @@ import g2pc.core.lib.config.G2pUnirestHelper; import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; +import g2pc.core.lib.constants.SftpConstants; +import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.MetaDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.response.*; import g2pc.core.lib.dto.common.security.G2pTokenResponse; import g2pc.core.lib.dto.common.security.TokenExpiryDto; -import g2pc.core.lib.exceptionhandler.ErrorResponse; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; +import g2pc.core.lib.dto.status.message.response.TxnStatusResponseDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.enums.StatusTransactionTypeEnum; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.core.lib.service.SftpHandlerService; import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; import g2pc.dp.core.lib.repository.MsgTrackerRepository; +import g2pc.dp.core.lib.repository.TxnTrackerRepository; import g2pc.dp.core.lib.service.ResponseBuilderService; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; +import g2pc.dp.core.lib.utils.DpCommonUtils; import kong.unirest.HttpResponse; import kong.unirest.UnirestException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.http.HttpStatus; @@ -35,6 +59,9 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.ParseException; import java.util.*; @@ -42,12 +69,38 @@ @Slf4j public class ResponseBuilderServiceImpl implements ResponseBuilderService { - @Autowired private RedisTemplate redisTemplate; - @Autowired - private CommonUtils commonUtils; + @Value("${crypto.to_dc.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.to_dc.support_signature}") + private boolean isSign; + + @Value("${crypto.to_dc.password}") + private String p12Password; + + @Value("${crypto.to_dc.id}") + private String dpId; + + @Value("${crypto.to_dc.key_path}") + private String farmerKeyPath; + + @Value("${client.api_urls.client_search_api}") + String onSearchURL; + + @Value("${client.api_urls.client_status_api}") + String onStatusURL; + + @Value("${keycloak.from-dc.client-id}") + private String dcClientId; + + @Value("${keycloak.from-dc.client-secret}") + private String dcClientSecret; + + @Value("${keycloak.from-dc.url}") + private String keyClockClientTokenUrl; @Autowired G2pUnirestHelper g2pUnirestHelper; @@ -58,21 +111,36 @@ public class ResponseBuilderServiceImpl implements ResponseBuilderService { @Autowired G2pTokenService g2pTokenService; - @Value("${crypto.to_dc.support_encryption}") - private boolean isEncrypt; + @Autowired + AsymmetricSignatureService asymmetricSignatureService; - @Value("${crypto.to_dc.support_signature}") - private boolean isSign; + @Autowired + MsgTrackerRepository msgTrackerRepository; - @Value("${crypto.to_dc.password}") - private String p12Password; @Autowired - AsymmetricSignatureService asymmetricSignatureService; + TxnTrackerRepository txnTrackerRepository; + + @Autowired + TxnTrackerDbService txnTrackerDbService; + + @Autowired + private SftpHandlerService sftpHandlerService; + + + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + + @Autowired + private ResourceLoader resourceLoader; + + @Autowired + private DpCommonUtils dpCommonUtils; /** * Get response header * - * @param msgTrackerEntity required + * @param msgTrackerEntity msgTrackerEntity used to create ResponseHeaderFto * @return ResponseHeaderDTO */ @Override @@ -90,7 +158,7 @@ public ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity) headerDTO.setStatusReasonMessage(msgTrackerEntity.getStatusReasonMessage()); headerDTO.setTotalCount(msgTrackerEntity.getTotalCount()); headerDTO.setCompletedCount(msgTrackerEntity.getCompletedCount()); - Map metaMap = new HashMap<>(); + Map metaMap = new HashMap<>(); MetaDTO metaDTO = new MetaDTO(); metaDTO.setData(metaMap); headerDTO.setMeta(metaDTO); @@ -100,8 +168,8 @@ public ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity) /** * Build a response message * - * @param transactionId required - * @param searchResponseDTOList required + * @param transactionId transactionId to build response message + * @param searchResponseDTOList list of searchResponseDto to store in response message * @return ResponseMessageDTO */ @Override @@ -116,15 +184,15 @@ public ResponseMessageDTO buildResponseMessage(String transactionId, List "+isEncrypt); - log.info("Is signed ? -> "+isSign); - responseString = createSignature( isEncrypt, isSign , responseString , fis , encryptedSalt); + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + responseString = createSignature(isEncrypt, isSign, responseString, fis, txnType); String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); HttpResponse response = g2pUnirestHelper.g2pPost(uri) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + jwtToken) .body(responseString) .asString(); - log.info("on-search response status = {}", response.getStatus()); + log.info(txnType + "Response status = {}", response.getStatus()); if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { - G2pcError g2pcError = new G2pcError(HttpStatus.INTERNAL_SERVER_ERROR.toString(), response.getBody()); - return g2pcError; + return new G2pcError(HttpStatus.INTERNAL_SERVER_ERROR.toString(), response.getBody()); } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { - G2pcError g2pcError = new G2pcError( HttpStatus.UNAUTHORIZED.toString(), response.getBody()); - return g2pcError; + return new G2pcError(HttpStatus.UNAUTHORIZED.toString(), response.getBody()); } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { - G2pcError g2pcError = new G2pcError(HttpStatus.BAD_REQUEST.toString(), response.getBody()); - return g2pcError; + return new G2pcError(HttpStatus.BAD_REQUEST.toString(), response.getBody()); } else if (response.getStatus() != HttpStatus.OK.value()) { - G2pcError g2pcError = new G2pcError("err.service.unavailable", response.getBody()); - return g2pcError; + return new G2pcError("err.service.unavailable", response.getBody()); } - G2pcError g2pcError = new G2pcError(HttpStatus.OK.toString(), response.getBody()); - return g2pcError; + return new G2pcError(HttpStatus.OK.toString(), response.getBody()); } /** * Method to store token in cache * - * @param cacheKey - * @param tokenExpiryDto - * @throws JsonProcessingException + * @param cacheKey cacheKey cache key for which data is storing + * @param tokenExpiryDto token expiry dto + * @throws JsonProcessingException might be thrown */ @Override public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException { - ValueOperations val = redisTemplate.opsForValue(); val.set(cacheKey, new ObjectMapper().writeValueAsString(tokenExpiryDto)); } @@ -181,9 +253,9 @@ public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws Jso /** * Method to get token stored in cache * - * @param clientId - * @return - * @throws JsonProcessingException + * @param clientId client Id + * @return TokenExpiryDto tokenExpiryDto + * @throws JsonProcessingException might be thrown */ @Override public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException { @@ -202,13 +274,13 @@ public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingEx /** * The method to get validated token * - * @param keyCloakUrl - * @param clientId - * @param clientSecret - * @return - * @throws IOException - * @throws ParseException - * @throws UnirestException + * @param keyCloakUrl keycloak url to validate token + * @param clientId client id + * @param clientSecret client secret + * @return String validated token + * @throws IOException IOException might be thrown + * @throws ParseException ParseException might be thrown + * @throws UnirestException UnirestException might be thrown */ @Override public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, ParseException, UnirestException { @@ -217,7 +289,7 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie String jwtToken = ""; if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); - jwtToken = tokenResponse.getAccess_token(); + jwtToken = tokenResponse.getAccessToken(); saveToken(clientId, g2pTokenService.createTokenExpiryDto(tokenResponse)); } else { jwtToken = tokenExpiryDto.getToken(); @@ -228,64 +300,69 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie /** * The method is to create signature * - * @param isSign - * @param isEncrypt - * @param responseString - * @return - * @throws Exception + * @param isSign signature flag + * @param isEncrypt encryption flag + * @param responseString response string + * @return String signature + * @throws Exception Exception might be thrown */ - private String createSignature( boolean isEncrypt, boolean isSign, String responseString - , InputStream fis , String encryptedSalt) throws Exception { - + @SuppressWarnings("unchecked") + private String createSignature(boolean isEncrypt, boolean isSign, String responseString + , InputStream fis, String txnType) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). readValue(responseString); - - byte[] json = objectMapper.writeValueAsBytes( responseDTO.getMessage()); - ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); ResponseHeaderDTO responseHeaderDTO = (ResponseHeaderDTO) responseDTO.getHeader(); String responseHeaderString = objectMapper.writeValueAsString(responseHeaderDTO); - String messageString = objectMapper.writeValueAsString(messageDTO); String signature = null; + String messageString = ""; + if (txnType.equals(CoreConstants.DP_STATUS_URL)) { + StatusResponseMessageDTO messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + messageString = objectMapper.writeValueAsString(messageDTO); + } else { + ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + messageString = objectMapper.writeValueAsString(messageDTO); + } - if(isSign){ - if(isEncrypt){ - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedSalt+encryptedMessageString); + if (isSign) { + if (isEncrypt) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); responseDTO.getHeader().setIsMsgEncrypted(true); - Map meta= (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,true); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, true); responseDTO.getHeader().getMeta().setData(meta); responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+encryptedMessageString ,fis ,p12Password); + byte[] asymmetricSignature = asymmetricSignatureService.sign(responseHeaderString + encryptedMessageString, fis, p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); - log.info("Encrypted message ->"+encryptedMessageString); - log.info("Hashed Signature ->"+signature); - }else{ + log.info("Encrypted message ->" + encryptedMessageString); + log.info("Hashed Signature ->" + signature); + } else { responseDTO.getHeader().setIsMsgEncrypted(false); - Map meta= (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,true); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, true); responseDTO.getHeader().getMeta().setData(meta); responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+messageString,fis,p12Password); + byte[] asymmetricSignature = asymmetricSignatureService.sign(responseHeaderString + messageString, fis, p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); - log.info("Hashed Signature ->"+signature); + log.info("Hashed Signature ->" + signature); } } else { - if(isEncrypt){ - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedSalt+encryptedMessageString); + if (isEncrypt) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); responseDTO.getHeader().setIsMsgEncrypted(true); - Map meta= (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,false); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, false); responseDTO.getHeader().getMeta().setData(meta); - log.info("Encrypted message ->"+encryptedMessageString); - }else{ + log.info("Encrypted message ->" + encryptedMessageString); + } else { responseDTO.getHeader().setIsMsgEncrypted(false); - Map meta= (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN,false); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, false); responseDTO.getHeader().getMeta().setData(meta); } } @@ -293,4 +370,167 @@ private String createSignature( boolean isEncrypt, boolean isSign, String respon responseString = objectMapper.writeValueAsString(responseDTO); return responseString; } + + /** + * @param statusRequestMessageDTO status request message dto + * @return StatusResponseMessageDTO + */ + @Override + public StatusResponseMessageDTO buildStatusResponseMessage(StatusRequestMessageDTO statusRequestMessageDTO) { + StatusResponseMessageDTO statusResponseMessageDTO = new StatusResponseMessageDTO(); + statusResponseMessageDTO.setTransactionId(statusRequestMessageDTO.getTransactionId()); + statusResponseMessageDTO.setCorrelationId(CommonUtils.generateUniqueId("C")); + String searchTransactionId = (String) statusRequestMessageDTO.getTxnStatusRequest().getAttributeValue(); + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(searchTransactionId); + MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); + List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); + TxnStatusResponseDTO txnStatusResponseDTO = new TxnStatusResponseDTO(); + + if (statusRequestMessageDTO.getTxnStatusRequest().getTxnType().equals(StatusTransactionTypeEnum.SEARCH.toValue())) { + txnStatusResponseDTO.setTxnType(StatusTransactionTypeEnum.ON_SUBSCRIBE.toValue()); + ResponseMessageDTO responseMessageDTO = new ResponseMessageDTO(); + responseMessageDTO.setTransactionId(msgTrackerEntity.getTransactionId()); + responseMessageDTO.setCorrelationId(msgTrackerEntity.getCorrelationId()); + List searchResponse = new ArrayList<>(); + for (TxnTrackerEntity txnTrackerEntity : txnTrackerEntityList) { + + SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); + searchResponseDTO.setReferenceId(txnTrackerEntity.getReferenceId()); + searchResponseDTO.setTimestamp(txnTrackerEntity.getTimestamp()); + searchResponseDTO.setStatus(txnTrackerEntity.getStatus()); + searchResponseDTO.setStatusReasonCode(txnTrackerEntity.getStatusReasonCode()); + searchResponseDTO.setStatusReasonMessage(txnTrackerEntity.getStatusReasonMessage()); + DataDTO dataDTO = new DataDTO(); + dataDTO.setVersion(txnTrackerEntity.getVersion()); + dataDTO.setRegType(txnTrackerEntity.getRegType()); + dataDTO.setRegSubType(txnTrackerEntity.getRegSubType()); + dataDTO.setRegRecordType(txnTrackerEntity.getRegRecordType()); + searchResponseDTO.setData(dataDTO); + searchResponse.add(searchResponseDTO); + } + responseMessageDTO.setSearchResponse(searchResponse); + txnStatusResponseDTO.setTxnStatus(responseMessageDTO); + txnTrackerDbService.updateStatusResponseDetails(responseMessageDTO, statusResponseMessageDTO.getTransactionId()); + } + + statusResponseMessageDTO.setTxnStatusResponse(txnStatusResponseDTO); + + return statusResponseMessageDTO; + } + + /** + * @param signatureString signature to be added in request + * @param responseHeaderDTO header dto ti be added in request + * @param statusResponseMessageDTO response message dto to be added + * @return response dto + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public String buildStatusResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, StatusResponseMessageDTO statusResponseMessageDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + StatusResponseDTO statusResponseDTO = new StatusResponseDTO(); + statusResponseDTO.setSignature(signatureString); + statusResponseDTO.setHeader(responseHeaderDTO); + statusResponseDTO.setMessage(statusResponseMessageDTO); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(statusResponseDTO); + } + + /** + * The method is to send on search response using sftp + * + * @param responseString required + * @return String + * @throws Exception jsonProcessingException might be thrown + */ + @Override + public G2pcError sendOnSearchResponseSftp(String responseString, InputStream fis, + String txnType, SftpServerConfigDTO sftpServerConfigDTO) throws Exception { + log.info("Send on-search response"); + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + responseString = createSignature(isEncrypt, isSign, responseString, fis, txnType); + + String originalFilename = UUID.randomUUID() + ".json"; + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), originalFilename); + Files.createFile(tempFile); + Files.write(tempFile, responseString.getBytes()); + + Boolean status = sftpHandlerService.uploadFileToSftp(sftpServerConfigDTO, tempFile.toString(), + sftpServerConfigDTO.getRemoteOutboundDirectory()); + Files.delete(tempFile); + G2pcError g2pcError; + if (Boolean.FALSE.equals(status)) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), SftpConstants.UPLOAD_ERROR_MESSAGE); + log.error(SftpConstants.UPLOAD_ERROR_MESSAGE); + } else { + g2pcError = new G2pcError(HttpStatus.OK.toString(), SftpConstants.UPLOAD_SUCCESS_MESSAGE); + } + return g2pcError; + } + + @Override + public G2pcError buildOnSearchScheduler(List refRecordsStringsList, CacheDTO cacheDTO, Boolean sunbirdEnabled) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + G2pcError g2pcError = new G2pcError(); + String protocol = cacheDTO.getProtocol(); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO, protocol, sunbirdEnabled); + List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( + requestDTO, refRecordsStringsList, protocol, sunbirdEnabled); + ResponseHeaderDTO headerDTO = getResponseHeaderDTO(msgTrackerEntity); + ResponseMessageDTO responseMessageDTO = buildResponseMessage(transactionId, searchResponseDTOList); + Map meta = (Map) headerDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID, dpId); + requestDTO.getHeader().getMeta().setData(meta); + String responseString = buildResponseString("signature", + headerDTO, responseMessageDTO); + responseString = CommonUtils.formatString(responseString); + log.info("on-search response = {}", responseString); + Resource resource = resourceLoader.getResource(farmerKeyPath); + InputStream fis = resource.getInputStream(); + + if (protocol.equals(CoreConstants.SEND_PROTOCOL_HTTPS)) { + g2pcError = sendOnSearchResponse(responseString, onSearchURL, dcClientId, + dcClientSecret, keyClockClientTokenUrl, fis, CoreConstants.SEARCH_TXN_TYPE); + } else if (protocol.equals(CoreConstants.SEND_PROTOCOL_SFTP)) { + SftpServerConfigDTO sftpServerConfigDTO = dpCommonUtils.getSftpConfigForDp(); + g2pcError = sendOnSearchResponseSftp(responseString, fis, + CoreConstants.SEARCH_TXN_TYPE, sftpServerConfigDTO); + } + if (!Objects.requireNonNull(g2pcError).getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } + + + return g2pcError; + } + + @Override + public G2pcError buildOnStatusScheduler(CacheDTO cacheDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveStatusRequestDetails(statusRequestDTO); + ResponseHeaderDTO responseHeaderDTO = getResponseHeaderDTO(msgTrackerEntity); + + StatusResponseMessageDTO statusResponseMessageDTO = buildStatusResponseMessage(statusRequestMessageDTO); + + Map meta = (Map) responseHeaderDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID, dpId); + statusRequestDTO.getHeader().getMeta().setData(meta); + + String statusResponseString = buildStatusResponseString("signature", responseHeaderDTO, statusResponseMessageDTO); + statusResponseString = CommonUtils.formatString(statusResponseString); + log.info("on-status response = {}", statusResponseString); + Resource resource = resourceLoader.getResource(farmerKeyPath); + InputStream fis = resource.getInputStream(); + return sendOnSearchResponse(statusResponseString, onStatusURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, CoreConstants.DP_STATUS_URL); + + } } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java index 16285d8..ac319ae 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java @@ -5,13 +5,16 @@ import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.ResponsePaginationDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.search.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.response.ResponsePaginationDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.enums.LocalesENUM; import g2pc.core.lib.enums.QueryTypeEnum; @@ -20,6 +23,7 @@ import g2pc.dp.core.lib.entity.MsgTrackerEntity; import g2pc.dp.core.lib.entity.TxnTrackerEntity; import g2pc.dp.core.lib.repository.MsgTrackerRepository; +import g2pc.dp.core.lib.repository.TxnTrackerRepository; import g2pc.dp.core.lib.service.TxnTrackerDbService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -40,14 +44,17 @@ public class TxnTrackerDbServiceImpl implements TxnTrackerDbService { @Autowired private MsgTrackerRepository msgTrackerRepository; + @Autowired + private TxnTrackerRepository txnTrackerRepository; + /** * Save request details * - * @param requestDTO required + * @param requestDTO requestDTO to save in Db * @return request details entity */ @Override - public MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO) throws JsonProcessingException { + public MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); @@ -68,6 +75,7 @@ public MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO) throws JsonPro msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(requestDTO)); + msgTrackerEntity.setProtocol(protocol); List searchRequestDTOList = messageDTO.getSearchRequest(); for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { @@ -107,8 +115,8 @@ public int getRecordCount(Object records) { /** * Build a search response * - * @param txnTrackerEntity required - * @param dataDTO required + * @param txnTrackerEntity txnTrackerEntity to build search response + * @param dataDTO dataDTO to build search response dto * @return SearchResponseDTO */ @Override @@ -134,7 +142,7 @@ public SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, /** * Build data * - * @param regRecordsString required + * @param regRecordsString regRecord to store in data dto * @return DataDTO */ @Override @@ -155,19 +163,19 @@ public DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEnt /** * Get updated search response list * - * @param requestDTO required - * @param refRecordsStringsList required + * @param requestDTO request dto to be updated + * @param refRecordsStringsList list of records * @return updated search response list */ @Override public List getUpdatedSearchResponseList(RequestDTO requestDTO, - List refRecordsStringsList) throws IOException { + List refRecordsStringsList, + String protocol, + Boolean sunbirdEnabled) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - List searchResponseDTOList = new ArrayList<>(); - - MsgTrackerEntity msgTrackerEntity = saveRequestDetails(requestDTO); + MsgTrackerEntity msgTrackerEntity = saveRequestDetails(requestDTO, protocol, sunbirdEnabled); List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); int totalCount = txnTrackerEntityList.size(); @@ -206,4 +214,84 @@ public List getUpdatedSearchResponseList(RequestDTO requestDT msgTrackerRepository.save(msgTrackerEntity); return searchResponseDTOList; } + + /** + * @param statusRequestDTO statusRequestDTO to save in db + * @return MsgTrackerEntity + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public MsgTrackerEntity saveStatusRequestDetails(StatusRequestDTO statusRequestDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + MsgTrackerEntity entity; + + HeaderDTO headerDTO = statusRequestDTO.getHeader(); + StatusRequestMessageDTO messageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + if (msgTrackerEntityOptional.isEmpty()) { + MsgTrackerEntity msgTrackerEntity = new MsgTrackerEntity(); + msgTrackerEntity.setVersion(headerDTO.getVersion()); + msgTrackerEntity.setMessageId(headerDTO.getMessageId()); + msgTrackerEntity.setMessageTs(headerDTO.getMessageTs()); + msgTrackerEntity.setAction(headerDTO.getAction()); + msgTrackerEntity.setSenderId(headerDTO.getSenderId()); + msgTrackerEntity.setReceiverId(headerDTO.getReceiverId()); + msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); + msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(statusRequestDTO)); + + TxnStatusRequestDTO txnStatusRequestDTO = messageDTO.getTxnStatusRequest(); + TxnTrackerEntity txnTrackerEntity = new TxnTrackerEntity(); + txnTrackerEntity.setTimestamp(null); + txnTrackerEntity.setVersion(null); + txnTrackerEntity.setRegType(null); + txnTrackerEntity.setRegSubType(null); + txnTrackerEntity.setQueryType(null); + txnTrackerEntity.setQuery(null); + txnTrackerEntity.setTxnStatus(null); + txnTrackerEntity.setTxnType(txnStatusRequestDTO.getTxnType()); + txnTrackerEntity.setCreatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setMsgTrackerEntity(msgTrackerEntity); + msgTrackerEntity.getTxnTrackerEntityList().add(txnTrackerEntity); + + entity = msgTrackerRepository.save(msgTrackerEntity); + } else { + entity = msgTrackerEntityOptional.get(); + } + return entity; + } + + /** + * @param responseMessageDTO responseMessageDTO to update in db + * @param transactionId transactionId to search in db + */ + @Override + public void updateStatusResponseDetails(ResponseMessageDTO responseMessageDTO, String transactionId) { + + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); + Optional trackerEntityOptional = txnTrackerRepository.findByMsgTrackerEntity(msgTrackerEntity); + TxnTrackerEntity txnTrackerEntity = trackerEntityOptional.get(); + txnTrackerEntity.setTxnStatus(responseMessageDTO.toString()); + txnTrackerRepository.save(txnTrackerEntity); + } + + /** + * @param transactionId transactionId used to search data + */ + @Override + public void updateMessageTrackerStatusDb(String transactionId) { + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); + msgTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setCorrelationId(CommonUtils.generateUniqueId("C")); + msgTrackerRepository.save(msgTrackerEntity); + } + } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java index c2242fc..434f5ef 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java @@ -10,7 +10,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; - import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -25,9 +24,8 @@ public class TxnTrackerRedisServiceImpl implements TxnTrackerRedisService { /** * Save a request in Redis cache - * - * @param cacheDTO required - * @param cacheKey required + * @param cacheDTO cache dto to save in cache + * @param cacheKey cache key for which data is stored */ @Override public void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { @@ -38,9 +36,9 @@ public void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonPr /** * Update a request in Redis cache after response * - * @param cacheKey required - * @param status required - * @param cacheDTO required + * @param cacheKey cache key for which data is stored + * @param status status to be + * @param cacheDTO cache dto to save in cache */ @Override public void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException { diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml index 40f2b74..f33aa65 100644 --- a/g2pc-dp-core-lib/src/main/resources/application.yml +++ b/g2pc-dp-core-lib/src/main/resources/application.yml @@ -1,44 +1,111 @@ -# -#spring: -# mvc: -# pathmatch: -# matching-strategy: ANT_PATH_MATCHER -# datasource: -# driverClassName: org.postgresql.Driver -# url: jdbc:postgresql://localhost:5432/gtwop?currentSchema=dp -# username: postgres -# password: root -# hikari: -# data-source-properties: -# stringtype: unspecified -# cachePrepStmts: true -# prepStmtCacheSize: 250 -# prepStmtCacheSqlLimit: 2048 -# useServerPrepStmts: true -# useLocalSessionState: true -# rewriteBatchedStatements: true -# cacheResultSetMetadata: true -# cacheServerConfiguration: true -# maintainTimeStats: false -# maximum-pool-size: 5 -# connection-timeout: 5000 -# jpa: -# properties: -# hibernate: -# jdbc: -# lob: -# non_contextual_creation: true -# dialect: org.hibernate.dialect.PostgreSQLDialect -# hibernate.ddl-auto: none -# show-sql: false -# open-in-view: false -# generate-ddl: false -# -#spring.data.redis: -# repositories.enabled: false -# host: localhost -# password: 123456789 -# port: 6376 -# -# -# + +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + +spring.data.redis: + repositories.enabled: false + host: localhost + password: 123456789 + port: 6376 + +crypto: + to_dc: + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER + from_dc: + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" + +client: + api_urls: + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + client_status_api: "http://localhost:8000/private/api/v1/registry/on-status" + +keycloak: + from_dc: + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + dp: + url: https://g2pc-dp1-lab.cdpi.dev/auth + username: admin + password: cdpi@9923 + master: + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients + clientId: admin-cli + clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN + client: + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token + realm: dp-farmer + clientId: dp-farmer-client + clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX + +sftp: + listener: + host: localhost + port: 2225 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + outbound_directory: /outbound + local: + inbound_directory: /home/prihir/g2pc/dp1/inbound + outbound_directory: /home/prihir/g2pc/dp1/outbound + + dc: + host: localhost + port: 2224 + user: cdpi + password: 1234 + remote: + outbound_directory: /outbound + + +sunbird: + api_urls: + response_data_api: http://3.109.26.38:8083/api/v1/Msg_Tracker + response_tracker_api: http://3.109.26.38:8083/api/v1/Txn_Tracker + enabled: true + elasticsearch: + host: 3.109.26.38 + port: 9200 + scheme: http \ No newline at end of file From 21e241698e5b9e2d956b6b46d4ee7cc5fd5b139d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:43:16 +0530 Subject: [PATCH 42/53] g2pc-dp-core-lib changes. --- .../dp/core/lib/constants/DpConstants.java | 6 +- .../dp/core/lib/entity/MsgTrackerEntity.java | 16 +-- .../dp/core/lib/entity/TxnTrackerEntity.java | 7 +- .../lib/repository/MsgTrackerRepository.java | 1 - .../lib/repository/TxnTrackerRepository.java | 1 - .../lib/service/RequestHandlerService.java | 10 +- .../lib/service/ResponseBuilderService.java | 25 ++++- .../core/lib/service/TxnTrackerDbService.java | 23 +++- .../lib/service/TxnTrackerRedisService.java | 1 - .../g2pc/dp/core/lib/utils/DpCommonUtils.java | 105 ++++++++++++++++++ 10 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java index 7ecbd7c..d4669c2 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java @@ -5,11 +5,9 @@ public class DpConstants { private DpConstants() { } - public static final String PENDING = "PENDING"; - - public static final String COMPLETED = "COMPLETED"; - public static final String SEARCH_REQUEST_RECEIVED = "Search request received"; public static final String RECORD_NOT_FOUND = "record_not_found"; + + public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java index afeb94d..466da4c 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; - import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @@ -68,21 +67,12 @@ public class MsgTrackerEntity { @Column(name = "locale") private String locale; - @Column(name = "rcvd_count") - private Integer rcvdCount; - - @Column(name = "pdng_count") - private Integer pdngCount; - - @Column(name = "succ_count") - private Integer succCount; - - @Column(name = "rjct_count") - private Integer rjctCount; - @Column(name = "raw_message") private String rawMessage; + @Column(name = "protocol") + private String protocol; + @Column(insertable = false, updatable = false) private Timestamp createdDate; diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java index 9c4a416..0047363 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; - import java.sql.Timestamp; @Builder @@ -60,6 +59,12 @@ public class TxnTrackerEntity { @Column(name = "no_of_records") private Integer noOfRecords; + @Column(name = "txn_type") + private String txnType; + + @Column(name = "txn_status") + private String txnStatus; + @Column(insertable = false, updatable = false) private Timestamp createdDate; diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java index 2811472..df89647 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java @@ -3,7 +3,6 @@ import g2pc.dp.core.lib.entity.MsgTrackerEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; - import java.util.Optional; @Repository diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java index d605737..5e0507c 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java @@ -4,7 +4,6 @@ import g2pc.dp.core.lib.entity.TxnTrackerEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; - import java.util.Optional; @Repository diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java index d9a1056..5942f15 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java @@ -2,15 +2,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.exceptions.G2pcValidationException; public interface RequestHandlerService { - AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey) throws Exception; + AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey, String protocol, Boolean sunbirdEnabled) throws Exception; + + + public AcknowledgementDTO buildCacheStatusRequest(String statusRequestData, String cacheKey,String protocol) throws JsonProcessingException; - public void validateRequestHeader(RequestHeaderDTO headerDTO) throws G2pcValidationException, JsonProcessingException; - public void validateRequestMessage(RequestMessageDTO messageDTO) throws G2pcValidationException, JsonProcessingException; } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java index 9f8bf27..181f289 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java @@ -1,15 +1,17 @@ package g2pc.dp.core.lib.service; import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcError; import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import g2pc.dp.core.lib.entity.TxnTrackerEntity; import kong.unirest.UnirestException; - import java.io.IOException; import java.io.InputStream; import java.text.ParseException; @@ -23,11 +25,22 @@ public interface ResponseBuilderService { String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException; - G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , InputStream fis , String encryptedSalt) throws Exception; + G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , InputStream fis , String txnType) throws Exception; public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException; public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException; -} + + StatusResponseMessageDTO buildStatusResponseMessage(StatusRequestMessageDTO statusRequestMessageDTO); + + String buildStatusResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, StatusResponseMessageDTO statusResponseMessageDTO) throws JsonProcessingException; + + G2pcError sendOnSearchResponseSftp(String responseString, InputStream fis , String txnType, SftpServerConfigDTO sftpServerConfigDTO) throws Exception; + + public G2pcError buildOnSearchScheduler(List refRecordsStringsList , CacheDTO cacheDTOO, Boolean sunbirdEnabled) throws Exception ; + + public G2pcError buildOnStatusScheduler(CacheDTO cacheDTO) throws Exception ; + + } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java index 4206f5c..e2ed67d 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java @@ -1,10 +1,11 @@ package g2pc.dp.core.lib.service; import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; import g2pc.dp.core.lib.entity.MsgTrackerEntity; import g2pc.dp.core.lib.entity.TxnTrackerEntity; @@ -13,14 +14,24 @@ public interface TxnTrackerDbService { - MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO) throws JsonProcessingException ; + MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException; int getRecordCount(Object records); List getUpdatedSearchResponseList(RequestDTO requestDTO, - List refRecordsStringsList) throws IOException; + List refRecordsStringsList, + String protocol, + Boolean sunbirdEnabled) throws IOException; DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity); SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO); + + MsgTrackerEntity saveStatusRequestDetails(StatusRequestDTO statusRequestDTO) throws JsonProcessingException; + + void updateStatusResponseDetails(ResponseMessageDTO responseMessageDTO, String transactionId); + + void updateMessageTrackerStatusDb(String transactionId); + + } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java index c90edee..55e1b2d 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.cache.CacheDTO; - import java.util.List; public interface TxnTrackerRedisService { diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java new file mode 100644 index 0000000..8debd68 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java @@ -0,0 +1,105 @@ +package g2pc.dp.core.lib.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pTokenService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class DpCommonUtils { + + @Value("${keycloak.dp.client.realm}") + private String keycloakRealm; + + @Value("${keycloak.dp.master.getClientUrl}") + private String getClientUrl; + + @Value("${crypto.to_dc.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.to_dc.support_signature}") + private boolean isSign; + + @Value("${keycloak.dp.client.url}") + private String keycloakURL; + + @Value("${keycloak.dp.client.clientId}") + private String keycloakClientId; + + @Value("${keycloak.dp.client.clientSecret}") + private String keycloakClientSecret; + + @Value("${keycloak.dp.master.url}") + private String masterUrl; + + @Value("${keycloak.dp.master.clientId}") + private String masterClientId; + + @Value("${keycloak.dp.master.clientSecret}") + private String masterClientSecret; + + @Value("${keycloak.dp.username}") + private String adminUsername; + + @Value("${keycloak.dp.password}") + private String adminPassword; + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${sftp.dc.host}") + private String sftpDcHost; + + @Value("${sftp.dc.port}") + private int sftpDcPort; + + @Value("${sftp.dc.user}") + private String sftpDcUser; + + @Value("${sftp.dc.password}") + private String sftpDcPassword; + + @Value("${sftp.dc.remote.outbound_directory}") + private String sftpDcRemoteOutboundDirectory; + + + public void handleToken() throws G2pHttpException, JsonProcessingException { + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspectUrl = keycloakURL + "/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspectUrl, token, + keycloakClientId, keycloakClientSecret); + log.info("Introspect response -> " + introspectResponse.getStatusCode()); + log.info("Introspect response body -> " + introspectResponse.getBody()); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, + g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, + adminUsername, adminPassword)) { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + } + } + + public SftpServerConfigDTO getSftpConfigForDp() { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setHost(sftpDcHost); + sftpServerConfigDTO.setPort(sftpDcPort); + sftpServerConfigDTO.setUser(sftpDcUser); + sftpServerConfigDTO.setPassword(sftpDcPassword); + sftpServerConfigDTO.setAllowUnknownKeys(true); + sftpServerConfigDTO.setStrictHostKeyChecking("no"); + sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); + return sftpServerConfigDTO; + } +} From 4d7b9794a71ee1029814bd26cf759a3df52c826b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:45:45 +0530 Subject: [PATCH 43/53] g2pc-ref-dc-client changes. --- g2pc-dp-core-lib/.gitignore | 36 -- g2pc-dp-core-lib/pom.xml | 70 --- .../lib/G2pcDpCoreLibraryApplication.java | 16 - .../dp/core/lib/constants/DpConstants.java | 13 - .../dp/core/lib/entity/MsgTrackerEntity.java | 85 --- .../dp/core/lib/entity/TxnTrackerEntity.java | 77 --- .../lib/repository/MsgTrackerRepository.java | 12 - .../lib/repository/TxnTrackerRepository.java | 13 - .../lib/service/RequestHandlerService.java | 14 - .../lib/service/ResponseBuilderService.java | 46 -- .../core/lib/service/TxnTrackerDbService.java | 37 -- .../lib/service/TxnTrackerRedisService.java | 16 - .../RequestHandlerServiceImpl.java | 90 --- .../ResponseBuilderServiceImpl.java | 536 ------------------ .../serviceimpl/TxnTrackerDbServiceImpl.java | 297 ---------- .../TxnTrackerRedisServiceImpl.java | 76 --- .../g2pc/dp/core/lib/utils/DpCommonUtils.java | 105 ---- .../src/main/resources/application.yml | 111 ---- .../G2pcDpCoreLibraryApplicationTests.java | 13 - .../g2pc-ref-dc-client/pom.xml | 16 +- .../dc/client/G2pcRefDcClientApplication.java | 40 +- .../g2pc/ref/dc/client/config/JdbcConfig.java | 35 ++ .../dc/client/config/ObjectMapperConfig.java | 2 - .../ref/dc/client/config/RegistryConfig.java | 95 +++- .../ref/dc/client/constants/Constants.java | 6 +- .../client/controller/rest/DcController.java | 258 ++++++++- .../rest/DcDashboardController.java | 106 +++- .../controller/sftp/DcSftpListener.java | 139 +++++ .../dto/dashboard/HttpsLeftPanelDataDTO.java | 17 + .../dc/client/dto/dashboard/SftpDcData.java | 19 + .../farmer_search.p12 | Bin .../private-key.pem | 0 .../{dc_to_farmer => dctofarmer}/private.crt | 0 .../{dc_to_farmer => dctofarmer}/private.csr | 0 .../public-key.pem | 0 .../mobile_search.p12 | Bin .../private-key.pem | 0 .../{dc_to_mobile => dctomobile}/private.crt | 0 .../{dc_to_mobile => dctomobile}/private.csr | 0 .../public-key.pem | 0 .../farmer_on_search.p12 | Bin .../private-key.pem | 0 .../{farmer_to_dc => farmertodc}/private.crt | 0 .../{farmer_to_dc => farmertodc}/private.csr | 0 .../public-key.pem | 0 .../mobile_on_search.p12 | Bin .../private-key.pem | 0 .../{mobile_to_dc => mobiletodc}/private.crt | 0 .../{mobile_to_dc => mobiletodc}/private.csr | 0 .../public-key.pem | 0 .../service/DcRequestBuilderService.java | 13 +- .../service/DcResponseHandlerService.java | 11 +- .../dc/client/service/DcSftpDataService.java | 4 + .../service/DcSftpPushUpdateService.java | 10 + .../client/service/DcValidationService.java | 18 +- .../DcRequestBuilderServiceImpl.java | 224 +++++--- .../DcResponseHandlerServiceImpl.java | 60 +- .../DcSftpPushUpdateServiceImpl.java | 51 ++ .../serviceimpl/DcValidationServiceImpl.java | 221 +++++--- .../ref/dc/client/utils/DcCommonUtils.java | 77 ++- .../src/main/resources/application-local.yml | 103 ++-- .../src/main/resources/application.yml | 165 ++++-- .../src/main/resources/logback-spring.xml | 9 + .../src/main/webapp/WEB-INF/jsp/dashboard.jsp | 90 --- .../webapp/WEB-INF/jsp/dashboardHttps.jsp | 357 ++++++++++++ .../main/webapp/WEB-INF/jsp/dashboardSftp.jsp | 223 ++++++++ .../webapp/WEB-INF/jsp/dashboardSftpSse.jsp | 485 ++++++++++++++++ .../G2pcRefDcClientApplicationTests.java | 50 +- 68 files changed, 2478 insertions(+), 2089 deletions(-) delete mode 100644 g2pc-dp-core-lib/.gitignore delete mode 100644 g2pc-dp-core-lib/pom.xml delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java delete mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java delete mode 100644 g2pc-dp-core-lib/src/main/resources/application.yml delete mode 100644 g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/JdbcConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/sftp/DcSftpListener.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/HttpsLeftPanelDataDTO.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/SftpDcData.java rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_farmer => dctofarmer}/farmer_search.p12 (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_farmer => dctofarmer}/private-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_farmer => dctofarmer}/private.crt (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_farmer => dctofarmer}/private.csr (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_farmer => dctofarmer}/public-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_mobile => dctomobile}/mobile_search.p12 (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_mobile => dctomobile}/private-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_mobile => dctomobile}/private.crt (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_mobile => dctomobile}/private.csr (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{dc_to_mobile => dctomobile}/public-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{farmer_to_dc => farmertodc}/farmer_on_search.p12 (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{farmer_to_dc => farmertodc}/private-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{farmer_to_dc => farmertodc}/private.crt (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{farmer_to_dc => farmertodc}/private.csr (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{farmer_to_dc => farmertodc}/public-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{mobile_to_dc => mobiletodc}/mobile_on_search.p12 (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{mobile_to_dc => mobiletodc}/private-key.pem (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{mobile_to_dc => mobiletodc}/private.crt (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{mobile_to_dc => mobiletodc}/private.csr (100%) rename g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/{mobile_to_dc => mobiletodc}/public-key.pem (100%) create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpDataService.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpPushUpdateService.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcSftpPushUpdateServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/logback-spring.xml delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardHttps.jsp create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftp.jsp create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftpSse.jsp diff --git a/g2pc-dp-core-lib/.gitignore b/g2pc-dp-core-lib/.gitignore deleted file mode 100644 index e23d88f..0000000 --- a/g2pc-dp-core-lib/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -mvnw -mvnw.cmd -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ -.idea/ - diff --git a/g2pc-dp-core-lib/pom.xml b/g2pc-dp-core-lib/pom.xml deleted file mode 100644 index 232de7e..0000000 --- a/g2pc-dp-core-lib/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.0.12 - - - g2pc.dp.core.lib - g2pc-dp-core-library - 0.0.1-SNAPSHOT - g2pc-dp-core-library - g2pc-dp-core-library - - 17 - - - - org.springframework.boot - spring-boot-starter - - - - org.projectlombok - lombok - true - - - g2pc.core.lib - g2pc-core-library - 0.0.1-SNAPSHOT - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.15.0 - - - org.postgresql - postgresql - 42.5.4 - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-test - test - - - redis.clients - jedis - 4.4.3 - jar - - - org.springframework.boot - spring-boot-starter-data-redis - - - com.networknt - json-schema-validator - 1.0.57 - - - diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java deleted file mode 100644 index 21fedb7..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java +++ /dev/null @@ -1,16 +0,0 @@ -package g2pc.dp.core.lib; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; - -@SpringBootApplication -@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib","g2pc.dp.core.lib.serviceimpl", - "g2pc.dp.core.lib.repository"}) -public class G2pcDpCoreLibraryApplication { - - public static void main(String[] args) { - SpringApplication.run(G2pcDpCoreLibraryApplication.class, args); - } - -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java deleted file mode 100644 index d4669c2..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java +++ /dev/null @@ -1,13 +0,0 @@ -package g2pc.dp.core.lib.constants; - -public class DpConstants { - - private DpConstants() { - } - - public static final String SEARCH_REQUEST_RECEIVED = "Search request received"; - - public static final String RECORD_NOT_FOUND = "record_not_found"; - - public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java deleted file mode 100644 index 466da4c..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java +++ /dev/null @@ -1,85 +0,0 @@ -package g2pc.dp.core.lib.entity; - -import jakarta.persistence.*; -import lombok.*; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; - -@Builder -@Data -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Table(name = "msg_tracker") -public class MsgTrackerEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @Column(name = "version") - private String version; - - @Column(name = "message_id") - private String messageId; - - @Column(name = "message_ts") - private String messageTs; - - @Column(name = "action") - private String action; - - @Column(name = "status") - private String status; - - @Column(name = "status_reason_code") - private String statusReasonCode; - - @Column(name = "status_reason_message") - private String statusReasonMessage; - - @Column(name = "total_count") - private Integer totalCount; - - @Column(name = "completed_count") - private Integer completedCount; - - @Column(name = "sender_id") - private String senderId; - - @Column(name = "receiver_id") - private String receiverId; - - @Column(name = "is_msg_encrypted") - private Boolean isMsgEncrypted; - - @Column(name = "meta") - private String meta; - - @Column(name = "transaction_id") - private String transactionId; - - @Column(name = "correlation_id") - private String correlationId; - - @Column(name = "locale") - private String locale; - - @Column(name = "raw_message") - private String rawMessage; - - @Column(name = "protocol") - private String protocol; - - @Column(insertable = false, updatable = false) - private Timestamp createdDate; - - @Column(insertable = false) - private Timestamp lastUpdatedDate; - - @ToString.Exclude - @OneToMany(mappedBy = "msgTrackerEntity", cascade = CascadeType.ALL) - private List txnTrackerEntityList = new ArrayList<>(); -} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java deleted file mode 100644 index 0047363..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java +++ /dev/null @@ -1,77 +0,0 @@ -package g2pc.dp.core.lib.entity; - -import jakarta.persistence.*; -import lombok.*; -import java.sql.Timestamp; - -@Builder -@Data -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Table(name = "txn_tracker") -public class TxnTrackerEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @Column(name = "reference_id") - private String referenceId; - - @Column(name = "consent") - private Boolean consent; - - @Column(name = "authorize") - private Boolean authorize; - - @Column(name = "timestamp") - private String timestamp; - - @Column(name = "status") - private String status; - - @Column(name = "status_reason_code") - private String statusReasonCode; - - @Column(name = "status_reason_message") - private String statusReasonMessage; - - @Column(name = "version") - private String version; - - @Column(name = "reg_type") - private String regType; - - @Column(name = "reg_sub_type") - private String regSubType; - - @Column(name = "query_type") - private String queryType; - - @Column(name = "query") - private String query; - - @Column(name = "reg_record_type") - private String regRecordType; - - @Column(name = "no_of_records") - private Integer noOfRecords; - - @Column(name = "txn_type") - private String txnType; - - @Column(name = "txn_status") - private String txnStatus; - - @Column(insertable = false, updatable = false) - private Timestamp createdDate; - - @Column(insertable = false) - private Timestamp lastUpdatedDate; - - @ManyToOne(targetEntity = MsgTrackerEntity.class, cascade = CascadeType.ALL) - @JoinColumn(name = "msg_tracker_id", referencedColumnName = "id") - private MsgTrackerEntity msgTrackerEntity; -} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java deleted file mode 100644 index df89647..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package g2pc.dp.core.lib.repository; - -import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import java.util.Optional; - -@Repository -public interface MsgTrackerRepository extends JpaRepository { - - Optional findByTransactionId(String transactionId); -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java deleted file mode 100644 index 5e0507c..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package g2pc.dp.core.lib.repository; - -import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import g2pc.dp.core.lib.entity.TxnTrackerEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import java.util.Optional; - -@Repository -public interface TxnTrackerRepository extends JpaRepository { - - Optional findByMsgTrackerEntity(MsgTrackerEntity msgTrackerEntity); -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java deleted file mode 100644 index 5942f15..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java +++ /dev/null @@ -1,14 +0,0 @@ -package g2pc.dp.core.lib.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.AcknowledgementDTO; - -public interface RequestHandlerService { - - AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey, String protocol, Boolean sunbirdEnabled) throws Exception; - - - public AcknowledgementDTO buildCacheStatusRequest(String statusRequestData, String cacheKey,String protocol) throws JsonProcessingException; - - -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java deleted file mode 100644 index 181f289..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java +++ /dev/null @@ -1,46 +0,0 @@ -package g2pc.dp.core.lib.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.cache.CacheDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.security.TokenExpiryDto; -import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; -import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; -import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; -import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; -import g2pc.core.lib.exceptions.G2pcError; -import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import kong.unirest.UnirestException; -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.util.List; - -public interface ResponseBuilderService { - - ResponseMessageDTO buildResponseMessage(String transactionId, List searchResponseDTOList); - - ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity); - - String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException; - - G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , InputStream fis , String txnType) throws Exception; - - public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; - - public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException; - - public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException; - - StatusResponseMessageDTO buildStatusResponseMessage(StatusRequestMessageDTO statusRequestMessageDTO); - - String buildStatusResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, StatusResponseMessageDTO statusResponseMessageDTO) throws JsonProcessingException; - - G2pcError sendOnSearchResponseSftp(String responseString, InputStream fis , String txnType, SftpServerConfigDTO sftpServerConfigDTO) throws Exception; - - public G2pcError buildOnSearchScheduler(List refRecordsStringsList , CacheDTO cacheDTOO, Boolean sunbirdEnabled) throws Exception ; - - public G2pcError buildOnStatusScheduler(CacheDTO cacheDTO) throws Exception ; - - } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java deleted file mode 100644 index e2ed67d..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java +++ /dev/null @@ -1,37 +0,0 @@ -package g2pc.dp.core.lib.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.search.message.request.RequestDTO; -import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; -import g2pc.core.lib.dto.search.message.response.DataDTO; -import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; -import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import g2pc.dp.core.lib.entity.TxnTrackerEntity; - -import java.io.IOException; -import java.util.List; - -public interface TxnTrackerDbService { - - MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException; - - int getRecordCount(Object records); - - List getUpdatedSearchResponseList(RequestDTO requestDTO, - List refRecordsStringsList, - String protocol, - Boolean sunbirdEnabled) throws IOException; - - DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity); - - SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO); - - MsgTrackerEntity saveStatusRequestDetails(StatusRequestDTO statusRequestDTO) throws JsonProcessingException; - - void updateStatusResponseDetails(ResponseMessageDTO responseMessageDTO, String transactionId); - - void updateMessageTrackerStatusDb(String transactionId); - - -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java deleted file mode 100644 index 55e1b2d..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java +++ /dev/null @@ -1,16 +0,0 @@ -package g2pc.dp.core.lib.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.cache.CacheDTO; -import java.util.List; - -public interface TxnTrackerRedisService { - - void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; - - void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException; - - List getCacheKeys(String cacheKeySearchString); - - String getRequestData(String cacheKey) throws JsonProcessingException; -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java deleted file mode 100644 index 9b34479..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -package g2pc.dp.core.lib.serviceimpl; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.cache.CacheDTO; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.search.message.request.RequestDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; -import g2pc.core.lib.enums.HeaderStatusENUM; -import g2pc.core.lib.utils.CommonUtils; -import g2pc.dp.core.lib.constants.DpConstants; -import g2pc.dp.core.lib.service.RequestHandlerService; -import g2pc.dp.core.lib.service.TxnTrackerDbService; -import g2pc.dp.core.lib.service.TxnTrackerRedisService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class RequestHandlerServiceImpl implements RequestHandlerService { - - @Autowired - private CommonUtils commonUtils; - - @Autowired - private TxnTrackerRedisService txnTrackerRedisService; - - @Autowired - private TxnTrackerDbService txnTrackerDbService; - - /** - * Build a request to save in Redis cache - * @param requestData requestData to be stored in redis cache - * @param cacheKey cacheKey for which data to be stored - * @return Acknowledgement - */ - @Override - public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - log.info("Request saved in cache with status pending"); - CacheDTO cacheDTO = new CacheDTO(); - cacheDTO.setData(requestData); - cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); - cacheDTO.setProtocol(protocol); - cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); - cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); - txnTrackerDbService.saveRequestDetails(requestDTO, protocol,sunbirdEnabled); - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); - acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); - return acknowledgementDTO; - } - - - - /** - * @param statusRequestData request data to store - * @param cacheKey cacheKey for which data storing - * @param protocol protocol to store in cache - * @return AcknowledgementDTO - * @throws JsonProcessingException jsonProcessingException might be thrown - */ - @Override - public AcknowledgementDTO buildCacheStatusRequest(String statusRequestData, String cacheKey, String protocol) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - log.info("Request saved in cache with status pending"); - CacheDTO cacheDTO = new CacheDTO(); - cacheDTO.setData(statusRequestData); - cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); - cacheDTO.setProtocol(protocol); - cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); - cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); - StatusRequestDTO requestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); - txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); - txnTrackerDbService.saveStatusRequestDetails(requestDTO); - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); - acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); - - return acknowledgementDTO; - } - -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java deleted file mode 100644 index 6a5c927..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java +++ /dev/null @@ -1,536 +0,0 @@ -package g2pc.dp.core.lib.serviceimpl; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.config.G2pUnirestHelper; -import g2pc.core.lib.constants.CoreConstants; -import g2pc.core.lib.constants.G2pSecurityConstants; -import g2pc.core.lib.constants.SftpConstants; -import g2pc.core.lib.dto.common.cache.CacheDTO; -import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.header.MetaDTO; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.security.G2pTokenResponse; -import g2pc.core.lib.dto.common.security.TokenExpiryDto; -import g2pc.core.lib.dto.search.message.request.QueryDTO; -import g2pc.core.lib.dto.search.message.request.RequestDTO; -import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; -import g2pc.core.lib.dto.search.message.response.DataDTO; -import g2pc.core.lib.dto.search.message.response.ResponseDTO; -import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; -import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; -import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; -import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; -import g2pc.core.lib.dto.status.message.response.TxnStatusResponseDTO; -import g2pc.core.lib.enums.ExceptionsENUM; -import g2pc.core.lib.enums.HeaderStatusENUM; -import g2pc.core.lib.enums.StatusTransactionTypeEnum; -import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; -import g2pc.core.lib.security.service.AsymmetricSignatureService; -import g2pc.core.lib.security.service.G2pEncryptDecrypt; -import g2pc.core.lib.security.service.G2pTokenService; -import g2pc.core.lib.service.SftpHandlerService; -import g2pc.core.lib.utils.CommonUtils; -import g2pc.dp.core.lib.constants.DpConstants; -import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import g2pc.dp.core.lib.entity.TxnTrackerEntity; -import g2pc.dp.core.lib.repository.MsgTrackerRepository; -import g2pc.dp.core.lib.repository.TxnTrackerRepository; -import g2pc.dp.core.lib.service.ResponseBuilderService; -import g2pc.dp.core.lib.service.TxnTrackerDbService; -import g2pc.dp.core.lib.service.TxnTrackerRedisService; -import g2pc.dp.core.lib.utils.DpCommonUtils; -import kong.unirest.HttpResponse; -import kong.unirest.UnirestException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.ParseException; -import java.util.*; - -@Service -@Slf4j -public class ResponseBuilderServiceImpl implements ResponseBuilderService { - - @Autowired - private RedisTemplate redisTemplate; - - @Value("${crypto.to_dc.support_encryption}") - private boolean isEncrypt; - - @Value("${crypto.to_dc.support_signature}") - private boolean isSign; - - @Value("${crypto.to_dc.password}") - private String p12Password; - - @Value("${crypto.to_dc.id}") - private String dpId; - - @Value("${crypto.to_dc.key_path}") - private String farmerKeyPath; - - @Value("${client.api_urls.client_search_api}") - String onSearchURL; - - @Value("${client.api_urls.client_status_api}") - String onStatusURL; - - @Value("${keycloak.from-dc.client-id}") - private String dcClientId; - - @Value("${keycloak.from-dc.client-secret}") - private String dcClientSecret; - - @Value("${keycloak.from-dc.url}") - private String keyClockClientTokenUrl; - - @Autowired - G2pUnirestHelper g2pUnirestHelper; - - @Autowired - G2pEncryptDecrypt encryptDecrypt; - - @Autowired - G2pTokenService g2pTokenService; - - @Autowired - AsymmetricSignatureService asymmetricSignatureService; - - @Autowired - MsgTrackerRepository msgTrackerRepository; - - @Autowired - TxnTrackerRepository txnTrackerRepository; - - @Autowired - TxnTrackerDbService txnTrackerDbService; - - @Autowired - private SftpHandlerService sftpHandlerService; - - - @Autowired - private TxnTrackerRedisService txnTrackerRedisService; - - - @Autowired - private ResourceLoader resourceLoader; - - @Autowired - private DpCommonUtils dpCommonUtils; - - /** - * Get response header - * - * @param msgTrackerEntity msgTrackerEntity used to create ResponseHeaderFto - * @return ResponseHeaderDTO - */ - @Override - public ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity) { - ResponseHeaderDTO headerDTO = new ResponseHeaderDTO(); - headerDTO.setVersion(msgTrackerEntity.getVersion()); - headerDTO.setMessageId(msgTrackerEntity.getMessageId()); - headerDTO.setMessageTs(msgTrackerEntity.getMessageTs()); - headerDTO.setAction(msgTrackerEntity.getAction()); - headerDTO.setSenderId(msgTrackerEntity.getSenderId()); - headerDTO.setReceiverId(msgTrackerEntity.getReceiverId()); - headerDTO.setIsMsgEncrypted(msgTrackerEntity.getIsMsgEncrypted()); - headerDTO.setStatus(msgTrackerEntity.getStatus()); - headerDTO.setStatusReasonCode(msgTrackerEntity.getStatusReasonCode()); - headerDTO.setStatusReasonMessage(msgTrackerEntity.getStatusReasonMessage()); - headerDTO.setTotalCount(msgTrackerEntity.getTotalCount()); - headerDTO.setCompletedCount(msgTrackerEntity.getCompletedCount()); - Map metaMap = new HashMap<>(); - MetaDTO metaDTO = new MetaDTO(); - metaDTO.setData(metaMap); - headerDTO.setMeta(metaDTO); - return headerDTO; - } - - /** - * Build a response message - * - * @param transactionId transactionId to build response message - * @param searchResponseDTOList list of searchResponseDto to store in response message - * @return ResponseMessageDTO - */ - @Override - public ResponseMessageDTO buildResponseMessage(String transactionId, List searchResponseDTOList) { - ResponseMessageDTO messageDTO = new ResponseMessageDTO(); - messageDTO.setTransactionId(transactionId); - messageDTO.setCorrelationId(CommonUtils.generateUniqueId("C")); - messageDTO.setSearchResponse(searchResponseDTOList); - return messageDTO; - } - - /** - * Build a response string - * - * @param signatureString signature to store - * @param responseHeaderDTO response header - * @param messageDTO message - * @return response String - */ - @Override - public String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, - ResponseMessageDTO messageDTO) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - ResponseDTO responseDTO = new ResponseDTO(); - responseDTO.setSignature(signatureString); - responseDTO.setHeader(responseHeaderDTO); - responseDTO.setMessage(messageDTO); - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(responseDTO); - } - - /** - * @param responseString response string to store - * @param uri endpoint for transaction - * @param clientId keycloak clientId - * @param clientSecret keycloak clientSecret - * @param keyClockClientTokenUrl keycloak ClientTokenUrl - * @param fis .p12 file input stream - * @param txnType txnType - * @return G2pcError - * @throws Exception - */ - @Override - public G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl - , InputStream fis, String txnType) throws Exception { - log.info("Send on-search response"); - log.info("Is encrypted ? -> " + isEncrypt); - log.info("Is signed ? -> " + isSign); - responseString = createSignature(isEncrypt, isSign, responseString, fis, txnType); - String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); - HttpResponse response = g2pUnirestHelper.g2pPost(uri) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + jwtToken) - .body(responseString) - .asString(); - log.info(txnType + "Response status = {}", response.getStatus()); - if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { - return new G2pcError(HttpStatus.INTERNAL_SERVER_ERROR.toString(), response.getBody()); - } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { - return new G2pcError(HttpStatus.UNAUTHORIZED.toString(), response.getBody()); - } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { - return new G2pcError(HttpStatus.BAD_REQUEST.toString(), response.getBody()); - } else if (response.getStatus() != HttpStatus.OK.value()) { - return new G2pcError("err.service.unavailable", response.getBody()); - } - return new G2pcError(HttpStatus.OK.toString(), response.getBody()); - } - - /** - * Method to store token in cache - * - * @param cacheKey cacheKey cache key for which data is storing - * @param tokenExpiryDto token expiry dto - * @throws JsonProcessingException might be thrown - */ - @Override - public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException { - ValueOperations val = redisTemplate.opsForValue(); - val.set(cacheKey, new ObjectMapper().writeValueAsString(tokenExpiryDto)); - } - - /** - * Method to get token stored in cache - * - * @param clientId client Id - * @return TokenExpiryDto tokenExpiryDto - * @throws JsonProcessingException might be thrown - */ - @Override - public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - Set redisKeys = this.redisTemplate.keys(clientId); - List cacheKeysList = new ArrayList((Collection) Objects.requireNonNull(redisKeys)); - if (!cacheKeysList.isEmpty()) { - String cacheKey = cacheKeysList.get(0); - String tokenData = (String) this.redisTemplate.opsForValue().get(cacheKey); - TokenExpiryDto tokenExpiryDto = objectMapper.readerFor(TokenExpiryDto.class).readValue(tokenData); - return tokenExpiryDto; - } - return null; - } - - /** - * The method to get validated token - * - * @param keyCloakUrl keycloak url to validate token - * @param clientId client id - * @param clientSecret client secret - * @return String validated token - * @throws IOException IOException might be thrown - * @throws ParseException ParseException might be thrown - * @throws UnirestException UnirestException might be thrown - */ - @Override - public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, ParseException, UnirestException { - TokenExpiryDto tokenExpiryDto = getTokenFromCache(clientId); - - String jwtToken = ""; - if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { - G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); - jwtToken = tokenResponse.getAccessToken(); - saveToken(clientId, g2pTokenService.createTokenExpiryDto(tokenResponse)); - } else { - jwtToken = tokenExpiryDto.getToken(); - } - return jwtToken; - } - - /** - * The method is to create signature - * - * @param isSign signature flag - * @param isEncrypt encryption flag - * @param responseString response string - * @return String signature - * @throws Exception Exception might be thrown - */ - @SuppressWarnings("unchecked") - private String createSignature(boolean isEncrypt, boolean isSign, String responseString - , InputStream fis, String txnType) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). - readValue(responseString); - byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); - ResponseHeaderDTO responseHeaderDTO = (ResponseHeaderDTO) responseDTO.getHeader(); - String responseHeaderString = objectMapper.writeValueAsString(responseHeaderDTO); - String signature = null; - String messageString = ""; - if (txnType.equals(CoreConstants.DP_STATUS_URL)) { - StatusResponseMessageDTO messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); - messageString = objectMapper.writeValueAsString(messageDTO); - } else { - ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); - messageString = objectMapper.writeValueAsString(messageDTO); - } - - if (isSign) { - if (isEncrypt) { - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedMessageString); - responseDTO.getHeader().setIsMsgEncrypted(true); - Map meta = (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN, true); - responseDTO.getHeader().getMeta().setData(meta); - responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign(responseHeaderString + encryptedMessageString, fis, p12Password); - signature = Base64.getEncoder().encodeToString(asymmetricSignature); - log.info("Encrypted message ->" + encryptedMessageString); - log.info("Hashed Signature ->" + signature); - } else { - responseDTO.getHeader().setIsMsgEncrypted(false); - Map meta = (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN, true); - responseDTO.getHeader().getMeta().setData(meta); - responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign(responseHeaderString + messageString, fis, p12Password); - signature = Base64.getEncoder().encodeToString(asymmetricSignature); - log.info("Hashed Signature ->" + signature); - } - } else { - if (isEncrypt) { - String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedMessageString); - responseDTO.getHeader().setIsMsgEncrypted(true); - Map meta = (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN, false); - responseDTO.getHeader().getMeta().setData(meta); - log.info("Encrypted message ->" + encryptedMessageString); - } else { - responseDTO.getHeader().setIsMsgEncrypted(false); - Map meta = (Map) responseDTO.getHeader().getMeta().getData(); - meta.put(CoreConstants.IS_SIGN, false); - responseDTO.getHeader().getMeta().setData(meta); - } - } - responseDTO.setSignature(signature); - responseString = objectMapper.writeValueAsString(responseDTO); - return responseString; - } - - /** - * @param statusRequestMessageDTO status request message dto - * @return StatusResponseMessageDTO - */ - @Override - public StatusResponseMessageDTO buildStatusResponseMessage(StatusRequestMessageDTO statusRequestMessageDTO) { - StatusResponseMessageDTO statusResponseMessageDTO = new StatusResponseMessageDTO(); - statusResponseMessageDTO.setTransactionId(statusRequestMessageDTO.getTransactionId()); - statusResponseMessageDTO.setCorrelationId(CommonUtils.generateUniqueId("C")); - String searchTransactionId = (String) statusRequestMessageDTO.getTxnStatusRequest().getAttributeValue(); - Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(searchTransactionId); - MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); - List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); - TxnStatusResponseDTO txnStatusResponseDTO = new TxnStatusResponseDTO(); - - if (statusRequestMessageDTO.getTxnStatusRequest().getTxnType().equals(StatusTransactionTypeEnum.SEARCH.toValue())) { - txnStatusResponseDTO.setTxnType(StatusTransactionTypeEnum.ON_SUBSCRIBE.toValue()); - ResponseMessageDTO responseMessageDTO = new ResponseMessageDTO(); - responseMessageDTO.setTransactionId(msgTrackerEntity.getTransactionId()); - responseMessageDTO.setCorrelationId(msgTrackerEntity.getCorrelationId()); - List searchResponse = new ArrayList<>(); - for (TxnTrackerEntity txnTrackerEntity : txnTrackerEntityList) { - - SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); - searchResponseDTO.setReferenceId(txnTrackerEntity.getReferenceId()); - searchResponseDTO.setTimestamp(txnTrackerEntity.getTimestamp()); - searchResponseDTO.setStatus(txnTrackerEntity.getStatus()); - searchResponseDTO.setStatusReasonCode(txnTrackerEntity.getStatusReasonCode()); - searchResponseDTO.setStatusReasonMessage(txnTrackerEntity.getStatusReasonMessage()); - DataDTO dataDTO = new DataDTO(); - dataDTO.setVersion(txnTrackerEntity.getVersion()); - dataDTO.setRegType(txnTrackerEntity.getRegType()); - dataDTO.setRegSubType(txnTrackerEntity.getRegSubType()); - dataDTO.setRegRecordType(txnTrackerEntity.getRegRecordType()); - searchResponseDTO.setData(dataDTO); - searchResponse.add(searchResponseDTO); - } - responseMessageDTO.setSearchResponse(searchResponse); - txnStatusResponseDTO.setTxnStatus(responseMessageDTO); - txnTrackerDbService.updateStatusResponseDetails(responseMessageDTO, statusResponseMessageDTO.getTransactionId()); - } - - statusResponseMessageDTO.setTxnStatusResponse(txnStatusResponseDTO); - - return statusResponseMessageDTO; - } - - /** - * @param signatureString signature to be added in request - * @param responseHeaderDTO header dto ti be added in request - * @param statusResponseMessageDTO response message dto to be added - * @return response dto - * @throws JsonProcessingException jsonProcessingException might be thrown - */ - @Override - public String buildStatusResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, StatusResponseMessageDTO statusResponseMessageDTO) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - StatusResponseDTO statusResponseDTO = new StatusResponseDTO(); - statusResponseDTO.setSignature(signatureString); - statusResponseDTO.setHeader(responseHeaderDTO); - statusResponseDTO.setMessage(statusResponseMessageDTO); - return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(statusResponseDTO); - } - - /** - * The method is to send on search response using sftp - * - * @param responseString required - * @return String - * @throws Exception jsonProcessingException might be thrown - */ - @Override - public G2pcError sendOnSearchResponseSftp(String responseString, InputStream fis, - String txnType, SftpServerConfigDTO sftpServerConfigDTO) throws Exception { - log.info("Send on-search response"); - log.info("Is encrypted ? -> " + isEncrypt); - log.info("Is signed ? -> " + isSign); - responseString = createSignature(isEncrypt, isSign, responseString, fis, txnType); - - String originalFilename = UUID.randomUUID() + ".json"; - Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), originalFilename); - Files.createFile(tempFile); - Files.write(tempFile, responseString.getBytes()); - - Boolean status = sftpHandlerService.uploadFileToSftp(sftpServerConfigDTO, tempFile.toString(), - sftpServerConfigDTO.getRemoteOutboundDirectory()); - Files.delete(tempFile); - G2pcError g2pcError; - if (Boolean.FALSE.equals(status)) { - g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), SftpConstants.UPLOAD_ERROR_MESSAGE); - log.error(SftpConstants.UPLOAD_ERROR_MESSAGE); - } else { - g2pcError = new G2pcError(HttpStatus.OK.toString(), SftpConstants.UPLOAD_SUCCESS_MESSAGE); - } - return g2pcError; - } - - @Override - public G2pcError buildOnSearchScheduler(List refRecordsStringsList, CacheDTO cacheDTO, Boolean sunbirdEnabled) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - G2pcError g2pcError = new G2pcError(); - String protocol = cacheDTO.getProtocol(); - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO, protocol, sunbirdEnabled); - List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( - requestDTO, refRecordsStringsList, protocol, sunbirdEnabled); - ResponseHeaderDTO headerDTO = getResponseHeaderDTO(msgTrackerEntity); - ResponseMessageDTO responseMessageDTO = buildResponseMessage(transactionId, searchResponseDTOList); - Map meta = (Map) headerDTO.getMeta().getData(); - meta.put(CoreConstants.DP_ID, dpId); - requestDTO.getHeader().getMeta().setData(meta); - String responseString = buildResponseString("signature", - headerDTO, responseMessageDTO); - responseString = CommonUtils.formatString(responseString); - log.info("on-search response = {}", responseString); - Resource resource = resourceLoader.getResource(farmerKeyPath); - InputStream fis = resource.getInputStream(); - - if (protocol.equals(CoreConstants.SEND_PROTOCOL_HTTPS)) { - g2pcError = sendOnSearchResponse(responseString, onSearchURL, dcClientId, - dcClientSecret, keyClockClientTokenUrl, fis, CoreConstants.SEARCH_TXN_TYPE); - } else if (protocol.equals(CoreConstants.SEND_PROTOCOL_SFTP)) { - SftpServerConfigDTO sftpServerConfigDTO = dpCommonUtils.getSftpConfigForDp(); - g2pcError = sendOnSearchResponseSftp(responseString, fis, - CoreConstants.SEARCH_TXN_TYPE, sftpServerConfigDTO); - } - if (!Objects.requireNonNull(g2pcError).getCode().equals(HttpStatus.OK.toString())) { - throw new G2pHttpException(g2pcError); - } - - - return g2pcError; - } - - @Override - public G2pcError buildOnStatusScheduler(CacheDTO cacheDTO) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); - StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveStatusRequestDetails(statusRequestDTO); - ResponseHeaderDTO responseHeaderDTO = getResponseHeaderDTO(msgTrackerEntity); - - StatusResponseMessageDTO statusResponseMessageDTO = buildStatusResponseMessage(statusRequestMessageDTO); - - Map meta = (Map) responseHeaderDTO.getMeta().getData(); - meta.put(CoreConstants.DP_ID, dpId); - statusRequestDTO.getHeader().getMeta().setData(meta); - - String statusResponseString = buildStatusResponseString("signature", responseHeaderDTO, statusResponseMessageDTO); - statusResponseString = CommonUtils.formatString(statusResponseString); - log.info("on-status response = {}", statusResponseString); - Resource resource = resourceLoader.getResource(farmerKeyPath); - InputStream fis = resource.getInputStream(); - return sendOnSearchResponse(statusResponseString, onStatusURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, CoreConstants.DP_STATUS_URL); - - } -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java deleted file mode 100644 index ac319ae..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java +++ /dev/null @@ -1,297 +0,0 @@ -package g2pc.dp.core.lib.serviceimpl; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.search.message.request.RequestDTO; -import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; -import g2pc.core.lib.dto.search.message.request.SearchRequestDTO; -import g2pc.core.lib.dto.search.message.response.DataDTO; -import g2pc.core.lib.dto.search.message.response.ResponsePaginationDTO; -import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; -import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; -import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; -import g2pc.core.lib.enums.HeaderStatusENUM; -import g2pc.core.lib.enums.LocalesENUM; -import g2pc.core.lib.enums.QueryTypeEnum; -import g2pc.core.lib.utils.CommonUtils; -import g2pc.dp.core.lib.constants.DpConstants; -import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import g2pc.dp.core.lib.entity.TxnTrackerEntity; -import g2pc.dp.core.lib.repository.MsgTrackerRepository; -import g2pc.dp.core.lib.repository.TxnTrackerRepository; -import g2pc.dp.core.lib.service.TxnTrackerDbService; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -@Service -@Slf4j -public class TxnTrackerDbServiceImpl implements TxnTrackerDbService { - - @Autowired - private MsgTrackerRepository msgTrackerRepository; - - @Autowired - private TxnTrackerRepository txnTrackerRepository; - - /** - * Save request details - * - * @param requestDTO requestDTO to save in Db - * @return request details entity - */ - @Override - public MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - - MsgTrackerEntity entity; - - HeaderDTO headerDTO = requestDTO.getHeader(); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); - if (msgTrackerEntityOptional.isEmpty()) { - MsgTrackerEntity msgTrackerEntity = new MsgTrackerEntity(); - msgTrackerEntity.setVersion(headerDTO.getVersion()); - msgTrackerEntity.setMessageId(headerDTO.getMessageId()); - msgTrackerEntity.setMessageTs(headerDTO.getMessageTs()); - msgTrackerEntity.setAction(headerDTO.getAction()); - msgTrackerEntity.setSenderId(headerDTO.getSenderId()); - msgTrackerEntity.setReceiverId(headerDTO.getReceiverId()); - msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); - msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); - msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(requestDTO)); - msgTrackerEntity.setProtocol(protocol); - - List searchRequestDTOList = messageDTO.getSearchRequest(); - for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { - TxnTrackerEntity txnTrackerEntity = new TxnTrackerEntity(); - txnTrackerEntity.setReferenceId(searchRequestDTO.getReferenceId()); - txnTrackerEntity.setTimestamp(searchRequestDTO.getTimestamp()); - txnTrackerEntity.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); - txnTrackerEntity.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); - txnTrackerEntity.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); - txnTrackerEntity.setQueryType(searchRequestDTO.getSearchCriteria().getQueryType()); - txnTrackerEntity.setQuery(objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery())); - txnTrackerEntity.setCreatedDate(new Timestamp(System.currentTimeMillis())); - txnTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); - txnTrackerEntity.setMsgTrackerEntity(msgTrackerEntity); - msgTrackerEntity.getTxnTrackerEntityList().add(txnTrackerEntity); - } - entity = msgTrackerRepository.save(msgTrackerEntity); - } else { - entity = msgTrackerEntityOptional.get(); - } - return entity; - } - - /** - * Get record count - * - * @param records required - * @return record count - */ - @SuppressWarnings("unchecked") - @Override - public int getRecordCount(Object records) { - Map objectMap = new ObjectMapper().convertValue(records, Map.class); - return objectMap.size(); - } - - /** - * Build a search response - * - * @param txnTrackerEntity txnTrackerEntity to build search response - * @param dataDTO dataDTO to build search response dto - * @return SearchResponseDTO - */ - @Override - public SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO) { - ResponsePaginationDTO paginationDTO = new ResponsePaginationDTO(); - paginationDTO.setPageSize(10); - paginationDTO.setPageNumber(1); - paginationDTO.setTotalCount(100); - - SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); - searchResponseDTO.setReferenceId(txnTrackerEntity.getReferenceId()); - searchResponseDTO.setTimestamp(txnTrackerEntity.getTimestamp()); - searchResponseDTO.setStatus(txnTrackerEntity.getStatus()); - searchResponseDTO.setStatusReasonCode(txnTrackerEntity.getStatusReasonCode()); - searchResponseDTO.setStatusReasonMessage(txnTrackerEntity.getStatusReasonMessage()); - searchResponseDTO.setData(dataDTO); - searchResponseDTO.setPagination(paginationDTO); - searchResponseDTO.setLocale(LocalesENUM.EN.toValue()); - - return searchResponseDTO; - } - - /** - * Build data - * - * @param regRecordsString regRecord to store in data dto - * @return DataDTO - */ - @Override - public DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity) { - DataDTO dataDTO = new DataDTO(); - dataDTO.setVersion(txnTrackerEntity.getVersion()); - dataDTO.setRegType(txnTrackerEntity.getRegType()); - dataDTO.setRegSubType(txnTrackerEntity.getRegSubType()); - dataDTO.setRegRecordType(txnTrackerEntity.getRegRecordType()); - if (StringUtils.isNotEmpty(regRecordsString)) { - dataDTO.setRegRecords(regRecordsString); - } else { - dataDTO.setRegRecords(null); - } - return dataDTO; - } - - /** - * Get updated search response list - * - * @param requestDTO request dto to be updated - * @param refRecordsStringsList list of records - * @return updated search response list - */ - @Override - public List getUpdatedSearchResponseList(RequestDTO requestDTO, - List refRecordsStringsList, - String protocol, - Boolean sunbirdEnabled) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - List searchResponseDTOList = new ArrayList<>(); - MsgTrackerEntity msgTrackerEntity = saveRequestDetails(requestDTO, protocol, sunbirdEnabled); - - List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); - int totalCount = txnTrackerEntityList.size(); - int completedCount = 0; - int index = 0; - for (TxnTrackerEntity txnTrackerEntity : txnTrackerEntityList) { - String refRecordsString = refRecordsStringsList.get(index); - - txnTrackerEntity.setConsent(true); - txnTrackerEntity.setAuthorize(true); - txnTrackerEntity.setRegRecordType(QueryTypeEnum.NAMEDQUERY.toValue()); - DataDTO dataDTO = buildData(refRecordsString, txnTrackerEntity); - if (refRecordsString.isEmpty()) { - txnTrackerEntity.setStatus(HeaderStatusENUM.RJCT.toValue()); - txnTrackerEntity.setStatusReasonCode(DpConstants.RECORD_NOT_FOUND); - txnTrackerEntity.setStatusReasonMessage(DpConstants.RECORD_NOT_FOUND); - txnTrackerEntity.setNoOfRecords(0); - } else { - txnTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); - txnTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); - txnTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); - txnTrackerEntity.setNoOfRecords(1); - completedCount++; - } - searchResponseDTOList.add(buildSearchResponse(txnTrackerEntity, dataDTO)); - index++; - } - msgTrackerEntity.setTxnTrackerEntityList(txnTrackerEntityList); - msgTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); - msgTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); - msgTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); - msgTrackerEntity.setTotalCount(totalCount); - msgTrackerEntity.setCompletedCount(completedCount); - msgTrackerEntity.setCorrelationId(CommonUtils.generateUniqueId("C")); - msgTrackerEntity.setLocale(LocalesENUM.EN.toValue()); - msgTrackerRepository.save(msgTrackerEntity); - return searchResponseDTOList; - } - - /** - * @param statusRequestDTO statusRequestDTO to save in db - * @return MsgTrackerEntity - * @throws JsonProcessingException jsonProcessingException might be thrown - */ - @Override - public MsgTrackerEntity saveStatusRequestDetails(StatusRequestDTO statusRequestDTO) throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); - - MsgTrackerEntity entity; - - HeaderDTO headerDTO = statusRequestDTO.getHeader(); - StatusRequestMessageDTO messageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); - if (msgTrackerEntityOptional.isEmpty()) { - MsgTrackerEntity msgTrackerEntity = new MsgTrackerEntity(); - msgTrackerEntity.setVersion(headerDTO.getVersion()); - msgTrackerEntity.setMessageId(headerDTO.getMessageId()); - msgTrackerEntity.setMessageTs(headerDTO.getMessageTs()); - msgTrackerEntity.setAction(headerDTO.getAction()); - msgTrackerEntity.setSenderId(headerDTO.getSenderId()); - msgTrackerEntity.setReceiverId(headerDTO.getReceiverId()); - msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); - msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); - msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(statusRequestDTO)); - - TxnStatusRequestDTO txnStatusRequestDTO = messageDTO.getTxnStatusRequest(); - TxnTrackerEntity txnTrackerEntity = new TxnTrackerEntity(); - txnTrackerEntity.setTimestamp(null); - txnTrackerEntity.setVersion(null); - txnTrackerEntity.setRegType(null); - txnTrackerEntity.setRegSubType(null); - txnTrackerEntity.setQueryType(null); - txnTrackerEntity.setQuery(null); - txnTrackerEntity.setTxnStatus(null); - txnTrackerEntity.setTxnType(txnStatusRequestDTO.getTxnType()); - txnTrackerEntity.setCreatedDate(new Timestamp(System.currentTimeMillis())); - txnTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); - txnTrackerEntity.setMsgTrackerEntity(msgTrackerEntity); - msgTrackerEntity.getTxnTrackerEntityList().add(txnTrackerEntity); - - entity = msgTrackerRepository.save(msgTrackerEntity); - } else { - entity = msgTrackerEntityOptional.get(); - } - return entity; - } - - /** - * @param responseMessageDTO responseMessageDTO to update in db - * @param transactionId transactionId to search in db - */ - @Override - public void updateStatusResponseDetails(ResponseMessageDTO responseMessageDTO, String transactionId) { - - Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); - MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); - Optional trackerEntityOptional = txnTrackerRepository.findByMsgTrackerEntity(msgTrackerEntity); - TxnTrackerEntity txnTrackerEntity = trackerEntityOptional.get(); - txnTrackerEntity.setTxnStatus(responseMessageDTO.toString()); - txnTrackerRepository.save(txnTrackerEntity); - } - - /** - * @param transactionId transactionId used to search data - */ - @Override - public void updateMessageTrackerStatusDb(String transactionId) { - Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); - MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); - msgTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); - msgTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); - msgTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); - msgTrackerEntity.setCorrelationId(CommonUtils.generateUniqueId("C")); - msgTrackerRepository.save(msgTrackerEntity); - } - -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java deleted file mode 100644 index 434f5ef..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -package g2pc.dp.core.lib.serviceimpl; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.cache.CacheDTO; -import g2pc.core.lib.utils.CommonUtils; -import g2pc.dp.core.lib.service.TxnTrackerRedisService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; -import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -@Service -@Slf4j -public class TxnTrackerRedisServiceImpl implements TxnTrackerRedisService { - - @Autowired - private RedisTemplate redisTemplate; - - /** - * Save a request in Redis cache - * @param cacheDTO cache dto to save in cache - * @param cacheKey cache key for which data is stored - */ - @Override - public void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { - ValueOperations val = redisTemplate.opsForValue(); - val.set(cacheKey, (new ObjectMapper()).writeValueAsString(cacheDTO)); - } - - /** - * Update a request in Redis cache after response - * - * @param cacheKey cache key for which data is stored - * @param status status to be - * @param cacheDTO cache dto to save in cache - */ - @Override - public void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException { - log.info("Updated cache status"); - - cacheDTO.setStatus(status); - cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); - ValueOperations val = redisTemplate.opsForValue(); - val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); - } - - /** - * Get all cache keys from Redis - * - * @param cacheKeySearchString required unique to DP - * @return List of cache keys - */ - @Override - public List getCacheKeys(String cacheKeySearchString) { - Set redisKeys = redisTemplate.keys(cacheKeySearchString); - - return new ArrayList<>(Objects.requireNonNull(redisKeys)); - } - - /** - * Geta a single request with its cache key - * - * @param cacheKey required unique to DP - * @return Request data - */ - @Override - public String getRequestData(String cacheKey) { - return redisTemplate.opsForValue().get(cacheKey); - } -} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java deleted file mode 100644 index 8debd68..0000000 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java +++ /dev/null @@ -1,105 +0,0 @@ -package g2pc.dp.core.lib.utils; - -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; -import g2pc.core.lib.enums.ExceptionsENUM; -import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; -import g2pc.core.lib.security.BearerTokenUtil; -import g2pc.core.lib.security.service.G2pTokenService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class DpCommonUtils { - - @Value("${keycloak.dp.client.realm}") - private String keycloakRealm; - - @Value("${keycloak.dp.master.getClientUrl}") - private String getClientUrl; - - @Value("${crypto.to_dc.support_encryption}") - private boolean isEncrypt; - - @Value("${crypto.to_dc.support_signature}") - private boolean isSign; - - @Value("${keycloak.dp.client.url}") - private String keycloakURL; - - @Value("${keycloak.dp.client.clientId}") - private String keycloakClientId; - - @Value("${keycloak.dp.client.clientSecret}") - private String keycloakClientSecret; - - @Value("${keycloak.dp.master.url}") - private String masterUrl; - - @Value("${keycloak.dp.master.clientId}") - private String masterClientId; - - @Value("${keycloak.dp.master.clientSecret}") - private String masterClientSecret; - - @Value("${keycloak.dp.username}") - private String adminUsername; - - @Value("${keycloak.dp.password}") - private String adminPassword; - - @Autowired - G2pTokenService g2pTokenService; - - @Value("${sftp.dc.host}") - private String sftpDcHost; - - @Value("${sftp.dc.port}") - private int sftpDcPort; - - @Value("${sftp.dc.user}") - private String sftpDcUser; - - @Value("${sftp.dc.password}") - private String sftpDcPassword; - - @Value("${sftp.dc.remote.outbound_directory}") - private String sftpDcRemoteOutboundDirectory; - - - public void handleToken() throws G2pHttpException, JsonProcessingException { - log.info("Is encrypted ? -> " + isEncrypt); - log.info("Is signed ? -> " + isSign); - String token = BearerTokenUtil.getBearerTokenHeader(); - String introspectUrl = keycloakURL + "/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspectUrl, token, - keycloakClientId, keycloakClientSecret); - log.info("Introspect response -> " + introspectResponse.getStatusCode()); - log.info("Introspect response body -> " + introspectResponse.getBody()); - if (introspectResponse.getStatusCode().value() == 401) { - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); - } - if (!g2pTokenService.validateToken(masterUrl, getClientUrl, - g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, - adminUsername, adminPassword)) { - throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); - } - } - - public SftpServerConfigDTO getSftpConfigForDp() { - SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); - sftpServerConfigDTO.setHost(sftpDcHost); - sftpServerConfigDTO.setPort(sftpDcPort); - sftpServerConfigDTO.setUser(sftpDcUser); - sftpServerConfigDTO.setPassword(sftpDcPassword); - sftpServerConfigDTO.setAllowUnknownKeys(true); - sftpServerConfigDTO.setStrictHostKeyChecking("no"); - sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); - return sftpServerConfigDTO; - } -} diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml deleted file mode 100644 index f33aa65..0000000 --- a/g2pc-dp-core-lib/src/main/resources/application.yml +++ /dev/null @@ -1,111 +0,0 @@ - -spring: - mvc: - pathmatch: - matching-strategy: ANT_PATH_MATCHER - datasource: - driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW - hikari: - data-source-properties: - stringtype: unspecified - cachePrepStmts: true - prepStmtCacheSize: 250 - prepStmtCacheSqlLimit: 2048 - useServerPrepStmts: true - useLocalSessionState: true - rewriteBatchedStatements: true - cacheResultSetMetadata: true - cacheServerConfiguration: true - maintainTimeStats: false - maximum-pool-size: 5 - connection-timeout: 5000 - jpa: - properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - dialect: org.hibernate.dialect.PostgreSQLDialect - hibernate.ddl-auto: none - show-sql: false - open-in-view: false - generate-ddl: false - -spring.data.redis: - repositories.enabled: false - host: localhost - password: 123456789 - port: 6376 - -crypto: - to_dc: - support_encryption: true - support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER - from_dc: - support_encryption: true - support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" - -client: - api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - client_status_api: "http://localhost:8000/private/api/v1/registry/on-status" - -keycloak: - from_dc: - url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY - dp: - url: https://g2pc-dp1-lab.cdpi.dev/auth - username: admin - password: cdpi@9923 - master: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients - clientId: admin-cli - clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN - client: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token - realm: dp-farmer - clientId: dp-farmer-client - clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX - -sftp: - listener: - host: localhost - port: 2225 - user: cdpi - password: 1234 - remote: - inbound_directory: /inbound - outbound_directory: /outbound - local: - inbound_directory: /home/prihir/g2pc/dp1/inbound - outbound_directory: /home/prihir/g2pc/dp1/outbound - - dc: - host: localhost - port: 2224 - user: cdpi - password: 1234 - remote: - outbound_directory: /outbound - - -sunbird: - api_urls: - response_data_api: http://3.109.26.38:8083/api/v1/Msg_Tracker - response_tracker_api: http://3.109.26.38:8083/api/v1/Txn_Tracker - enabled: true - elasticsearch: - host: 3.109.26.38 - port: 9200 - scheme: http \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java b/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java deleted file mode 100644 index 4178ad7..0000000 --- a/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package g2pc.dp.core.lib; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class G2pcDpCoreLibraryApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml index ae164d8..ce48ac1 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/pom.xml @@ -56,17 +56,11 @@ springdoc-openapi-ui 1.6.15 - com.networknt json-schema-validator 1.0.82 - - org.apache.commons - commons-csv - 1.10.0 - jakarta.validation jakarta.validation-api @@ -87,6 +81,16 @@ spring-boot-devtools true + + javax.servlet.jsp.jstl + javax.servlet.jsp.jstl-api + 1.2.1 + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.10.2 + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java index 4b4a8da..6fb5d2f 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/G2pcRefDcClientApplication.java @@ -1,17 +1,45 @@ package g2pc.ref.dc.client; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.ComponentScan; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.scheduling.annotation.EnableScheduling; - -@ComponentScan({"g2pc.ref.dc.client","g2pc.core.lib","g2pc.dc.core.lib","g2pc.ref.dc.client"}) -@SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) +@ComponentScan({"g2pc.ref.dc.client", "g2pc.core.lib", "g2pc.dc.core.lib", "g2pc.ref.dc.client"}) +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) +@EnableScheduling public class G2pcRefDcClientApplication { - public static void main(String[] args) { - SpringApplication.run(G2pcRefDcClientApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(G2pcRefDcClientApplication.class, args); + } + + @Value("${spring.second-datasource.url}") + private String url; + + @Value("${spring.second-datasource.username}") + private String username; + + @Value("${spring.second-datasource.password}") + private String password; + + @Value("${spring.second-datasource.driverClassName}") + private String driverClassName; + + JdbcTemplate jdbcTemplate; + @PostConstruct + public void init() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(driverClassName); + dataSource.setUrl(url); + dataSource.setUsername(username); + dataSource.setPassword(password); + jdbcTemplate = new JdbcTemplate(dataSource); + } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/JdbcConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/JdbcConfig.java new file mode 100644 index 0000000..92c5f6f --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/JdbcConfig.java @@ -0,0 +1,35 @@ +package g2pc.ref.dc.client.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; + +@Service +public class JdbcConfig{ + + @Value("${spring.second-datasource.url}") + private String url; + + @Value("${spring.second-datasource.username}") + private String username; + + @Value("${spring.second-datasource.password}") + private String password; + + @Value("${spring.second-datasource.driverClassName}") + private String driverClassName; + + public JdbcTemplate getJdbcTemplate() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName(driverClassName); + dataSource.setUrl(url); + dataSource.setUsername(username); + dataSource.setPassword(password); + return new JdbcTemplate(dataSource); + } +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java index bf3ed88..354c144 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/ObjectMapperConfig.java @@ -1,10 +1,8 @@ package g2pc.ref.dc.client.config; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.ref.dc.client.dto.mobile.response.DataMobileDTO; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java index dbd9c85..4ffc971 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java @@ -1,12 +1,12 @@ package g2pc.ref.dc.client.config; import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.constants.SftpConstants; import g2pc.core.lib.enums.SortOrderEnum; import g2pc.ref.dc.client.constants.Constants; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; - import java.util.HashMap; import java.util.Map; @@ -68,6 +68,50 @@ public class RegistryConfig { @Value("${crypto.to_dp_mobile.password}") private String mobileKeyPassword; + @Value("${sftp.dp1.host}") + private String sftpDp1Host; + + @Value("${sftp.dp1.port}") + private int sftpDp1Port; + + @Value("${sftp.dp1.user}") + private String sftpDp1User; + + @Value("${sftp.dp1.password}") + private String sftpDp1Password; + + @Value("${sftp.dp1.remote.inbound_directory}") + private String sftpDp1RemoteInboundDirectory; + + @Value("${sftp.dp2.host}") + private String sftpDp2Host; + + @Value("${sftp.dp2.port}") + private int sftpDp2Port; + + @Value("${sftp.dp2.user}") + private String sftpDp2User; + + @Value("${sftp.dp2.password}") + private String sftpDp2Password; + + @Value("${sftp.dp2.remote.inbound_directory}") + private String sftpDp2RemoteInboundDirectory; + + @Value("${crypto.sample.password}") + private String dummyKeyPassword; + + @Value("${crypto.sample.key.path}") + private String dummyKeyPath; + + @Value("${registry.api_urls.farmer_status_api}") + private String farmerStatusUrl; + + @Value("${registry.api_urls.mobile_status_api}") + private String mobileStatusUrl; + + + /** * Map to represent which query params are required for which registry * @@ -79,11 +123,13 @@ public Map getQueryParamsConfig() { Map farmerRegistryMap = new HashMap<>(); farmerRegistryMap.put("farmer_id", ""); farmerRegistryMap.put("season", ""); + farmerRegistryMap.put("status", ""); Map mobileRegistryMap = new HashMap<>(); mobileRegistryMap.put("mobile_number", ""); mobileRegistryMap.put("season", ""); + mobileRegistryMap.put("status", ""); queryParamsConfig.put(Constants.FARMER_REGISTRY, farmerRegistryMap); queryParamsConfig.put(Constants.MOBILE_REGISTRY, mobileRegistryMap); @@ -95,11 +141,11 @@ public Map getQueryParamsConfig() { * * @return Map to represent registry specific config values */ - public Map getRegistrySpecificConfig() { + public Map getRegistrySpecificConfig(String isSignEncrypt) { Map queryParamsConfig = new HashMap<>(); - Map farmerRegistryMap = getFarmerRegistryMap(); - Map mobileRegistryMap = getMobileRegistryMap(); + Map farmerRegistryMap = getFarmerRegistryMap(isSignEncrypt); + Map mobileRegistryMap = getMobileRegistryMap(isSignEncrypt); queryParamsConfig.put(Constants.FARMER_REGISTRY, farmerRegistryMap); queryParamsConfig.put(Constants.MOBILE_REGISTRY, mobileRegistryMap); @@ -111,7 +157,7 @@ public Map getRegistrySpecificConfig() { * * @return Map to represent registry specific config values for farmer */ - private Map getFarmerRegistryMap() { + private Map getFarmerRegistryMap(String isSignEncrypt) { Map farmerRegistryMap = new HashMap<>(); farmerRegistryMap.put(CoreConstants.QUERY_NAME, "paid_farmer"); farmerRegistryMap.put(CoreConstants.REG_TYPE, "ns:FARMER_REGISTRY"); @@ -126,10 +172,25 @@ private Map getFarmerRegistryMap() { farmerRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_SECRET, farmerClientSecret); farmerRegistryMap.put(CoreConstants.SUPPORT_ENCRYPTION, "" + isFarmerEncrypt); farmerRegistryMap.put(CoreConstants.SUPPORT_SIGNATURE, "" + isFarmerSign); - farmerRegistryMap.put(CoreConstants.KEY_PATH, farmerKeyPath); - farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, farmerKeyPassword); + if(isSignEncrypt.equals("2")){ + farmerRegistryMap.put(CoreConstants.KEY_PATH, dummyKeyPath); + farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, dummyKeyPassword); + } else { + farmerRegistryMap.put(CoreConstants.KEY_PATH, farmerKeyPath); + farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, farmerKeyPassword); + } + farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchURL); farmerRegistryMap.put(CoreConstants.DP_CLEAR_DB_URL, farmerClearDbURL); + farmerRegistryMap.put(SftpConstants.SFTP_HOST, sftpDp1Host); + farmerRegistryMap.put(SftpConstants.SFTP_PORT, String.valueOf(sftpDp1Port)); + farmerRegistryMap.put(SftpConstants.SFTP_USER, sftpDp1User); + farmerRegistryMap.put(SftpConstants.SFTP_PASSWORD, sftpDp1Password); + farmerRegistryMap.put(SftpConstants.SFTP_SESSION_CONFIG, "no"); + farmerRegistryMap.put(SftpConstants.SFTP_ALLOW_UNKNOWN_KEYS, String.valueOf(true)); + farmerRegistryMap.put(SftpConstants.SFTP_REMOTE_INBOUND_DIRECTORY, sftpDp1RemoteInboundDirectory); + farmerRegistryMap.put(CoreConstants.DP_STATUS_URL , farmerStatusUrl); + return farmerRegistryMap; } @@ -138,7 +199,7 @@ private Map getFarmerRegistryMap() { * * @return Map to represent registry specific config values for mobile */ - private Map getMobileRegistryMap() { + private Map getMobileRegistryMap(String isSignEncrypt) { Map mobileRegistryMap = new HashMap<>(); mobileRegistryMap.put(CoreConstants.QUERY_NAME, "mobile_registered"); mobileRegistryMap.put(CoreConstants.REG_TYPE, "ns:MOBILE_REGISTRY"); @@ -153,10 +214,24 @@ private Map getMobileRegistryMap() { mobileRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_SECRET, mobileClientSecret); mobileRegistryMap.put(CoreConstants.SUPPORT_ENCRYPTION, "" + isMobileEncrypt); mobileRegistryMap.put(CoreConstants.SUPPORT_SIGNATURE, "" + isMobileSign); - mobileRegistryMap.put(CoreConstants.KEY_PATH, mobileKeyPath); - mobileRegistryMap.put(CoreConstants.KEY_PASSWORD, mobileKeyPassword); + if(isSignEncrypt.equals("2")){ + mobileRegistryMap.put(CoreConstants.KEY_PATH, dummyKeyPath); + mobileRegistryMap.put(CoreConstants.KEY_PASSWORD, dummyKeyPassword); + } + else{ + mobileRegistryMap.put(CoreConstants.KEY_PATH, mobileKeyPath); + mobileRegistryMap.put(CoreConstants.KEY_PASSWORD, mobileKeyPassword); + } mobileRegistryMap.put(CoreConstants.DP_SEARCH_URL, mobileSearchURL); mobileRegistryMap.put(CoreConstants.DP_CLEAR_DB_URL, mobileClearDbURL); + mobileRegistryMap.put(SftpConstants.SFTP_HOST, sftpDp2Host); + mobileRegistryMap.put(SftpConstants.SFTP_PORT, String.valueOf(sftpDp2Port)); + mobileRegistryMap.put(SftpConstants.SFTP_USER, sftpDp2User); + mobileRegistryMap.put(SftpConstants.SFTP_PASSWORD, sftpDp2Password); + mobileRegistryMap.put(SftpConstants.SFTP_SESSION_CONFIG, "no"); + mobileRegistryMap.put(SftpConstants.SFTP_ALLOW_UNKNOWN_KEYS, String.valueOf(true)); + mobileRegistryMap.put(SftpConstants.SFTP_REMOTE_INBOUND_DIRECTORY, sftpDp2RemoteInboundDirectory); + mobileRegistryMap.put(CoreConstants.DP_STATUS_URL,mobileStatusUrl); return mobileRegistryMap; } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java index b286f1a..2ca7a28 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/constants/Constants.java @@ -19,11 +19,15 @@ private Constants() { public static final String ON_SEARCH_RESPONSE_RECEIVED = "On-Search response received successfully"; - //registry name constants + public static final String ON_STATUS_RESPONSE_RECEIVED = "On-Status response received successfully"; + public static final String FARMER_REGISTRY = "farmer_registry"; public static final String MOBILE_REGISTRY = "mobile_registry"; public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; + public static final String UPLOAD_ERROR = "Failed to upload file:"; + + public static final String GENERATE_REQUEST_ERROR_MESSAGE = "Exception in generateRequest : "; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java index 95eab69..6b0165d 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java @@ -8,39 +8,56 @@ import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.service.ElasticsearchService; +import g2pc.core.lib.service.SftpHandlerService; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.entity.ResponseTrackerEntity; import g2pc.dc.core.lib.repository.ResponseDataRepository; +import g2pc.dc.core.lib.repository.ResponseTrackerRepository; import g2pc.dc.core.lib.service.RequestBuilderService; +import g2pc.ref.dc.client.config.JdbcConfig; import g2pc.ref.dc.client.config.RegistryConfig; import g2pc.ref.dc.client.constants.Constants; +import g2pc.ref.dc.client.dto.dashboard.HttpsLeftPanelDataDTO; import g2pc.ref.dc.client.service.DcRequestBuilderService; import g2pc.ref.dc.client.service.DcResponseHandlerService; +import g2pc.ref.dc.client.service.DcSftpPushUpdateService; import g2pc.ref.dc.client.service.DcValidationService; import g2pc.ref.dc.client.utils.DcCommonUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import kong.unirest.HttpRequestWithBody; import kong.unirest.HttpResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.io.IOException; -import java.text.ParseException; +import javax.annotation.PostConstruct; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; @RestController @@ -49,6 +66,9 @@ @Tag(name = "Data Consumer", description = "DC APIs") public class DcController { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Autowired private DcRequestBuilderService dcRequestBuilderService; @@ -76,6 +96,43 @@ public class DcController { @Autowired private G2pUnirestHelper g2pUnirestHelper; + @Autowired + private SftpHandlerService sftpHandlerService; + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private ResponseTrackerRepository responseTrackerRepository; + + @Autowired + private DcSftpPushUpdateService dcSftpPushUpdateService; + + @Autowired + private ElasticsearchService elasticsearchService; + + @Value("${spring.second-datasource.url}") + private String url; + + @Value("${spring.second-datasource.username}") + private String username; + + @Value("${spring.second-datasource.password}") + private String password; + + @Value("${spring.second-datasource.driverClassName}") + private String driverClassName; + + @Autowired + private JdbcConfig jdbcConfig; + + private JdbcTemplate jdbcTemplate; + + @PostConstruct + public void init() { + jdbcTemplate=jdbcConfig.getJdbcTemplate(); + } + /** * Get consumer search request * @@ -89,13 +146,14 @@ public class DcController { @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/public/api/v1/consumer/search/payload") - public Map createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { + public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { log.info("Payload received from postman"); - Map acknowledgement = new HashMap<>(); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); if (ObjectUtils.isNotEmpty(payloadMap)) { - acknowledgement = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); + acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap), + CoreConstants.SEND_PROTOCOL_HTTPS, "", "", ""); } - return acknowledgement; + return acknowledgementDTO; } /** @@ -111,14 +169,23 @@ public Map createSearchRequestsFromPayload(@RequestBody Map createSearchRequestsFromCsv(@RequestPart(value = "file") MultipartFile payloadFile) throws Exception { - Map acknowledgement = new HashMap<>(); + public AcknowledgementDTO createSearchRequestsFromCsv(@RequestPart(value = "file") MultipartFile payloadFile) throws Exception { log.info("Payload received from csv file"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); if (ObjectUtils.isNotEmpty(payloadFile)) { - acknowledgement = dcRequestBuilderService.generatePayloadFromCsv(payloadFile); + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), "payload.csv"); + if (Files.exists(tempFile)) { + commonUtils.deleteFolder(tempFile); + } + Files.createFile(tempFile); + payloadFile.transferTo(tempFile.toFile()); + acknowledgementDTO = dcRequestBuilderService.generateRequest( + requestBuilderService.generatePayloadFromCsv(tempFile.toFile()), CoreConstants.SEND_PROTOCOL_HTTPS, + dcRequestBuilderService.demoTestEncryptionSignature(tempFile.toFile()), + payloadFile.getName(), ""); + Files.delete(tempFile); } - //TODO: convert returning map to acknowledgementDTO - return acknowledgement; + return acknowledgementDTO; } /** @@ -150,7 +217,7 @@ public AcknowledgementDTO handleOnSearchResponse(@RequestBody String responseStr try { dcValidationService.validateResponseDto(responseDTO); if (ObjectUtils.isNotEmpty(responseDTO)) { - acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO); + acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO, null, sunbirdEnabled); } } catch (JsonProcessingException | IllegalArgumentException e) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); @@ -198,9 +265,9 @@ public void clearDb() throws G2pHttpException, IOException { responseDataRepository.deleteAll(); log.info("DC DB cleared"); - for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig("").entrySet()) { try { - Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig("").get(configEntryMap.getKey()); String jwtToken = requestBuilderService.getValidatedToken(registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString()); @@ -212,8 +279,161 @@ public void clearDb() throws G2pHttpException, IOException { .asString(); log.info("DP " + registrySpecificConfigMap.get(CoreConstants.REG_TYPE) + " DB cleared with response " + response.getStatus()); } catch (Exception e) { - log.error("Exception in clearDb: {}", e); + log.error("Exception in clearDb: ", e); + } + } + try { + jdbcTemplate.execute("DELETE FROM \"V_Response_Tracker\""); + jdbcTemplate.execute("DELETE FROM \"V_Response_Data\""); + jdbcTemplate.execute("DELETE FROM \"V_Msg_Tracker\""); + jdbcTemplate.execute("DELETE FROM \"V_Txn_Tracker\""); + log.info("Sunbird DB data cleared"); + } catch (Exception e) { + log.error("Exception in clear Sunbird DB : ", e); + } + try { + elasticsearchService.clearData("msg_tracker"); + elasticsearchService.clearData("txn_tracker"); + elasticsearchService.clearData("response_tracker"); + elasticsearchService.clearData("response_data"); + log.info("Sunbird elastic data cleared"); + } catch (Exception e) { + log.error("Sunbird elastic data cleared: ", e); + } + Set keys = redisTemplate.keys("*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + log.info("DC Redis cache cleared"); + } + + @Operation(summary = "Receive consumer search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/consumer/status/payload") + public AcknowledgementDTO createStatusRequest(@RequestParam String transactionId, @RequestParam String transactionType) throws Exception { + log.info("Payload received for status"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(transactionId)) { + acknowledgementDTO = dcRequestBuilderService.generateStatusRequest(transactionId, transactionType, CoreConstants.SEND_PROTOCOL_HTTPS); + } + return acknowledgementDTO; + } + + /** + * Listen to registry response + * + * @param responseString required + * @return On-status response received acknowledgement + */ + @SuppressWarnings("unchecked") + @Operation(summary = "Listen to registry response") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.ON_SEARCH_RESPONSE_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/on-status") + public AcknowledgementDTO handleOnStatusResponse(@RequestBody String responseString) throws Exception { + commonUtils.handleToken(); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + StatusResponseDTO statusResponseDTO = objectMapper.readerFor(StatusResponseDTO.class). + readValue(responseString); + StatusResponseMessageDTO statusResponseMessageDTO; + Map metaData = (Map) statusResponseDTO.getHeader().getMeta().getData(); + statusResponseMessageDTO = dcValidationService.signatureValidation(metaData, statusResponseDTO); + statusResponseDTO.setMessage(statusResponseMessageDTO); + try { + dcValidationService.validateStatusResponseDTO(statusResponseDTO); + if (ObjectUtils.isNotEmpty(statusResponseDTO)) { + acknowledgementDTO = dcResponseHandlerService.getStatusResponse(statusResponseDTO); + } + } catch (JsonProcessingException | IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + return acknowledgementDTO; + } + + /** + * Listen to CSV file payload to handle using SFTP + * + * @param file required + * @return AcknowledgementDTO + */ + @Operation(summary = "Listen to CSV file payload to handle using SFTP") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping(value = "/public/api/v1/consumer/search/sftp/csv") + public AcknowledgementDTO createStatusRequestSftp(@RequestParam("file") MultipartFile file) { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + try { + if (!Objects.equals(file.getContentType(), "text/csv")) { + acknowledgementDTO.setStatus(HeaderStatusENUM.RJCT.toValue()); + acknowledgementDTO.setMessage("Invalid file type"); + return acknowledgementDTO; + } + String originalFilename = UUID.randomUUID() + ".csv"; + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), originalFilename); + Files.createFile(tempFile); + file.transferTo(tempFile.toFile()); + SftpServerConfigDTO sftpServerConfigDTO = commonUtils.getSftpConfigForDc(); + Boolean status = sftpHandlerService.uploadFileToSftp(sftpServerConfigDTO, tempFile.toString(), + sftpServerConfigDTO.getRemoteInboundDirectory()); + Files.delete(tempFile); + if (Boolean.FALSE.equals(status)) { + acknowledgementDTO.setStatus(HeaderStatusENUM.RJCT.toValue()); + acknowledgementDTO.setMessage(Constants.UPLOAD_ERROR); + return acknowledgementDTO; } + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + acknowledgementDTO.setMessage("File uploaded successfully"); + } catch (IOException e) { + log.error(Constants.UPLOAD_ERROR, e); + acknowledgementDTO.setStatus(HeaderStatusENUM.RJCT.toValue()); + acknowledgementDTO.setMessage(Constants.UPLOAD_ERROR); } + return acknowledgementDTO; + } + + @GetMapping("/dashboard/leftPanel/data") + public List fetchLeftPanelData() { + List leftPanelDataDTOList; + if (Boolean.FALSE.equals(sunbirdEnabled)) { + leftPanelDataDTOList = new ArrayList<>(); + Optional> optionalList = responseTrackerRepository.findAllByAction("search"); + if (optionalList.isEmpty()) { + return leftPanelDataDTOList; + } + List responseTrackerEntityList = optionalList.get(); + for (ResponseTrackerEntity responseTrackerEntity : responseTrackerEntityList) { + HttpsLeftPanelDataDTO leftPanelDataDTO = new HttpsLeftPanelDataDTO(); + leftPanelDataDTO.setMessageTs(responseTrackerEntity.getMessageTs()); + leftPanelDataDTO.setTransactionId(responseTrackerEntity.getTransactionId()); + leftPanelDataDTO.setStatus(responseTrackerEntity.getStatus()); + leftPanelDataDTOList.add(leftPanelDataDTO); + } + } else { + String sql = "SELECT message_ts, transaction_id, status FROM \"V_Response_Tracker\" WHERE action = 'search'"; + leftPanelDataDTOList = jdbcTemplate.query(sql, (rs, rowNum) -> { + HttpsLeftPanelDataDTO leftPanelDataDTO = new HttpsLeftPanelDataDTO(); + leftPanelDataDTO.setMessageTs(rs.getString("message_ts")); + leftPanelDataDTO.setTransactionId(rs.getString("transaction_id")); + leftPanelDataDTO.setStatus(rs.getString("status")); + return leftPanelDataDTO; + }); + } + return leftPanelDataDTOList; + } + + @GetMapping(value = "/dashboard/sftp/dc/data", produces = "text/event-stream") + public SseEmitter sseEmitterFirstPanel() { + return dcSftpPushUpdateService.register(); } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java index d63dc8c..ed43367 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java @@ -1,7 +1,5 @@ package g2pc.ref.dc.client.controller.rest; -import g2pc.core.lib.dto.common.security.G2pTokenResponse; -import g2pc.core.lib.security.service.G2pTokenService; import g2pc.dc.core.lib.service.RequestBuilderService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +15,9 @@ @Slf4j public class DcDashboardController { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Value("${dashboard.left_panel_url}") private String leftPanelUrl; @@ -26,8 +27,8 @@ public class DcDashboardController { @Value("${dashboard.bottom_panel_url}") private String bottomPanelUrl; - @Value("${dashboard.post_endpoint_url}") - private String postEndpointUrl; + @Value("${dashboard.post_https_endpoint_url}") + private String postHttpsEndpointUrl; @Value("${dashboard.clear_dc_db_endpoint_url}") private String clearDcDbEndpointUrl; @@ -41,19 +42,104 @@ public class DcDashboardController { @Value("${keycloak.dc.client.clientSecret}") private String dcClientSecret; + @Value("${dashboard.left_panel_data_endpoint_url}") + private String leftPanelDataEndpointUrl; + + @Value("${dashboard.sftp_post_endpoint_url}") + private String sftpPostEndpointUrl; + + @Value("${dashboard.sftp_dc_data_endpoint_url}") + private String sftpDcDataEndpointUrl; + + @Value("${dashboard.sftp_dp1_data_endpoint_url}") + private String sftpDp1DataEndpointUrl; + + @Value("${dashboard.sftp_dp2_data_endpoint_url}") + private String sftpDp2DataEndpointUrl; + + @Value("${dashboard.dc_status_endpoint_url}") + private String dcStatusEndpointUrl; + + @Value("${dashboard.sftp_left_panel_url}") + private String sftpLeftPanelUrl; + + @Value("${dashboard.sftp_right_panel_url}") + private String sftpRightPanelUrl; + + @Value("${dashboard.sftp_bottom_panel_url}") + private String sftpBottomPanelUrl; + + @Value("${dashboard.https_sunbird_left_panel_url}") + private String httpsSunbirdLeftPanelUrl; + + @Value("${dashboard.https_sunbird_right_panel_url}") + private String httpsSunbirdRightPanelUrl; + + @Value("${dashboard.https_sunbird_bottom_panel_url}") + private String httpsSunbirdBottomPanelUrl; + + @Value("${dashboard.sftp_sunbird_left_panel_url}") + private String sftpSunbirdLeftPanelUrl; + + @Value("${dashboard.sftp_sunbird_right_panel_url}") + private String sftpSunbirdRightPanelUrl; + + @Value("${dashboard.sftp_sunbird_bottom_panel_url}") + private String sftpSunbirdBottomPanelUrl; + @Autowired private RequestBuilderService requestBuilderService; - @GetMapping("/dashboard") + @GetMapping("/dashboard/https") public String showDashboardPage(Model model) throws IOException, ParseException { String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + if (Boolean.TRUE.equals(sunbirdEnabled)) { + model.addAttribute("left_panel_url", httpsSunbirdLeftPanelUrl); + model.addAttribute("right_panel_url", httpsSunbirdRightPanelUrl); + model.addAttribute("bottom_panel_url", httpsSunbirdBottomPanelUrl); + log.info("httpsSunbirdLeftPanelUrl: {}", httpsSunbirdLeftPanelUrl); + log.info("httpsSunbirdRightPanelUrl: {}", httpsSunbirdRightPanelUrl); + log.info("httpsSunbirdBottomPanelUrl: {}", httpsSunbirdBottomPanelUrl); + } else { + model.addAttribute("left_panel_url", leftPanelUrl); + model.addAttribute("right_panel_url", rightPanelUrl); + model.addAttribute("bottom_panel_url", bottomPanelUrl); + } + model.addAttribute("post_https_endpoint_url", postHttpsEndpointUrl); + model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); + model.addAttribute("jwtToken", jwtToken); + model.addAttribute("left_panel_data_endpoint_url", leftPanelDataEndpointUrl); + model.addAttribute("dc_status_endpoint_url", dcStatusEndpointUrl); + return "dashboardHttps"; + } + + @GetMapping("/dashboard/sftp") + public String showDashboardSftpPage(Model model) throws IOException, ParseException { + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + if (Boolean.TRUE.equals(sunbirdEnabled)) { + model.addAttribute("sftp_left_panel_url", sftpSunbirdLeftPanelUrl); + model.addAttribute("sftp_right_panel_url", sftpSunbirdRightPanelUrl); + model.addAttribute("sftp_bottom_panel_url", sftpSunbirdBottomPanelUrl); + } else { + model.addAttribute("sftp_left_panel_url", sftpLeftPanelUrl); + model.addAttribute("sftp_right_panel_url", sftpRightPanelUrl); + model.addAttribute("sftp_bottom_panel_url", sftpBottomPanelUrl); + } + model.addAttribute("sftp_post_endpoint_url", sftpPostEndpointUrl); + model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); + model.addAttribute("jwtToken", jwtToken); + return "dashboardSftp"; + } - model.addAttribute("left_panel_url", leftPanelUrl); - model.addAttribute("right_panel_url", rightPanelUrl); - model.addAttribute("bottom_panel_url", bottomPanelUrl); - model.addAttribute("post_endpoint_url", postEndpointUrl); + @GetMapping("/dashboard/sftp/sse") + public String showDashboardSftpPageSse(Model model) throws IOException, ParseException { + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + model.addAttribute("sftp_post_endpoint_url", sftpPostEndpointUrl); + model.addAttribute("sftp_dc_data_endpoint_url", sftpDcDataEndpointUrl); + model.addAttribute("sftp_dp1_data_endpoint_url", sftpDp1DataEndpointUrl); + model.addAttribute("sftp_dp2_data_endpoint_url", sftpDp2DataEndpointUrl); model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); model.addAttribute("jwtToken", jwtToken); - return "dashboard"; + return "dashboardSftpSse"; } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/sftp/DcSftpListener.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/sftp/DcSftpListener.java new file mode 100644 index 0000000..b684f6c --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/sftp/DcSftpListener.java @@ -0,0 +1,139 @@ +package g2pc.ref.dc.client.controller.sftp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.service.RequestBuilderService; +import g2pc.ref.dc.client.dto.dashboard.SftpDcData; +import g2pc.ref.dc.client.service.DcRequestBuilderService; +import g2pc.ref.dc.client.service.DcResponseHandlerService; +import g2pc.ref.dc.client.service.DcSftpPushUpdateService; +import g2pc.ref.dc.client.service.DcValidationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; + +@Configuration +@Slf4j +public class DcSftpListener { + + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpLocalDirectoryInbound; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpLocalDirectoryOutbound; + + @Autowired + private DcRequestBuilderService dcRequestBuilderService; + + @Autowired + private RequestBuilderService requestBuilderService; + + @Autowired + private DcResponseHandlerService dcResponseHandlerService; + + @Autowired + DcValidationService dcValidationService; + + @Autowired + private DcSftpPushUpdateService dcSftpPushUpdateService; + + private Queue dataQueue = new LinkedList<>(); + + /** + * Method used to handle input listener + * + * @param message + */ + @ServiceActivator(inputChannel = "sftpInbound") + public void handleMessageInbound(Message message) { + try { + File file = message.getPayload(); + log.info("Received Message from inbound directory: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file) && file.getName().contains(".csv")) { + AcknowledgementDTO acknowledgementDTO = dcRequestBuilderService.generateRequest( + requestBuilderService.generatePayloadFromCsv(file), CoreConstants.SEND_PROTOCOL_SFTP, + dcRequestBuilderService.demoTestEncryptionSignature(file), file.getName(), null); + log.info("AcknowledgementDTO: {}", acknowledgementDTO); + } + Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); + } catch (Exception e) { + log.error("Error: ", e); + } + } + + /** + * Method used to handle output listener + * + * @param message message + * @throws Exception + */ + @SuppressWarnings("unchecked") + @ServiceActivator(inputChannel = "sftpOutbound") + public void handleMessageOutbound(Message message) { + try { + File file = message.getPayload(); + log.info("Received Message from outbound directory: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file) && file.getName().contains(".json")) { + String responseString = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); + ResponseMessageDTO messageDTO; + Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); + messageDTO = dcValidationService.signatureValidation(metaData, responseDTO); + responseDTO.setMessage(messageDTO); + try { + dcValidationService.validateResponseDto(responseDTO); + if (ObjectUtils.isNotEmpty(responseDTO)) { + dcResponseHandlerService.getResponse(responseDTO, file.getName(), sunbirdEnabled); + } + log.info("on search response handled successfully"); + } catch (JsonProcessingException | IllegalArgumentException e) { + log.info("on search response handled error : ", e); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + Files.deleteIfExists(Path.of(sftpLocalDirectoryOutbound + "/" + file.getName())); + } catch (Exception e) { + log.error("Error: ", e); + } + } + + /** + * Method to handle error + * + * @param message message + */ + @ServiceActivator(inputChannel = "errorChannel") + public void handleError(Message message) { + Throwable error = (Throwable) message.getPayload(); + log.error("Handling ERROR: {}", error.getMessage()); + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/HttpsLeftPanelDataDTO.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/HttpsLeftPanelDataDTO.java new file mode 100644 index 0000000..61b8f63 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/HttpsLeftPanelDataDTO.java @@ -0,0 +1,17 @@ +package g2pc.ref.dc.client.dto.dashboard; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class HttpsLeftPanelDataDTO { + + private String messageTs; + + private String transactionId; + + private String status; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/SftpDcData.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/SftpDcData.java new file mode 100644 index 0000000..3afa907 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/dto/dashboard/SftpDcData.java @@ -0,0 +1,19 @@ +package g2pc.ref.dc.client.dto.dashboard; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SftpDcData { + + private String messageTs; + + private String transactionId; + + private String fileName; + + private String sftpDirectoryType; +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/farmer_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/farmer_search.p12 similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/farmer_search.p12 rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/farmer_search.p12 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/private-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/private-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private.crt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/private.crt similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private.crt rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/private.crt diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private.csr b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/private.csr similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private.csr rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/private.csr diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/public-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/public-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/public-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctofarmer/public-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/mobile_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/mobile_search.p12 similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/mobile_search.p12 rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/mobile_search.p12 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/private-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/private-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.crt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/private.crt similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.crt rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/private.crt diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.csr b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/private.csr similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.csr rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/private.csr diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/public-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/public-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/public-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dctomobile/public-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/farmer_on_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/farmer_on_search.p12 similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/farmer_on_search.p12 rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/farmer_on_search.p12 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/private-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/private-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.crt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/private.crt similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.crt rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/private.crt diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.csr b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/private.csr similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.csr rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/private.csr diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/public-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/public-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/public-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmertodc/public-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/mobile_on_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/mobile_on_search.p12 similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/mobile_on_search.p12 rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/mobile_on_search.p12 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/private-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/private-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private.crt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/private.crt similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private.crt rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/private.crt diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private.csr b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/private.csr similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private.csr rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/private.csr diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/public-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/public-key.pem similarity index 100% rename from g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/public-key.pem rename to g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobiletodc/public-key.pem diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java index 804581f..ba78155 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcRequestBuilderService.java @@ -1,17 +1,18 @@ package g2pc.ref.dc.client.service; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.exceptions.G2pcError; -import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; import java.util.List; import java.util.Map; public interface DcRequestBuilderService { - //TODO: use acknowledgementDTO instead of Map - Map generateRequest(List> payloadMapList) throws Exception; + AcknowledgementDTO generateRequest(List> payloadMapList, String protocol, + String isSignEncrypt, String payloadFilename, String inboundFilename) throws Exception; - //TODO: use acknowledgementDTO instead of Map - Map generatePayloadFromCsv(MultipartFile payloadFile) throws Exception; + AcknowledgementDTO generateStatusRequest(String transactionID, String transactionType, String protocol) throws Exception; + + String demoTestEncryptionSignature(File payloadFile) throws IOException; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java index 9a345b9..5f4c813 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcResponseHandlerService.java @@ -2,10 +2,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.exceptions.G2pHttpException; + +import java.io.IOException; public interface DcResponseHandlerService { - AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException; + AcknowledgementDTO getResponse(ResponseDTO responseDTO, String outboundFilename, Boolean sunbirdEnabled) throws IOException, G2pHttpException; + + AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws IOException, G2pHttpException; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpDataService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpDataService.java new file mode 100644 index 0000000..e2d5a1b --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpDataService.java @@ -0,0 +1,4 @@ +package g2pc.ref.dc.client.service; + +public interface DcSftpDataService { +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpPushUpdateService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpPushUpdateService.java new file mode 100644 index 0000000..eecbdcf --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcSftpPushUpdateService.java @@ -0,0 +1,10 @@ +package g2pc.ref.dc.client.service; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface DcSftpPushUpdateService { + + SseEmitter register(); + + void pushUpdate(Object update); +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java index b922125..35320b2 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/service/DcValidationService.java @@ -1,22 +1,22 @@ package g2pc.ref.dc.client.service; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; import org.springframework.stereotype.Service; - import java.io.IOException; import java.util.Map; - -/** - * The interface Dc validation service. - */ @Service public interface DcValidationService { public void validateResponseDto(ResponseDTO responseDTO) throws Exception; - public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException; - ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception; + + StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception; + + void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException; } + diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java index e2c6a5e..6a27d11 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java @@ -1,45 +1,50 @@ package g2pc.ref.dc.client.serviceimpl; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.constants.SftpConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.header.HeaderDTO; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.*; +import g2pc.core.lib.dto.search.message.request.SearchCriteriaDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; +import g2pc.core.lib.enums.ActionsENUM; +import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.service.ElasticsearchService; import g2pc.core.lib.utils.CommonUtils; +import g2pc.dc.core.lib.constants.DcConstants; +import g2pc.dc.core.lib.dto.ResponseTrackerDto; +import g2pc.dc.core.lib.entity.ResponseTrackerEntity; +import g2pc.dc.core.lib.repository.ResponseTrackerRepository; import g2pc.dc.core.lib.service.RequestBuilderService; import g2pc.dc.core.lib.service.TxnTrackerService; import g2pc.ref.dc.client.config.RegistryConfig; +import g2pc.ref.dc.client.constants.Constants; import g2pc.ref.dc.client.service.DcRequestBuilderService; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; -import org.apache.commons.lang3.ObjectUtils; +import org.elasticsearch.action.search.SearchResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; import org.springframework.core.io.Resource; -import java.io.InputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.Reader; +import java.io.*; import java.util.*; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; - @Service @Slf4j public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Autowired private RequestBuilderService requestBuilderService; @@ -52,6 +57,12 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { @Autowired private ResourceLoader resourceLoader; + @Autowired + ResponseTrackerRepository responseTrackerRepository; + + @Autowired + private ElasticsearchService elasticsearchService; + /** * Create, save and send a request from payload * @@ -60,103 +71,158 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { */ @SuppressWarnings("unchecked") @Override - public Map generateRequest(List> payloadMapList) throws Exception { + public AcknowledgementDTO generateRequest(List> payloadMapList, String protocol, + String isSignEncrypt, String payloadFilename, String inboundFilename) throws Exception { Map g2pcErrorMap = new HashMap<>(); - String transactionId = CommonUtils.generateUniqueId("T"); + List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); - txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig(isSignEncrypt).entrySet()) { - List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); - for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { List> queryMapFilteredList = queryMapList.stream() .map(map -> map.entrySet().stream() .filter(entry -> entry.getKey().equals(configEntryMap.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))).toList(); - Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig(isSignEncrypt).get(configEntryMap.getKey()); List searchCriteriaDTOList = new ArrayList<>(); for (Map queryParamsMap : queryMapFilteredList) { SearchCriteriaDTO searchCriteriaDTO = requestBuilderService.getSearchCriteriaDTO(queryParamsMap, registrySpecificConfigMap); searchCriteriaDTOList.add(searchCriteriaDTO); } - String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId); + String transactionId = CommonUtils.generateUniqueId("T"); + String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId, ActionsENUM.SEARCH); + String encryptedSalt = ""; + G2pcError g2pcError = new G2pcError(); + switch (isSignEncrypt) { + case "0": + break; + case "1": + encryptedSalt = "salt"; + case "2": + break; + } + try { + if (protocol.equals(CoreConstants.SEND_PROTOCOL_HTTPS)) { + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + + InputStream fis = resource.getInputStream(); + g2pcError = requestBuilderService.sendRequest(requestString, + registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), CoreConstants.SEARCH_TXN_TYPE); + g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); + log.info("DP_SEARCH_URL = {}", registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); + } else if (protocol.equals(CoreConstants.SEND_PROTOCOL_SFTP)) { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setUser(registrySpecificConfigMap.get(SftpConstants.SFTP_USER).toString()); + sftpServerConfigDTO.setHost(registrySpecificConfigMap.get(SftpConstants.SFTP_HOST).toString()); + sftpServerConfigDTO.setPort(Integer.parseInt(registrySpecificConfigMap.get(SftpConstants.SFTP_PORT).toString())); + sftpServerConfigDTO.setPassword(registrySpecificConfigMap.get(SftpConstants.SFTP_PASSWORD).toString()); + sftpServerConfigDTO.setStrictHostKeyChecking(registrySpecificConfigMap.get(SftpConstants.SFTP_SESSION_CONFIG).toString()); + sftpServerConfigDTO.setRemoteInboundDirectory(registrySpecificConfigMap.get(SftpConstants.SFTP_REMOTE_INBOUND_DIRECTORY).toString()); + + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + InputStream fis = resource.getInputStream(); + inboundFilename = UUID.randomUUID() + ".json"; + g2pcError = requestBuilderService.sendRequestSftp(requestString, + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), CoreConstants.SEARCH_TXN_TYPE, + sftpServerConfigDTO, inboundFilename); + g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); + if (g2pcError != null && g2pcError.getCode().contains("err")) { + log.info("Uploaded failed for : {}", registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + throw new Exception("Uploaded failed for : " + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } else { + log.info("Uploaded to inbound of : {}", registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } + } + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue(), protocol); + txnTrackerService.saveRequestTransaction(requestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId, protocol); + G2pcError g2pcErrorDb= txnTrackerService.saveRequestInDB(requestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), protocol, g2pcError, + payloadFilename, inboundFilename,sunbirdEnabled); + g2pcErrorMap.put(configEntryMap.getKey(),g2pcErrorDb); + } catch (Exception e) { + log.error(Constants.GENERATE_REQUEST_ERROR_MESSAGE + ": {}", e.getMessage()); + } + } + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(g2pcErrorMap); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + return acknowledgementDTO; + } + + @SuppressWarnings("unchecked") + @Override + public AcknowledgementDTO generateStatusRequest(String transactionID, String transactionType, String protocol) throws Exception { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + String statusRequestTransactionId = CommonUtils.generateUniqueId("T"); + ObjectMapper objectMapper = new ObjectMapper(); + String encryptedSalt = ""; + Map fieldValues = new HashMap<>(); + fieldValues.put("transaction_id.keyword",transactionID); + SearchResponse responseTrackerSearchResponse = elasticsearchService.exactSearch("response_tracker", fieldValues); + if (responseTrackerSearchResponse.getHits().getHits().length > 0) { + + String responseTrackerDtoString = responseTrackerSearchResponse.getHits().getHits()[0].getSourceAsString(); + ResponseTrackerDto responseTrackerDto = objectMapper.readerFor(ResponseTrackerDto.class). + readValue(responseTrackerDtoString); + String registryType = responseTrackerDto.getRegistryType().substring(3).toLowerCase(); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig("").get(registryType); + TxnStatusRequestDTO txnStatusRequestDTO = requestBuilderService.buildTransactionRequest(transactionID, transactionType); + String statusRequestString = requestBuilderService.buildStatusRequest(txnStatusRequestDTO, statusRequestTransactionId, ActionsENUM.STATUS); + G2pcError g2pcError = null; try { Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); - String encryptedSalt = ""; InputStream fis = resource.getInputStream(); - G2pcError g2pcError = requestBuilderService.sendRequest(requestString, - registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), + g2pcError = requestBuilderService.sendRequest(statusRequestString, + registrySpecificConfigMap.get(CoreConstants.DP_STATUS_URL).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), fis, encryptedSalt, - registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString()); - g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); - - txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); - txnTrackerService.saveRequestTransaction(requestString, - registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); - txnTrackerService.saveRequestInDB(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), "status"); + log.info("" + g2pcError); } catch (Exception e) { - log.error("Exception in generateRequest: {}", e); + log.error(Constants.GENERATE_REQUEST_ERROR_MESSAGE, e); } + txnTrackerService.saveInitialStatusTransaction(transactionType, statusRequestTransactionId, HeaderStatusENUM.RCVD.toValue(), protocol); + txnTrackerService.saveRequestTransaction(statusRequestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), statusRequestTransactionId, protocol); + txnTrackerService.saveRequestInStatusDB(statusRequestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + acknowledgementDTO.setMessage(g2pcError); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + } else { + G2pcError g2pcError = new G2pcError(); + g2pcError.setCode(ExceptionsENUM.ERROR_REQUEST_NOT_FOUND.toValue()); + g2pcError.setMessage("Data for transaction id " + transactionID + "is not found"); + acknowledgementDTO.setMessage(g2pcError); } - //TODO: convert returning map to acknowledgementDTO - return g2pcErrorMap; + return acknowledgementDTO; } - /** - * Create a payload for request - * - * @param payloadFile csv file containing query params data - * @return acknowledgement of the request - */ - @Override - public Map generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { - Reader reader = new BufferedReader(new InputStreamReader(payloadFile.getInputStream())); - CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); - List> payloadMapList = getPayloadMapList(csvParser); - Map acknowledgement = new HashMap<>(); - if (ObjectUtils.isNotEmpty(payloadMapList)) { - acknowledgement = generateRequest(payloadMapList); - } - //TODO: convert returning map to acknowledgementDTO - return acknowledgement; - } - private static List> getPayloadMapList(CSVParser csvParser) { + public String demoTestEncryptionSignature(File payloadFile) throws IOException { + Reader reader = null; + reader = new BufferedReader(new FileReader(payloadFile)); + CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); List csvRecordList = csvParser.getRecords(); - CSVRecord headerRecord = csvRecordList.get(0); - List headerList = new ArrayList<>(); - for (int i = 0; i < headerRecord.size(); i++) { - headerList.add(headerRecord.get(i)); - } - List> payloadMapList = new ArrayList<>(); - for (int i = 1; i < csvRecordList.size(); i++) { - CSVRecord csvRecord = csvRecordList.get(i); - Map payloadMap = new HashMap<>(); - for (int j = 0; j < headerRecord.size(); j++) { - payloadMap.put(headerList.get(j), csvRecord.get(j)); - } - payloadMapList.add(payloadMap); - } - return payloadMapList; - } + CSVRecord headerRecord = (CSVRecord) csvRecordList.get(0); + + return headerRecord.get(headerRecord.size() - 1); - private void sendRequestDemo(String requestString, String uri) { - try { - HttpResponse response = Unirest.post(uri) - .header("Content-Type", "application/json") - .body(requestString) - .asString(); - log.info("request send response status = {}", response.getStatus()); - } catch (Exception ex) { - log.error("request send error "); - } } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java index 1c53022..83a400b 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcResponseHandlerServiceImpl.java @@ -3,18 +3,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.dto.common.AcknowledgementDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO;; -import g2pc.core.lib.utils.CommonUtils; -import g2pc.dc.core.lib.service.ResponseHandlerService; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.dc.core.lib.service.TxnTrackerService; import g2pc.ref.dc.client.constants.Constants; -import g2pc.ref.dc.client.repository.RegistryTransactionsRepository; import g2pc.ref.dc.client.service.DcResponseHandlerService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import java.io.IOException; @Service @Slf4j @@ -23,15 +24,56 @@ public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { @Autowired private TxnTrackerService txnTrackerService; + /** + * @param responseDTO responseDTO + * @return AcknowledgementDTO + * @throws JsonProcessingException jsonProcessingException might be thrown + */ @Override - public AcknowledgementDTO getResponse(ResponseDTO responseDTO) throws JsonProcessingException { + public AcknowledgementDTO getResponse(ResponseDTO responseDTO, String outboundFilename, Boolean sunbirdEnabled) throws IOException, G2pHttpException { AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); ObjectMapper objectMapper = new ObjectMapper(); - txnTrackerService.updateTransactionDbAndCache(responseDTO); + G2pcError g2pcError = txnTrackerService.updateTransactionDbAndCache(responseDTO, outboundFilename, sunbirdEnabled); log.info("on-search response received from registry : {}", objectMapper.writeValueAsString(responseDTO)); - acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED); - acknowledgementDTO.setStatus(Constants.COMPLETED); + log.info("on-search database updation response from sunbird - " + g2pcError.getCode()); + if (g2pcError.getCode().equals(HttpStatus.OK.toString())) { + acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED.toString()); + acknowledgementDTO.setStatus(Constants.COMPLETED); + + } else { + acknowledgementDTO.setMessage(Constants.INVALID_RESPONSE.toString()); + acknowledgementDTO.setStatus(Constants.PENDING); + throw new G2pHttpException(g2pcError); + + } + + return acknowledgementDTO; + } + + /** + * @param statusResponseDTO statusResponseDTO + * @return AcknowledgementDTO + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws IOException, G2pHttpException { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + ObjectMapper objectMapper = new ObjectMapper(); + + G2pcError g2pcError = txnTrackerService.updateStatusTransactionDbAndCache(statusResponseDTO); + log.info("on-status response received from registry : {}", objectMapper.writeValueAsString(statusResponseDTO)); + log.info("on-status database updation response from sunbird - " + g2pcError.getCode()); + if (g2pcError.getCode().equals(HttpStatus.OK.toString())) { + acknowledgementDTO.setMessage(Constants.ON_STATUS_RESPONSE_RECEIVED.toString()); + acknowledgementDTO.setStatus(Constants.COMPLETED); + + } else { + acknowledgementDTO.setMessage(Constants.INVALID_RESPONSE.toString()); + acknowledgementDTO.setStatus(Constants.PENDING); + throw new G2pHttpException(g2pcError); + + } return acknowledgementDTO; } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcSftpPushUpdateServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcSftpPushUpdateServiceImpl.java new file mode 100644 index 0000000..d16dbe1 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcSftpPushUpdateServiceImpl.java @@ -0,0 +1,51 @@ +package g2pc.ref.dc.client.serviceimpl; + +import g2pc.core.lib.utils.CommonUtils; +import g2pc.ref.dc.client.dto.dashboard.SftpDcData; +import g2pc.ref.dc.client.service.DcSftpPushUpdateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +public class DcSftpPushUpdateServiceImpl implements DcSftpPushUpdateService { + + private final List emitters = new ArrayList<>(); + + public SseEmitter register() { + int minutes = 15; + long timeout = (long) minutes * 60000; + SseEmitter emitter = new SseEmitter(timeout); + this.emitters.add(emitter); + emitter.onCompletion(() -> this.emitters.remove(emitter)); + emitter.onTimeout(() -> this.emitters.remove(emitter)); + log.info("SSE emitter registered" + emitter); + return emitter; + } + + public void pushUpdate(Object update) { + List deadEmitters = new ArrayList<>(); + this.emitters.forEach(emitter -> { + try { + emitter.send(update); + } catch (IOException e) { + deadEmitters.add(emitter); + } + }); + this.emitters.removeAll(deadEmitters); + } + + public SftpDcData buildSftpDcData(String transactionId, String filename) { + SftpDcData sftpDcData = new SftpDcData(); + sftpDcData.setMessageTs(CommonUtils.getCurrentTimeStamp()); + sftpDcData.setTransactionId(transactionId); + sftpDcData.setFileName(filename); + sftpDcData.setSftpDirectoryType("INBOUND"); + return sftpDcData; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java index 93ed0f7..f3a3d61 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java @@ -1,25 +1,21 @@ package g2pc.ref.dc.client.serviceimpl; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.ResponseDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pcUtilityClass; import g2pc.dc.core.lib.service.ResponseHandlerService; import g2pc.ref.dc.client.constants.Constants; import g2pc.ref.dc.client.service.DcValidationService; @@ -29,7 +25,6 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; - import java.io.IOException; import java.io.InputStream; import java.security.SignatureException; @@ -46,6 +41,8 @@ public class DcValidationServiceImpl implements DcValidationService { @Autowired G2pEncryptDecrypt encryptDecrypt; + @Autowired + G2pcUtilityClass g2pcUtilityClass; @Autowired private AsymmetricSignatureService asymmetricSignatureService; @@ -96,70 +93,22 @@ public void validateResponseDto(ResponseDTO responseDTO) throws Exception { String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(responseDTO.getHeader()); - ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). - readValue(headerString); - responseHandlerService.validateResponseHeader(headerDTO); + g2pcUtilityClass.validateResponse(headerString,CoreConstants.RESPONSE_HEADER); byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); - - validateRegRecords(messageDTO); - responseHandlerService.validateResponseMessage(messageDTO); + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(messageDTO); + g2pcUtilityClass.validateResponse(messageString,CoreConstants.SEARCH_RESPONSE); } - /** - * Validate reg records. - * - * @param messageDTO the message dto - * @throws G2pcValidationException the g 2 pc validation exception - * @throws JsonProcessingException the json processing exception - */ - @Override - public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException { - - ObjectMapper objectMapper = new ObjectMapper(); - //TODO: work on commented line - List searchResponseList = messageDTO.getSearchResponse(); - - for(SearchResponseDTO searchResponseDTO : searchResponseList){ - DataDTO dataDTO = searchResponseDTO.getData(); - String regRecordString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(dataDTO.getRegRecords()); - log.info("MessageString -> " + regRecordString); - InputStream schemaStream; - if(dataDTO.getRegType().toString().equals("ns:MOBILE_REGISTRY")){ - schemaStream = DcValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/RegRecordMobileSchema.json"); - } else { - schemaStream = DcValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/RegRecordFarmerSchema.json"); - } - JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); - JsonSchema schemaRegRecord = null; - if (schemaStream != null) { - schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); - List errorCombinedMessage = new ArrayList<>(); - for (ValidationMessage error : errorMessage) { - log.info("Validation errors in Reg records" + error); - errorCombinedMessage.add(new G2pcError("", error.getMessage())); - - } - if (errorMessage.size() > 0) { - throw new G2pcValidationException(errorCombinedMessage); - } - } - - } /** * Method to validate signature and encrypted message - * @param metaData - * @param responseDTO - * @return - * @throws Exception + * @param metaData metaData + * @param responseDTO responseDTO from which signature is validated. + * @return validated ResponseMessageDTO + * @throws Exception exception might be thrown */ @Override public ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception { @@ -186,6 +135,7 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp ResponseMessageDTO messageDTO; + Boolean isMsgEncrypted = responseDTO.getHeader().getIsMsgEncrypted(); if(isSign){ if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); @@ -194,7 +144,7 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp InputStream fis = resource.getInputStream(); if(isEncrypt){ - if(!responseDTO.getHeader().getIsMsgEncrypted()){ + if(!isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); } @@ -210,7 +160,7 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp log.info("Rejecting the on-search request in signature is not valid"); throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); } - if(responseDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ String deprecatedMessageString; try{ deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); @@ -224,7 +174,7 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); } }else{ - if(responseDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); @@ -249,7 +199,7 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } if(isEncrypt){ - if(!responseDTO.getHeader().getIsMsgEncrypted()){ + if(!isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } String messageString = responseDTO.getMessage().toString(); @@ -264,7 +214,7 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp readValue(deprecatedMessageString); }else{ - if(responseDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); @@ -273,5 +223,132 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp } return messageDTO; } + + @Override + public StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception { + String p12Password =""; + boolean isEncrypt = false; + boolean isSign= false; + String keyPath=""; + if(metaData.get(CoreConstants.DP_ID).equals(farmerID)){ + p12Password = farmerp12Password; + isEncrypt = isFarmerEncrypt; + isSign = isFarmerSign; + keyPath = farmerKeyPath; + } else if(metaData.get(CoreConstants.DP_ID).equals(mobileID)){ + p12Password = mobilep12Password; + isEncrypt=isMobileEncrypt; + isSign = isMobileSign; + keyPath = mobileKeyPath; + } + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); + ObjectMapper objectMapper = new ObjectMapper(); + StatusResponseMessageDTO messageDTO; + Boolean isMsgEncrypted = statusResponseDTO.getHeader().getIsMsgEncrypted(); + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(keyPath); + InputStream fis = resource.getInputStream(); + + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String responseHeaderString = objectMapper.writeValueAsString(statusResponseDTO.getHeader()); + String responseSignature = statusResponseDTO.getSignature(); + String messageString = statusResponseDTO.getMessage().toString(); + String data = responseHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(isMsgEncrypted){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusResponseMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(statusResponseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + String responseHeaderString = objectMapper.writeValueAsString(statusResponseDTO.getHeader()); + String responseSignature = statusResponseDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = responseHeaderString+messageString; + log.info("Signature ->"+responseSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = statusResponseDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(),"Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusResponseMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(statusResponseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + } + } + return messageDTO; + } + + /** + * + * @param statusResponseDTO statusResponseDTO to validate + * @throws IOException exception might be thrown + * @throws G2pcValidationException exception might be thrown + */ + @Override + public void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] json = objectMapper.writeValueAsBytes(statusResponseDTO.getMessage()); + StatusResponseMessageDTO statusResponseMessageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusResponseMessageDTO); + g2pcUtilityClass.validateResponse(messageString,CoreConstants.STATUS_RESPONSE); + } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java index d9ba882..cc8d8a7 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.constants.SftpConstants; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; @@ -12,6 +14,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + @Service public class DcCommonUtils { @@ -31,9 +38,15 @@ public class DcCommonUtils { private String getClientUrl; @Value("${keycloak.dc.client.clientId}") - private String masterClientId; + private String dcClientId; @Value("${keycloak.dc.client.clientSecret}") + private String dcClientSecret; + + @Value("${keycloak.dc.master.clientId}") + private String masterClientId; + + @Value("${keycloak.dc.master.clientSecret}") private String masterClientSecret; @Value("${keycloak.dc.username}") @@ -42,16 +55,70 @@ public class DcCommonUtils { @Value("${keycloak.dc.password}") private String adminPassword; + @Value("${sftp.listener.host}") + private String sftpDcHost; + + @Value("${sftp.listener.port}") + private int sftpDcPort; + + @Value("${sftp.listener.user}") + private String sftpDcUser; + + @Value("${sftp.listener.password}") + private String sftpDcPassword; + + @Value("${sftp.listener.remote.inbound_directory}") + private String sftpDcRemoteInboundDirectory; + + @Value("${sftp.listener.remote.outbound_directory}") + private String sftpDcRemoteOutboundDirectory; + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpDcLocalInboundDirectory; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpDcLocalOutboundDirectory; + public void handleToken() throws G2pHttpException, JsonProcessingException { String token = BearerTokenUtil.getBearerTokenHeader(); String introspect = keycloakURL + "/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, masterClientId, masterClientSecret); + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, dcClientId, dcClientSecret); if (introspectResponse.getStatusCode().value() == 401) { throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); } - if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, adminUsername, adminPassword)) { - //TODO:check this - //throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), + masterClientId, masterClientSecret, adminUsername, adminPassword)) { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + } + } + + public SftpServerConfigDTO getSftpConfigForDc() { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setHost(sftpDcHost); + sftpServerConfigDTO.setPort(sftpDcPort); + sftpServerConfigDTO.setUser(sftpDcUser); + sftpServerConfigDTO.setPassword(sftpDcPassword); + sftpServerConfigDTO.setAllowUnknownKeys(true); + sftpServerConfigDTO.setStrictHostKeyChecking("no"); + sftpServerConfigDTO.setRemoteInboundDirectory(sftpDcRemoteInboundDirectory); + sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); + sftpServerConfigDTO.setLocalInboundDirectory(sftpDcLocalInboundDirectory); + sftpServerConfigDTO.setLocalOutboundDirectory(sftpDcLocalOutboundDirectory); + return sftpServerConfigDTO; + } + + public void deleteFolder(Path path) { + try { + if (Files.isRegularFile(path)) { + Files.delete(path); + return; + } + try (Stream paths = Files.walk(path)) { + paths.filter(p -> p.compareTo(path) != 0).forEach(p -> deleteFolder(p)); // delete all the children folders or files; + Files.delete(path); // delete the folder itself; + } + } catch (IOException ignored) { + ignored.printStackTrace(); } } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml index e7e9667..a381cda 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml @@ -9,10 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: not_set - username: not_set - password: not_set - + url: jdbc:postgresql://localhost:5432/dc1?currentSchema=g2pc + username: postgres + password: postgres hikari: data-source-properties: stringtype: unspecified @@ -57,65 +56,73 @@ spring.data.redis: password: 123456789 port: 6379 + api_urls: + #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" + #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + keycloak: from_dp: farmer: - url: not_set - clientId: not_set - clientSecret: not_set + url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" + clientId: "dp-farmer-client" + clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" mobile: - url: not_set - clientId: not_set - clientSecret: not_set + url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" + clientId: "dp-mobile-client" + clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" dc: - url: not_set - username: not_set - password: not_set + url: https://g2pc-dc-lab.cdpi.dev/auth + username: admin + password: cdpi@9922 master: - url: not_set - getClientUrl: not_set - clientId: not_set - clientSecret: not_set + url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients + clientId: admin-cli + clientSecret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs client: - url: not_set - realm: not_set - clientId: not_set - clientSecret: not_set + url: https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token + realm: data-consumer + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY crypto: to_dp_farmer: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" to_dp_mobile: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + support_encryption: true + support_signature: true + password: "mobile_search" + key_path: "classpath:mobile_search.p12" from_dp_farmer: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER from_dp_mobile: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + support_encryption: true + support_signature: true + password: "mobile_on_search" + key_path: "classpath:mobile_on_search.p12" + id: MOBILE registry: api_urls: - farmer_search_api: not_set - mobile_search_api: not_set + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + farmer_search_public_api: "https://g2pc-dp1-lab.cdpi.dev/dp-farmer/public/api/v1/registry/search" + mobile_search_public_api: "https://g2pc-dp2-lab.cdpi.dev/dp-mno/public/api/v1/registry/search" dashboard: - left_panel_url: not_set - right_panel_url: not_set - bottom_panel_url: not_set - post_endpoint_url: not_set - clear_dc_db_endpoint_url: not_set - clear_dp1_db_endpoint_url: not_set - clear_dp2_db_endpoint_url: not_set \ No newline at end of file + left_panel_url: "http://3.109.26.38:3005/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + right_panel_url: "http://3.109.26.38:3005/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + bottom_panel_url: "http://3.109.26.38:3005/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + post_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" + clear_dc_db_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" + clear_dp_db_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" + post_sftp_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/sftp/csv" diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml index e7e9667..4e9b2a3 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -9,10 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: not_set - username: not_set - password: not_set - + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW hikari: data-source-properties: stringtype: unspecified @@ -27,6 +26,13 @@ spring: maintainTimeStats: false maximum-pool-size: 5 connection-timeout: 5000 + + second-datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/registry?currentSchema=public + username: postgres + password: K6tnrCU0wqXOwPW + jpa: properties: hibernate: @@ -53,69 +59,128 @@ server: spring.data.redis: repositories.enabled: false - host: localhost - password: 123456789 + host: 3.109.26.38 + password: cdpi@99221 port: 6379 keycloak: from_dp: farmer: - url: not_set - clientId: not_set - clientSecret: not_set + url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" + clientId: "dp-farmer-client" + clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" mobile: - url: not_set - clientId: not_set - clientSecret: not_set + url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" + clientId: "dp-mobile-client" + clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" dc: - url: not_set - username: not_set - password: not_set + url: https://g2pc-dc-lab.cdpi.dev/auth + username: admin + password: cdpi@9922 master: - url: not_set - getClientUrl: not_set - clientId: not_set - clientSecret: not_set + url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients + clientId: admin-cli + clientSecret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs client: - url: not_set - realm: not_set - clientId: not_set - clientSecret: not_set + url: https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token + realm: data-consumer + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY crypto: to_dp_farmer: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" to_dp_mobile: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + support_encryption: true + support_signature: true + password: "mobile_search" + key_path: "classpath:mobile_search.p12" from_dp_farmer: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER from_dp_mobile: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + support_encryption: true + support_signature: true + password: "mobile_on_search" + key_path: "classpath:mobile_on_search.p12" + id: MOBILE + sample: + password: "test" + key.path: "classpath:1693731657.p12" registry: api_urls: - farmer_search_api: not_set - mobile_search_api: not_set + farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" + mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + farmer_status_api: "http://localhost:9001/private/api/v1/registry/txn/status" + mobile_status_api: "http://localhost:9002/private/api/v1/registry/txn/status" dashboard: - left_panel_url: not_set - right_panel_url: not_set - bottom_panel_url: not_set - post_endpoint_url: not_set - clear_dc_db_endpoint_url: not_set - clear_dp1_db_endpoint_url: not_set - clear_dp2_db_endpoint_url: not_set \ No newline at end of file + left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + post_https_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/csv" + clear_dc_db_endpoint_url: "http://localhost:8000/private/api/v1/registry/clear-db" + clear_dp1_db_endpoint_url: "http://localhost:9001/private/api/v1/registry/clear-db" + clear_dp2_db_endpoint_url: "http://localhost:9002/private/api/v1/registry/clear-db" + left_panel_data_endpoint_url: "http://localhost:8000/dashboard/leftPanel/data" + sftp_post_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/sftp/csv" + sftp_dc_data_endpoint_url: "http://localhost:8000/dashboard/sftp/dc/data" + sftp_dp1_data_endpoint_url: "http://localhost:9001/dashboard/sftp/dp1/data" + sftp_dp2_data_endpoint_url: "http://localhost:9002/dashboard/sftp/dp2/data" + dc_status_endpoint_url: "http://localhost:8000/private/api/v1/consumer/status/payload?transactionType=search&transactionId=" + sftp_left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/aa62b4d5-f0c6-4c5d-97eb-753343c89a32/sftp-left-panel-data?orgId=1&refresh=5s&from=1705573550702&to=1705595150703&panelId=1" + sftp_right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/c319354b-d0a9-4541-ae9f-d052e31fa275/sftp-right-panel-data?orgId=1&refresh=5s&from=1705574488336&to=1705596088337&panelId=1" + sftp_bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/c63fe588-c69c-4918-bb96-97fba722afc8/sftp-bottom-panel-data?orgId=1&refresh=5s&from=1705574366440&to=1705595966440&panelId=1" + https_sunbird_left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/d5398a4d-e778-408b-b26a-6717d7d119e6/https-sunbird-left-panel_data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + https_sunbird_right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/dde256ef-413c-420b-8e95-752b164bc533/https-sunbird-right-panel_data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + https_sunbird_bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/e5fa2e19-8d97-40e5-b971-566b5eac5c87/https-sunbird-bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + sftp_sunbird_left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/e5450e12-17d8-43b3-bd8f-1b0fc12e0880/sftp-sunbird-left-panel-data?orgId=1&refresh=5s&from=1705573550702&to=1705595150703&panelId=1" + sftp_sunbird_right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/bce49bdf-a4a2-45fc-8c1e-9eb9f00a56e6/sftp-sunbird-right-panel-data?orgId=1&refresh=5s&from=1705574488336&to=1705596088337&panelId=1" + sftp_sunbird_bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/e6424a1e-8c7c-4a3d-9ae9-cb93f4fb8eb3/sftp-sunbird-bottom-panel-data?orgId=1&refresh=5s&from=1705574366440&to=1705595966440&panelId=1" + +sftp: + listener: + host: localhost + port: 2224 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + outbound_directory: /outbound + local: + inbound_directory: /home/prihir/g2pc/dc/inbound + outbound_directory: /home/prihir/g2pc/dc/outbound + + dp1: + host: localhost + port: 2225 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + + dp2: + host: localhost + port: 2226 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + +sunbird: + api_urls: + response_data_api: http://3.109.26.38:8083/api/v1/Response_Data + response_tracker_api: http://3.109.26.38:8083/api/v1/Response_Tracker + enabled: true + elasticsearch: + host: 3.109.26.38 + port: 9200 + scheme: http \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/logback-spring.xml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..ab33f92 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/logback-spring.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp deleted file mode 100644 index d8a16e9..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp +++ /dev/null @@ -1,90 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> - - - - - Dashboard - - - -

-
-
- - - - - -
-
- -
-
-
- -
-
-
- -
- - - \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardHttps.jsp b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardHttps.jsp new file mode 100644 index 0000000..dc17dff --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardHttps.jsp @@ -0,0 +1,357 @@ +<%@ page contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> + + + + + + + + Dashboard-HTTPS + + + +
+
+
+ + + + + + +
+
+ + + + + + + + + + + +
Data Consumer - Request Tracker +
message_tstransaction_idstatus
+
+
+
+ +
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftp.jsp b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftp.jsp new file mode 100644 index 0000000..1bf39d0 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftp.jsp @@ -0,0 +1,223 @@ +<%@ page contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> + + + + + + Dashboard-SFTP + + + +
+
+
+ + + + + + +
+
+ +
+
+
+ +
+
+
+ +
+ + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftpSse.jsp b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftpSse.jsp new file mode 100644 index 0000000..d0e9fda --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboardSftpSse.jsp @@ -0,0 +1,485 @@ +<%@ page contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> + + + + + + Dashboard-SFTP + + + +
+
+
+ + + + + + +
+
+ + + + + + + + + + + +
Data Consumer - Inbound +
message_tstransaction_idfile_name
+
+
+
+
+ + + + + + + + + + + + +
Data Producer - Inbound +
dp_namemessage_tstransaction_idfile_name
+
+
+
+
+
+ + + + + + + + + + + +
Data Consumer - Outbound +
message_tstransaction_idfile_name
+
+
+ + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java index b8bddca..27dc43f 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/test/java/g2pc/ref/dc/client/G2pcRefDcClientApplicationTests.java @@ -1,16 +1,64 @@ package g2pc.ref.dc.client; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.dc.core.lib.dto.ResponseDataDto; +import g2pc.dc.core.lib.dto.ResponseTrackerDto; +import kong.unirest.HttpResponse; +import kong.unirest.JsonNode; +import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; - @SpringBootTest @Slf4j class G2pcRefDcClientApplicationTests { + @Autowired + G2pUnirestHelper g2pUnirestHelper; + @Test void contextLoads() { } + + @Test + void saveSunbirdResponseEntity() throws JsonProcessingException { + String response_tracker_uri = "http://localhost:8081/api/v1/Response_Tracker"; + ObjectMapper objectMapper = new ObjectMapper(); + ResponseTrackerDto responseTrackerDto = new ResponseTrackerDto(); + responseTrackerDto.setVersion("version"); + responseTrackerDto.setMessageId("messageID"); + responseTrackerDto.setMessageTs("messageTs"); + responseTrackerDto.setAction("action"); + responseTrackerDto.setSenderId("senderId"); + responseTrackerDto.setReceiverId("receiverID"); + responseTrackerDto.setIsMsgEncrypted(true); + responseTrackerDto.setTransactionId("TransactionID"); + responseTrackerDto.setRegistryType("regType"); + responseTrackerDto.setProtocol("protocol"); + responseTrackerDto.setPayloadFilename("payloadFilename"); + responseTrackerDto.setInboundFilename("inboundFilename"); + ResponseDataDto responseDataDto = new ResponseDataDto(); + responseDataDto.setReferenceId("searchRequestDTO.getReferenceId()"); + responseDataDto.setTimestamp("searchRequestDTO.getTimestamp()"); + responseDataDto.setVersion("version"); + responseDataDto.setRegType("regtpe"); + responseDataDto.setRegSubType("regsubtype"); + responseDataDto.setStatus("status"); + responseDataDto.setStatusReasonCode("g2pcerrocode"); + String responseTrackerString = objectMapper.writeValueAsString(responseTrackerDto); + HttpResponse response = Unirest.post(response_tracker_uri) + .header("Content-Type", "application/json") + .body(responseTrackerString) + .asJson();; + + + log.info(response+""); + + + } } From 8866212ab86d9934355728d601fd1e2eb47aafeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:45:49 +0530 Subject: [PATCH 44/53] g2pc-ref-dc-client changes. --- g2pc-dp-core-lib/README.md | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 g2pc-dp-core-lib/README.md diff --git a/g2pc-dp-core-lib/README.md b/g2pc-dp-core-lib/README.md deleted file mode 100644 index e6b8cdb..0000000 --- a/g2pc-dp-core-lib/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# G2pc DP Core Lib - -## Overview -### Json Schema validations -* In this project Json schema input stream return by parent g2p-core lib. -* In RequestHandlerServiceImpl class input stream is called and will validate the Request DTO header and message -* If any thing doesn't match with the json schema exception handling is written for same. -* Below are some reference code for same. - -```` - InputStream schemaStream = commonUtils.getRequestMessageString(); - JsonNode jsonNodeMessage = objectMapper.readTree(messageString); - JsonSchema schemaMessage = null; - if(schemaStream !=null){ - schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaMessage.validate(jsonNodeMessage); - List errorcombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorcombinedMessage.add(new G2pcError("",error.getMessage())); - - } -```` \ No newline at end of file From 58205af2052bf08294fd6f7bba2a159c1e3b4ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:46:43 +0530 Subject: [PATCH 45/53] g2pc-ref-farmer-regsvc changes. --- .../docker-compose.yaml | 64 ------ .../controller/rest/RegistryController.java | 118 +++++++---- .../regsvc/entity/FarmerInfoEntity.java | 2 - .../farmer/regsvc/scheduler/Scheduler.java | 118 +++-------- .../FarmerResponseBuilderServiceImpl.java | 55 ++++- .../FarmerValidationServiceImpl.java | 195 +++++++++++++----- .../farmer/regsvc/utils/DpCommonUtils.java | 77 ------- .../src/main/resources/application-local.yml | 66 +++--- .../src/main/resources/application.yml | 101 ++++++--- .../G2pcRefFarmerRegsvcApplicationTests.java | 62 ++++-- 10 files changed, 445 insertions(+), 413 deletions(-) delete mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/docker-compose.yaml delete mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/utils/DpCommonUtils.java diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/docker-compose.yaml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/docker-compose.yaml deleted file mode 100644 index 82f9d3f..0000000 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/docker-compose.yaml +++ /dev/null @@ -1,64 +0,0 @@ -version: "3.6" -services: - - # Keycloak Database - - keycloaktestdb: - image: postgres:14 - container_name: keycloaktestdb - ports: - - 5430:5432 - volumes: - - keycloak_data:/var/lib/postgresql/data - #- ./sql/init-keycloak.sql:/docker-entrypoint-initdb.d/init-keycloak.sql - environment: - POSTGRES_PASSWORD: p0sTgReSpw4ventivlocal - - # Keycloak - - testventivkeycloak: - image: quay.io/keycloak/keycloak:21.0.2 - container_name: testkeycloak - command: - [ - "start-dev", - #"-Djboss.http.port=8081", - "--http-port=8081", - #"--hostname-port=8081", - #"--hostname=ventiv-dev.ventiv", - #"--http-enabled=false", - #"--hostname-strict-https=true", - "--http-relative-path=/auth", - ] - ports: - - "8081:8081" - depends_on: - - keycloaktestdb - - environment: - # KC admin - - KEYCLOAK_ADMIN=admin - - KEYCLOAK_ADMIN_PASSWORD=kEyCl0aKpw4ventivlocal - - SECRET_KEY=edc8525ee8f84d4abc62b212121df09367c7de878e62cc1e7e75f9976931aaa0 - - BACKEND_BASE_URL=https://44f0-206-84-230-185.ngrok-free.app/api - # KC DB details - - KEYCLOAK_KERBEROS_ENABLE=false - - KC_DB=postgres - - KC_DB_URL=jdbc:postgresql://keycloaktestdb/keycloak - #- KC_DB_URL_HOST=keycloakdb - - KC_DB_USERNAME=postgres - - KC_DB_PASSWORD=p0sTgReSpw4ventivlocal - - # -KC - - KC_HOSTNAME_URL=http://localhost:8081/auth/ - #- KC_SPI_HOSTNAME_DEFAULT_ADMIN=http://localhost:8081/auth/admin - - KC_HOSTNAME_ADMIN_URL=http://localhost:8081/auth/ - - - KC_HOSTNAME_STRICT=true - - KC_HOSTNAME_STRICT_HTTPS=true - - KC_EDGE=proxy - - - PROXY_ADDRESS_FORWARDING=true - - KEYCLOAK_FRONTEND_URL=http://localhost:8081/auth/ -volumes: - keycloak_data: diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java index 6c2e303..3aff875 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/RegistryController.java @@ -2,27 +2,28 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; -import g2pc.core.lib.security.BearerTokenUtil; import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; import g2pc.core.lib.security.service.G2pTokenService; import g2pc.dp.core.lib.repository.MsgTrackerRepository; import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.utils.DpCommonUtils; import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.service.DpSftpPushUpdateService; import g2pc.ref.farmer.regsvc.service.FarmerValidationService; -import g2pc.ref.farmer.regsvc.utils.DpCommonUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -30,14 +31,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; -import java.text.ParseException; import java.util.Map; +import java.util.Set; /** * The type Registry controller. @@ -48,12 +50,12 @@ @Tag(name = "Provider", description = "Provider APIs") public class RegistryController { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Autowired private RequestHandlerService requestHandlerService; - /** - * The Farmer validation service. - */ @Autowired FarmerValidationService farmerValidationService; @@ -72,30 +74,14 @@ public class RegistryController { @Autowired private MsgTrackerRepository msgTrackerRepository; - /** - * Get search request from DC - * - * @param requestDTO required - * @return Search request received acknowledgement - */ - @Operation(summary = "Receive search request") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), - @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), - @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), - @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) - @PostMapping("/public/api/v1/registry/search") - public AcknowledgementDTO demoSearch(@RequestBody RequestDTO requestDTO) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + @Autowired + private RedisTemplate redisTemplate; - String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + @Autowired + private DpSftpPushUpdateService dpSftpPushUpdateService; - return requestHandlerService.buildCacheRequest(objectMapper.writeValueAsString(requestDTO), cacheKey); - } + @Value("${dashboard.cors_origin_url}") + private String corsOriginUrl; /** * Get search request from DC @@ -114,7 +100,7 @@ public AcknowledgementDTO demoSearch(@RequestBody RequestDTO requestDTO) throws @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/search") - public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { + public AcknowledgementDTO handleRequest(@RequestBody String requestString) throws Exception { dpCommonUtils.handleToken(); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -132,7 +118,8 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody String reque try { farmerValidationService.validateRequestDTO(requestDTO); return requestHandlerService.buildCacheRequest( - objectMapper.writeValueAsString(requestDTO), cacheKey); + objectMapper.writeValueAsString(requestDTO), cacheKey, + CoreConstants.SEND_PROTOCOL_HTTPS, sunbirdEnabled); } catch (G2pcValidationException e) { throw new G2pcValidationException(e.getG2PcErrorList()); } catch (JsonProcessingException e) { @@ -173,10 +160,71 @@ public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) { /** * Clear message tracker DB + * Clear Redis cache */ @GetMapping("/private/api/v1/registry/clear-db") public void clearDb() throws G2pHttpException, IOException { dpCommonUtils.handleToken(); msgTrackerRepository.deleteAll(); + log.info("DP-1 DB cleared"); + Set keys = redisTemplate.keys("*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + log.info("DP-1 Redis cache cleared"); + } + + @PostMapping("/public/api/v1/registry/search") + public AcknowledgementDTO demoSearch(@RequestBody RequestDTO requestDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + + return requestHandlerService.buildCacheRequest(objectMapper.writeValueAsString(requestDTO), cacheKey, + CoreConstants.SEND_PROTOCOL_HTTPS,sunbirdEnabled); + } + + @Operation(summary = "Receive search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/txn/status") + public AcknowledgementDTO handleStatusRequest(@RequestBody String requestString) throws Exception { + dpCommonUtils.handleToken(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class). + readValue(requestString); + StatusRequestMessageDTO statusRequestMessageDTO = null; + + Map metaData = (Map) statusRequestDTO.getHeader().getMeta().getData(); + + statusRequestMessageDTO = farmerValidationService.signatureValidation(metaData, statusRequestDTO); + statusRequestDTO.setMessage(statusRequestMessageDTO); + String cacheKey = Constants.STATUS_CACHE_KEY_STRING + statusRequestMessageDTO.getTransactionId(); + try { + farmerValidationService.validateStatusRequestDTO(statusRequestDTO); + return requestHandlerService.buildCacheStatusRequest( + objectMapper.writeValueAsString(statusRequestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_HTTPS); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + @GetMapping(value = "/dashboard/sftp/dp1/data", produces = "text/event-stream") + public SseEmitter sseEmitterFirstPanel() { + return dpSftpPushUpdateService.register(); } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java index 1515de7..2bba62d 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/entity/FarmerInfoEntity.java @@ -5,8 +5,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; - - @Builder @Data @AllArgsConstructor diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java index de03d28..ea4f75b 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/scheduler/Scheduler.java @@ -2,55 +2,38 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; -import g2pc.core.lib.utils.CommonUtils; import g2pc.dp.core.lib.entity.MsgTrackerEntity; -import g2pc.dp.core.lib.repository.MsgTrackerRepository; -import g2pc.dp.core.lib.repository.TxnTrackerRepository; -import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.dp.core.lib.service.ResponseBuilderService; import g2pc.dp.core.lib.service.TxnTrackerDbService; import g2pc.dp.core.lib.service.TxnTrackerRedisService; import g2pc.ref.farmer.regsvc.constants.Constants; import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; import jakarta.transaction.Transactional; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import java.io.IOException; -import java.io.InputStream; import java.util.List; -import java.util.Map; @Slf4j @Service public class Scheduler { - @Value("${client.api_urls.client_search_api}") - String onSearchURL; - - - @Autowired - private RequestHandlerService requestHandlerService; + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; @Autowired private ResponseBuilderService responseBuilderService; @@ -61,45 +44,20 @@ public class Scheduler { @Autowired private TxnTrackerRedisService txnTrackerRedisService; - @Value("${keycloak.from-dc.client-id}") - private String dcClientId; - - @Value("${keycloak.from-dc.client-secret}") - private String dcClientSecret; - - @Value("${keycloak.from-dc.url}") - private String keyClockClientTokenUrl; - - @Autowired - private MsgTrackerRepository msgTrackerRepository; - - @Autowired - private TxnTrackerRepository txnTrackerRepository; - @Autowired private TxnTrackerDbService txnTrackerDbService; - @Autowired - private ResourceLoader resourceLoader; - - @Value("${crypto.to_dc.id}") - private String dp_id; - - @Value("${crypto.to_dc.key_path}") - private String farmer_key_path; - /** * This method is a scheduled task that runs every minute. * It retrieves data from a Redis cache, processes it, and sends a response. * If the status of the data is 'PDNG', it processes the data and updates the status to 'SUCC'. * If an exception occurs during the process, it logs the exception message. - * - * @throws IOException if an I/O error occurs */ + @SuppressWarnings("unchecked") @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. @Transactional - public void responseScheduler() throws Exception { - //try { + public void responseScheduler() { + try { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); @@ -108,11 +66,9 @@ public void responseScheduler() throws Exception { String requestData = txnTrackerRedisService.getRequestData(cacheKey); CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + String protocol = cacheDTO.getProtocol(); RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO, protocol, sunbirdEnabled); List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() .map(txnTrackerEntity -> { try { @@ -122,46 +78,34 @@ public void responseScheduler() throws Exception { } }).toList(); List refRecordsStringsList = farmerResponseBuilderService.getRegFarmerRecords(queryDTOList); - - List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( - requestDTO, refRecordsStringsList); - - ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); - - ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); - Map meta = (Map) headerDTO.getMeta().getData(); - meta.put(CoreConstants.DP_ID, dp_id); - requestDTO.getHeader().getMeta().setData(meta); - String responseString = responseBuilderService.buildResponseString("signature", - headerDTO, responseMessageDTO); - responseString = CommonUtils.formatString(responseString); - log.info("on-search response = {}", responseString); - Resource resource = resourceLoader.getResource(farmer_key_path); - String encryptedSalt = ""; - InputStream fis = resource.getInputStream(); - G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, encryptedSalt); + G2pcError g2pcError = responseBuilderService.buildOnSearchScheduler(refRecordsStringsList, cacheDTO, sunbirdEnabled); + log.info("on-search response - " + g2pcError.getCode()); if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { throw new G2pHttpException(g2pcError); } else { txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); - } } } - //} catch (Exception ex) { - // log.error("Exception in responseScheduler: {}", ex.getMessage()); - //} - } - - private void sendResponseDemo(String responseString, String uri) { - try { - HttpResponse response = Unirest.post(uri) - .header("Content-Type", "application/json") - .body(responseString) - .asString(); - log.info("response send response status = {}", response.getStatus()); + List statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); + for (String cacheKey : statusCacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + G2pcError g2pcError = responseBuilderService.buildOnStatusScheduler(cacheDTO); + log.info("on-status response - " + g2pcError.getCode()); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } + } } catch (Exception ex) { - log.error("Exception in sendResponseDemo: {}", ex.getMessage()); + log.error("Exception in responseScheduler: {}", ex.getMessage()); } } } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java index bbad5b7..093f891 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerResponseBuilderServiceImpl.java @@ -1,10 +1,9 @@ package g2pc.ref.farmer.regsvc.serviceimpl; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.service.ElasticsearchService; import g2pc.ref.farmer.regsvc.dto.request.QueryParamsFarmerDTO; import g2pc.ref.farmer.regsvc.dto.response.RegRecordFarmerDTO; import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; @@ -12,7 +11,9 @@ import g2pc.ref.farmer.regsvc.service.FarmerResponseBuilderService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.elasticsearch.action.search.SearchResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; @@ -22,9 +23,15 @@ @Slf4j public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderService { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Autowired private FarmerInfoRepository farmerInfoRepository; + @Autowired + private ElasticsearchService elasticsearchService; + /** * Get farmer records information from DB * @@ -32,7 +39,7 @@ public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderSe * @return Farmer records */ @Override - public RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity) { + public RegRecordFarmerDTO getRegRecordFarmerDTOFromDb(FarmerInfoEntity farmerInfoEntity) { RegRecordFarmerDTO dto = new RegRecordFarmerDTO(); dto.setFarmerId(farmerInfoEntity.getFarmerId()); dto.setFarmerName(farmerInfoEntity.getFarmerName()); @@ -43,6 +50,19 @@ public RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntit return dto; } + /** + * Get farmer records information from Sunbird + * + * @param searchResponse required + * @return Farmer records + */ + @Override + public RegRecordFarmerDTO getRegRecordFarmerDTOFromSunbird(SearchResponse searchResponse) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + String sourceAsString = searchResponse.getHits().getHits()[0].getSourceAsString(); + return objectMapper.readValue(sourceAsString, RegRecordFarmerDTO.class); + } + /** * Get farmer records information string * @@ -58,12 +78,27 @@ public List getRegFarmerRecords(List queryDTOList) throws IOEx QueryParamsFarmerDTO queryParamsFarmerDTO = objectMapper.readValue(queryParams, QueryParamsFarmerDTO.class); String farmerId = queryParamsFarmerDTO.getFarmerId(); String season = queryParamsFarmerDTO.getSeason(); - Optional optional = farmerInfoRepository.findBySeasonAndFarmerId(season, farmerId); - if (optional.isPresent()) { - RegRecordFarmerDTO regRecordFarmerDTO = getRegRecordFarmerDTO(optional.get()); - regFarmerRecordsList.add(objectMapper.writeValueAsString(regRecordFarmerDTO)); + if (Boolean.TRUE.equals(sunbirdEnabled)) { + log.info("Record fetched from sunbird"); + Map fieldValues = new HashMap<>(); + fieldValues.put("farmer_id.keyword", farmerId); + fieldValues.put("season.keyword", season); + SearchResponse response = elasticsearchService.exactSearch("farmer_info", fieldValues); + if (response.getHits().getHits().length > 0) { + RegRecordFarmerDTO regRecordFarmerDTO = getRegRecordFarmerDTOFromSunbird(response); + regFarmerRecordsList.add(objectMapper.writeValueAsString(regRecordFarmerDTO)); + } else { + regFarmerRecordsList.add(StringUtils.EMPTY); + } } else { - regFarmerRecordsList.add(StringUtils.EMPTY); + log.info("Record fetched from postgres"); + Optional optional = farmerInfoRepository.findBySeasonAndFarmerId(season, farmerId); + if (optional.isPresent()) { + RegRecordFarmerDTO regRecordFarmerDTO = getRegRecordFarmerDTOFromDb(optional.get()); + regFarmerRecordsList.add(objectMapper.writeValueAsString(regRecordFarmerDTO)); + } else { + regFarmerRecordsList.add(StringUtils.EMPTY); + } } } return regFarmerRecordsList; diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java index 241c613..c26d7f3 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/FarmerValidationServiceImpl.java @@ -10,16 +10,18 @@ import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pcUtilityClass; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.farmer.regsvc.constants.Constants; import g2pc.ref.farmer.regsvc.dto.request.QueryFarmerDTO; @@ -34,6 +36,7 @@ import java.io.IOException; import java.io.InputStream; +import java.security.InvalidAlgorithmParameterException; import java.security.SignatureException; import java.util.*; @@ -72,6 +75,9 @@ public class FarmerValidationServiceImpl implements FarmerValidationService { @Value("${crypto.from_dc.key_path}") private String farmer_key_path; + @Autowired + G2pcUtilityClass g2pcUtilityClass; + /** * Validate request dto. * @@ -86,57 +92,17 @@ public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationExcep QueryFarmerDTO.class, QueryParamsFarmerDTO.class); byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); - List searchRequestList = messageDTO.getSearchRequest(); - for(SearchRequestDTO searchRequestDTO : searchRequestList){ - String queryString = objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery()); - QueryDTO queryFarmerDTO = objectMapper.readerFor(QueryDTO.class). - readValue(queryString); - validateQueryDto(queryFarmerDTO); - } String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(requestDTO.getHeader()); - RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). - readValue(headerString); - requestHandlerService.validateRequestHeader(headerDTO); - requestHandlerService.validateRequestMessage(messageDTO); + g2pcUtilityClass.validateResponse(headerString , CoreConstants.REQUEST_HEADER); + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(messageDTO); + g2pcUtilityClass.validateResponse(messageString,CoreConstants.SEARCH_REQUEST); } - /** - * Validate query dto. - * - * @param queryFarmerDTO the query farmer dto - * @throws G2pcValidationException the validation exception - * @throws JsonProcessingException the json processing exception - */ - @Override - public void validateQueryDto(QueryDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { - ObjectMapper objectMapper = new ObjectMapper(); - log.info("Query object -> " + queryFarmerDTO); - String queryString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(queryFarmerDTO); - log.info("Query String" + queryString); - InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/farmerQuerySchema.json"); - JsonNode jsonNode = objectMapper.readTree(queryString); - JsonSchema schema = null; - if (schemaStreamQuery != null) { - schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStreamQuery); - } - Set errorMessage = schema.validate(jsonNode); - List errorcombinedMessage = new ArrayList<>(); - for (ValidationMessage error : errorMessage) { - log.info("Validation errors" + error); - errorcombinedMessage.add(new G2pcError("", error.getMessage())); - - } - if (errorMessage.size() > 0) { - throw new G2pcValidationException(errorcombinedMessage); - } - } /** * Method to validate signature and encrypted message @@ -150,6 +116,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque ObjectMapper objectMapper = new ObjectMapper(); RequestMessageDTO messageDTO; + Boolean isMsgEncrypted = requestDTO.getHeader().getIsMsgEncrypted(); if(isSign){ if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); @@ -157,7 +124,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque Resource resource = resourceLoader.getResource(farmer_key_path); InputStream fis = resource.getInputStream(); if(isEncrypt){ - if(!requestDTO.getHeader().getIsMsgEncrypted()){ + if(!isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } @@ -176,11 +143,11 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque log.info("Rejecting the on-search request in farmer as signature is not valid"); throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); } - if(requestDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ String deprecatedMessageString; try{ deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); - } catch (RuntimeException e ){ + } catch (RuntimeException | InvalidAlgorithmParameterException e ){ log.info("Rejecting the on-search request in farmer as signature is not valid"); throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); } @@ -191,7 +158,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } }else{ - if(requestDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); @@ -217,14 +184,14 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } if(isEncrypt){ - if(!requestDTO.getHeader().getIsMsgEncrypted()){ + if(!isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } String messageString = requestDTO.getMessage().toString(); String deprecatedMessageString; try{ deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); - } catch (RuntimeException e ){ + } catch (RuntimeException | InvalidAlgorithmParameterException e ){ log.info("Rejecting the on-search request in farmer as signature is not valid"); throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); } @@ -233,7 +200,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque readValue(deprecatedMessageString); }else{ - if(requestDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); @@ -243,5 +210,121 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque requestDTO.setMessage(messageDTO); return messageDTO; } + + @Override + public StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + StatusRequestMessageDTO messageDTO; + Boolean isMsgEncrypted = requestDTO.getHeader().getIsMsgEncrypted(); + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(farmer_key_path); + InputStream fis = resource.getInputStream(); + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = requestDTO.getMessage().toString(); + String data = requestHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ->"+e.getMessage())); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(isMsgEncrypted){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusRequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = requestHeaderString+messageString; + log.info("Signature ->"+requestSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusRequestMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + } + } + requestDTO.setMessage(messageDTO); + return messageDTO; + } + + @Override + public void validateStatusRequestDTO(StatusRequestDTO statusRequestDTO) throws IOException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] json = objectMapper.writeValueAsBytes(statusRequestDTO.getMessage()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusRequestDTO.getHeader()); + g2pcUtilityClass.validateResponse(headerString , CoreConstants.REQUEST_HEADER); + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusRequestMessageDTO); + g2pcUtilityClass.validateResponse(messageString,CoreConstants.STATUS_REQUEST); + } + + } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/utils/DpCommonUtils.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/utils/DpCommonUtils.java deleted file mode 100644 index 45f8215..0000000 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/utils/DpCommonUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -package g2pc.ref.farmer.regsvc.utils; - -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.enums.ExceptionsENUM; -import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; -import g2pc.core.lib.security.BearerTokenUtil; -import g2pc.core.lib.security.service.G2pTokenService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class DpCommonUtils { - - @Value("${keycloak.dp.client.realm}") - private String keycloakRealm; - - @Value("${keycloak.dp.master.getClientUrl}") - private String getClientUrl; - - @Value("${crypto.to_dc.support_encryption}") - private boolean isEncrypt; - - @Value("${crypto.to_dc.support_signature}") - private boolean isSign; - - @Value("${keycloak.dp.client.url}") - private String keycloakURL; - - @Value("${keycloak.dp.client.clientId}") - private String keycloakClientId; - - @Value("${keycloak.dp.client.clientSecret}") - private String keycloakClientSecret; - - @Value("${keycloak.dp.master.url}") - private String masterUrl; - - @Value("${keycloak.dp.master.clientId}") - private String masterClientId; - - @Value("${keycloak.dp.master.clientSecret}") - private String masterClientSecret; - - @Value("${keycloak.dp.username}") - private String adminUsername; - - @Value("${keycloak.dp.password}") - private String adminPassword; - - @Autowired - G2pTokenService g2pTokenService; - - public void handleToken() throws G2pHttpException, JsonProcessingException { - log.info("Is encrypted ? -> " + isEncrypt); - log.info("Is signed ? -> " + isSign); - String token = BearerTokenUtil.getBearerTokenHeader(); - String introspectUrl = keycloakURL + "/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspectUrl, token, - keycloakClientId, keycloakClientSecret); - log.info("Introspect response -> " + introspectResponse.getStatusCode()); - log.info("Introspect response body -> " + introspectResponse.getBody()); - if (introspectResponse.getStatusCode().value() == 401) { - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); - } - if (!g2pTokenService.validateToken(masterUrl, getClientUrl, - g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, - adminUsername, adminPassword)) { - //TODO:check this - //throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); - } - } -} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml index bafebcc..fa3a6e6 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml @@ -9,9 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: not_set - username: not_set - password: not_set + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW hikari: data-source-properties: @@ -47,54 +47,52 @@ spring: exclude: static/**,public/** server: - port: not_set + port: 9001 error: include-message: always spring.data.redis: repositories.enabled: false - host: not_set - password: not_set - port: not_set + host: 3.109.26.38 + password: cdpi@99222 + port: 6378 client: api_urls: - client_search_api: not_set + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" keycloak: from_dc: - url: not_set - clientId: not_set - clientSecret: not_set + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY dp: - url: not_set - username: not_set - password: not_set + url: https://g2pc-dp1-lab.cdpi.dev/auth + username: admin + password: cdpi@9923 master: - url: not_set - getClientUrl: not_set - clientId: not_set - clientSecret: not_set + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients + clientId: admin-cli + clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN client: - url: not_set - realm: not_set - clientId: not_set - clientSecret: not_set - realmClientId: not_set - realmClientSecret: not_set + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token + realm: dp-farmer + clientId: dp-farmer-client + clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX crypto: to_dc: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER from_dc: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" dashboard: - dp_dashboard_url: not_set \ No newline at end of file + dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml index bafebcc..9dedf1c 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml @@ -9,9 +9,10 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: not_set - username: not_set - password: not_set + # Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc + username: postgres + password: K6tnrCU0wqXOwPW hikari: data-source-properties: @@ -47,54 +48,86 @@ spring: exclude: static/**,public/** server: - port: not_set + port: 9001 error: include-message: always spring.data.redis: repositories.enabled: false - host: not_set - password: not_set - port: not_set + host: 3.109.26.38 + password: cdpi@99222 + port: 6378 client: api_urls: - client_search_api: not_set + client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + client_status_api: "http://localhost:8000/private/api/v1/registry/on-status" keycloak: from_dc: - url: not_set - clientId: not_set - clientSecret: not_set + url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" + clientId: dc-client + clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY dp: - url: not_set - username: not_set - password: not_set + url: https://g2pc-dp1-lab.cdpi.dev/auth + username: admin + password: cdpi@9923 master: - url: not_set - getClientUrl: not_set - clientId: not_set - clientSecret: not_set + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients + clientId: admin-cli + clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN client: - url: not_set - realm: not_set - clientId: not_set - clientSecret: not_set - realmClientId: not_set - realmClientSecret: not_set + url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token + realm: dp-farmer + clientId: dp-farmer-client + clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX crypto: to_dc: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + support_encryption: true + support_signature: true + password: "farmer_on_search" + key_path: "classpath:farmer_on_search.p12" + id: FARMER from_dc: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + support_encryption: true + support_signature: true + password: "farmer_search" + key_path: "classpath:farmer_search.p12" dashboard: - dp_dashboard_url: not_set \ No newline at end of file + dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + cors_origin_url: "http://localhost:8000" + +sftp: + listener: + host: localhost + port: 2225 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + outbound_directory: /outbound + local: + inbound_directory: /home/prihir/g2pc/dp1/inbound + outbound_directory: /home/prihir/g2pc/dp1/outbound + + dc: + host: localhost + port: 2224 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + outbound_directory: /outbound + +sunbird: + api_urls: + response_data_api: http://3.109.26.38:8083/api/v1/Msg_Tracker + response_tracker_api: http://3.109.26.38:8083/api/v1/Txn_Tracker + enabled: true + elasticsearch: + host: 3.109.26.38 + port: 9200 + scheme: http \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java index c7a76f6..9ce756a 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/test/java/g2pc/ref/farmer/regsvc/G2pcRefFarmerRegsvcApplicationTests.java @@ -1,29 +1,63 @@ package g2pc.ref.farmer.regsvc; import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.core.lib.service.ElasticsearchService; import g2pc.ref.farmer.regsvc.scheduler.Scheduler; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.action.search.SearchResponse; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.io.IOException; @SpringBootTest +@Slf4j class G2pcRefFarmerRegsvcApplicationTests { - @Autowired - private Scheduler scheduler; - - @Autowired - G2pTokenService g2pTokenService ; - - @Test - void contextLoads() { - } - - @Test - void testResponseScheduler() throws Exception { - scheduler.responseScheduler(); - } + @Autowired + private Scheduler scheduler; + + @Autowired + G2pTokenService g2pTokenService; + + @Test + void contextLoads() { + } + + @Test + void testResponseScheduler() throws Exception { + scheduler.responseScheduler(); + } + + @Test + void testObjectMapper(){ + String jsonString = "{\"version\":\"1.0.0\",\"message_id\":\"M446-2727-0859-1434-3027\",\"message_ts\":\"2024-01-29T12:17:43+05:30\",\"action\":\"search\",\"status\":\"\",\"status_reason_code\":\"\",\"status_reason_message\":\"\",\"total_count\":0,\"completed_count\":0,\"sender_id\":\"spp.example.org\",\"receiver_id\":\"pymts.example.org\",\"is_msg_encrypted\":false,\"meta\":\"\",\"transaction_id\":\"T272-0195-3439-6563-8238\",\"correlation_id\":\"\",\"registry_type\":\"ns:FARMER_REGISTRY\",\"protocol\":\"https\",\"payload_filename\":\"file\",\"inbound_filename\":\"\",\"outbound_filename\":\"\",\"created_date\":\"2024-01-29T12:17:44+05:30\",\"last_updated_date\":\"2024-01-29T12:17:44+05:30\",\"osOwner\":[\"anonymous\"],\"osid\":\"1-a4640192-341e-4446-aa37-51e613a58e9e\"}"; + + // Create an ObjectMapper instance + ObjectMapper objectMapper = new ObjectMapper(); + + try { + // Convert JSON string to HashMap + Map resultMap = objectMapper.readValue(jsonString, new TypeReference>() {}); + + String osid = (String) resultMap.get("osid"); + // Convert Object values to String + Map stringMap = new HashMap<>(); + for (Map.Entry entry : resultMap.entrySet()) { + stringMap.put(entry.getKey(), entry.getValue().toString()); + } + + // Print the result + System.out.println(stringMap); + } catch (IOException e) { + e.printStackTrace(); + } + } } From cbe4145cd78bbf14264c0f32f14c8f7423c713e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:47:29 +0530 Subject: [PATCH 46/53] g2pc-ref-mno-regsvc changes. --- .../g2pc-ref-farmer-regsvc/README.md | 87 ------- .../ref/farmer/regsvc/config/CorsConfig.java | 22 ++ .../farmer/regsvc/constants/Constants.java | 4 + .../rest/DpDashboardController.java | 2 + .../controller/sftp/DcSftpListener.java | 99 ++++++++ .../ref/farmer/regsvc/dto/SftpDpData.java | 19 ++ .../dto/request/QueryParamsFarmerDTO.java | 2 - .../regsvc/dto/response/DataFarmerDTO.java | 1 - .../dto/response/RegRecordFarmerDTO.java | 3 +- .../repository/FarmerInfoRepository.java | 2 - .../service/DpSftpPushUpdateService.java | 10 + .../service/FarmerResponseBuilderService.java | 12 +- .../service/FarmerValidationService.java | 19 +- .../DpSftpPushUpdateServiceImpl.java | 50 ++++ .../src/main/resources/logback-spring.xml | 9 + .../g2pc-ref-mno-regsvc/pom.xml | 5 + .../controller/rest/RegistryController.java | 173 +++++++------ .../ref/mno/regsvc/scheduler/Scheduler.java | 100 ++++---- .../MobileResponseBuilderServiceImpl.java | 5 +- .../MobileValidationServiceImpl.java | 235 ++++++++++++------ .../src/main/resources/application.yml | 83 +++++-- .../G2pcRefMnoRegsvcApplicationTests.java | 2 + 22 files changed, 623 insertions(+), 321 deletions(-) create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/CorsConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/sftp/DcSftpListener.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/SftpDpData.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/DpSftpPushUpdateService.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/logback-spring.xml diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md index c606d2c..e69de29 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/README.md @@ -1,87 +0,0 @@ -# G2pc Ref Farmer Reg Svc - -## JSON schema validations -This project is an implementation of the JSON Schema Draft v4, - -### Custom validation using json schema -For Farmer registry Query params are the 20% are custom changes , in which for this query param different schema has been written -in this repo. - -### Maven Dependency -```` - - com.github.erosb - everit-json-schema - 1.14.2 - -```` - - - -#### Below are some samples schema which written in this project. -```` -{ - "$schema": "https://json-schema.org/draft-04/schema#", - "$id": "https://example.com/message.schema.json", - "title": "Query schema", - "description": "", - "additionalProperties": false, - "type": "object", - "properties": { - "query_name" : { - "type": "string" - }, - "query_params": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/nonEmptyString", - "type": "string" - }, - "farmer_id": { - "type": "array", - "items": { - "$ref": "#/definitions/nonEmptyString", - "type": "string" - } - }, - "season": { - "$ref": "#/definitions/nonEmptyString", - "type": "string" - } - }, - "required": ["farmer_id","season"] - } - }, - "required": ["query_params"], - "definitions": { - "nonEmptyString": { - "type": "string", - "minLength": 1 - } - } -} - - -```` - -Using below code we can read the above schema json -```` - InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/farmerQuerySchema.json"); - JsonNode jsonNode = objectMapper.readTree(queryString); - JsonSchema schema = null; - if(schemaStreamQuery !=null){ - schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStreamQuery); - } - Set errorMessage = schema.validate(jsonNode); - List errorcombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorcombinedMessage.add(new G2pcError("",error.getMessage())); - - } -```` - - diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/CorsConfig.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/CorsConfig.java new file mode 100644 index 0000000..a4e9e3c --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/config/CorsConfig.java @@ -0,0 +1,22 @@ +package g2pc.ref.farmer.regsvc.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Value("${dashboard.cors_origin_url}") + private String corsOriginUrl; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins(corsOriginUrl) + .allowedMethods("*") + .allowedHeaders("*"); + } +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java index 9e050af..2458276 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/constants/Constants.java @@ -18,4 +18,8 @@ private Constants() { public static final String CACHE_KEY_STRING = "request-farmer-"; public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; + + public static final String STATUS_CACHE_KEY_STRING = "status-request-farmer-"; + + public static final String STATUS_CACHE_KEY_SEARCH_STRING = "status-request-farmer*"; } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java index 1dd51c7..f9eb627 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/rest/DpDashboardController.java @@ -16,4 +16,6 @@ public String showDashboardPage(Model model) { model.addAttribute("dp_dashboard_url", dpDashboardUrl); return "dashboard"; } + + } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/sftp/DcSftpListener.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/sftp/DcSftpListener.java new file mode 100644 index 0000000..37e82b6 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/controller/sftp/DcSftpListener.java @@ -0,0 +1,99 @@ +package g2pc.ref.farmer.regsvc.controller.sftp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.dto.SftpDpData; +import g2pc.ref.farmer.regsvc.service.DpSftpPushUpdateService; +import g2pc.ref.farmer.regsvc.service.FarmerValidationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@Configuration +@Slf4j +public class DcSftpListener { + + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpLocalDirectoryInbound; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpLocalDirectoryOutbound; + + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + FarmerValidationService farmerValidationService; + + @Autowired + private DpSftpPushUpdateService dpSftpPushUpdateService; + + @SuppressWarnings("unchecked") + @ServiceActivator(inputChannel = "sftpInbound") + public void handleMessageInbound(Message message) { + try { + File file = message.getPayload(); + log.info("Received Message from inbound directory of dp-1: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file) && file.getName().contains(".json")) { + String requestString = new String(Files.readAllBytes(file.toPath())); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO; + + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + + messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); + requestDTO.setMessage(messageDTO); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + try { + farmerValidationService.validateRequestDTO(requestDTO); + requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey, + CoreConstants.SEND_PROTOCOL_SFTP, sunbirdEnabled); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); + } catch (Exception e) { + log.error("Error: ", e); + } + } + + @ServiceActivator(inputChannel = "errorChannel") + public void handleError(Message message) { + Throwable error = (Throwable) message.getPayload(); + log.error("Handling ERROR: {}", error.getMessage()); + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/SftpDpData.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/SftpDpData.java new file mode 100644 index 0000000..7800054 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/SftpDpData.java @@ -0,0 +1,19 @@ +package g2pc.ref.farmer.regsvc.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SftpDpData { + + private String dpType; + + private String messageTs; + + private String transactionId; + + private String fileName; +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java index 26bcc91..1fb4526 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/request/QueryParamsFarmerDTO.java @@ -4,8 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; -import java.util.List; - @Getter @Setter @ToString diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java index 85d2059..5300299 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/DataFarmerDTO.java @@ -5,7 +5,6 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; - import java.util.List; @Getter diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java index 0f3242d..3deb88a 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/dto/response/RegRecordFarmerDTO.java @@ -1,13 +1,14 @@ package g2pc.ref.farmer.regsvc.dto.response; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; - @Getter @Setter @ToString @AllArgsConstructor @NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class RegRecordFarmerDTO { @JsonProperty("farmer_id") diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java index c7227da..9eb7ed1 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/repository/FarmerInfoRepository.java @@ -3,8 +3,6 @@ import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; - -import java.util.List; import java.util.Optional; @Repository diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/DpSftpPushUpdateService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/DpSftpPushUpdateService.java new file mode 100644 index 0000000..19e81db --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/DpSftpPushUpdateService.java @@ -0,0 +1,10 @@ +package g2pc.ref.farmer.regsvc.service; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface DpSftpPushUpdateService { + + SseEmitter register(); + + void pushUpdate(Object update); +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java index 173afcf..f9f58ae 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerResponseBuilderService.java @@ -1,9 +1,10 @@ package g2pc.ref.farmer.regsvc.service; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.search.message.request.QueryDTO; import g2pc.ref.farmer.regsvc.dto.response.RegRecordFarmerDTO; import g2pc.ref.farmer.regsvc.entity.FarmerInfoEntity; +import org.elasticsearch.action.search.SearchResponse; import java.io.IOException; import java.util.List; @@ -11,7 +12,12 @@ public interface FarmerResponseBuilderService { - RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity); + RegRecordFarmerDTO getRegRecordFarmerDTOFromDb(FarmerInfoEntity farmerInfoEntity); + + RegRecordFarmerDTO getRegRecordFarmerDTOFromSunbird(SearchResponse searchResponse) throws JsonProcessingException; List getRegFarmerRecords(List queryDTOList) throws IOException; + + + } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java index ff57645..c55090c 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/service/FarmerValidationService.java @@ -1,10 +1,12 @@ package g2pc.ref.farmer.regsvc.service; -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; + +import java.io.IOException; import java.util.Map; /** * The interface Farmer validation service. @@ -13,7 +15,10 @@ public interface FarmerValidationService { void validateRequestDTO (RequestDTO requestDTO) throws Exception; - void validateQueryDto (QueryDTO queryFarmerDTO) throws G2pcValidationException, JsonProcessingException; - RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; -} + + StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception ; + + void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, G2pcValidationException; + + } diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java new file mode 100644 index 0000000..0e475da --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/java/g2pc/ref/farmer/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java @@ -0,0 +1,50 @@ +package g2pc.ref.farmer.regsvc.serviceimpl; + +import g2pc.ref.farmer.regsvc.dto.SftpDpData; +import g2pc.ref.farmer.regsvc.service.DpSftpPushUpdateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +public class DpSftpPushUpdateServiceImpl implements DpSftpPushUpdateService { + + private final List emitters = new ArrayList<>(); + + public SseEmitter register() { + int minutes = 15; + long timeout = (long) minutes * 60000; + SseEmitter emitter = new SseEmitter(timeout); + this.emitters.add(emitter); + emitter.onCompletion(() -> this.emitters.remove(emitter)); + emitter.onTimeout(() -> this.emitters.remove(emitter)); + log.info("SSE emitter registered" + emitter); + return emitter; + } + + public void pushUpdate(Object update) { + List deadEmitters = new ArrayList<>(); + this.emitters.forEach(emitter -> { + try { + emitter.send(update); + } catch (IOException e) { + deadEmitters.add(emitter); + } + }); + this.emitters.removeAll(deadEmitters); + } + + public SftpDpData getSftpDpData(String dpType, String messageTs, String transactionId, String filename){ + SftpDpData sftpDpData = new SftpDpData(); + sftpDpData.setDpType(dpType); + sftpDpData.setMessageTs(messageTs); + sftpDpData.setTransactionId(transactionId); + sftpDpData.setFileName(filename); + return sftpDpData; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/logback-spring.xml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..529d464 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/logback-spring.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml index 70bd624..1dc7938 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/pom.xml @@ -75,6 +75,11 @@ spring-boot-devtools true + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.10.2 + diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java index 14edae4..b4aeba1 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/rest/RegistryController.java @@ -2,22 +2,24 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; -import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; -import g2pc.core.lib.security.BearerTokenUtil; -import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.dp.core.lib.repository.MsgTrackerRepository; import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.utils.DpCommonUtils; import g2pc.ref.mno.regsvc.constants.Constants; +import g2pc.ref.mno.regsvc.service.DpSftpPushUpdateService; import g2pc.ref.mno.regsvc.service.MobileValidationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -26,11 +28,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; import java.util.Map; +import java.util.Set; /** * The type Registry controller. @@ -41,66 +47,26 @@ @Tag(name = "Provider", description = "Provider APIs") public class RegistryController { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Autowired private RequestHandlerService requestHandlerService; - /** - * The Mobile validation service. - */ @Autowired MobileValidationService mobileValidationService; @Autowired - G2pTokenService g2pTokenService; - - @Value("${keycloak.realm}") - private String keycloakRealm; - - @Value("${keycloak.url}") - private String keycloakURL; - - @Value("${keycloak.mobile.admin-url}") - private String masterAdminUrl; - - @Value("${keycloak.mobile.get-client-url}") - private String getClientUrl; + private DpCommonUtils dpCommonUtils; - @Value("${keycloak.admin.realm.client-id}") - private String adminRealmClientId; - - @Value("${keycloak.admin.realm.client-secret}") - private String adminRealmClientSecret; - - @Value("${keycloak.admin.client-id}") - private String adminClientId; - - @Value("${keycloak.admin.client-secret}") - private String adminClientSecret; - - @Value("${keycloak.admin.username}") - private String adminUsername; + @Autowired + private MsgTrackerRepository msgTrackerRepository; - @Value("${keycloak.admin.password}") - private String adminPassword; + @Autowired + private RedisTemplate redisTemplate; - @Operation(summary = "Receive search request") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), - @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), - @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), - @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) - @PostMapping("/public/api/v1/registry/search") - public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). - readValue(requestString); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); - return requestHandlerService.buildCacheRequest( - objectMapper.writeValueAsString(requestDTO), cacheKey); - } + @Autowired + private DpSftpPushUpdateService dpSftpPushUpdateService; /** * Get search request from DC @@ -109,7 +75,7 @@ public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws E * @return Search request received acknowledgement * @throws JsonProcessingException the json processing exception * @throws ResponseStatusException the response status exception - * @throws G2pcValidationException the validation exception + * @throws G2pcValidationException the validation exception */ @Operation(summary = "Receive search request") @ApiResponses(value = { @@ -117,18 +83,11 @@ public AcknowledgementDTO demoSearch(@RequestBody String requestString) throws E @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @SuppressWarnings("unchecked") @PostMapping("/private/api/v1/registry/search") public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); - String token = BearerTokenUtil.getBearerTokenHeader(); - String introspect = keycloakURL+"/realms/"+keycloakRealm+"/protocol/openid-connect/token/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect,token,adminRealmClientId,adminRealmClientSecret); - if(introspectResponse.getStatusCode().value()==401){ - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(),introspectResponse.getBody())); - } - if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , adminClientId , adminClientSecret , adminUsername , adminPassword)){ - throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); - } + dpCommonUtils.handleToken(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); @@ -136,24 +95,21 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody String reque readValue(requestString); RequestMessageDTO messageDTO = null; - Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); - messageDTO = mobileValidationService.signatureValidation(metaData,requestDTO); + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + messageDTO = mobileValidationService.signatureValidation(metaData, requestDTO); requestDTO.setMessage(messageDTO); String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { mobileValidationService.validateRequestDTO(requestDTO); return requestHandlerService.buildCacheRequest( - objectMapper.writeValueAsString(requestDTO), cacheKey); + objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_HTTPS, sunbirdEnabled); } catch (G2pcValidationException e) { - throw new G2pcValidationException(e.getG2PcErrorList()); - } - catch (JsonProcessingException e){ - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR , e.getMessage()); - } - catch (Exception e){ - throw new ResponseStatusException(HttpStatus.MULTI_STATUS , e.getMessage()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.MULTI_STATUS, e.getMessage()); } } @@ -169,8 +125,7 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody String reque @ResponseStatus(HttpStatus.BAD_REQUEST) public ValidationErrorResponse handleValidationException( - G2pcValidationException ex) - { + G2pcValidationException ex) { return new ValidationErrorResponse( ex.getG2PcErrorList()); } @@ -183,9 +138,65 @@ public AcknowledgementDTO registerCandidateInformation(@RequestBody String reque */ @ExceptionHandler(value = G2pHttpException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) - public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) - { + public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) { return new ErrorResponse(ex.getG2PcError()); } + + /** + * Clear message tracker DB + * Clear Redis cache + */ + @GetMapping("/private/api/v1/registry/clear-db") + public void clearDb() throws G2pHttpException, IOException { + dpCommonUtils.handleToken(); + msgTrackerRepository.deleteAll(); + log.info("DP-2 DB cleared"); + Set keys = redisTemplate.keys("*"); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + log.info("DP-2 Redis cache cleared"); + } + + @Operation(summary = "Receive status request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @SuppressWarnings("unchecked") + @PostMapping("/private/api/v1/registry/txn/status") + public AcknowledgementDTO handleStatusRequest(@RequestBody String requestString) throws Exception { + dpCommonUtils.handleToken(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class). + readValue(requestString); + StatusRequestMessageDTO statusRequestMessageDTO = null; + + Map metaData = (Map) statusRequestDTO.getHeader().getMeta().getData(); + + statusRequestMessageDTO = mobileValidationService.signatureValidation(metaData, statusRequestDTO); + statusRequestDTO.setMessage(statusRequestMessageDTO); + String cacheKey = Constants.STATUS_CACHE_KEY_STRING + statusRequestMessageDTO.getTransactionId(); + try { + mobileValidationService.validateStatusRequestDTO(statusRequestDTO); + return requestHandlerService.buildCacheStatusRequest( + objectMapper.writeValueAsString(statusRequestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_HTTPS); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + @GetMapping(value = "/dashboard/sftp/dp2/data", produces = "text/event-stream") + public SseEmitter sseEmitterFirstPanel() { + return dpSftpPushUpdateService.register(); + } } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java index f2af9ed..0e1892e 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/scheduler/Scheduler.java @@ -5,22 +5,19 @@ import g2pc.core.lib.dto.common.cache.CacheDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; -import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.dto.common.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.enums.HeaderStatusENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; -import g2pc.core.lib.utils.CommonUtils; -import g2pc.dp.core.lib.constants.DpConstants; import g2pc.dp.core.lib.entity.MsgTrackerEntity; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.dp.core.lib.service.ResponseBuilderService; import g2pc.dp.core.lib.service.TxnTrackerDbService; import g2pc.dp.core.lib.service.TxnTrackerRedisService; +import g2pc.dp.core.lib.utils.DpCommonUtils; import g2pc.ref.mno.regsvc.constants.Constants; import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; import jakarta.transaction.Transactional; @@ -32,38 +29,41 @@ import org.springframework.stereotype.Service; import java.io.IOException; -import java.util.ArrayList; import java.util.List; +import org.springframework.core.io.ResourceLoader; + @Slf4j @Service public class Scheduler { + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + @Value("${client.api_urls.client_search_api}") String onSearchURL; + @Value("${client.api_urls.client_status_api}") + String onStatusURL; + @Autowired private RequestHandlerService requestHandlerService; @Autowired private ResponseBuilderService responseBuilderService; + @Autowired + private ResourceLoader resourceLoader; @Autowired private MobileResponseBuilderService mobileResponseBuilderService; - @Value("${keycloak.data-consumer.client-id}") + @Value("${keycloak.from-dc.client-id}") private String dcClientId; - @Value("${keycloak.data-consumer.client-secret}") + @Value("${keycloak.from-dc.client-secret}") private String dcClientSecret; - @Value("${keycloak.realm}") - private String keycloakRealm; - - @Value("${keycloak.url}") - private String keycloakURL; - - @Value("${keycloak.data-consumer.url}") + @Value("${keycloak.from-dc.url}") private String keyClockClientTokenUrl; @Autowired @@ -72,6 +72,15 @@ public class Scheduler { @Autowired private TxnTrackerDbService txnTrackerDbService; + @Value("${crypto.to_dc.id}") + private String dpId; + + @Value("${crypto.to_dc.key_path}") + private String farmerKeyPath; + + @Autowired + private DpCommonUtils dpCommonUtils; + /** * This method is a scheduled task that runs every minute. * It retrieves data from a Redis cache, processes it, and sends a response. @@ -80,6 +89,7 @@ public class Scheduler { * * @throws IOException if an I/O error occurs */ + @SuppressWarnings("unchecked") @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. @Transactional public void responseScheduler() throws IOException { @@ -92,11 +102,9 @@ public void responseScheduler() throws IOException { String requestData = txnTrackerRedisService.getRequestData(cacheKey); CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + String protocol = cacheDTO.getProtocol(); RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); - RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); - String transactionId = messageDTO.getTransactionId(); - - MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO); + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO, protocol, sunbirdEnabled); List queryDTOList = msgTrackerEntity.getTxnTrackerEntityList().stream() .map(txnTrackerEntity -> { try { @@ -106,30 +114,34 @@ public void responseScheduler() throws IOException { } }).toList(); List refRecordsStringsList = mobileResponseBuilderService.getRegMobileRecords(queryDTOList); - - List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( - requestDTO, refRecordsStringsList); - - ResponseHeaderDTO headerDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); - - ResponseMessageDTO responseMessageDTO = responseBuilderService.buildResponseMessage(transactionId, searchResponseDTOList); - String responseString = responseBuilderService.buildResponseString("signature", - headerDTO, responseMessageDTO); - responseString = CommonUtils.formatString(responseString); - log.info("on-search response = {}", responseString); - //G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(responseString, onSearchURL, dcClientId, dcClientSecret, keyClockClientTokenUrl); - - //if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { - // throw new G2pHttpException(g2pcError); - //} else { - txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); - //} + G2pcError g2pcError = responseBuilderService.buildOnSearchScheduler(refRecordsStringsList, cacheDTO, sunbirdEnabled); + log.info("on-search database updation response from sunbird - " + g2pcError.getCode()); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } + } + List statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); + for (String cacheKey : statusCacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + G2pcError g2pcError = responseBuilderService.buildOnStatusScheduler(cacheDTO); + log.info("on-status database updation response from sunbird - " + g2pcError.getCode()); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } } } - //} catch (G2pHttpException e) { - // log.error("Exception thrown from on-search endpoint" + e.getG2PcError().getMessage()); } catch (Exception ex) { - log.error("Scheduler error : {}", ex); + log.error("Exception in responseScheduler: {}", ex.getMessage()); } } -} +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java index c5c5235..e9b1cfd 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileResponseBuilderServiceImpl.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.config.G2pUnirestHelper; -import g2pc.core.lib.dto.common.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; import g2pc.ref.mno.regsvc.dto.response.RegRecordMobileDTO; import g2pc.ref.mno.regsvc.service.MobileResponseBuilderService; import kong.unirest.HttpResponse; @@ -11,7 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; - import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -35,8 +34,10 @@ public class MobileResponseBuilderServiceImpl implements MobileResponseBuilderSe @Override public List getRegMobileRecords(List queryDTOList) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); + log.info("getRegMobileRecords : {}", objectMapper.writeValueAsString(queryDTOList)); List regMnoRecordsList = new ArrayList<>(); String uri = mobileInfoURL; + log.info("mobileInfoURL : {}", uri); HttpResponse response = g2pUnirestHelper.g2pPost(uri) .body(objectMapper.writeValueAsString(queryDTOList)) .asString(); diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java index 8805e86..fa34b31 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/MobileValidationServiceImpl.java @@ -1,25 +1,21 @@ package g2pc.ref.mno.regsvc.serviceimpl; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.constants.G2pSecurityConstants; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; -import g2pc.core.lib.dto.common.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; import g2pc.core.lib.security.service.AsymmetricSignatureService; import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pcUtilityClass; import g2pc.dp.core.lib.service.RequestHandlerService; import g2pc.ref.mno.regsvc.constants.Constants; import g2pc.ref.mno.regsvc.dto.request.QueryMobileDTO; @@ -28,8 +24,13 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; +import java.io.IOException; import java.io.InputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.SignatureException; import java.util.*; /** * The type Mobile validation service. @@ -47,19 +48,28 @@ public class MobileValidationServiceImpl implements MobileValidationService { @Autowired G2pEncryptDecrypt encryptDecrypt; - @Value("${crypto.mobile.support_encryption}") + @Value("${crypto.from_dc.support_encryption}") private boolean isEncrypt; - @Value("${crypto.mobile.support_signature}") + @Value("${crypto.from_dc.support_signature}") private boolean isSign; + @Value("${crypto.from_dc.password}") + private String p12Password; @Autowired private AsymmetricSignatureService asymmetricSignatureService; + @Autowired + private ResourceLoader resourceLoader; + + @Value("${crypto.from_dc.key_path}") + private String mobileKeyPath; + @Autowired + G2pcUtilityClass g2pcUtilityClass; /** * Method to validate Request dto * @param requestDTO the request dto - * @throws Exception + * @throws Exception required */ @Override public void validateRequestDTO(RequestDTO requestDTO) throws Exception { @@ -68,62 +78,22 @@ public void validateRequestDTO(RequestDTO requestDTO) throws Exception { QueryMobileDTO.class, QueryParamsMobileDTO.class); byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); - List searchRequestList = messageDTO.getSearchRequest(); - for(SearchRequestDTO searchRequestDTO : searchRequestList){ - String queryString = objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery()); - QueryDTO queryMobileDto = objectMapper.readerFor(QueryDTO.class). - readValue(queryString); - validateQueryDto(queryMobileDto); - } - String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(requestDTO.getHeader()); - RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). - readValue(headerString); - requestHandlerService.validateRequestHeader(headerDTO); - requestHandlerService.validateRequestMessage(messageDTO); - } - - /** - * Method to validate query deto - * @param queryMobileDTO the query mobile dto - * @throws G2pcValidationException - * @throws JsonProcessingException - */ - @Override - public void validateQueryDto(QueryDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - log.info("Query object -> "+queryMobileDTO); - String queryString = new ObjectMapper() + g2pcUtilityClass.validateResponse(headerString , CoreConstants.REQUEST_HEADER); + String messageString = new ObjectMapper() .writerWithDefaultPrettyPrinter() - .writeValueAsString(queryMobileDTO); - log.info("Query String" + queryString); - InputStream schemaStreamQuery = MobileValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/mobileQuerySchema.json"); - JsonNode jsonNode = objectMapper.readTree(queryString); - JsonSchema schema = null; - if(schemaStreamQuery !=null){ - schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStreamQuery); - } - Set errorMessage = schema.validate(jsonNode); - List errorcombinedMessage= new ArrayList<>(); - for (ValidationMessage error : errorMessage){ - log.info("Validation errors" + error ); - errorcombinedMessage.add(new G2pcError("",error.getMessage())); - } - if (errorMessage.size()>0){ - throw new G2pcValidationException(errorcombinedMessage); - } + .writeValueAsString(messageDTO); + g2pcUtilityClass.validateResponse(messageString,CoreConstants.SEARCH_REQUEST); } /** * Method to validate signature and encryption of request dto - * @param metaData - * @param requestDTO - * @return - * @throws Exception + * @param metaData required + * @param requestDTO required + * @return RequestMessageDTO request message dto + * @throws Exception required */ @Override public RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO) throws Exception { @@ -131,12 +101,15 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque log.info("Is signed ? -> " + isSign); ObjectMapper objectMapper = new ObjectMapper(); RequestMessageDTO messageDTO; + Boolean isMsgEncrypted = requestDTO.getHeader().getIsMsgEncrypted(); if(isSign){ if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); } + Resource resource= resourceLoader.getResource(mobileKeyPath); + InputStream fis = resource.getInputStream(); if(isEncrypt){ - if(!requestDTO.getHeader().getIsMsgEncrypted()){ + if(!isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } @@ -144,14 +117,19 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque String requestSignature = requestDTO.getSignature(); String messageString = requestDTO.getMessage().toString(); String data = requestHeaderString+messageString; - if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature)) ){ + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); } - if(requestDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ String deprecatedMessageString; try{ deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); - } catch (RuntimeException e ){ + } catch (RuntimeException | InvalidAlgorithmParameterException e ){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); } log.info("Decrypted Message string ->"+deprecatedMessageString); @@ -161,7 +139,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } }else{ - if(requestDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); @@ -171,8 +149,13 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque String messageString = objectMapper.writeValueAsString(messageDTO); String data = requestHeaderString+messageString; log.info("Signature ->"+requestSignature); - if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature)) ){ + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); } } @@ -181,14 +164,14 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } if(isEncrypt){ - if(!requestDTO.getHeader().getIsMsgEncrypted()){ + if(!isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } String messageString = requestDTO.getMessage().toString(); String deprecatedMessageString; try{ deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); - } catch (RuntimeException e ){ + } catch (RuntimeException | InvalidAlgorithmParameterException e ){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); } log.info("Decrypted Message string ->"+deprecatedMessageString); @@ -196,7 +179,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque readValue(deprecatedMessageString); }else{ - if(requestDTO.getHeader().getIsMsgEncrypted()){ + if(isMsgEncrypted){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); } byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); @@ -206,5 +189,117 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque return messageDTO; } + @Override + public StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + StatusRequestMessageDTO messageDTO; + Boolean isMsgEncrypted = requestDTO.getHeader().getIsMsgEncrypted(); + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(mobileKeyPath); + InputStream fis = resource.getInputStream(); + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = requestDTO.getMessage().toString(); + String data = requestHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ->"+e.getMessage())); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(isMsgEncrypted){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException | InvalidAlgorithmParameterException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusRequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = requestHeaderString+messageString; + log.info("Signature ->"+requestSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException | InvalidAlgorithmParameterException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusRequestMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + } + } + requestDTO.setMessage(messageDTO); + return messageDTO; + } + + @Override + public void validateStatusRequestDTO(StatusRequestDTO statusRequestDTO) throws IOException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] json = objectMapper.writeValueAsBytes(statusRequestDTO.getMessage()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusRequestDTO.getHeader()); + g2pcUtilityClass.validateResponse(headerString , CoreConstants.REQUEST_HEADER); + String messageString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusRequestMessageDTO); + g2pcUtilityClass.validateResponse(messageString,CoreConstants.STATUS_REQUEST); + } + } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml index b7812ec..70e7678 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml @@ -8,9 +8,9 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: not_set + url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp2?currentSchema=g2pc username: postgres - password: not_set + password: K6tnrCU0wqXOwPW hikari: data-source-properties: @@ -52,39 +52,80 @@ server: spring.data.redis: repositories.enabled: false - host: not_set - password: not_set + host: 3.109.26.38 + password: cdpi@99223 port: 6377 client: api_urls: client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - mno_info_url: "http://localhost:9200/private/api/v1/registry/mobile/info" + mno_info_url: "http://localhost:9005/private/api/v1/registry/mobile/info" + client_status_api: "http://localhost:8000/private/api/v1/registry/on-status" keycloak: - data-consumer: + from-dc: url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" client-id: dc-client - client-secret: not_set - mobile: - admin-url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - get-client-url: https://g2pc-dp2-lab.cdpi.dev/auth/admin/realms/dp-mobile/clients - realm: dp-mobile - url: https://g2pc-dp2-lab.cdpi.dev/auth - admin: - realm: - client-id: admin-cli - client-secret: not_set - client-id: admin-cli - client-secret: not_set + client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + dp: + url: https://g2pc-dp2-lab.cdpi.dev/auth username: admin - password: not_set + password: cdpi@9924 + master: + url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://g2pc-dp2-lab.cdpi.dev/auth/admin/realms/dp-mobile/clients + clientId: admin-cli + clientSecret: awR1cImNnsiOnBeWpZQWUfKFr1gOopBo + client: + url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token + realm: dp-mobile + clientId: dp-mobile-client + clientSecret: d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg crypto: - consumer: + to_dc: support_encryption: true support_signature: true - mobile: + password: "mobile_on_search" + key_path: "classpath:mobile_on_search.p12" + id: MOBILE + from_dc: support_encryption: true support_signature: true + password: "mobile_search" + key_path: "classpath:mobile_search.p12" +dashboard: + dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + cors_origin_url: "http://localhost:8000" + +sftp: + listener: + host: localhost + port: 2226 + user: cdpi + password: 1234 + remote: + inbound_directory: /inbound + outbound_directory: /outbound + local: + inbound_directory: /home/prihir/g2pc/dp2/inbound + outbound_directory: /home/prihir/g2pc/dp2/outbound + + dc: + host: localhost + port: 2224 + user: cdpi + password: 1234 + remote: + outbound_directory: /outbound + +sunbird: + api_urls: + response_data_api: http://3.109.26.38:8083/api/v1/Msg_Tracker + response_tracker_api: http://3.109.26.38:8083/api/v1/Txn_Tracker + enabled: false + elasticsearch: + host: 3.109.26.38 + port: 9200 + scheme: http \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java index dd69f06..bf76d24 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/test/java/g2pc/ref/mno/regsvc/G2pcRefMnoRegsvcApplicationTests.java @@ -23,4 +23,6 @@ void testResponseScheduler() throws IOException { scheduler.responseScheduler(); } + + } From c3361692605bdd08445ad07a9977f480c7b7fd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:47:53 +0530 Subject: [PATCH 47/53] g2pc-ref-mno-regsvc changes. --- .../ref/mno/regsvc/config/CorsConfig.java | 21 ++++ .../ref/mno/regsvc/constants/Constants.java | 2 + .../controller/sftp/DcSftpListener.java | 93 ++++++++++++++++++ .../g2pc/ref/mno/regsvc/dto/SftpDpData.java | 19 ++++ .../regsvc/dto/request/QueryMobileDTO.java | 2 - .../dto/request/QueryParamsMobileDTO.java | 1 - .../regsvc/dto/response/DataMobileDTO.java | 3 - .../service/DpSftpPushUpdateService.java | 10 ++ .../service/MobileResponseBuilderService.java | 4 +- .../service/MobileValidationService.java | 16 +-- .../DpSftpPushUpdateServiceImpl.java | 50 ++++++++++ .../src/main/resources/1693731657.p12 | Bin 0 -> 6013 bytes .../src/main/resources/mobile_on_search.p12 | Bin 0 -> 2365 bytes .../src/main/resources/mobile_search.p12 | Bin 0 -> 2365 bytes .../src/main/resources/private.p12 | Bin 0 -> 2547 bytes .../src/main/webapp/WEB-INF/jsp/dashboard.jsp | 29 +++++- .../src/main/webapp/resources/css/styles.css | 25 ----- 17 files changed, 233 insertions(+), 42 deletions(-) create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/CorsConfig.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/sftp/DcSftpListener.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/SftpDpData.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/DpSftpPushUpdateService.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/1693731657.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/mobile_on_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/mobile_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/private.p12 delete mode 100644 g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/CorsConfig.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/CorsConfig.java new file mode 100644 index 0000000..6dab4e5 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/config/CorsConfig.java @@ -0,0 +1,21 @@ +package g2pc.ref.mno.regsvc.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Value("${dashboard.cors_origin_url}") + private String corsOriginUrl; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins(corsOriginUrl) + .allowedMethods("*") + .allowedHeaders("*"); + } +} \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java index 289903c..1f96fbd 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/constants/Constants.java @@ -19,5 +19,7 @@ private Constants() { public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; + public static final String STATUS_CACHE_KEY_STRING = "status-request-farmer-"; + public static final String STATUS_CACHE_KEY_SEARCH_STRING = "status-request-farmer*"; } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/sftp/DcSftpListener.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/sftp/DcSftpListener.java new file mode 100644 index 0000000..b3b2887 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/controller/sftp/DcSftpListener.java @@ -0,0 +1,93 @@ +package g2pc.ref.mno.regsvc.controller.sftp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.mno.regsvc.constants.Constants; +import g2pc.ref.mno.regsvc.service.MobileValidationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@Configuration +@Slf4j +public class DcSftpListener { + + @Value("${sunbird.enabled}") + private Boolean sunbirdEnabled; + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpLocalDirectoryInbound; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpLocalDirectoryOutbound; + + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + MobileValidationService mobileValidationService; + + @SuppressWarnings("unchecked") + @ServiceActivator(inputChannel = "sftpInbound") + public void handleMessageInbound(Message message) { + try { + File file = message.getPayload(); + log.info("Received Message from inbound directory of dp-2: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file) && file.getName().contains(".json")) { + String requestString = new String(Files.readAllBytes(file.toPath())); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO; + + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + + messageDTO = mobileValidationService.signatureValidation(metaData, requestDTO); + requestDTO.setMessage(messageDTO); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + try { + mobileValidationService.validateRequestDTO(requestDTO); + requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_SFTP, sunbirdEnabled); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); + } catch (Exception e) { + log.error("Error: ", e); + } + } + + @ServiceActivator(inputChannel = "errorChannel") + public void handleError(Message message) { + Throwable error = (Throwable) message.getPayload(); + log.error("Handling ERROR: {}", error.getMessage()); + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/SftpDpData.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/SftpDpData.java new file mode 100644 index 0000000..7aafbd6 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/SftpDpData.java @@ -0,0 +1,19 @@ +package g2pc.ref.mno.regsvc.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SftpDpData { + + private String dpType; + + private String messageTs; + + private String transactionId; + + private String fileName; +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java index b06fb99..0c0a981 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryMobileDTO.java @@ -2,8 +2,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeName; -import g2pc.core.lib.dto.common.message.request.QueryDTO; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java index f13d592..b0319f1 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/request/QueryParamsMobileDTO.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeName; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java index b99adc6..882fa81 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/dto/response/DataMobileDTO.java @@ -1,13 +1,10 @@ package g2pc.ref.mno.regsvc.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeName; -import g2pc.core.lib.dto.common.message.response.DataDTO; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; - import java.util.List; @Getter diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/DpSftpPushUpdateService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/DpSftpPushUpdateService.java new file mode 100644 index 0000000..3b71fd6 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/DpSftpPushUpdateService.java @@ -0,0 +1,10 @@ +package g2pc.ref.mno.regsvc.service; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface DpSftpPushUpdateService { + + SseEmitter register(); + + void pushUpdate(Object update); +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java index 2035f45..093334b 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileResponseBuilderService.java @@ -1,9 +1,7 @@ package g2pc.ref.mno.regsvc.service; -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.request.QueryDTO; import java.io.IOException; import java.util.List; diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java index aeb9edc..21808a2 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/service/MobileValidationService.java @@ -1,10 +1,11 @@ package g2pc.ref.mno.regsvc.service; -import com.fasterxml.jackson.core.JsonProcessingException; -import g2pc.core.lib.dto.common.message.request.QueryDTO; -import g2pc.core.lib.dto.common.message.request.RequestDTO; -import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; import g2pc.core.lib.exceptions.G2pcValidationException; +import java.io.IOException; import java.util.Map; /** @@ -14,7 +15,10 @@ public interface MobileValidationService { void validateRequestDTO (RequestDTO requestDTO) throws Exception; - void validateQueryDto (QueryDTO queryMobileDTO) throws G2pcValidationException, JsonProcessingException; - RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; + + StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception ; + + void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, G2pcValidationException; + } diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java new file mode 100644 index 0000000..dd07371 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/java/g2pc/ref/mno/regsvc/serviceimpl/DpSftpPushUpdateServiceImpl.java @@ -0,0 +1,50 @@ +package g2pc.ref.mno.regsvc.serviceimpl; + +import g2pc.ref.mno.regsvc.dto.SftpDpData; +import g2pc.ref.mno.regsvc.service.DpSftpPushUpdateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +public class DpSftpPushUpdateServiceImpl implements DpSftpPushUpdateService { + + private final List emitters = new ArrayList<>(); + + public SseEmitter register() { + int minutes = 15; + long timeout = (long) minutes * 60000; + SseEmitter emitter = new SseEmitter(timeout); + this.emitters.add(emitter); + emitter.onCompletion(() -> this.emitters.remove(emitter)); + emitter.onTimeout(() -> this.emitters.remove(emitter)); + log.info("SSE emitter registered" + emitter); + return emitter; + } + + public void pushUpdate(Object update) { + List deadEmitters = new ArrayList<>(); + this.emitters.forEach(emitter -> { + try { + emitter.send(update); + } catch (IOException e) { + deadEmitters.add(emitter); + } + }); + this.emitters.removeAll(deadEmitters); + } + + public SftpDpData getSftpDpData(String dpType, String messageTs, String transactionId, String filename){ + SftpDpData sftpDpData = new SftpDpData(); + sftpDpData.setDpType(dpType); + sftpDpData.setMessageTs(messageTs); + sftpDpData.setTransactionId(transactionId); + sftpDpData.setFileName(filename); + return sftpDpData; + } +} diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/1693731657.p12 b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/1693731657.p12 new file mode 100644 index 0000000000000000000000000000000000000000..9a827995e886b9ea6e2ffa05e3563f75d41f6ad3 GIT binary patch literal 6013 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 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/mobile_on_search.p12 b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/mobile_on_search.p12 new file mode 100644 index 0000000000000000000000000000000000000000..43fe9ea8f40af2758e660706e8749dc418160506 GIT binary patch literal 2365 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 jWogr#r!E8te@FGEGUGJp0s;sC8$Dp4 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/private.p12 b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/private.p12 new file mode 100644 index 0000000000000000000000000000000000000000..c067bffa1a9b5b24e01686440e6eb38f44699e81 GIT binary patch literal 2547 zcmai$X*3iH8^_Itk&!j~*w+k08pe{C5=o&UvNUQYX+{jP(-@&7g+Z?+WeE{Ymc|ld z=$1XwbupRj>x|UR?sd=mp89w{Jm)$8|NlJa`SkqGgQP+^KmZ6yg%8J zfG1RFCYTCM+qYAYR50*&#QTH_T0>Gn`$IhYZSHphKwkJIATZ=xrr6e@^P zH%r^wIxQP#`i-&aS=W$nm(?I-l_$@zy4Pph>+}fIme})T=q34{XhRd91uY_+8JSqq zyo;$VY9xJ5?9km93FAYBgi7kE(UDwjoPULfF^9c#=`DyLS0sxXd8#lF*+35XaW3Ny zH>myR? zype(#xCOd}EAJ{OyEk{*@z&VlO6M`d>mx(7q>Z7-yk%XI>OkjE$+x2^S6rO{I)_zt z4J&8`jq{K%$JpM{1xv4^%d+lG!yheRCj=$9=9-#oDQ5x^{?%PWYb>Dpv7w>8H|Cjm zpA9Ma`NVY*LbsH<3R!s6ncY$tC?=h!+N^MC(a-pe){4sBg}0*9{fo7`f_Qi$9EZ+Y(D+Cg8kLcf!{He0E?j0{Nks#6GO}R3rWrFMK6b zmw47o%DweS7Y+)1V*Z**lj;CQq2)@(dwYQ;$)f$Sld3>U%I$z}8N^G?#BroB)RNH? z+rt{gOJjQAA;RMbNpit_@}7DyyHzT$d~R;;v=n6n{MM#ls|uQEG1E6@@zn}GimHs7 z6Cvg+%Bd=1$R#0TKz#|hPI$1UF4BSoECU+qFL)R2cVALpaEz}{?Q zuj4*J^hZZB23j#a>KBY-;Ale$-%`RuS&*;bg56Y+0(81G3foVn5(@W=qfR$)&$~1vO%T3h{&^ z%L_@F#61X-3c3C3p8o*^ z|M--`RzdC~9z%Uo+x033ctNqhln;xcojU>sK1>({mH4>3FEOioL*&Oj<}|Ll2$Xph z*2~KZjgu$iI6S2&tpk5?S6J>1=l^mMx95tv#b_TMv^0*(df>Br^>c)oU4Qkgg7kc;d2*5{IgM*Z@HCSx{Bp*?cEmBQC&H**fHLu zHkn|9GG#>345`sY)-o|yUfYw`xyI=?tJMR-KZhxVP1q_=U<+6Ui{%{>|7kYIEmv5 z#;j%;z)du#nZD?QdqpH)bHp&g9_^8m*;r5ZUCU#(HJ+uu$pJTb5v|cE$+)x~O(f{4 z{i&jpM2wKv^i;W0qX?m>eHaCv{UYi@Qr_q%gA~L&s#zOP~9N zRgrphrmgK{^Yvy9O8jh$>rkr)W$Z*d9~nqwDoK9v3e(o)cb>2VG1T1(hxdU%%WhjqjGw>F#X_?&qiq~6tU zMWmmkL|yk&X)YA_Z20->^>y2*#}RA<$1z$VmZjbKymS6PGdF9GMohAw9oHA|9tv#(i6TkD?_$oo#P?YE5J4S*_6hF6eYl)t>gr0HrD z((-Bo+RWdP1OQb#$_Ul~&0w6qK#SDk3 uNiVYG!VsooVaCX^wiOm_+jQ(iBL@0C!2EXfk5QM*3KaiA?SBC%Gnn`Q literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp index 16c7bf3..222cc2a 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/WEB-INF/jsp/dashboard.jsp @@ -4,12 +4,37 @@ Dashboard - +
diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css deleted file mode 100644 index 73abba4..0000000 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/webapp/resources/css/styles.css +++ /dev/null @@ -1,25 +0,0 @@ -body { - background-color: rgba(0, 0, 0, 0.1); - color: #fff; - display: flex; - justify-content: space-around; - align-items: center; - height: 100vh; - margin: 0; - padding: 0; - flex-direction: column; -} - -.panel { - width: 99%; - height: 97%; - display: flex; - justify-content: center; - align-items: center; -} - -.contentPanel { - background-color: lightyellow; - border: 1px solid #fff; -} - From 8cd4a2ae0732125ea584b417c42625bad51d5158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:53:43 +0530 Subject: [PATCH 48/53] g2pc-core-lib-changes. --- g2pc-core-lib/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/g2pc-core-lib/pom.xml b/g2pc-core-lib/pom.xml index 64bb9c4..a7878aa 100644 --- a/g2pc-core-lib/pom.xml +++ b/g2pc-core-lib/pom.xml @@ -91,6 +91,7 @@ commons-lang3 3.14.0 + com.jcraft jsch From e8b7a5ce02fc2fe13c497fc0c5841aa192c90313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 13:57:40 +0530 Subject: [PATCH 49/53] g2pc-dc-core-lib application.yml changes. --- .../src/main/resources/application.yml | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/g2pc-dc-core-lib/src/main/resources/application.yml b/g2pc-dc-core-lib/src/main/resources/application.yml index 4d25e7a..9fc6158 100644 --- a/g2pc-dc-core-lib/src/main/resources/application.yml +++ b/g2pc-dc-core-lib/src/main/resources/application.yml @@ -4,9 +4,9 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: stringtype: unspecified @@ -41,39 +41,39 @@ spring.data.redis: sftp: listener: - host: localhost - port: 2224 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound - outbound_directory: /outbound + inbound_directory: not_set + outbound_directory: not_set local: - inbound_directory: /home/prihir/g2pc/dc/inbound - outbound_directory: /home/prihir/g2pc/dc/outbound + inbound_directory: not_set + outbound_directory: not_set dp1: - host: localhost - port: 2225 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound + inbound_directory: not_set dp2: - host: localhost - port: 2226 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound + inbound_directory: not_set sunbird: api_urls: - response_data_api: http://3.109.26.38:8083/api/v1/Response_Data - response_tracker_api: http://3.109.26.38:8083/api/v1/Response_Tracker + response_data_api: not_set + response_tracker_api: not_set enabled: false elasticsearch: - host: 3.109.26.38 - port: 9200 - scheme: http + host: not_set + port: not_set + scheme: not_set From a383d63e77650a0e5cad92ab8b40ea008a3ca22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 14:02:48 +0530 Subject: [PATCH 50/53] g2pc-dp-core-lib application.yml changes --- g2pc-dp-core-lib/.gitignore | 36 ++ g2pc-dp-core-lib/README.md | 25 + g2pc-dp-core-lib/pom.xml | 70 +++ .../lib/G2pcDpCoreLibraryApplication.java | 16 + .../dp/core/lib/constants/DpConstants.java | 13 + .../dp/core/lib/entity/MsgTrackerEntity.java | 85 +++ .../dp/core/lib/entity/TxnTrackerEntity.java | 77 +++ .../lib/repository/MsgTrackerRepository.java | 12 + .../lib/repository/TxnTrackerRepository.java | 13 + .../lib/service/RequestHandlerService.java | 14 + .../lib/service/ResponseBuilderService.java | 46 ++ .../core/lib/service/TxnTrackerDbService.java | 37 ++ .../lib/service/TxnTrackerRedisService.java | 16 + .../RequestHandlerServiceImpl.java | 90 +++ .../ResponseBuilderServiceImpl.java | 536 ++++++++++++++++++ .../serviceimpl/TxnTrackerDbServiceImpl.java | 297 ++++++++++ .../TxnTrackerRedisServiceImpl.java | 76 +++ .../g2pc/dp/core/lib/utils/DpCommonUtils.java | 105 ++++ .../src/main/resources/application.yml | 111 ++++ .../G2pcDpCoreLibraryApplicationTests.java | 13 + 20 files changed, 1688 insertions(+) create mode 100644 g2pc-dp-core-lib/.gitignore create mode 100644 g2pc-dp-core-lib/README.md create mode 100644 g2pc-dp-core-lib/pom.xml create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java create mode 100644 g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java create mode 100644 g2pc-dp-core-lib/src/main/resources/application.yml create mode 100644 g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java diff --git a/g2pc-dp-core-lib/.gitignore b/g2pc-dp-core-lib/.gitignore new file mode 100644 index 0000000..e23d88f --- /dev/null +++ b/g2pc-dp-core-lib/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +mvnw +mvnw.cmd +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +.idea/ + diff --git a/g2pc-dp-core-lib/README.md b/g2pc-dp-core-lib/README.md new file mode 100644 index 0000000..e6b8cdb --- /dev/null +++ b/g2pc-dp-core-lib/README.md @@ -0,0 +1,25 @@ +# G2pc DP Core Lib + +## Overview +### Json Schema validations +* In this project Json schema input stream return by parent g2p-core lib. +* In RequestHandlerServiceImpl class input stream is called and will validate the Request DTO header and message +* If any thing doesn't match with the json schema exception handling is written for same. +* Below are some reference code for same. + +```` + InputStream schemaStream = commonUtils.getRequestMessageString(); + JsonNode jsonNodeMessage = objectMapper.readTree(messageString); + JsonSchema schemaMessage = null; + if(schemaStream !=null){ + schemaMessage = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). + getSchema(schemaStream); + } + Set errorMessage = schemaMessage.validate(jsonNodeMessage); + List errorcombinedMessage= new ArrayList<>(); + for (ValidationMessage error : errorMessage){ + log.info("Validation errors" + error ); + errorcombinedMessage.add(new G2pcError("",error.getMessage())); + + } +```` \ No newline at end of file diff --git a/g2pc-dp-core-lib/pom.xml b/g2pc-dp-core-lib/pom.xml new file mode 100644 index 0000000..232de7e --- /dev/null +++ b/g2pc-dp-core-lib/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.12 + + + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + g2pc-dp-core-library + g2pc-dp-core-library + + 17 + + + + org.springframework.boot + spring-boot-starter + + + + org.projectlombok + lombok + true + + + g2pc.core.lib + g2pc-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + redis.clients + jedis + 4.4.3 + jar + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.networknt + json-schema-validator + 1.0.57 + + + diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java new file mode 100644 index 0000000..21fedb7 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplication.java @@ -0,0 +1,16 @@ +package g2pc.dp.core.lib; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({"g2pc.core.lib","g2pc.dp.core.lib","g2pc.dp.core.lib.serviceimpl", + "g2pc.dp.core.lib.repository"}) +public class G2pcDpCoreLibraryApplication { + + public static void main(String[] args) { + SpringApplication.run(G2pcDpCoreLibraryApplication.class, args); + } + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java new file mode 100644 index 0000000..d4669c2 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/constants/DpConstants.java @@ -0,0 +1,13 @@ +package g2pc.dp.core.lib.constants; + +public class DpConstants { + + private DpConstants() { + } + + public static final String SEARCH_REQUEST_RECEIVED = "Search request received"; + + public static final String RECORD_NOT_FOUND = "record_not_found"; + + public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java new file mode 100644 index 0000000..466da4c --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/MsgTrackerEntity.java @@ -0,0 +1,85 @@ +package g2pc.dp.core.lib.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "msg_tracker") +public class MsgTrackerEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "version") + private String version; + + @Column(name = "message_id") + private String messageId; + + @Column(name = "message_ts") + private String messageTs; + + @Column(name = "action") + private String action; + + @Column(name = "status") + private String status; + + @Column(name = "status_reason_code") + private String statusReasonCode; + + @Column(name = "status_reason_message") + private String statusReasonMessage; + + @Column(name = "total_count") + private Integer totalCount; + + @Column(name = "completed_count") + private Integer completedCount; + + @Column(name = "sender_id") + private String senderId; + + @Column(name = "receiver_id") + private String receiverId; + + @Column(name = "is_msg_encrypted") + private Boolean isMsgEncrypted; + + @Column(name = "meta") + private String meta; + + @Column(name = "transaction_id") + private String transactionId; + + @Column(name = "correlation_id") + private String correlationId; + + @Column(name = "locale") + private String locale; + + @Column(name = "raw_message") + private String rawMessage; + + @Column(name = "protocol") + private String protocol; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; + + @ToString.Exclude + @OneToMany(mappedBy = "msgTrackerEntity", cascade = CascadeType.ALL) + private List txnTrackerEntityList = new ArrayList<>(); +} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java new file mode 100644 index 0000000..0047363 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/entity/TxnTrackerEntity.java @@ -0,0 +1,77 @@ +package g2pc.dp.core.lib.entity; + +import jakarta.persistence.*; +import lombok.*; +import java.sql.Timestamp; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "txn_tracker") +public class TxnTrackerEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "reference_id") + private String referenceId; + + @Column(name = "consent") + private Boolean consent; + + @Column(name = "authorize") + private Boolean authorize; + + @Column(name = "timestamp") + private String timestamp; + + @Column(name = "status") + private String status; + + @Column(name = "status_reason_code") + private String statusReasonCode; + + @Column(name = "status_reason_message") + private String statusReasonMessage; + + @Column(name = "version") + private String version; + + @Column(name = "reg_type") + private String regType; + + @Column(name = "reg_sub_type") + private String regSubType; + + @Column(name = "query_type") + private String queryType; + + @Column(name = "query") + private String query; + + @Column(name = "reg_record_type") + private String regRecordType; + + @Column(name = "no_of_records") + private Integer noOfRecords; + + @Column(name = "txn_type") + private String txnType; + + @Column(name = "txn_status") + private String txnStatus; + + @Column(insertable = false, updatable = false) + private Timestamp createdDate; + + @Column(insertable = false) + private Timestamp lastUpdatedDate; + + @ManyToOne(targetEntity = MsgTrackerEntity.class, cascade = CascadeType.ALL) + @JoinColumn(name = "msg_tracker_id", referencedColumnName = "id") + private MsgTrackerEntity msgTrackerEntity; +} \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java new file mode 100644 index 0000000..df89647 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/MsgTrackerRepository.java @@ -0,0 +1,12 @@ +package g2pc.dp.core.lib.repository; + +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository +public interface MsgTrackerRepository extends JpaRepository { + + Optional findByTransactionId(String transactionId); +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java new file mode 100644 index 0000000..5e0507c --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/repository/TxnTrackerRepository.java @@ -0,0 +1,13 @@ +package g2pc.dp.core.lib.repository; + +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository +public interface TxnTrackerRepository extends JpaRepository { + + Optional findByMsgTrackerEntity(MsgTrackerEntity msgTrackerEntity); +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java new file mode 100644 index 0000000..5942f15 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/RequestHandlerService.java @@ -0,0 +1,14 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.AcknowledgementDTO; + +public interface RequestHandlerService { + + AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey, String protocol, Boolean sunbirdEnabled) throws Exception; + + + public AcknowledgementDTO buildCacheStatusRequest(String statusRequestData, String cacheKey,String protocol) throws JsonProcessingException; + + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java new file mode 100644 index 0000000..181f289 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java @@ -0,0 +1,46 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import kong.unirest.UnirestException; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.util.List; + +public interface ResponseBuilderService { + + ResponseMessageDTO buildResponseMessage(String transactionId, List searchResponseDTOList); + + ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity); + + String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException; + + G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , InputStream fis , String txnType) throws Exception; + + public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; + + public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException; + + public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, UnirestException, ParseException; + + StatusResponseMessageDTO buildStatusResponseMessage(StatusRequestMessageDTO statusRequestMessageDTO); + + String buildStatusResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, StatusResponseMessageDTO statusResponseMessageDTO) throws JsonProcessingException; + + G2pcError sendOnSearchResponseSftp(String responseString, InputStream fis , String txnType, SftpServerConfigDTO sftpServerConfigDTO) throws Exception; + + public G2pcError buildOnSearchScheduler(List refRecordsStringsList , CacheDTO cacheDTOO, Boolean sunbirdEnabled) throws Exception ; + + public G2pcError buildOnStatusScheduler(CacheDTO cacheDTO) throws Exception ; + + } diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java new file mode 100644 index 0000000..e2ed67d --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerDbService.java @@ -0,0 +1,37 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; + +import java.io.IOException; +import java.util.List; + +public interface TxnTrackerDbService { + + MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException; + + int getRecordCount(Object records); + + List getUpdatedSearchResponseList(RequestDTO requestDTO, + List refRecordsStringsList, + String protocol, + Boolean sunbirdEnabled) throws IOException; + + DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity); + + SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO); + + MsgTrackerEntity saveStatusRequestDetails(StatusRequestDTO statusRequestDTO) throws JsonProcessingException; + + void updateStatusResponseDetails(ResponseMessageDTO responseMessageDTO, String transactionId); + + void updateMessageTrackerStatusDb(String transactionId); + + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java new file mode 100644 index 0000000..55e1b2d --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/TxnTrackerRedisService.java @@ -0,0 +1,16 @@ +package g2pc.dp.core.lib.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import java.util.List; + +public interface TxnTrackerRedisService { + + void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException; + + void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException; + + List getCacheKeys(String cacheKeySearchString); + + String getRequestData(String cacheKey) throws JsonProcessingException; +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java new file mode 100644 index 0000000..9b34479 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/RequestHandlerServiceImpl.java @@ -0,0 +1,90 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class RequestHandlerServiceImpl implements RequestHandlerService { + + @Autowired + private CommonUtils commonUtils; + + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + @Autowired + private TxnTrackerDbService txnTrackerDbService; + + /** + * Build a request to save in Redis cache + * @param requestData requestData to be stored in redis cache + * @param cacheKey cacheKey for which data to be stored + * @return Acknowledgement + */ + @Override + public AcknowledgementDTO buildCacheRequest(String requestData, String cacheKey, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + log.info("Request saved in cache with status pending"); + CacheDTO cacheDTO = new CacheDTO(); + cacheDTO.setData(requestData); + cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); + cacheDTO.setProtocol(protocol); + cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); + txnTrackerDbService.saveRequestDetails(requestDTO, protocol,sunbirdEnabled); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + return acknowledgementDTO; + } + + + + /** + * @param statusRequestData request data to store + * @param cacheKey cacheKey for which data storing + * @param protocol protocol to store in cache + * @return AcknowledgementDTO + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public AcknowledgementDTO buildCacheStatusRequest(String statusRequestData, String cacheKey, String protocol) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + log.info("Request saved in cache with status pending"); + CacheDTO cacheDTO = new CacheDTO(); + cacheDTO.setData(statusRequestData); + cacheDTO.setStatus(HeaderStatusENUM.PDNG.toValue()); + cacheDTO.setProtocol(protocol); + cacheDTO.setCreatedDate(CommonUtils.getCurrentTimeStamp()); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + StatusRequestDTO requestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + txnTrackerRedisService.saveRequestDetails(cacheDTO, cacheKey); + txnTrackerDbService.saveStatusRequestDetails(requestDTO); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + acknowledgementDTO.setMessage(DpConstants.SEARCH_REQUEST_RECEIVED); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + + return acknowledgementDTO; + } + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java new file mode 100644 index 0000000..6a5c927 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java @@ -0,0 +1,536 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.constants.G2pSecurityConstants; +import g2pc.core.lib.constants.SftpConstants; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.MetaDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.common.security.G2pTokenResponse; +import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.dto.search.message.request.QueryDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseDTO; +import g2pc.core.lib.dto.status.message.response.StatusResponseMessageDTO; +import g2pc.core.lib.dto.status.message.response.TxnStatusResponseDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.enums.StatusTransactionTypeEnum; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.service.AsymmetricSignatureService; +import g2pc.core.lib.security.service.G2pEncryptDecrypt; +import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.core.lib.service.SftpHandlerService; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; +import g2pc.dp.core.lib.repository.MsgTrackerRepository; +import g2pc.dp.core.lib.repository.TxnTrackerRepository; +import g2pc.dp.core.lib.service.ResponseBuilderService; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; +import g2pc.dp.core.lib.utils.DpCommonUtils; +import kong.unirest.HttpResponse; +import kong.unirest.UnirestException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.*; + +@Service +@Slf4j +public class ResponseBuilderServiceImpl implements ResponseBuilderService { + + @Autowired + private RedisTemplate redisTemplate; + + @Value("${crypto.to_dc.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.to_dc.support_signature}") + private boolean isSign; + + @Value("${crypto.to_dc.password}") + private String p12Password; + + @Value("${crypto.to_dc.id}") + private String dpId; + + @Value("${crypto.to_dc.key_path}") + private String farmerKeyPath; + + @Value("${client.api_urls.client_search_api}") + String onSearchURL; + + @Value("${client.api_urls.client_status_api}") + String onStatusURL; + + @Value("${keycloak.from-dc.client-id}") + private String dcClientId; + + @Value("${keycloak.from-dc.client-secret}") + private String dcClientSecret; + + @Value("${keycloak.from-dc.url}") + private String keyClockClientTokenUrl; + + @Autowired + G2pUnirestHelper g2pUnirestHelper; + + @Autowired + G2pEncryptDecrypt encryptDecrypt; + + @Autowired + G2pTokenService g2pTokenService; + + @Autowired + AsymmetricSignatureService asymmetricSignatureService; + + @Autowired + MsgTrackerRepository msgTrackerRepository; + + @Autowired + TxnTrackerRepository txnTrackerRepository; + + @Autowired + TxnTrackerDbService txnTrackerDbService; + + @Autowired + private SftpHandlerService sftpHandlerService; + + + @Autowired + private TxnTrackerRedisService txnTrackerRedisService; + + + @Autowired + private ResourceLoader resourceLoader; + + @Autowired + private DpCommonUtils dpCommonUtils; + + /** + * Get response header + * + * @param msgTrackerEntity msgTrackerEntity used to create ResponseHeaderFto + * @return ResponseHeaderDTO + */ + @Override + public ResponseHeaderDTO getResponseHeaderDTO(MsgTrackerEntity msgTrackerEntity) { + ResponseHeaderDTO headerDTO = new ResponseHeaderDTO(); + headerDTO.setVersion(msgTrackerEntity.getVersion()); + headerDTO.setMessageId(msgTrackerEntity.getMessageId()); + headerDTO.setMessageTs(msgTrackerEntity.getMessageTs()); + headerDTO.setAction(msgTrackerEntity.getAction()); + headerDTO.setSenderId(msgTrackerEntity.getSenderId()); + headerDTO.setReceiverId(msgTrackerEntity.getReceiverId()); + headerDTO.setIsMsgEncrypted(msgTrackerEntity.getIsMsgEncrypted()); + headerDTO.setStatus(msgTrackerEntity.getStatus()); + headerDTO.setStatusReasonCode(msgTrackerEntity.getStatusReasonCode()); + headerDTO.setStatusReasonMessage(msgTrackerEntity.getStatusReasonMessage()); + headerDTO.setTotalCount(msgTrackerEntity.getTotalCount()); + headerDTO.setCompletedCount(msgTrackerEntity.getCompletedCount()); + Map metaMap = new HashMap<>(); + MetaDTO metaDTO = new MetaDTO(); + metaDTO.setData(metaMap); + headerDTO.setMeta(metaDTO); + return headerDTO; + } + + /** + * Build a response message + * + * @param transactionId transactionId to build response message + * @param searchResponseDTOList list of searchResponseDto to store in response message + * @return ResponseMessageDTO + */ + @Override + public ResponseMessageDTO buildResponseMessage(String transactionId, List searchResponseDTOList) { + ResponseMessageDTO messageDTO = new ResponseMessageDTO(); + messageDTO.setTransactionId(transactionId); + messageDTO.setCorrelationId(CommonUtils.generateUniqueId("C")); + messageDTO.setSearchResponse(searchResponseDTOList); + return messageDTO; + } + + /** + * Build a response string + * + * @param signatureString signature to store + * @param responseHeaderDTO response header + * @param messageDTO message + * @return response String + */ + @Override + public String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, + ResponseMessageDTO messageDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + ResponseDTO responseDTO = new ResponseDTO(); + responseDTO.setSignature(signatureString); + responseDTO.setHeader(responseHeaderDTO); + responseDTO.setMessage(messageDTO); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(responseDTO); + } + + /** + * @param responseString response string to store + * @param uri endpoint for transaction + * @param clientId keycloak clientId + * @param clientSecret keycloak clientSecret + * @param keyClockClientTokenUrl keycloak ClientTokenUrl + * @param fis .p12 file input stream + * @param txnType txnType + * @return G2pcError + * @throws Exception + */ + @Override + public G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl + , InputStream fis, String txnType) throws Exception { + log.info("Send on-search response"); + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + responseString = createSignature(isEncrypt, isSign, responseString, fis, txnType); + String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); + HttpResponse response = g2pUnirestHelper.g2pPost(uri) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + jwtToken) + .body(responseString) + .asString(); + log.info(txnType + "Response status = {}", response.getStatus()); + if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { + return new G2pcError(HttpStatus.INTERNAL_SERVER_ERROR.toString(), response.getBody()); + } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { + return new G2pcError(HttpStatus.UNAUTHORIZED.toString(), response.getBody()); + } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { + return new G2pcError(HttpStatus.BAD_REQUEST.toString(), response.getBody()); + } else if (response.getStatus() != HttpStatus.OK.value()) { + return new G2pcError("err.service.unavailable", response.getBody()); + } + return new G2pcError(HttpStatus.OK.toString(), response.getBody()); + } + + /** + * Method to store token in cache + * + * @param cacheKey cacheKey cache key for which data is storing + * @param tokenExpiryDto token expiry dto + * @throws JsonProcessingException might be thrown + */ + @Override + public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(tokenExpiryDto)); + } + + /** + * Method to get token stored in cache + * + * @param clientId client Id + * @return TokenExpiryDto tokenExpiryDto + * @throws JsonProcessingException might be thrown + */ + @Override + public TokenExpiryDto getTokenFromCache(String clientId) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + Set redisKeys = this.redisTemplate.keys(clientId); + List cacheKeysList = new ArrayList((Collection) Objects.requireNonNull(redisKeys)); + if (!cacheKeysList.isEmpty()) { + String cacheKey = cacheKeysList.get(0); + String tokenData = (String) this.redisTemplate.opsForValue().get(cacheKey); + TokenExpiryDto tokenExpiryDto = objectMapper.readerFor(TokenExpiryDto.class).readValue(tokenData); + return tokenExpiryDto; + } + return null; + } + + /** + * The method to get validated token + * + * @param keyCloakUrl keycloak url to validate token + * @param clientId client id + * @param clientSecret client secret + * @return String validated token + * @throws IOException IOException might be thrown + * @throws ParseException ParseException might be thrown + * @throws UnirestException UnirestException might be thrown + */ + @Override + public String getValidatedToken(String keyCloakUrl, String clientId, String clientSecret) throws IOException, ParseException, UnirestException { + TokenExpiryDto tokenExpiryDto = getTokenFromCache(clientId); + + String jwtToken = ""; + if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { + G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); + jwtToken = tokenResponse.getAccessToken(); + saveToken(clientId, g2pTokenService.createTokenExpiryDto(tokenResponse)); + } else { + jwtToken = tokenExpiryDto.getToken(); + } + return jwtToken; + } + + /** + * The method is to create signature + * + * @param isSign signature flag + * @param isEncrypt encryption flag + * @param responseString response string + * @return String signature + * @throws Exception Exception might be thrown + */ + @SuppressWarnings("unchecked") + private String createSignature(boolean isEncrypt, boolean isSign, String responseString + , InputStream fis, String txnType) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); + byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); + ResponseHeaderDTO responseHeaderDTO = (ResponseHeaderDTO) responseDTO.getHeader(); + String responseHeaderString = objectMapper.writeValueAsString(responseHeaderDTO); + String signature = null; + String messageString = ""; + if (txnType.equals(CoreConstants.DP_STATUS_URL)) { + StatusResponseMessageDTO messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + messageString = objectMapper.writeValueAsString(messageDTO); + } else { + ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); + messageString = objectMapper.writeValueAsString(messageDTO); + } + + if (isSign) { + if (isEncrypt) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); + responseDTO.getHeader().setIsMsgEncrypted(true); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, true); + responseDTO.getHeader().getMeta().setData(meta); + responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + byte[] asymmetricSignature = asymmetricSignatureService.sign(responseHeaderString + encryptedMessageString, fis, p12Password); + signature = Base64.getEncoder().encodeToString(asymmetricSignature); + log.info("Encrypted message ->" + encryptedMessageString); + log.info("Hashed Signature ->" + signature); + } else { + responseDTO.getHeader().setIsMsgEncrypted(false); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, true); + responseDTO.getHeader().getMeta().setData(meta); + responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); + byte[] asymmetricSignature = asymmetricSignatureService.sign(responseHeaderString + messageString, fis, p12Password); + signature = Base64.getEncoder().encodeToString(asymmetricSignature); + log.info("Hashed Signature ->" + signature); + } + } else { + if (isEncrypt) { + String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString, G2pSecurityConstants.SECRET_KEY); + responseDTO.setMessage(encryptedMessageString); + responseDTO.getHeader().setIsMsgEncrypted(true); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, false); + responseDTO.getHeader().getMeta().setData(meta); + log.info("Encrypted message ->" + encryptedMessageString); + } else { + responseDTO.getHeader().setIsMsgEncrypted(false); + Map meta = (Map) responseDTO.getHeader().getMeta().getData(); + meta.put(CoreConstants.IS_SIGN, false); + responseDTO.getHeader().getMeta().setData(meta); + } + } + responseDTO.setSignature(signature); + responseString = objectMapper.writeValueAsString(responseDTO); + return responseString; + } + + /** + * @param statusRequestMessageDTO status request message dto + * @return StatusResponseMessageDTO + */ + @Override + public StatusResponseMessageDTO buildStatusResponseMessage(StatusRequestMessageDTO statusRequestMessageDTO) { + StatusResponseMessageDTO statusResponseMessageDTO = new StatusResponseMessageDTO(); + statusResponseMessageDTO.setTransactionId(statusRequestMessageDTO.getTransactionId()); + statusResponseMessageDTO.setCorrelationId(CommonUtils.generateUniqueId("C")); + String searchTransactionId = (String) statusRequestMessageDTO.getTxnStatusRequest().getAttributeValue(); + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(searchTransactionId); + MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); + List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); + TxnStatusResponseDTO txnStatusResponseDTO = new TxnStatusResponseDTO(); + + if (statusRequestMessageDTO.getTxnStatusRequest().getTxnType().equals(StatusTransactionTypeEnum.SEARCH.toValue())) { + txnStatusResponseDTO.setTxnType(StatusTransactionTypeEnum.ON_SUBSCRIBE.toValue()); + ResponseMessageDTO responseMessageDTO = new ResponseMessageDTO(); + responseMessageDTO.setTransactionId(msgTrackerEntity.getTransactionId()); + responseMessageDTO.setCorrelationId(msgTrackerEntity.getCorrelationId()); + List searchResponse = new ArrayList<>(); + for (TxnTrackerEntity txnTrackerEntity : txnTrackerEntityList) { + + SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); + searchResponseDTO.setReferenceId(txnTrackerEntity.getReferenceId()); + searchResponseDTO.setTimestamp(txnTrackerEntity.getTimestamp()); + searchResponseDTO.setStatus(txnTrackerEntity.getStatus()); + searchResponseDTO.setStatusReasonCode(txnTrackerEntity.getStatusReasonCode()); + searchResponseDTO.setStatusReasonMessage(txnTrackerEntity.getStatusReasonMessage()); + DataDTO dataDTO = new DataDTO(); + dataDTO.setVersion(txnTrackerEntity.getVersion()); + dataDTO.setRegType(txnTrackerEntity.getRegType()); + dataDTO.setRegSubType(txnTrackerEntity.getRegSubType()); + dataDTO.setRegRecordType(txnTrackerEntity.getRegRecordType()); + searchResponseDTO.setData(dataDTO); + searchResponse.add(searchResponseDTO); + } + responseMessageDTO.setSearchResponse(searchResponse); + txnStatusResponseDTO.setTxnStatus(responseMessageDTO); + txnTrackerDbService.updateStatusResponseDetails(responseMessageDTO, statusResponseMessageDTO.getTransactionId()); + } + + statusResponseMessageDTO.setTxnStatusResponse(txnStatusResponseDTO); + + return statusResponseMessageDTO; + } + + /** + * @param signatureString signature to be added in request + * @param responseHeaderDTO header dto ti be added in request + * @param statusResponseMessageDTO response message dto to be added + * @return response dto + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public String buildStatusResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, StatusResponseMessageDTO statusResponseMessageDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + StatusResponseDTO statusResponseDTO = new StatusResponseDTO(); + statusResponseDTO.setSignature(signatureString); + statusResponseDTO.setHeader(responseHeaderDTO); + statusResponseDTO.setMessage(statusResponseMessageDTO); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(statusResponseDTO); + } + + /** + * The method is to send on search response using sftp + * + * @param responseString required + * @return String + * @throws Exception jsonProcessingException might be thrown + */ + @Override + public G2pcError sendOnSearchResponseSftp(String responseString, InputStream fis, + String txnType, SftpServerConfigDTO sftpServerConfigDTO) throws Exception { + log.info("Send on-search response"); + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + responseString = createSignature(isEncrypt, isSign, responseString, fis, txnType); + + String originalFilename = UUID.randomUUID() + ".json"; + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), originalFilename); + Files.createFile(tempFile); + Files.write(tempFile, responseString.getBytes()); + + Boolean status = sftpHandlerService.uploadFileToSftp(sftpServerConfigDTO, tempFile.toString(), + sftpServerConfigDTO.getRemoteOutboundDirectory()); + Files.delete(tempFile); + G2pcError g2pcError; + if (Boolean.FALSE.equals(status)) { + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), SftpConstants.UPLOAD_ERROR_MESSAGE); + log.error(SftpConstants.UPLOAD_ERROR_MESSAGE); + } else { + g2pcError = new G2pcError(HttpStatus.OK.toString(), SftpConstants.UPLOAD_SUCCESS_MESSAGE); + } + return g2pcError; + } + + @Override + public G2pcError buildOnSearchScheduler(List refRecordsStringsList, CacheDTO cacheDTO, Boolean sunbirdEnabled) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + G2pcError g2pcError = new G2pcError(); + String protocol = cacheDTO.getProtocol(); + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class).readValue(cacheDTO.getData()); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveRequestDetails(requestDTO, protocol, sunbirdEnabled); + List searchResponseDTOList = txnTrackerDbService.getUpdatedSearchResponseList( + requestDTO, refRecordsStringsList, protocol, sunbirdEnabled); + ResponseHeaderDTO headerDTO = getResponseHeaderDTO(msgTrackerEntity); + ResponseMessageDTO responseMessageDTO = buildResponseMessage(transactionId, searchResponseDTOList); + Map meta = (Map) headerDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID, dpId); + requestDTO.getHeader().getMeta().setData(meta); + String responseString = buildResponseString("signature", + headerDTO, responseMessageDTO); + responseString = CommonUtils.formatString(responseString); + log.info("on-search response = {}", responseString); + Resource resource = resourceLoader.getResource(farmerKeyPath); + InputStream fis = resource.getInputStream(); + + if (protocol.equals(CoreConstants.SEND_PROTOCOL_HTTPS)) { + g2pcError = sendOnSearchResponse(responseString, onSearchURL, dcClientId, + dcClientSecret, keyClockClientTokenUrl, fis, CoreConstants.SEARCH_TXN_TYPE); + } else if (protocol.equals(CoreConstants.SEND_PROTOCOL_SFTP)) { + SftpServerConfigDTO sftpServerConfigDTO = dpCommonUtils.getSftpConfigForDp(); + g2pcError = sendOnSearchResponseSftp(responseString, fis, + CoreConstants.SEARCH_TXN_TYPE, sftpServerConfigDTO); + } + if (!Objects.requireNonNull(g2pcError).getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } + + + return g2pcError; + } + + @Override + public G2pcError buildOnStatusScheduler(CacheDTO cacheDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveStatusRequestDetails(statusRequestDTO); + ResponseHeaderDTO responseHeaderDTO = getResponseHeaderDTO(msgTrackerEntity); + + StatusResponseMessageDTO statusResponseMessageDTO = buildStatusResponseMessage(statusRequestMessageDTO); + + Map meta = (Map) responseHeaderDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID, dpId); + statusRequestDTO.getHeader().getMeta().setData(meta); + + String statusResponseString = buildStatusResponseString("signature", responseHeaderDTO, statusResponseMessageDTO); + statusResponseString = CommonUtils.formatString(statusResponseString); + log.info("on-status response = {}", statusResponseString); + Resource resource = resourceLoader.getResource(farmerKeyPath); + InputStream fis = resource.getInputStream(); + return sendOnSearchResponse(statusResponseString, onStatusURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, CoreConstants.DP_STATUS_URL); + + } +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java new file mode 100644 index 0000000..ac319ae --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerDbServiceImpl.java @@ -0,0 +1,297 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.request.SearchRequestDTO; +import g2pc.core.lib.dto.search.message.response.DataDTO; +import g2pc.core.lib.dto.search.message.response.ResponsePaginationDTO; +import g2pc.core.lib.dto.search.message.response.SearchResponseDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestDTO; +import g2pc.core.lib.dto.status.message.request.StatusRequestMessageDTO; +import g2pc.core.lib.dto.status.message.request.TxnStatusRequestDTO; +import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.enums.LocalesENUM; +import g2pc.core.lib.enums.QueryTypeEnum; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.constants.DpConstants; +import g2pc.dp.core.lib.entity.MsgTrackerEntity; +import g2pc.dp.core.lib.entity.TxnTrackerEntity; +import g2pc.dp.core.lib.repository.MsgTrackerRepository; +import g2pc.dp.core.lib.repository.TxnTrackerRepository; +import g2pc.dp.core.lib.service.TxnTrackerDbService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +@Slf4j +public class TxnTrackerDbServiceImpl implements TxnTrackerDbService { + + @Autowired + private MsgTrackerRepository msgTrackerRepository; + + @Autowired + private TxnTrackerRepository txnTrackerRepository; + + /** + * Save request details + * + * @param requestDTO requestDTO to save in Db + * @return request details entity + */ + @Override + public MsgTrackerEntity saveRequestDetails(RequestDTO requestDTO, String protocol, Boolean sunbirdEnabled) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + MsgTrackerEntity entity; + + HeaderDTO headerDTO = requestDTO.getHeader(); + RequestMessageDTO messageDTO = objectMapper.convertValue(requestDTO.getMessage(), RequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + if (msgTrackerEntityOptional.isEmpty()) { + MsgTrackerEntity msgTrackerEntity = new MsgTrackerEntity(); + msgTrackerEntity.setVersion(headerDTO.getVersion()); + msgTrackerEntity.setMessageId(headerDTO.getMessageId()); + msgTrackerEntity.setMessageTs(headerDTO.getMessageTs()); + msgTrackerEntity.setAction(headerDTO.getAction()); + msgTrackerEntity.setSenderId(headerDTO.getSenderId()); + msgTrackerEntity.setReceiverId(headerDTO.getReceiverId()); + msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); + msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(requestDTO)); + msgTrackerEntity.setProtocol(protocol); + + List searchRequestDTOList = messageDTO.getSearchRequest(); + for (SearchRequestDTO searchRequestDTO : searchRequestDTOList) { + TxnTrackerEntity txnTrackerEntity = new TxnTrackerEntity(); + txnTrackerEntity.setReferenceId(searchRequestDTO.getReferenceId()); + txnTrackerEntity.setTimestamp(searchRequestDTO.getTimestamp()); + txnTrackerEntity.setVersion(searchRequestDTO.getSearchCriteria().getVersion()); + txnTrackerEntity.setRegType(searchRequestDTO.getSearchCriteria().getRegType()); + txnTrackerEntity.setRegSubType(searchRequestDTO.getSearchCriteria().getRegSubType()); + txnTrackerEntity.setQueryType(searchRequestDTO.getSearchCriteria().getQueryType()); + txnTrackerEntity.setQuery(objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery())); + txnTrackerEntity.setCreatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setMsgTrackerEntity(msgTrackerEntity); + msgTrackerEntity.getTxnTrackerEntityList().add(txnTrackerEntity); + } + entity = msgTrackerRepository.save(msgTrackerEntity); + } else { + entity = msgTrackerEntityOptional.get(); + } + return entity; + } + + /** + * Get record count + * + * @param records required + * @return record count + */ + @SuppressWarnings("unchecked") + @Override + public int getRecordCount(Object records) { + Map objectMap = new ObjectMapper().convertValue(records, Map.class); + return objectMap.size(); + } + + /** + * Build a search response + * + * @param txnTrackerEntity txnTrackerEntity to build search response + * @param dataDTO dataDTO to build search response dto + * @return SearchResponseDTO + */ + @Override + public SearchResponseDTO buildSearchResponse(TxnTrackerEntity txnTrackerEntity, DataDTO dataDTO) { + ResponsePaginationDTO paginationDTO = new ResponsePaginationDTO(); + paginationDTO.setPageSize(10); + paginationDTO.setPageNumber(1); + paginationDTO.setTotalCount(100); + + SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); + searchResponseDTO.setReferenceId(txnTrackerEntity.getReferenceId()); + searchResponseDTO.setTimestamp(txnTrackerEntity.getTimestamp()); + searchResponseDTO.setStatus(txnTrackerEntity.getStatus()); + searchResponseDTO.setStatusReasonCode(txnTrackerEntity.getStatusReasonCode()); + searchResponseDTO.setStatusReasonMessage(txnTrackerEntity.getStatusReasonMessage()); + searchResponseDTO.setData(dataDTO); + searchResponseDTO.setPagination(paginationDTO); + searchResponseDTO.setLocale(LocalesENUM.EN.toValue()); + + return searchResponseDTO; + } + + /** + * Build data + * + * @param regRecordsString regRecord to store in data dto + * @return DataDTO + */ + @Override + public DataDTO buildData(String regRecordsString, TxnTrackerEntity txnTrackerEntity) { + DataDTO dataDTO = new DataDTO(); + dataDTO.setVersion(txnTrackerEntity.getVersion()); + dataDTO.setRegType(txnTrackerEntity.getRegType()); + dataDTO.setRegSubType(txnTrackerEntity.getRegSubType()); + dataDTO.setRegRecordType(txnTrackerEntity.getRegRecordType()); + if (StringUtils.isNotEmpty(regRecordsString)) { + dataDTO.setRegRecords(regRecordsString); + } else { + dataDTO.setRegRecords(null); + } + return dataDTO; + } + + /** + * Get updated search response list + * + * @param requestDTO request dto to be updated + * @param refRecordsStringsList list of records + * @return updated search response list + */ + @Override + public List getUpdatedSearchResponseList(RequestDTO requestDTO, + List refRecordsStringsList, + String protocol, + Boolean sunbirdEnabled) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + List searchResponseDTOList = new ArrayList<>(); + MsgTrackerEntity msgTrackerEntity = saveRequestDetails(requestDTO, protocol, sunbirdEnabled); + + List txnTrackerEntityList = msgTrackerEntity.getTxnTrackerEntityList(); + int totalCount = txnTrackerEntityList.size(); + int completedCount = 0; + int index = 0; + for (TxnTrackerEntity txnTrackerEntity : txnTrackerEntityList) { + String refRecordsString = refRecordsStringsList.get(index); + + txnTrackerEntity.setConsent(true); + txnTrackerEntity.setAuthorize(true); + txnTrackerEntity.setRegRecordType(QueryTypeEnum.NAMEDQUERY.toValue()); + DataDTO dataDTO = buildData(refRecordsString, txnTrackerEntity); + if (refRecordsString.isEmpty()) { + txnTrackerEntity.setStatus(HeaderStatusENUM.RJCT.toValue()); + txnTrackerEntity.setStatusReasonCode(DpConstants.RECORD_NOT_FOUND); + txnTrackerEntity.setStatusReasonMessage(DpConstants.RECORD_NOT_FOUND); + txnTrackerEntity.setNoOfRecords(0); + } else { + txnTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); + txnTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); + txnTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); + txnTrackerEntity.setNoOfRecords(1); + completedCount++; + } + searchResponseDTOList.add(buildSearchResponse(txnTrackerEntity, dataDTO)); + index++; + } + msgTrackerEntity.setTxnTrackerEntityList(txnTrackerEntityList); + msgTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setTotalCount(totalCount); + msgTrackerEntity.setCompletedCount(completedCount); + msgTrackerEntity.setCorrelationId(CommonUtils.generateUniqueId("C")); + msgTrackerEntity.setLocale(LocalesENUM.EN.toValue()); + msgTrackerRepository.save(msgTrackerEntity); + return searchResponseDTOList; + } + + /** + * @param statusRequestDTO statusRequestDTO to save in db + * @return MsgTrackerEntity + * @throws JsonProcessingException jsonProcessingException might be thrown + */ + @Override + public MsgTrackerEntity saveStatusRequestDetails(StatusRequestDTO statusRequestDTO) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class); + + MsgTrackerEntity entity; + + HeaderDTO headerDTO = statusRequestDTO.getHeader(); + StatusRequestMessageDTO messageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + String transactionId = messageDTO.getTransactionId(); + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + if (msgTrackerEntityOptional.isEmpty()) { + MsgTrackerEntity msgTrackerEntity = new MsgTrackerEntity(); + msgTrackerEntity.setVersion(headerDTO.getVersion()); + msgTrackerEntity.setMessageId(headerDTO.getMessageId()); + msgTrackerEntity.setMessageTs(headerDTO.getMessageTs()); + msgTrackerEntity.setAction(headerDTO.getAction()); + msgTrackerEntity.setSenderId(headerDTO.getSenderId()); + msgTrackerEntity.setReceiverId(headerDTO.getReceiverId()); + msgTrackerEntity.setIsMsgEncrypted(headerDTO.getIsMsgEncrypted()); + msgTrackerEntity.setTransactionId(messageDTO.getTransactionId()); + msgTrackerEntity.setRawMessage(objectMapper.writeValueAsString(statusRequestDTO)); + + TxnStatusRequestDTO txnStatusRequestDTO = messageDTO.getTxnStatusRequest(); + TxnTrackerEntity txnTrackerEntity = new TxnTrackerEntity(); + txnTrackerEntity.setTimestamp(null); + txnTrackerEntity.setVersion(null); + txnTrackerEntity.setRegType(null); + txnTrackerEntity.setRegSubType(null); + txnTrackerEntity.setQueryType(null); + txnTrackerEntity.setQuery(null); + txnTrackerEntity.setTxnStatus(null); + txnTrackerEntity.setTxnType(txnStatusRequestDTO.getTxnType()); + txnTrackerEntity.setCreatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setLastUpdatedDate(new Timestamp(System.currentTimeMillis())); + txnTrackerEntity.setMsgTrackerEntity(msgTrackerEntity); + msgTrackerEntity.getTxnTrackerEntityList().add(txnTrackerEntity); + + entity = msgTrackerRepository.save(msgTrackerEntity); + } else { + entity = msgTrackerEntityOptional.get(); + } + return entity; + } + + /** + * @param responseMessageDTO responseMessageDTO to update in db + * @param transactionId transactionId to search in db + */ + @Override + public void updateStatusResponseDetails(ResponseMessageDTO responseMessageDTO, String transactionId) { + + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); + Optional trackerEntityOptional = txnTrackerRepository.findByMsgTrackerEntity(msgTrackerEntity); + TxnTrackerEntity txnTrackerEntity = trackerEntityOptional.get(); + txnTrackerEntity.setTxnStatus(responseMessageDTO.toString()); + txnTrackerRepository.save(txnTrackerEntity); + } + + /** + * @param transactionId transactionId used to search data + */ + @Override + public void updateMessageTrackerStatusDb(String transactionId) { + Optional msgTrackerEntityOptional = msgTrackerRepository.findByTransactionId(transactionId); + MsgTrackerEntity msgTrackerEntity = msgTrackerEntityOptional.get(); + msgTrackerEntity.setStatus(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonCode(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setStatusReasonMessage(HeaderStatusENUM.SUCC.toValue()); + msgTrackerEntity.setCorrelationId(CommonUtils.generateUniqueId("C")); + msgTrackerRepository.save(msgTrackerEntity); + } + +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java new file mode 100644 index 0000000..434f5ef --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/TxnTrackerRedisServiceImpl.java @@ -0,0 +1,76 @@ +package g2pc.dp.core.lib.serviceimpl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.dto.common.cache.CacheDTO; +import g2pc.core.lib.utils.CommonUtils; +import g2pc.dp.core.lib.service.TxnTrackerRedisService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Service +@Slf4j +public class TxnTrackerRedisServiceImpl implements TxnTrackerRedisService { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * Save a request in Redis cache + * @param cacheDTO cache dto to save in cache + * @param cacheKey cache key for which data is stored + */ + @Override + public void saveRequestDetails(CacheDTO cacheDTO, String cacheKey) throws JsonProcessingException { + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, (new ObjectMapper()).writeValueAsString(cacheDTO)); + } + + /** + * Update a request in Redis cache after response + * + * @param cacheKey cache key for which data is stored + * @param status status to be + * @param cacheDTO cache dto to save in cache + */ + @Override + public void updateRequestDetails(String cacheKey, String status, CacheDTO cacheDTO) throws JsonProcessingException { + log.info("Updated cache status"); + + cacheDTO.setStatus(status); + cacheDTO.setLastUpdatedDate(CommonUtils.getCurrentTimeStamp()); + ValueOperations val = redisTemplate.opsForValue(); + val.set(cacheKey, new ObjectMapper().writeValueAsString(cacheDTO)); + } + + /** + * Get all cache keys from Redis + * + * @param cacheKeySearchString required unique to DP + * @return List of cache keys + */ + @Override + public List getCacheKeys(String cacheKeySearchString) { + Set redisKeys = redisTemplate.keys(cacheKeySearchString); + + return new ArrayList<>(Objects.requireNonNull(redisKeys)); + } + + /** + * Geta a single request with its cache key + * + * @param cacheKey required unique to DP + * @return Request data + */ + @Override + public String getRequestData(String cacheKey) { + return redisTemplate.opsForValue().get(cacheKey); + } +} diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java new file mode 100644 index 0000000..8debd68 --- /dev/null +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/utils/DpCommonUtils.java @@ -0,0 +1,105 @@ +package g2pc.dp.core.lib.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.dto.sftp.SftpServerConfigDTO; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pTokenService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class DpCommonUtils { + + @Value("${keycloak.dp.client.realm}") + private String keycloakRealm; + + @Value("${keycloak.dp.master.getClientUrl}") + private String getClientUrl; + + @Value("${crypto.to_dc.support_encryption}") + private boolean isEncrypt; + + @Value("${crypto.to_dc.support_signature}") + private boolean isSign; + + @Value("${keycloak.dp.client.url}") + private String keycloakURL; + + @Value("${keycloak.dp.client.clientId}") + private String keycloakClientId; + + @Value("${keycloak.dp.client.clientSecret}") + private String keycloakClientSecret; + + @Value("${keycloak.dp.master.url}") + private String masterUrl; + + @Value("${keycloak.dp.master.clientId}") + private String masterClientId; + + @Value("${keycloak.dp.master.clientSecret}") + private String masterClientSecret; + + @Value("${keycloak.dp.username}") + private String adminUsername; + + @Value("${keycloak.dp.password}") + private String adminPassword; + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${sftp.dc.host}") + private String sftpDcHost; + + @Value("${sftp.dc.port}") + private int sftpDcPort; + + @Value("${sftp.dc.user}") + private String sftpDcUser; + + @Value("${sftp.dc.password}") + private String sftpDcPassword; + + @Value("${sftp.dc.remote.outbound_directory}") + private String sftpDcRemoteOutboundDirectory; + + + public void handleToken() throws G2pHttpException, JsonProcessingException { + log.info("Is encrypted ? -> " + isEncrypt); + log.info("Is signed ? -> " + isSign); + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspectUrl = keycloakURL + "/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspectUrl, token, + keycloakClientId, keycloakClientSecret); + log.info("Introspect response -> " + introspectResponse.getStatusCode()); + log.info("Introspect response body -> " + introspectResponse.getBody()); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, + g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, + adminUsername, adminPassword)) { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + } + } + + public SftpServerConfigDTO getSftpConfigForDp() { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setHost(sftpDcHost); + sftpServerConfigDTO.setPort(sftpDcPort); + sftpServerConfigDTO.setUser(sftpDcUser); + sftpServerConfigDTO.setPassword(sftpDcPassword); + sftpServerConfigDTO.setAllowUnknownKeys(true); + sftpServerConfigDTO.setStrictHostKeyChecking("no"); + sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); + return sftpServerConfigDTO; + } +} diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml new file mode 100644 index 0000000..aba780c --- /dev/null +++ b/g2pc-dp-core-lib/src/main/resources/application.yml @@ -0,0 +1,111 @@ + +spring: + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + datasource: + driverClassName: org.postgresql.Driver + url: not_set + username: not_set + password: not_set + hikari: + data-source-properties: + stringtype: unspecified + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + useLocalSessionState: true + rewriteBatchedStatements: true + cacheResultSetMetadata: true + cacheServerConfiguration: true + maintainTimeStats: false + maximum-pool-size: 5 + connection-timeout: 5000 + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate.ddl-auto: none + show-sql: false + open-in-view: false + generate-ddl: false + +spring.data.redis: + repositories.enabled: false + host: not_set + password: not_set + port: not_set + +crypto: + to_dc: + support_encryption: true + support_signature: true + password: not_set + key_path: not_set + id: not_set + from_dc: + support_encryption: true + support_signature: true + password: not_set + key_path: not_set + +client: + api_urls: + client_search_api: not_set + client_status_api: not_set + +keycloak: + from_dc: + url: not_set + clientId: not_set + clientSecret: not_set + dp: + url: not_set + username: not_set + password: not_set + master: + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set + client: + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set + +sftp: + listener: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + outbound_directory: not_set + local: + inbound_directory: not_set + outbound_directory: not_set + + dc: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + outbound_directory: not_set + + +sunbird: + api_urls: + response_data_api: not_set + response_tracker_api: not_set + enabled: not_set + elasticsearch: + host: not_set + port: not_set + scheme: not_set \ No newline at end of file diff --git a/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java b/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java new file mode 100644 index 0000000..4178ad7 --- /dev/null +++ b/g2pc-dp-core-lib/src/test/java/g2pc/dp/core/lib/G2pcDpCoreLibraryApplicationTests.java @@ -0,0 +1,13 @@ +package g2pc.dp.core.lib; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class G2pcDpCoreLibraryApplicationTests { + + @Test + void contextLoads() { + } + +} From c60198554c22ccf3a29f3367fa3d7693cda1843f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 14:12:12 +0530 Subject: [PATCH 51/53] g2pc-ref-dc-client application.yml changes --- .../src/main/resources/application-local.yml | 161 +++++++++------ .../src/main/resources/application.yml | 183 ++++++++---------- 2 files changed, 190 insertions(+), 154 deletions(-) diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml index a381cda..4275b00 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml @@ -9,9 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/dc1?currentSchema=g2pc - username: postgres - password: postgres + url: not_set + username: not_set + password: not_set hikari: data-source-properties: stringtype: unspecified @@ -26,6 +26,13 @@ spring: maintainTimeStats: false maximum-pool-size: 5 connection-timeout: 5000 + + second-datasource: + driverClassName: not_set + url: not_set + username: not_set + password: not_set + jpa: properties: hibernate: @@ -52,77 +59,117 @@ server: spring.data.redis: repositories.enabled: false - host: localhost - password: 123456789 + host: 3.109.26.38 + password: cdpi@99221 port: 6379 - api_urls: - #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" - #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - keycloak: from_dp: farmer: - url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" - clientId: "dp-farmer-client" - clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" + url: not_set + clientId: not_set + clientSecret: not_set mobile: - url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" - clientId: "dp-mobile-client" - clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" + url: not_set + clientId: not_set + clientSecret: not_set dc: - url: https://g2pc-dc-lab.cdpi.dev/auth - username: admin - password: cdpi@9922 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients - clientId: admin-cli - clientSecret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token - realm: data-consumer - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: to_dp_farmer: - support_encryption: true - support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" + password: not_set + key_path: not_set to_dp_mobile: - support_encryption: true - support_signature: true - password: "mobile_search" - key_path: "classpath:mobile_search.p12" + password: not_set + key_path: not_set from_dp_farmer: - support_encryption: true - support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER + password: not_set + key_path: not_set from_dp_mobile: - support_encryption: true - support_signature: true - password: "mobile_on_search" - key_path: "classpath:mobile_on_search.p12" - id: MOBILE + password: not_set + key_path: not_set + sample: + password: not_set + key.path: not_set registry: api_urls: - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - farmer_search_public_api: "https://g2pc-dp1-lab.cdpi.dev/dp-farmer/public/api/v1/registry/search" - mobile_search_public_api: "https://g2pc-dp2-lab.cdpi.dev/dp-mno/public/api/v1/registry/search" + farmer_search_api: not_set + mobile_search_api: not_set + farmer_status_api: not_set + mobile_status_api: not_set dashboard: - left_panel_url: "http://3.109.26.38:3005/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - right_panel_url: "http://3.109.26.38:3005/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - bottom_panel_url: "http://3.109.26.38:3005/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - post_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" - clear_dc_db_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" - clear_dp_db_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" - post_sftp_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/sftp/csv" + left_panel_url: not_set + right_panel_url: not_set + bottom_panel_url: not_set + post_https_endpoint_url: not_set + clear_dc_db_endpoint_url: not_set + clear_dp1_db_endpoint_url: not_set + clear_dp2_db_endpoint_url: not_set + left_panel_data_endpoint_url: not_set + sftp_post_endpoint_url: not_set + sftp_dc_data_endpoint_url: not_set + sftp_dp1_data_endpoint_url: not_set + sftp_dp2_data_endpoint_url: not_set + dc_status_endpoint_url: not_set + sftp_left_panel_url: not_set + sftp_right_panel_url: not_set + sftp_bottom_panel_url: not_set + https_sunbird_left_panel_url: not_set + https_sunbird_right_panel_url: not_set + https_sunbird_bottom_panel_url: not_set + sftp_sunbird_left_panel_url: not_set + sftp_sunbird_right_panel_url: not_set + sftp_sunbird_bottom_panel_url: not_set + +sftp: + listener: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + outbound_directory: not_set + local: + inbound_directory: not_set + outbound_directory: not_set + + dp1: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + + dp2: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + +sunbird: + api_urls: + response_data_api: not_set + response_tracker_api: not_set + elasticsearch: + host: not_set + port: not_set + scheme: not_set \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml index 4e9b2a3..33351c4 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -9,9 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: stringtype: unspecified @@ -28,10 +28,10 @@ spring: connection-timeout: 5000 second-datasource: - driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/registry?currentSchema=public - username: postgres - password: K6tnrCU0wqXOwPW + driverClassName: not_set + url: not_set + username: not_set + password: not_set jpa: properties: @@ -59,128 +59,117 @@ server: spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: cdpi@99221 - port: 6379 + host: not_set + password: not_set + port: not_set keycloak: from_dp: farmer: - url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" - clientId: "dp-farmer-client" - clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" + url: not_set + clientId: not_set + clientSecret: not_set mobile: - url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" - clientId: "dp-mobile-client" - clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" + url: not_set + clientId: not_set + clientSecret: not_set dc: - url: https://g2pc-dc-lab.cdpi.dev/auth - username: admin - password: cdpi@9922 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients - clientId: admin-cli - clientSecret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token - realm: data-consumer - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: to_dp_farmer: - support_encryption: true - support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" + password: not_set + key_path: not_set to_dp_mobile: - support_encryption: true - support_signature: true - password: "mobile_search" - key_path: "classpath:mobile_search.p12" + password: not_set + key_path: not_set from_dp_farmer: - support_encryption: true - support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER + password: not_set + key_path: not_set from_dp_mobile: - support_encryption: true - support_signature: true - password: "mobile_on_search" - key_path: "classpath:mobile_on_search.p12" - id: MOBILE + password: not_set + key_path: not_set sample: - password: "test" - key.path: "classpath:1693731657.p12" + password: not_set + key.path: not_set registry: api_urls: - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - farmer_status_api: "http://localhost:9001/private/api/v1/registry/txn/status" - mobile_status_api: "http://localhost:9002/private/api/v1/registry/txn/status" + farmer_search_api: not_set + mobile_search_api: not_set + farmer_status_api: not_set + mobile_status_api: not_set dashboard: - left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - post_https_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/csv" - clear_dc_db_endpoint_url: "http://localhost:8000/private/api/v1/registry/clear-db" - clear_dp1_db_endpoint_url: "http://localhost:9001/private/api/v1/registry/clear-db" - clear_dp2_db_endpoint_url: "http://localhost:9002/private/api/v1/registry/clear-db" - left_panel_data_endpoint_url: "http://localhost:8000/dashboard/leftPanel/data" - sftp_post_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/sftp/csv" - sftp_dc_data_endpoint_url: "http://localhost:8000/dashboard/sftp/dc/data" - sftp_dp1_data_endpoint_url: "http://localhost:9001/dashboard/sftp/dp1/data" - sftp_dp2_data_endpoint_url: "http://localhost:9002/dashboard/sftp/dp2/data" - dc_status_endpoint_url: "http://localhost:8000/private/api/v1/consumer/status/payload?transactionType=search&transactionId=" - sftp_left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/aa62b4d5-f0c6-4c5d-97eb-753343c89a32/sftp-left-panel-data?orgId=1&refresh=5s&from=1705573550702&to=1705595150703&panelId=1" - sftp_right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/c319354b-d0a9-4541-ae9f-d052e31fa275/sftp-right-panel-data?orgId=1&refresh=5s&from=1705574488336&to=1705596088337&panelId=1" - sftp_bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/c63fe588-c69c-4918-bb96-97fba722afc8/sftp-bottom-panel-data?orgId=1&refresh=5s&from=1705574366440&to=1705595966440&panelId=1" - https_sunbird_left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/d5398a4d-e778-408b-b26a-6717d7d119e6/https-sunbird-left-panel_data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - https_sunbird_right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/dde256ef-413c-420b-8e95-752b164bc533/https-sunbird-right-panel_data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - https_sunbird_bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/e5fa2e19-8d97-40e5-b971-566b5eac5c87/https-sunbird-bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - sftp_sunbird_left_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/e5450e12-17d8-43b3-bd8f-1b0fc12e0880/sftp-sunbird-left-panel-data?orgId=1&refresh=5s&from=1705573550702&to=1705595150703&panelId=1" - sftp_sunbird_right_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/bce49bdf-a4a2-45fc-8c1e-9eb9f00a56e6/sftp-sunbird-right-panel-data?orgId=1&refresh=5s&from=1705574488336&to=1705596088337&panelId=1" - sftp_sunbird_bottom_panel_url: "https://g2pc-dc-lab.cdpi.dev/grafana/d-solo/e6424a1e-8c7c-4a3d-9ae9-cb93f4fb8eb3/sftp-sunbird-bottom-panel-data?orgId=1&refresh=5s&from=1705574366440&to=1705595966440&panelId=1" + left_panel_url: not_set + right_panel_url: not_set + bottom_panel_url: not_set + post_https_endpoint_url: not_set + clear_dc_db_endpoint_url: not_set + clear_dp1_db_endpoint_url: not_set + clear_dp2_db_endpoint_url: not_set + left_panel_data_endpoint_url: not_set + sftp_post_endpoint_url: not_set + sftp_dc_data_endpoint_url: not_set + sftp_dp1_data_endpoint_url: not_set + sftp_dp2_data_endpoint_url: not_set + dc_status_endpoint_url: not_set + sftp_left_panel_url: not_set + sftp_right_panel_url: not_set + sftp_bottom_panel_url: not_set + https_sunbird_left_panel_url: not_set + https_sunbird_right_panel_url: not_set + https_sunbird_bottom_panel_url: not_set + sftp_sunbird_left_panel_url: not_set + sftp_sunbird_right_panel_url: not_set + sftp_sunbird_bottom_panel_url: not_set sftp: listener: - host: localhost - port: 2224 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound - outbound_directory: /outbound + inbound_directory: not_set + outbound_directory: not_set local: - inbound_directory: /home/prihir/g2pc/dc/inbound - outbound_directory: /home/prihir/g2pc/dc/outbound + inbound_directory: not_set + outbound_directory: not_set dp1: - host: localhost - port: 2225 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound + inbound_directory: not_set dp2: - host: localhost - port: 2226 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound + inbound_directory: not_set sunbird: api_urls: - response_data_api: http://3.109.26.38:8083/api/v1/Response_Data - response_tracker_api: http://3.109.26.38:8083/api/v1/Response_Tracker - enabled: true + response_data_api: not_set + response_tracker_api: not_set elasticsearch: - host: 3.109.26.38 - port: 9200 - scheme: http \ No newline at end of file + host: not_set + port: not_set + scheme: not_set \ No newline at end of file From 9b036ba380e2da451f0afa2a838bd35e7ea0580b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 14:18:05 +0530 Subject: [PATCH 52/53] g2pc-ref-farmer-regsvc application.yml changes --- .../src/main/resources/application-local.yml | 90 +++++++++++------ .../src/main/resources/application.yml | 99 +++++++++---------- 2 files changed, 111 insertions(+), 78 deletions(-) diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml index fa3a6e6..b444415 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application-local.yml @@ -9,9 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: @@ -47,52 +47,86 @@ spring: exclude: static/**,public/** server: - port: 9001 + port: not_set error: include-message: always spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: cdpi@99222 - port: 6378 + host: not_set + password: not_set + port: not_set client: api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + client_search_api: not_set + client_status_api: not_set keycloak: from_dc: - url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + clientId: not_set + clientSecret: not_set dp: - url: https://g2pc-dp1-lab.cdpi.dev/auth - username: admin - password: cdpi@9923 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients - clientId: admin-cli - clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token - realm: dp-farmer - clientId: dp-farmer-client - clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: to_dc: support_encryption: true support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER + password: not_set + key_path: not_set + id: not_set from_dc: support_encryption: true support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" + password: not_set + key_path: not_set dashboard: - dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" \ No newline at end of file + dp_dashboard_url: not_set + cors_origin_url: not_set + +sftp: + listener: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + outbound_directory: not_set + local: + inbound_directory: not_set + outbound_directory: not_set + + dc: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + outbound_directory: not_set + +sunbird: + api_urls: + response_data_api: not_set + response_tracker_api: not_set + enabled: true + elasticsearch: + host: not_set + port: not_set + scheme: not_set \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml index 9dedf1c..b444415 100644 --- a/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-farmer-regsvc/src/main/resources/application.yml @@ -9,10 +9,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - # Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: @@ -48,86 +47,86 @@ spring: exclude: static/**,public/** server: - port: 9001 + port: not_set error: include-message: always spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: cdpi@99222 - port: 6378 + host: not_set + password: not_set + port: not_set client: api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - client_status_api: "http://localhost:8000/private/api/v1/registry/on-status" + client_search_api: not_set + client_status_api: not_set keycloak: from_dc: - url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + clientId: not_set + clientSecret: not_set dp: - url: https://g2pc-dp1-lab.cdpi.dev/auth - username: admin - password: cdpi@9923 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients - clientId: admin-cli - clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token - realm: dp-farmer - clientId: dp-farmer-client - clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: to_dc: support_encryption: true support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER + password: not_set + key_path: not_set + id: not_set from_dc: support_encryption: true support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" + password: not_set + key_path: not_set dashboard: - dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - cors_origin_url: "http://localhost:8000" + dp_dashboard_url: not_set + cors_origin_url: not_set sftp: listener: - host: localhost - port: 2225 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound - outbound_directory: /outbound + inbound_directory: not_set + outbound_directory: not_set local: - inbound_directory: /home/prihir/g2pc/dp1/inbound - outbound_directory: /home/prihir/g2pc/dp1/outbound + inbound_directory: not_set + outbound_directory: not_set dc: - host: localhost - port: 2224 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound - outbound_directory: /outbound + inbound_directory: not_set + outbound_directory: not_set sunbird: api_urls: - response_data_api: http://3.109.26.38:8083/api/v1/Msg_Tracker - response_tracker_api: http://3.109.26.38:8083/api/v1/Txn_Tracker + response_data_api: not_set + response_tracker_api: not_set enabled: true elasticsearch: - host: 3.109.26.38 - port: 9200 - scheme: http \ No newline at end of file + host: not_set + port: not_set + scheme: not_set \ No newline at end of file From e3b3287d572f058dc028d8bf72122adc21ac3529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Wed, 14 Feb 2024 14:23:16 +0530 Subject: [PATCH 53/53] g2pc-ref-mno-regsvc application.yml changes --- .../src/main/resources/application-local.yml | 93 +++++++++++++----- .../src/main/resources/application.yml | 98 +++++++++---------- 2 files changed, 119 insertions(+), 72 deletions(-) diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml index 6d50a53..f1a7ca7 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application-local.yml @@ -8,9 +8,10 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/g2pc?currentSchema=dp - username: postgres - password: postgres + url: not_set + username: not_set + password: not_set + hikari: data-source-properties: stringtype: unspecified @@ -45,40 +46,86 @@ spring: exclude: static/**,public/** server: - port: 9002 + port: not_set error: include-message: always spring.data.redis: repositories.enabled: false - host: localhost - password: 123456789 - port: 6379 + host: not_set + password: not_set@99223 + port: not_set client: api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - mno_info_url: "http://localhost:9200/private/api/v1/registry/mobile/info" + client_search_api: not_set + mno_info_url: not_set + client_status_api: not_set keycloak: - data-consumer: - url: "http://127.0.0.1:8081/auth/realms/data-consumer/protocol/openid-connect/token" - client-id: dc-client - client-secret: 82sb19hilQNGFBixW5AJInBHb0Xx2j6K - mobile: - admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token - get-client-url: http://127.0.0.1:8081/auth/admin/realms/dp-mobile/clients - realm: dp-mobile - url: http://127.0.0.1:8081/auth - admin: - client-id: admin-cli - client-secret: eJ7bErtDvu0D5yXP37zLjAgGC28S1ofT + from-dc: + url: not_set + client-id: not_set + client-secret: not_set + dp: + url: not_set + username: not_set + password: not_set + master: + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set + client: + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: - consumer: + to_dc: support_encryption: true support_signature: true - mobile: + password: not_set + key_path: not_set + id: not_set + from_dc: support_encryption: true support_signature: true + password: not_set + key_path: not_set + +dashboard: + dp_dashboard_url: not_set + cors_origin_url: not_set + +sftp: + listener: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + inbound_directory: not_set + outbound_directory: not_set + local: + inbound_directory: not_set + outbound_directory: not_set + dc: + host: not_set + port: not_set + user: not_set + password: not_set + remote: + outbound_directory: not_set + +sunbird: + api_urls: + response_data_api: not_set + response_tracker_api: not_set + enabled: false + elasticsearch: + host: not_set + port: not_set + scheme: not_set \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml index 70e7678..f1a7ca7 100644 --- a/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-mno-regsvc/src/main/resources/application.yml @@ -8,9 +8,9 @@ spring: matching-strategy: ANT_PATH_MATCHER datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp2?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: @@ -46,86 +46,86 @@ spring: exclude: static/**,public/** server: - port: 9002 + port: not_set error: include-message: always spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: cdpi@99223 - port: 6377 + host: not_set + password: not_set@99223 + port: not_set client: api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" - mno_info_url: "http://localhost:9005/private/api/v1/registry/mobile/info" - client_status_api: "http://localhost:8000/private/api/v1/registry/on-status" + client_search_api: not_set + mno_info_url: not_set + client_status_api: not_set keycloak: from-dc: - url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" - client-id: dc-client - client-secret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + client-id: not_set + client-secret: not_set dp: - url: https://g2pc-dp2-lab.cdpi.dev/auth - username: admin - password: cdpi@9924 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dp2-lab.cdpi.dev/auth/admin/realms/dp-mobile/clients - clientId: admin-cli - clientSecret: awR1cImNnsiOnBeWpZQWUfKFr1gOopBo + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token - realm: dp-mobile - clientId: dp-mobile-client - clientSecret: d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: to_dc: support_encryption: true support_signature: true - password: "mobile_on_search" - key_path: "classpath:mobile_on_search.p12" - id: MOBILE + password: not_set + key_path: not_set + id: not_set from_dc: support_encryption: true support_signature: true - password: "mobile_search" - key_path: "classpath:mobile_search.p12" + password: not_set + key_path: not_set dashboard: - dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - cors_origin_url: "http://localhost:8000" + dp_dashboard_url: not_set + cors_origin_url: not_set sftp: listener: - host: localhost - port: 2226 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - inbound_directory: /inbound - outbound_directory: /outbound + inbound_directory: not_set + outbound_directory: not_set local: - inbound_directory: /home/prihir/g2pc/dp2/inbound - outbound_directory: /home/prihir/g2pc/dp2/outbound + inbound_directory: not_set + outbound_directory: not_set dc: - host: localhost - port: 2224 - user: cdpi - password: 1234 + host: not_set + port: not_set + user: not_set + password: not_set remote: - outbound_directory: /outbound + outbound_directory: not_set sunbird: api_urls: - response_data_api: http://3.109.26.38:8083/api/v1/Msg_Tracker - response_tracker_api: http://3.109.26.38:8083/api/v1/Txn_Tracker + response_data_api: not_set + response_tracker_api: not_set enabled: false elasticsearch: - host: 3.109.26.38 - port: 9200 - scheme: http \ No newline at end of file + host: not_set + port: not_set + scheme: not_set \ No newline at end of file

cA{ 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 E4+%1I0{{R3 literal 0 HcmV?d00001 diff --git a/docs/src/images/dc-package-structure.png b/docs/src/images/dc-package-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..e4226db9666e64d06420291548c20994246c0739 GIT binary patch literal 30372 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℘g8}a$ z6%syC zvmQK%be{pg3TdaT*g;Rj`-ToslUPQ==-7aPEu*}`_WH%kO)DFtOsYw9BS0qDIypJV zU0DRncY3`(eyv>W&gX*y@PWx}7aOa%KdI;&gHxk_RofF^YAkhqbIi=b+7im>-*cNE zcrxdm?hmA*7WKv1s2p9{y(`qPAE6Q?F zevJJbCFlTKAx};SLTT2z4@++RNncM9MCUG=ON&29)-=?^qBeptTNE6XDbX+2c?!ed z71^aM=`K5aE=m;rQ}@=T*Y+5^Vu-C92$?+RUy+H~(BJiQx~{;V`j8G1Cq*W}1V3_6 zF3}a+%PG?acrHb!n{dYs>^DD=J*z)N!=~}Ec#;aENp$hCep@KzK-Nj^{5~Vh7Tv-` zUnIBmunE>^ovMv{NIf!p_iu&mSAW&OH+*sO&lP?0|(K?8j`n!XLJ}>`ZpnbqUPHsV)jtH$ z1S`vp*7Le%-sItswLXuKd#1Pin=V$cTSTd@6W%*Mj1`z|JTaU;kHHB$`Yo^~U-?-Q zcOcY5*6lhv225|%xlwH&GnMH`dee;2%1 zO@!rdzGkwK=0eXZA=Yk{nl;?4&#WW62i>d4S^d~}HlGrov@Y|+?b=!$?T+qq5h{g| zS-zh>WNG>7&(s>w$`0O#UfyUQ_qBWVeprCbc+W3fE+N6NH3N@Oi9`=6DlcqOp_GYokr&0z0js4T<*V-=B}n;{-N!40v%5XT{1e< zIqi9mkq+m8$Z#jl8jB)Jw lmnxd?ROF!*z2HmY-~nDM;f+pf^xJ1K?klLt7swj>{|}u?szCq% literal 0 HcmV?d00001 diff --git a/docs/src/images/keycloak-dc-client.png b/docs/src/images/keycloak-dc-client.png new file mode 100644 index 0000000000000000000000000000000000000000..14cf2857c846cc51a4b02a699888b6b6b635b811 GIT binary patch literal 45433 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/keycloak-dp-client-secret.png b/docs/src/images/keycloak-dp-client-secret.png new file mode 100644 index 0000000000000000000000000000000000000000..654b79674ed8f41164447b6550d7632508a2418f GIT binary patch literal 60438 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 erUuBBJGU)L=qyQKKLtAYHnMgx(>cBLdQU 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 Date: Wed, 20 Dec 2023 12:40:06 +0530 Subject: [PATCH 33/53] reference images url updates and keycloak configuration steps added. --- docs/README.md | 53 +++++++++++++----- docs/src/images/keycloak-add-realm.png | Bin 0 -> 10889 bytes .../images/keycloak-client-auth-setting.png | Bin 0 -> 43409 bytes docs/src/images/keycloak-create-client.png | Bin 0 -> 12802 bytes .../images/keycloak-create-realm-button.png | Bin 0 -> 38875 bytes ...keycloak-master-admin-cli-client-scope.png | Bin 0 -> 57723 bytes .../keycloak-master-admin-cli-scope.png | Bin 0 -> 45514 bytes docs/src/images/keycloak-realm-creation.png | Bin 0 -> 59714 bytes docs/src/images/keycloak-token-expiry.png | Bin 0 -> 60868 bytes 9 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 docs/src/images/keycloak-add-realm.png create mode 100644 docs/src/images/keycloak-client-auth-setting.png create mode 100644 docs/src/images/keycloak-create-client.png create mode 100644 docs/src/images/keycloak-create-realm-button.png create mode 100644 docs/src/images/keycloak-master-admin-cli-client-scope.png create mode 100644 docs/src/images/keycloak-master-admin-cli-scope.png create mode 100644 docs/src/images/keycloak-realm-creation.png create mode 100644 docs/src/images/keycloak-token-expiry.png diff --git a/docs/README.md b/docs/README.md index 009ac57..ca9c155 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,10 +33,10 @@ - The DTOs outlined in this library correspond to the elements specified in the G2p specification’s endpoints. In essence, they represent the key components integral to the functionality described in the G2p standards. E.g - HeaderDTO , MessageDTO , ResponseDTO , etc. further details are listed in the technical overview. - DTOs are constructed based on the OOPs principle of reusability. Attributes are shared between request bodies and responses are identified, and common elements are defined in parent DTOs. Any remaining specific attributes are then placed in corresponding child DTOs. These can be more explained by the below example. E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/HeaderDTORelationship.png "a title") + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/HeaderDTORelationship.png) - This module declares functionalities for token-based authentication, digital signature, and securing messages through encryption using various algorithms. - As per the requirement , these encryption and digital signature functions can be called in the below combination. - ![Alt text]( /home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/Sign-encry-table.png "a title") + ![Alt text]( https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/Sign-encry-table.png "a title") - Custom validation exception - - JSON schemas have been used for validating both request and response components. - Below Custom validation exceptions are defined @@ -63,7 +63,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. - This service is basically to handle and process responses within a system. - Below is the communication diagram of DP and DC - - ![Alt text]( /home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/DC-DP-communication.png "a title") + ![Alt text]( https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/DC-DP-communication.png "a title") # Data Provider (DP) Implementation - In DP implementation , as explained in Overview of libraries , dependency of G2pc-dp-core-lib needs to be added. @@ -75,7 +75,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. 3. In this endpoint authentication also needs to be defined to ensure that the correct user is accessing the endpoint or not. 4. Also need to make sure that the correct signature and valid message is received. 5. This requestString will get validated as per g2p specification. Please refer to the link mentioned and image below. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/search-endpoint-spec.png "a title") + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/search-endpoint-spec.png "a title") 6. Once requestString gets validated data provider should save that data in redis cache and transaction data in db and send acknowledgement back to data consumer. Refer below sequence diagram for reference - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/search_sequence_diagram.png) - Implementation explained in below point when it act like data consumer - @@ -83,11 +83,11 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. 2. In this scheduler , dp will check whether there is any data stored in pending status with a particular cache key corresponding to that data provider. 3. If it gets data it will build the response data and the call /on-search endpoint is defined in the data consumer . Please refer to Image 2 for the same. 4. Refer below for understanding of flow from dp to parent libraries. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-scheduler-sequence-dia.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-scheduler-sequence-dia.png) # How to create a Data Provider ? 1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/spring_boot_dp_creation.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/spring_boot_dp_creation.png) 2. Extract the downloaded jar and open it in IDE. 3. Add below dependencies in pom.xml ```` @@ -161,7 +161,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. 4. Create package structure shown below. ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-package-strcuture.png) 5. Add .p12 file for search and on-search. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/.p12-dp.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/.p12-dp.png) 6. In the config package , create the ObjectMapperConfig.java class. ```` @Configuration @@ -786,11 +786,11 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque ```` 28. Create a schema folder in the resource folder for respective data provider Query. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp_schema.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp_schema.png) 29. With reference to below Query of farmer data provider. Refer specification - [specification](https://g2p-connect.github.io/specs/release/html/registry_core_api_v1.0.0.html#tag/Async/operation/post_reg_search) -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-specs-json.png) -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-specs.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-specs-json.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-specs.png) ```` { "$schema": "https://json-schema.org/draft-04/schema#", @@ -1072,10 +1072,10 @@ void testResponseScheduler() throws IOException { - Data consumers are going to act as consumers as well as providers. Implementation explained in below point when it act like data consumer - 1. When it acts like a consumer , it needs to define an endpoint which accepts payload . Example Shown in the image below. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/patload_postman.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/patload_postman.png) 2. Using this data consumer will decide which data provider’s endpoint it needs to call and which request it needs to build. 3. Once a request is created /search endpoint it will call and once positive acknowledgement is there it will save pending status in cache for particular transaction id. Refer below for more understanding. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/payload_sequence_diagram.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/payload_sequence_diagram.png) - Implementation explained in below point when it act like data provider - 1. As shown in Figure 2 data provider needs to implement the end point and also make calls to the endpoint of the data provider. 2. At first Data consumer service needs to write the /on-search end-point. @@ -1083,9 +1083,9 @@ Implementation explained in below point when it act like data consumer - 4. In this endpoint authentication also needs to be defined to ensure that the correct user is accessing the endpoint or not. 5. Also need to make sure that the correct signature and valid message is received. 6. This responseString will get validated as per g2p specification. Please refer to the link mentioned and image below. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/on_search_spec.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/on_search_spec.png) 7. Once responseString gets validated data consumers should update that data in redis cache and send acknowledgement back to the data consumer. Refer below for more understanding. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/on_search_seqeunce_dia.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/on_search_seqeunce_dia.png) # How to create a Data Consumer ? 1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. @@ -2058,7 +2058,30 @@ public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { } } ```` -35. + +# Keycloak configuration +## Steps for DC and DP - +1. Create data-consumer/data-provider realm. Click on Add realm shown below. +![Alt-text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-realm-creation.png) +2. Enter appropriate name in small cases. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-add-realm.png) +3. Change token expiry time as per requirement of testing. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-token-expiry.png) +4. Create client. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-create-realm-button.png) +5. Add appropriate client name. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-create-client.png) +6. Select Access type is confidential , enable service account and Enable Authorization. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-client-auth-setting.png) +7. Refer below image for client secret +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-dc-client.png) +8. Enable above all setting for admin-cli client and admin cli of master realm aswell. +9. Add below scopes in client scope of admin-cli of master realm. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-master-admin-cli-client-scope.png) +10. Add below scopes in scope of admin-cli of master realm. +![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-master-admin-cli-scope.png) + + diff --git a/docs/src/images/keycloak-add-realm.png b/docs/src/images/keycloak-add-realm.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0d84d37b9195c456df4da530548c6b4b2989c0 GIT binary patch literal 10889 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/keycloak-client-auth-setting.png b/docs/src/images/keycloak-client-auth-setting.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c8facb9d1b284e19619c1ae5dd4a0cc6fb08f7 GIT binary patch literal 43409 zcmbrmbzGcXvn`n5?(QxL(zv?@w?J?jhu{$0t#Jtf8VQ;p!JP!}#t9C=-QC@$^S$T0 zcg~%A&dhJ_zfV6~x^~stt5#Kaw1%1jCK?&qt5>fu-zmy!y?XV!=+!GYLKGy}lgvIt zxmT}(U%iu+((yJqTte0(-^)39xPXip+~f$^9EsRipJdFnwfMChd5+8-#SQQak%qxD z?Kl9s%s!(sWoD;H;z=vQeQ|$WDps;27x09q-+i&Eu+jgbBwf?)tHgazEgy@?Yde-F z6&A^i5#JT}@>&{&wp&I6j6xgkO@M|^8;+AG4WtXlS=VGy3CEEZMT>~Wk&aTx2jEJh zoUu?*GevllA;nr8n8U__rcNU5eA+1kn>g0aw}u{@eJ}V!3-L5=-l3GaAI!H@0XFd> z<>YU$f2b=4>Y_C`UI^zvwmx+B-JG0OX2$;zj$_|zs)S9n>GkLYCMsI#I=Dk7iI)%D zCH}ta$RX3;=H862^{S?~{feQ*UK_mm3FYh^q`>MzIOq$03QN=*5byw3WYg{E?HaFo zK6eLP3J)Gey-uESxsgZ$Ic)cVD+!Rzo7#W_bHQ?h`eM=U_f`HS*DgqPtSW4@o)^b2 z+(RlvR7+*Il>kyGU)vt#Dob(9oIu+oNkjqjnF{ zChEvlXtRhUIdpUCJ#=RZo#l@wZLKn3IaDusxjFC&uX4#hc(&fYAM*f_CB8Xsy9kLv z{(g0EI=0#6j;vK?f~(#ZP5_jQmAbl|(XkQqvc;U;;fL;u4Ndl!an+IZ@}XW#1f}9q{<7gcYFHKYYxcuqKdU1AyS}4)X<<&I^7zNMsUQ4ZLF0Lui$jHc%!YIo2H}t$w z7aZySlLsg;vm{a4V^1*X!xCA@N9+~p7fk9HlCo%Nhn~wnJs%sNpMy81R~*}K444`vhCP9yQsD}H39FkGBF*kL(Ay)l z=YyBFP^PknOCPD7aptLtT-xRmyQH;%*V^Z)tdrDeDd-b9wWH?suHRCz&ss21X~Q4e zB`As#zAPv{Fj$kD;QxSAqiqkgqFyBOog;yxHh~}>2T<`tOdC488~^msvQh9HoM<{W zID)5mntk5jP%2S+C>^B%JNHd^o-fyjPe@&XgPRbBg?Pa0`GNbtD-rO=H0Hp3xSt<( zhS?*evL@o7z(DAUy@lhFB?n-w{@{t5ZjU!kE?>4tJGUiBqa3K*O^o>&S zUOH)!9`$`&Oa(J6sB7a-uu_%^x^F|Lx}*# zi}oo`pj%g5Z~w(<%*n$(2+fnAXn??|BN_XMKYh(>Pa*k2A2u%+EhaVM!pRQ?*vxaV z7`tk9J(8GxvnA~@y|Z!FHiV+)iyZ-cTEb5sic0(aYB3-?s-0En(w@+|&v%Sd%UQ5* z_)uO*3%;^Tq!vZneQ2yKjcaKk>UI8uYQYhtyJo-l^ziy!cu{*_oDpW2e8fP$^`;(X zMa~m!t$IwF)o#Xml^oT8`| zg~}iM+>3Klou9RFId(NuC=jFV1Aa6BE{R@yRjtn-G27yo@s~e67-ICV^TR(Z2ZV>?obSl|*H}!z1Vc`$2#72UzzhEq zfS*qT<{bFJ!`m_<*=qNRRWLNSYbRsMP(ex=@L&PS7p`sm9aQJfr-F{2)p2(P(8nZz zImzgMgZ%$qt$tLeR)}QyU?sa{L1hSCgVfQGIEAuCiiWI!=>*d= z_VpkmqNSTq+Ixc!N2d3_hZ3ae_)K5PV$p;)W8_L2ia3Kl-iB<44wv_Fx14M}22_Pi zuPwF#=2ZACbrsJJ+_^Y*SjK+U53}1g(SLuRIIUW2s}}a|`-8+UCFFD*p=t@k2xrWc zA$v(LM_TEyur*@5SjaCfaQnu~PM{(-v}CsW)0y5+3}{IpC!ANT{U$xjM?6Pkt6}dR z<*5WtC*xM;Y>m7(V%ToaSsRo%0>m_hZfK2;$M-K-gLfRV2;#l;D>UJ~*7=6bPmU+u zbhV+mlI8Qhzs2j=HlSHL2x6$x8Xw^HLGQtoZ1SGuei-014e)qyk8^8$>VzxCa8O{+ zCO-8kuP8w~?P!v*NX^x5z-p)of9U>aY0IKwT1G}f(hQ%Hl7sO1dVvMFBw zzyGcao)v3nm{?yi860Ecwve)tGta@PYPllOca!9*i3{htHuIRLP5QvqW+q=X8;*Ll zFSZsc-IazKZ~K+2K-D3v>$v-}74@i&34?Rq>oM=X5ukro1Hc1DzXyMLLz{v!CLxLq zvcsh-CDR(`MKEW4SmLlH>LH;f?~9W~y%G4)Vm8&!#U&mxpf9xiy)t}YD_DsOR8xc6 zSc!(^Bm*5$r_q&U%kJ8o!tjf5h!}F}vV6~WEZ5ezTOZd}3+{e!x_L66d`kkFmb_MLApbnVwlaS(Jl zd~#O(Dvua$&R1@YXIfjSrX{GF|RFs(!KELbqu0!|i zR7RDSn!=!PH98@3r6}4d+BodZC&*3#Pvt<8@g!m}`nc za|$=W)=gWz`s=kNAx}t?WsdO>b#Bu0qR)Q;CF)QnmFsl#jEU*{f?xFeP z?5+_@#LfotL`D*xR$8dHYErs^RQ&Lntk9`e0x(PMhUzST;1CDJO3B;7BlQxQ;RvkR z=}?JZUFNgwA(9oP0cY4(L!!ZU3KR=F` zVnad8To@jI=m5uJmxmz|I%Ek8cnoGOa*cxD` zG#4Yhi=YOKEriWC&WX)ModeIYp=QqPIr2L)_2+0q{7rYBB{otO${2Eq8hyD+8l>?_ zk3im{cFBnrs!RX<&OXoac{X2xGvJ%A>)0YSahH9s!B(81M3FyEc}pq8#Kiie<0YMn zGsQgrKp80g62r5}F0bop#czcS2#M+sC$|DV&!P^cf#wasmw4f~n`Q~@DW8aFg!9qb zmcA+m5hbo6(pw|ByNpVt$XSzxVd_Ukzr1Rbi!CZgVQBvtW~N&sq~et;U9u#|Q&Z3Z zpjvyEYJ1`!*O!NZqzMlR(;A!L2_Ag3wB#MB#mqn>T^2_fvhVG`Wf2|YOEJ3iSr!yADi$8C~%K}bcq{3M0c6B-%BHAGDD{umXGPq$O&Sb?Ln7dS+` z9p})e;;mhqnH!mRZ)&r89-nYGw_@vK0^<^~X_z^v0<^?blxR)5cUCuw19gf;<)pty zE!D;<`gx<%zWZ)_L`JcLqQVvD794NUBa*w6{RKF}u2$_>M8T?g=HT}Sblr<5 z8b1IEbtL}Tw*>8U@JLk6Z(+h5=ggXIJh<4=%<{j=rUXCegb;WJaIf%MIQ*dId!L)< zMmDCO)8PlKB`f#NWbbm8Q!XL;kY`1#w8#tUAS$_=Qypezd}hyTQ_{^mmAS`jNm*P@ zjTvWgmk>rEhj=g=#ppYw3;S86Ju1b`pqZ7+$0=7F4|k3A`Fuln1n;MBXog{>3?-zJ z4vzRMgDIdH-Xuneqn>azNsB(`PRb+}h0sI=_W!1p6?N-1!qkaE%pY_j+(=0PAxX0b&mFsy0m z2q17u6x}d+*sDXwzRF5A`@8Axiicq;PP)-yHpRv5n>L?=jK-+7yo1a|ju_BnN)pOp z>$r}8Ia;A|kUlGHBFAQU42YiMm}4nrKPX6|!Wqa9S>i7VGJIOQ%m^BJivRL+*qy<|vv zEj@r@M(h~vw7@>iblWm9!HEALSHCgn zS`;maXS8Y~lr-!lcINouvYhTRb_&3OuBo3?;|E-?!CalFh0!r{(cRjDrwSB_RQvHa zfb+tDuEq_(Qe2u0gxy+u;4)mOyKch%{`;+vI#lUU)|J-pbTGSmfs*9w6b+j*Rnx~m zD=qh8D|k#%N^4v1QLg^%k)WqI+tSB9BI$!_X(U901QPO-H3LyBkrbJR&d!f6=iiZO zh28=htil@?l3tB(f$J zNHv9+>52Jq%U0`U`_(N(qEBw@FxozyVl|zJL!%NN7(EI4ye*WrbJj#C;k`ZIqG~-% zD1m3sfLK~2oegI zd3+<@k|RCF)YT&U)zk9QzXc)Kdq{IE%fva#MP4gbk^P>~IhH;M8W=Pw_#%$3@cw~? z2_&-mV#wXA3;D9V$B`t%7(R;MsJaT-Ou@Qg$}r9yxbc3rCYz)-p{=ZUJ@N$O^v?>F zx1MTNin!S7@tq4tBbO6|^5dX9pV zgVwmwFQTHwNBe8(qQv2Tp4&cXWyF9(y^QmxE^9CIW>>6^Rvzg;9l^QWQy(k|Yisu~ zV#OWa;0G${n%QkJ?mTdF4KjKjFV7Mb8IH${LJ(R=^J{nnQ+X3@a(}O0m(Q_dfoI`Y ztm;=iH$@HjO-ib6(*UkjW}ly)G$d#ulH2NQ^nU}~a{V`CCN{B9TX>LSrE$~5unMQK z%H|LXhhKmH2H(X=me){A`t@DNmri7VFcZ1f*)LB7rpAP6hv0;T+?KOP{_uCxXJpHq zzO!*-4Ma>S4tUs5WuRG=Jx1nhS+P}Xl>=aH-9>%%!lGRg-L(l^X3)X|X&s7Z9?Oj`MXDb>} zS$xM6+hNa)4bY8LgX)ZAkDBn1TiA??^ls=^BUNK~DHrBXF}!R4c@6C>RI-X_F`GGe zTOYLi8xxB!ZL85%GZpWwEjjNOS2zF$Bnibmb&tj0b$G;1V=JvW*>n^y+7s)E7=y^w z0^UeFoUu$@h@B=@b{2HO=rLT|pMDMyCQ?Pli)=2t%CXZ+;r zuOpoj+lNH-;%z<&-2B>PRm$=P8w@Wbyjg4-!`L(o?L=feEqcRbU)nJG@?;Y;LfEb$ z-{OOE!1Yhw+xUplf3EH>Y(L3Ez~9CCmEA|#G5C*orW$>+@5Qhz;H;m1d6C?^Ys zJ|TYfp_HgVdmq9B_i{HvzGLKq6(FK~$My6ETUt?tBXX{+IG#EXz@bJf#(|XRPeo6| z@LJ5lvw=E2{b6-<(M8N$TIRHKi26D3>{hVskxj*&%MkxbH zDHSNF#ymfsor0@+wC@2`+@VPYKU?RLw zIz~h?@bC;LjyXAc_p4zp*U#B&4ULmbjpcZQbL@$cn5Ntw1p0{DZnt^lqr z%Q3DJwXYW!efU)P!5Xkblycvqx zH>t_j&b}Wa#=>Kj5nV+@9||L{C&NU-gNR2z6>{je+#d?IW;m@+GPqerND;12ueYjM z#`TRm`6o*IIay`&?Zma_kXov1@dw*(PnaD1v@-7DOcMRqwumE%?M`a?P1p<@@SanA zO%7{tF3YKun48cmUv)`!IV|8s9PR_bl#6~U5h&`4os+8?jTPt$?~1>yG<00~OtrM@ z;VdrjejaDCta%U-qo>|FQMxrwwaeIQAubZzsor>hE;_OO^-E|x?qwM3@1YaoQlZnpD!`Vqg4XL*0 zO`4uJn@ZR7+#`B~@lLIz=v-{?uN!aEg{bzLpKU%|_2~TVH1|v#%v0xf&+S1k?L^cq zku|@I-Xk-s@RP%BY$p}Eya@8th6knD3$!KQv$Lo?3|VpI(Q*+)V=DVycfGC+@)S|Y zIsOPV_}cZH5#R-GbajDrm+bJJs;8_@9SA*M>!Nn;i`Sn>0_P<-*a{h#2u9JxkG%EX zo;fUn=$4rY%}&+bLGyr9e$obf!$zSZIm_=;0T)5u;tt<ly#MYLqEvib2~mbvqV$)^IO2ObyN1D*LOKN?()zr>fPzwafpfQYsBR2Uw>f*fB0P z0RpJA-bJtFB$kxi zAdVgPT@1OQ&i+q-dPG)3UG4C;ewOz4v&-uHATGDFO8OcQ8ANX1aURiyCxw~KKzJ|t zv7E)DZ>zTXY;^$TkD7=Bl6;Ub2u_L?Vn!EMtUZ-;ru>+3avXE?$r%R^Ji5V+^*l$3 zj5$z1+*+Z~a<+}}eYEf?-5Ua_H9Hbb60_+c&1|yR&Llw{Opc1@dp?djjL|T>vp1}G z(2Ij0x81sn@tTWwv7na?#Z528J7?|eJzr4&sz!J(p_%1Y%Sy393>Tg8yZBZ@QvD@> zWIVPIDGPhQl*(^+hO|ql`DDQxMiS=46&W)PVR-?6%2&ipC$m^2Up;M!BiCMNFr|-C zQV|8cXuU~w3z zTe#-PKjFS?ZY_24tmIUOWl_PXs5~rJwdvQPl?*%anaT*e$|e%da|0R!4M?w}6zPF9 zrF57(?W7^#jthI7(c&?3<{_VRVkKUxw4tDm1qr4cJ2;|?Ga=Fav_NIA^1tQWbjx5q z{e6HcY@IsBLI}`j+Z_yw**`}7@LsxGG)itKjq{CRrk$LvE_1iocg2t3we*S{^__D2 zE&t!P0~m5nPzGLKLIPnP9e3Du|0Yt=G0eY}Oe%y_102#WFgnIx=#l%gBzzcYrH*u* z&SdFlWK)})E&{s}{i6l|BF%3a!kqZFL(t+p@J~-rK%JEj-0S5z9HA?3a>CGWy+S0a zN;=Gad2X^bR7@bOR5r)UZh4NmC4Zr3ED(T^5?YZ z`7~_r6ur<=MtGtg>g3eyxZ3-HljlZLX_Ptj79ytNVT^HaftWgc+_Q27y2jybZ~Rb; zbn3O_7YkAETbyF5jtzlUY={!jV6LHFa4v4P0N6;GMibX#_6Mkso|4ar**-dNW}-iX zikf308Y_}W1s#%jMXa7HBj054Bn_bK;VPlBBERTiMG$3O@8}#FI}oQH8oUz(gvp+u zMDZOIcx6%9z=R)n*@y+!SQK?r`qelf~B z^6+uUf)&Z~Iw`JaDye7Iq&n8# zZV-ZHWAxly)_Be}iB|_6Q+g3If6(&TJPV;X?SK^>-zgSR@Pq-UPbsM2_ju@b+Kk;F768g}h^AuMZr{G&%S66`EC>NAz(Um! zHuTt+uf1~VBwXRx_!@VdH0hu@z8EviinZkHhr83TJVJG-^eS~)T(TU!0}|r0bvLw+ z6NQWOBdsxJur_G_i;;&j7*Qx+Ue!RDh<9Xkq^~?Z55S6)2nW%aEnSs(q`AMuTBD^! ziD&H`D|nbBXL_PIH|e8i+vZb}&|OpYBz{=~%{zGdTrHAu{PtH=a7|2jkzFhPtml20 z8G--Pz^Mqh&|r!)Mb@L;Ui+_5k8km%UF5rZ`TFEiPXce>`)2{1mTN z3gJfooLBKmDlxZHLKL?Gg4q#nVJNF-%<5Lhos{`GwEj|s;EN8>$a=Ar3KkXB#_0HG z7-}FwvMsz#Zqm)zp|Py;EYI&SM=CD-rz_;YyI=p+N&+iB7LKD-=qiUHVUqFM9xmM4 z|7|!)dXoSDQ{Mb=k0w%XrO)!d(5sL~a9Oz2q{s#ZIFljjYuL^||_H;_7Fe>4;*Sakq4{H1p zAhP{y(m>(7ZKOA+-8Wc)EiWXDU$roH#ALf4So71s$(^)sCq8sp2394lfC*Azw8nm8>yM9gs1M>ltf*dTM&n}msy8fy?X+3v(;i7a{mjo2^mQzSQ4vj2(iKvZ;}o*5_zuHj z8{~?ZcyYkBxDYqn(&r*n4L3h6_>+y~$Eb|FCnJ*0rUvJ`F_VB%19IL&xtnqdc(Zx z&qt`*nvw=S|1tXl8PlF6W1tv$)O@7D)Su}*ciFh@68z=|-#F6_@q4<@58I2t^QsRL z^bXWaP;W9d%Abk)4G+X3@8SDZT|uSQweW>Y(~8EZ?`3lLJ3jU5Jgjr{ekbxIc%lh{Z!a3AKUz7Gz2nU1jo^A~_G~Y z3IUWF^K!J2NWS9|f^ZolXocoT=BcRiP|+ei@kcYG&t~9?NdW*_#2>8TMUtZL^3RuV z1`voYt(y;tqIy9905|5vzCs(NlFFGx9deXM+{)?AHMS(6xciTK*g}PY$|YZZJpkM1 z4za}UILxRn@jWCR`Yrn@N+TC^ii#4l!pb>i{}p`jCVVt(q1lgT=NUuROQcmrS}B>vwZZ!`6tRygdp<$ihQhTRx3e4AE&jXT_85_sRz(!=EU_ z@SDQu|AwKPov=H>t@$tRDgfy=UH-mR)zLw%^d0X_YRq3uHv9XT4KJC5pV?%d)W#~- z|7+9Xe|xelekf-78pqd9c+I{b)Wm80>lo!!8<%P$?iCG8Hk?aaC-K5y$^{ zvi$e@0l`N^Q+UHB$0A0Xq}{5`00;?3k!Ij>T-QyScTQcxIT-3ah8baz>(r8ZH1QRd ze#b22aRWOfRFoaD!Zr;;kJw9c`%&a_*Wa<)pPHVO^*Y4^fw+o6vh7okgl%96k&vBG zi5+?8tcJjwa~=%?5fpkoXaMZ?`fC z4UxfbUP%f1h@+wr6^R5r5PpTjlwa6zIkXp^Vq&a4q3lXoN4@E|s~fXL#&J1D>$TE- z_5xdHCV$S3`|?GFAXUfQz&cp^6gQ)EfW;Ztyu)+%-xn$YbrHT4iQE1u|1NLkoz3dh z7D5SyL903jq+y}S^#Xz|=)2Eoxfr+CR|Yn4lM>91A(#5(U}7b?l~Q;*uq!t;_X06Z z%;~#RCU)6VdxhYJ{~}U;L8)i~RI)Do=0u_l;kW$O98|fVWY(btJL-Q7`w?gks+f0L ze0b%s%JD^)S8y2o_ zs0$|UHT(Pa@1U%#&VbUzsyn=h=p<+)#6wZ&6P_sg0Oa@JXlT(+^PV=w*P*EEP?p7J zSL(eh=Oj{HG)DR4%@=|Tdr78pX(MY+kqE>iQuQRQrf#f}Lcj>JUX_WQw-({lv6zo& zm>b#jcf|gI0U2N*mfMB$IHN{oRW&wD<)GjoU^++sX~wr#-C7cCRD%+~jIbjfNS6p} z1^XwS693^LFvtG8HCO`}mgqn3_&?kkU+7ep4wstwufr$6?DAi=^ZvJy_0OQqP9Cy& zjZz(0)36{z7;LT9=jXtv9`nCbfBsi=-83~&U?IPNjO@npTu+hz29*)G8JUN6)8$|B z_#dpPdrC|wO8Gc)uHgzioSpE_Qno!*kDMf92IyLe9cUBSburJ_Y~(vt z3|ia9IP88KhW1sqI1zLMARPR z{BzEmubTl&k9SzIZ;qLn#oL6#&_m*Jbi6laVmSf)cew4uqXo`DJleh>N#G<(F9?*! zHA=t#n5g(;xBJvgLdTkMJ@G}|$oF$-WgxBfP^;^<31i%(-R(n1uGB#|#qgX-bNV9U zmL*R@c6_j+g;`w@mgxQ28>X}!T=z9*%bBZ!Ha4ohlkKi=#OrG{9bL>8I2ZW(&8MT} zU}Mkp0sfhUsZGmoR$mGl8!_UaxNSG-`_nZTBi+@MF?lU_y=w%D#7Z5y1&A7o1Ugq1HI=+aw*camYZLlT9`dD}s0 zPAHRe7Hdfcv$X?S*=?pM@zul%oMP}zMFD&&Jd1Zvy}7w^;*l2;5|Ch%a0La9e4~+y z8SIXroUZWSy}2FlWvEdJn0$|>0}(HSkg>JzItUSil95fH1y=`w3x>>_d}?BMQBfc_eGfk z08q9y>SX~Ayz6ts82&i6a*ClBYM)PdwKn?`@S9AfIxil3vbmKwP+_#tLMu8VA6xK~ zFp@hGE&Akp9d>shnPg`&4@tWIQu=~uY^{nH811bHU{U`k-VY29RdNB79x=IpV8ADM z8e8Jg{}5rsxIKQeR@{Z!sm@;EbYe$%Bu6Y(Xo@$r?El|ez+)a`atGcy-K{LwhIJbxL@)%7)G&BejE^&m4d z(~LXAtihne&ZT%wO&Mj56b%Nk&2zs4Z54*tlb)qPT1^_&ZgjP~o;^+ST4 z)z`mVlp_m+t2jv7qlbWriJ8Wj7xIg(8!Q=jmi!tcaTH?f4gvB>3-ZN0htQ&I-8TTl z0^UMSS81&0rlOUn?7`XmJhGGYyVMk6Mtz9Ah=p2^DiNXVHX#)b^vL~-q-5jhE>fW> zWxV>CO?sZD*KQZj!`aUwu#A-q$o=bO$=kbSgH4ii>Aj&Fpx+vkD-r%a<+wSZJusn5s zkb9T7qQN5;rL7PKfIf}%a2qY*Fcs#Z4h#%D_H)`nKFi0CS1Nt0`H;#tdTcwh?le0J zAieYz7xum86ljJn);8oe3FW|oWkeHJl-mSzhpr- zbueiJdMJw^gIIB%di0+dbua^d$Ou_X94vN??|D3~!hc&hz2N`1FTd^?1*^TlHxrk{#I={TG~V&hvVyYgo{srUQRXR z>!tvUv!YfCA;zV#EFv8D1BT98yGj@nvfTMAEx}^9bb9}PzVJV(*#jw4 zs?wG5e{8=$L_k~|D?{a4AH&m+S3tnR!-If_hi8SidD6qf!=Y_9JjwB2nNr={?UI$D zOznb&j}K{VY^(+g!QYo$f{LR41yujlj{yE#mFzzTVYS}f`8GN^dWWnM?}y1fb9R=YUc=&P`4I{0nPbN}%0 zS4wmEOAbYyt}SXl{E3`4e_CJTWZzJ0p>EE=r?s^Cmubj~oJnDrCW`$pj%d`pWK{+= zZ}dMqO;3Ym3}>Rq94bTZ^d!0N9m$8GNTXF$U7h8gLk2C~`Q6^8oW6d3T)g}dR$r&v z{M+G{u|L;1EVSOIv#HH&Y%Y~o#C8lKQl3knm`R&2`cB!zk+Js5BXI$E(Ml&;uXazi zA>3LVFQLT4BNYmc>u$4opNI2WDo9n?9Z9|>LoMAMj2+coUDw)m*Z-m;&Zv7HWYdy- z=o|V=XvbIO2sA%xm?%v~{CuJwr9RwU2jI+ZBMW;6F9HE}em24^{&>JDej^54ugvh& z$0~a|&~#yI>O6qF6OC5uK4>OpInG@9HR!cq2i^wRC#R%^Wj4N!!~9f8CjtMxAq;T5 z+BnB1xPl+vS~TVLV=Gp>0mZ?SRbmk?V10cOrHeu~m3L5Z{V-X9I_R zn9Yu1ZJZ0zwN(hn|-b*Moa@>r7ZNwm57b?yOd_P11{g4 zdJR-VT$cklwOrBI;2BH}ktLw-@?o%=4t7UbvCI8$p>E5Y&Q`Yv!T%yhCeS;5s#yTK zl?b1${$}hZ_Lp>5rLD|qjwug>x5RW6{KN-G{m=*CX+bE?BPbqOR0w!LVJc#*4zwyz z{2TRHDqthPM!>EQ0^zWf#$i}izN$mCAN(?>PCnZv#hX6sq@4?Vj^|?ROzS?EJ0py6 z4&-`eCENJe{$G9wQaxH2MJwH@N%FQIc1Loe!kd{Jq<9%{%L?to|Q2z`xQF$3)gn` z1XV16f;GTRT;)tv$#O*`l=(l@f&^o=48QauyCPE7fdR#L1G1X$7>lfsSP-a_Gcx1O ziD=tvzb?NwGQvQDdd0)k&h~58k>#kq)|}jyb=5IS(Ws-&MUb~qb1F(rFWlQE^7M$k z-*OUhoFu>o|EGDafB8Q?Q3G6|rd8$#)il zaFgDp^tooj_kH}klTlYKu~}YDtGg@D#QyHvYg*H5BYO+|{{FbwpL^$!rb3_3iJN`T ziEle>!=IITn9@=C^7~xq8vyxC$zNTm^ui%d!`+fr)=;6r)R33}?a zKh8}qd$~bfcfr^I>2MbQ*ixnTSY_;?eAAzmSr7s@q@FOXPEk;>>1d&)^(RV7G-|4z z!p(t(BsXDG5GYRnN30#L>LPv>RwzdOgcQ878As(B3Db3zbucgg@J*{EhT~3j|;|;=kr$usZHh00OPwg3UQCb z{7ONhzQE>$dmsL?45ck71D>P!*Jc6Am$P(J&3W;tb(m!STUe{uAeN}$ zCsfXJ^0WNRg;X!fp8?0lKEzIpk|o@b?xWF789w%^!`U#J{d>)Y@QmnH=L9=8KUBUOO z-^`58N8W@L)-^8y{hoMl{~M&rrBiueAxar_qx+r+uE!Z1he>;kf_lzIip$pM;BP%z zIu>fJuI))zo>KgmGjh1x#lEm$1QZ>jk{v(X^B+VUv3TD(;a!g!IhXYq={een7qPhZ zbXWuL7%AMz8|^vxy$sq+9OrOtiP-AUOC=377z}hhw<-IB zeyY+17M*f1;V_0BhU#x?lI(<)=~dB*mZI&aVl%s8ILI3q8w6)(ixO<_&q3dFwsK;hUIFV&|`b7wRckL$PT6gt240 zsv`IG>rSwEnzrxw=s;Yw1gmRW?mYVgxjE_herL6v2O$q1KQ&P|rDl9_Lw@Oq&LV6J zB1JffVhik*Zl@+DK0aA4owk2osQ`7bbKL?=4O|C%~GAm4nyo9ySaTJ zEDUo%e^oeDpq3*_;^5PXeR#Wv=-&ZwVO*&)~NMDS8g_Q z_~~EHcd)zL`TI8p5fM?l9SQ;*jOYdteO{vC&cN8%*f8Yer?n*in`^rUU5wZ_1kSAf zC5#;z3jfZqn?JCn{fkTg!x{e(2PbpC$nyu!UT**8eBT`v7E@$dFbor1NN6&?LN+fk zzWDB>(ce(PtSXY&j4kEz;w1L?-Kq=(%Qazkn8cd(kS37zS$iBv5&ZN_^+tivP;c%% z$L{!95rWcWq!mAD!RRC9%MEqMI!t-@{yh8Y>rz8IM4;A|c26`E;kY6 zdyBX_95&PErY=aRNH;)|%?K?%sD*DO)K!$to(5Bk0Jyd81hD@?kXsM9)i**f3HEpw z!59H2T!;DVyJjoOuf#0uoIF_D^8X?trc~EBsR$HrtW{*4Vk(1ltirTr!_BJ5y$Ld7 zgi;Cj?*dx~fA^KGwloJw;2m9%d2HTqti0Tqx;*Q!_+GwEht)#2FL4(FF>EGQuPlJZ!iAsRm?hYy zWi0SobK61hnB{a2=@*-aDrZ<)OkAWV^uraJPS))VNGhLdshHAbLa-6{`pfpi42G;} znCN9K;``M&yQ6-$dy*gXad5~N;>w4OKb`!6ni@2^-6{j!=42I5e}*CFqk5APN52&a z9j9}b`hx?4DFd`ZWhB#>7S$tP)^Dq3PTw@3SiS!`7Py7K_K$dS=@;jIDs-m)4YQon ztt;kpUk9CN2kI01-0js20A-3!RPX?HU?6ds65MTGS5O-ZipeLfwucB#QqF;J->8IM2g z9_KcvHB6MGGX^wwrzH8w>0J4wa&D3Ur$)DN&72TZ&nnOW(}|RwE7#!Q|3%qb$5r+A z+oIClNOyz8qC*-4gr$HW0wS>JlJ4#<1!PfD5{k4)*IINV-LWWXr29_%y=U)z_kPd4 z=lnOFpEaEiGoCrVV~p>3s1N^`Gtbrwjtt`G?c4LG`>?H)CVATdWlFvaRHtj(?R=%h zzXW8xw5VjYE3P8!&en3wA(b9!+8F}1d!p#7N@OwMK0VXBZH2;Q;tUe0MQwCx-+ZX_ z0LI5w2c|7t(PBl35TmAw48kI*_DAkUWCS>$g2S+joq#e6cJFG!6>U zSlUZRcyCY!-<`KI-pESkogH*8jqN{0Al%=F%AS`sOE-h~Uld5oB%{+3Z zlA5*Id)h5~Uwz)4OxeETv74+xRv^kgWcA%W3pmXueRnqgb4B6$U(oL-(Ebf$2Q#DC64TF1fTtq`KvP$BJYLKXp;okz)qeb z!{Jd82pIhz~scJxyXr z7l8eAu5bzG6ngZSM)vH4CGVqZj*$P2S%B%m0fTfA7F7s-IbXPhC0Om_Q_Oi68s=k( zlgT24aoJm_4xQ+ZmmF%Dh^whQWB1k;%Z~wEs{mPvMGo>DDsK6$aLOc8Cz`m&WuOVs zbNRzEFIMe!PnlKkcU{!eb==(C-|^tVkpBL+Jab%Niz5`pgJH!4M=}>|#v#@}*{ zEGSrbc=6+ROp{4r5A;5kip(unVG|io(WNP=q?}k zT#n=yF%uy%T}ZGuFE=&*VF`rz+ghCu3;GJtN~DZ4(W^L0*yK|&3X<^1;*jF4^mzgB zG4z?!EN>{fB_eC`ltKrtJu}?$Q!wJ=xL@&pQb3hVj6aRn?-k6Lm$0J_qf9}z{wh*Y z=pG8QNuu8k>aTKtX|VSUM&|RFXP#^RG!5g(Aq$Z|SHZgr=OadSNhwemLnH+v3^? z5g79lVBU{>LPA2UtgYp2ZFfZR&O~;NEd^ygQk z6tNY&;`38?uE6Pjl4B>Oafn|cY_@o2L5RbOei2er@6ceFBxx?n zMA|y}v{6qrI^4p!d29|VZz<`;d3W^qpa8`GU zjdjAqdP3Y@W5<{F-=TEWsHt!{nTVJpagvR71*o9DvG#Muqy)jaV+NY6p!aUURc7rS zkrcG*4yGPpBvcfp9=z&0vim2q4dTnk7z|oz2Ip3zCW<{6>LA>>UL=f@L~K?HT9f@G zKG?uKX$P`2dWSP~2GP9GvFr25;4`7%EMB!p1R6@Yji%uQ?%g!^4=yxs{cR1+vGAE^ z2ZNcwJ+R{=W#e-pNP5lO*?Xaz$ywTQ`2#bPi#{;@O?1bF?~mrX@U~VZ#2yssg(dT0 zpbTDnGs|vVYh)4M_6Ncy`ay*Z6p+*nD&(u%P(S{b2E|ReiUiQ~=8&bRA&)f<$~(0Y zSapfkNXw38-ZYYrF=V;DmT~-gg%#~$TFHJwXp=3iq_2Y6Cqu8rYr#9$$%={B z^X$zFDwBlTcH`^F-5fF8C#h3#?bK*Qg5rRjQAl6tyB?pGiWV0;qQb4LEy~F@fhVxL zh3>YoIOfYK1_Oun+(MgP};yXWB%<3boz3V7* zty63v*rqU+tlx4QEwqv9vC95OZza}?WK7f$dXHPS?IU8{9H;B#(Tzonz%xJz}dtMv=rvS48MmP#tY=Yi732#Yk60n#cQ3-6j~nAk!Yoj-T+M#r&44{a3cygp0J77;d78#7nRhKm;uZ{v2; zt)U?KGdjEi(atruu$73#Q#H0QF%fy8Akf}5G&EmCXj(lW-$?oA1JcR)xq_q!%3L@) zx85nms%uVe3t5fZ`N*%{_U}V6uaN-!~}NfW=aqvJZ1NTTK3mqKrTCVY=`z!52>>+@Q$6UB0)BnV!j)F7+Zv1Ys$zRPKD&(zu7v*4WB+n<{ z(FM`1X(2f~vbN|NIsZvQ`_6%pCL?TSqZe`MgP*9Q9=`Pw1mFC`?%DjlgTp;m zm#B%}c|}~VFJzr7+;F=pADNvGx;4hXmW)pFfA(^nLxhg&4O|G;7#|z#&z)AN@i`-Q z)}0lsSlXG?#Bg(czYr8U5Ci>HHjhD4*kPo_UuGD- zwdba|DcQ|YOTo^!fv~b8{!H*fe#9HaNwYmNRu$IlA<*L!eOM%OgW(@15qieI&b|XN zLOnlXRg6Gq<`%#>NcNU&^rL}57%>yXgEv>vbQfS|q6vAqKTvCUP)5FO=8QvgGdA)H zzvC{zyYOUghfixjGqKea#Mlgk338ThpkqChHHBBPadEOtlo21YoFqQwW>LX&j0s6p zQKoT+Pr53K?Jaz^8OVO;fZ z5dB>R9a+>xyb^v^D+QGf*4UDc$V+9a2587(Rq1`wDZHAg1N}>$sXkt*YRvQBPf)uJ zJ41CMdf(JzE(KtW^&g0Y^-4}geOxt3PS#$&%eROfpva~13yJ<^BINdbrIf_m2sg=% zPPiV;bDBr?;CQE}butX?(20owiYz@RNRGq{bV_y`jf2`9K37=3DW7 zPA!)Crq1h5V6DOYhhdyjZ;}V$SuNrs4Fei+mUNvVq<`43C6+#WeVBJg_1UEeBsv?f zR!apM88*G6)81PyCHtW)C&(14lj&qMK9l_enCI?O` z}_Ct(WaifN8)BNdpaj!%y|M8#8ej(O#Pj1l1{xBCs zJKnZ*T&53oZ_KHPQ^bs$zG=@kLcd=WHwa*I+0VZ>L`qjq))g4YljI}$&F)~V)JlvB ze@aT%8n1U&koeg4`d#ja)@S>ps4Vo2xQi@}xoEcpI3E%{aTgHjJd<<$-L{G^y=guY zEVV7e1lA7d83a!m;y?3r5(+-m4JUjio(ih}qfg%7Gtlu_bY8*Djc0r3o2J%O;CcS` zDPGgE8pLF&UDsuWiS8VCRmz9!;8oGF_G0C`>(TkaXWa-U16wNR&wY)a1;ovg(pO8z z{I>T(q$LoOkYD%9xcfCH%ZLng#R$ViUoQ29pn2xhqrz;A4=T|ssbM`&>@iZO!ZPa7 z5^YnZu#?P&BB!2-TiM+|;!RD^tick@FYRd@dnBEJ(VL9)${wJbtwLNU>#W0HM#=!M zall6uonz#U&1C57AvsGBe1DyJjU3*gE5OAsDfTGHaolU$1xwOkF@VxfLEd8KurGri zC>aw>1BW{wk&xv3VO0ZBu_nAWotOh65iZn86Zms^llAH=gn~G9>Fz#_VvP%!7rtzC zRbJfU+)yf_U>OkRWo9pB;>2x*pj~HV@a1cZo$Kl}AquLsknVXMYhwiqc(OJsZ@?qx zQ>IVfy;VYyV}}9n!WmFl`XAUroV&w+>krU+dKfBW9+8GcT{Ju3k@$RbdnM30@EVh` zB@%!ri?!iVgsXG^1%7lK^qL7+W&&BYl~NBStbFAEs26{-nomDhxzAp(g^!O{THpRU znJ+xm3Q%$6ol^%pH!^7=M>WEhPfH9Q6cOzs!X(GG_czpl&C1d)b|7^wq)|0b`Qu;qP{5}M% z@Q?#Qt}C41=PU8?>Id~tV>v4Xe{ol8e`iMrY8+lj*7Lr8I{&Inbi9DSU~77KQs2_(zJce5U&^qBPYFg3x1_(7Y#%~&qs-6o0*KF&Qx3Nm=;;d0 z$8e*E%$YY|@uOpW8ELp(knB@~P2X&oXIlpKF3$!%e~OiLIo4{`|D9rHs&Ss2V|v0Yy~q+}{6IcY@l-u+8q&|I&*Zl?e?$(xHIn%po;;worKOuDfvK77gBisc zp*g`FG;!PDQjV`1i#`zPLrCgLgEEU&+?n8XWhcJDJs=aBhLl-<@hEdhKzn>qNk?CR zSNqK5Lvw)}vT$lV1OKU5x;U4GI&Zu=tKm{(%&aAi)u{z{`isP_=tgZ^H?TP2@1boXpytdADnKbP-`%VT)*HaX_>5{qAlYq zhr|bC(uj?s@)wa|ga|TDH2gA4I?wr0M`6B>=XqI0v^P(=Wr3mTQA;r>m2^@0b$a#< ze62$MSNe674-n;VpuWSQL) z!M>T2&e-pXS<`tH5e|%$9Nt_(TNA=fw6EkuyWs_6-H9JQ1o<>O13VSA2Gm~xpF)G` zJbRjmB7CH+6~X5;0kT#iyjMe>jVpVnj-U)8o##=Lgft#NGV0z|+%ry%E{ z#gNHuV+&7rD|YEs@N;r*d+J`x+C=-(XZMR(%q zxP@vs^a26Pm5+RZKb^|vD9lWkZ}Wue&o9Qz8W_WU^lPoysL-r ziMXXw6W>X!`bw5Ss`&WY`y>IjGK8dHK}HgMvVpNW47{z3{bo|D$nnd!%M?mz4j(Gn z@m~;A#-m2$eh;9Nh{K*0|Glxp#rZN+G5=zZV=YMl3^ad@Sm#XqKCEOy3HtC1`pdpW zx;2+@ACjK*lkto~FauK@J8cL_?j7+f?8cnY7rXT<3@@`3gR|$=f@XO-{0@NHl8C@Is7btmrZL8PhpqeTsJ>K*zCR- zcrmZ=(mwS#d2`_KNpcg-6$`jD=@m_1nQ2*NtA_3ogD4ifLEIqj2InI_edlO?T2K8< zZ|HAYZ0dK{7zb3p$`5OC$EF*-Ny$hV4hHx;Hr;LxL8Eh>QNTy-R%trqtvZC1_*hyj zpwYsJAs~f5e!RqwVxL>yP6HGSC&+Ocd1T$)wW9>~Gxmok%p@G%^7ruV2d|yJ=ulK- zV6u}blc5U!{{Hy1V1hjC&>H%QX8_D_MX%CuNGD?PRPLQb9s#Yqff*)DP!$BRuweb= zo~aV6=;YJG=ad?-GZK0VU%}yz*-*a?9m-HWy=hyf%AFwF0JaTSd zvPpCde0QBeUkHBR58^o`UmfW@A8NI){J zlyD|3;*}1B!R!Rismz6aEyc#8{U!I3;2^2c27FEzTV@G|l>CIWNBZnOj{HwiW;Xke zmJK;ATmv*r|YZiKcLg;yTRJ z2!HH=L0@zb~ZwA4nsmbD9Y@wB_53-toOM#YS$# zYxH%acK12IsO+Dwo#}aVu<0^P$#@qGQ}m_s33QXuNa8bP?(FK73Jkr#0sac>{h}I7 z#yNUmKtktQI-!YsK#vZPu>4;r@Hs+1dXm2%^s%ih^QDMIA^981hu=%I`O|wZK3p!M z?2TR|cN?4Sqy(W$%KU-?w9?X2&)ch0(Kw)W2;%5CatpcF{w(PbdcpN@($6%;*VosT zO-(D;h=7j(VGkX&&@G*GpxS*uJ?A|(^;uBf_wOmrlIUB`n53U(9EG2qovG&`ej7We zVIJWErG!&OiosTAY_}an*)dQJ;~@?X|4pxc>4- z?J$7Q^+uZu?{Atjiik{#|Jk|R8X!=b-Q&wHyy8B;oxpA-YkTrLYisAr%3tWVBI>q4Kp02*)^eD{g#~&}Eqg9r0Z{zG-fvWIH>}JD^;| zCutu1d{O`Ct1ng5s1XO_~yn5U)CBF+=>83?h&Q24xro*f-tOp8}Way6{7JvxstJ`Rpf% zz)>0GU`X3Fp&>q}{}?VtdC6|T6%ZT5FSv0~Sy6m`e) zEeiaNT38?IO3xiAldC>L?RVo8tm8dzS4dZc7*QkraEEHFvjdZ_5rwn!L2!9;zN8>w zcloD04jHt7d6&y1(CZWNiVoBW&Nh9hP-bIfw2H#>K_0UX*5x=8%v6MY`v=NVxA%N^ zO|^uoNx0p}BqIW?P60^8yQTrMQ&nHP@-GgINSy93T@1ij3YXo_xot>`OPeDwQl<#$ z{Lx4T)HR74ae}bkX!@L!GM!tR7?{7s8S~u4>#%&BSGFra94`~NC>hs3(DLi}AWuw` z?5SvIuXOjb*U$O;4r&GE4&-ZPqkzf#yiaYUFReR4C5hc=X?YM-8s23k1T%Ae=Z=r) zs~Y}(&jV9D7+}7$c^e)j^@^aWF0QVEfu`4?<7QPrmEZtY!sxyMiGUa)tt>y19=Bu? z+eNp4f@o)nuzClGwGmmZq;l_RL@eL%o0W$bc}@w{~!peU~y^SBOp zujGR>Tll&Fj@FczQEXul^V$Wu;d>eAop0BqIb})&@(6eOI~=XsQQDq>i(mDA7e+~= z0x)H(HtX2RbMdLjIy{I#oO@1P-L&{NottdInEXWxr8)RJ)zfY%iJA*xV47>DWXo?~ zj*SnxOrqU2cb}O(9sXVfD4q51sn|ZDklo^zec5ft>755`m^jXv<`%5Ap99(=X2GbT z4Y1^TL3}tDhW0PsT{UiJKn5?Eq@>Y0cxDMLjIbdhPm6nVaHZ)bi5&LM4qC?C9GLa9 z0%GG97B!P#I2n;cajxan&f)LiPWS!0+GGmIY1Eqy^Y^p78qm6kD(Gq-Ki*I*7?&;H zL6wB)qaU5#v$AWwC8I~t-tUTHrbTHhAlSoJ!5{QwE?B57w>y`oHkYSQ(N8?2aE2=g7aCg0H*eo6v4xhIfU4){s$;0iCIo`6pWn+eaP zlz*!h&x)nf3z+^{d$cnMq9ikD*VEN?#J3D26EV0S@OFskO{$$CSuFvk+Ae(u#7zoQ za;kx%yepUD7~78xye#6$^-76KCYm2sBdpH;s0p?w6bcJD;5Hm$ZrrKSkGD|dO z$tujGn^(JY8L_j4-e%qC2%Xv|U`n*xq3aq!O0Hqdjwo0oN3%P{eRKH;516rdxm>#M zRLkqCol8-Ydz3&@qm=je{6Mo(>@$JckvLyrqlf-s*CD!;$9?g%xPlW6tlQlE9h z;nYh#s`!dEk#J=UQqO1L42^OS-Zy+M-_{c6;9mi$xNUI>KRIM2clO62DInIK@G$Ob zIMcZ2Y#}S0SoYmHbO#(LmU9ANveF09=}LSz=30VVu(g3tpx=`Qzsmx5s8-44>N0VV zi-F_?44bwKofRaAp*5F>5Bp#(M$mzpG%#>`SWlzU#g=)k_cUw0LY|ixl3p5ai{$D|q*ORv8^Z^Q_XE@LDzRzn zRtbKnPG=xH1by{rgG|W|(1uPnt*lWr%2XQfA)GtFJOjrF!nmQQO9RGcy!{*C3W^j$ z&~TWeCk-M5F*2J9Vx|mpRmG-Djd2Rw86-Fss65_VJ!8dLgNJM#Y|65jTR;a8LPPzT zGGm9N$G&)R_TG&bA73XhzQmx~w$*&=LBNwmGqCNikwS6xmZ0S-qg53CWooC@7Le2eZKwTvkJn;T8BoMW34*o|Yj3i(o^iP1DaGsHFO)XleN0tMcIDlo^Co&+v ztb~?;YLQJHz&rnI|C`X5PLqLw0bk+}dMTjegpm}RUqs~WMM(1t2Z!=&_}y242Fcch zMyemLJ+iHy$t>!hSNN0PeRCtZv9Xb(&DCmMQ45hN?8%VyTEskh4Ui;bTaT{&B-5;k zS4)6nivT+0@)mFxWGl6vlKi;_@E*|j2Y3%U94Z`WE!Fo*@y|k37b?q3?dB~?#sUEn zC_Cly`qr)nkMS-PfSl+)43-29C8j0!h_#kM9g(sn8|rhvi|bp$RR@3{Ae8?f4u;ip zHg@(bdh!l`x`o!w}y*0ls<1Aj4fXROLPDz zdC7%ftQK%VFt$9vV>)W`GuN5z3U2+MO-&O554$%#$l2EGFsYIz{Eps;gAzVYH`h&C zqtyNZ{n=Mr5N*H7C=vTKhgFSI0sT?I^K|l!#CHu9^wFiI0~!Tv;l(u!=Djlyf3nQE zJC;9ac6GJG`nTd`98dv=M2MR_q1 zoa*;pekgp#2$+9q$NAr30^=#%hWi|Xzq=aUyrw4DBD>|@ApbNk8no~|$axXh0!QfG zaM$60_aoW|JhZ3ur;|#1N;^M``MwC&v43DP&1ZYFnHh?BGzpMKcfTUN1bCmOT!=P|Qh~aqp`6frx z7#c!b^yPO08Ka9w-}rpjf&PBN;ahvSP+xP%8eG|lgAvz9K0fDglCwh*m5H&D>X>Y~ z_fj^qM?5@)=z~h*1&u^^U+mZZ=>3$O>C&;{VKsRxM-YB?NyzdFf2gHo__7<)-AVa< zwA3}z5%fSp{wbW4D%P3KWv0I8=TG_=_eXE=pD{!Avc0P)UP%#%GinmPeA`|c-5I}S zqiZ+TlYpsEV}-aw)|eSKbh@?Eic_u>WTTEq$|s~si>-Y{3qEG9gQi3tj)1 ziI0u0ZWHLoN;O3J@&f;c8H7GMT3I*8zd5hIO-tu(ugJW=YgMTyKkloKd9e(XW(jQ@?=9$T$d)3KATcfq<_E`)mkjCqxRNHyQ>;a6BSJKC-y^_g4CZ zNqm&o*8V9dty@C#l|k&skJ$48N3wb$P95e57r|T%>=H-^=@5|zt!(ml z+PydO3`%YWvSnVL<;7xY@e9(`VCR@vy#1H-H|<+>xCud+fGApQJ*f(rQ)R^9KM+$K zG%5paoi=oKVCdN{uG~qxg4o-`*tFM;2iCL?FRmt3n!GTxm2m^6;3y-h)U$-2^P=RE z1+28lgSrb=wv?f#zxGV5DEgBLdBq@la^N{WN>DKA=II{Bg8KMoLb=MDOFc5mQXDg~ zKGvTf0}U$(Ojwjl;}uohCW9DxO?R^3by41mzM?cYJv+SOJ!nJDD-zAmLKkdct&fA~ z0yWnYM-BAZM|vMXrdqojU>yTcnBWe>nlPi25rr5@VGYRddvDuHe*yy??KAZQ&`G)` z%N|ZwsR+^HBn^y@+5-F^x?S5qmBq92%v3d1I{Cc2Ke(&PCWxwvioA14kW z^r@ZMqbqShi(;@5E7S7Qd|t~i0W~%>Fihh&m;0g{*lTvZYU>NQO(f47akO@_NIdz* zOg@Si8?^F4@Y&Ft8k}fqq;SAc3(YJKbVRj|P&OW{{$@<&g{snMKV;KkNFC?p@=74y zjnAgzZB(qDg`5$bm4z-=d4EqQ*6lI#xeftY8uR!_%#Ml#G}?mEX9#0=6#ul|^2nr= zM~(W-RMz7K9X=aNZbcyu`UacG9_o-~L5^k{%^}&flQbuWL1(xJE$F(@V}&CxADG_D zl_GzCKD|yoOU}zPMgCSPVphbq+>t_@OvBe6=!P`RyC|!Nc=rV)^VT|qkj&z`_BHv4 zxCB9lT+|iJuA`g2qy8hny(MT}{iKRWB6eCoQqW#o2zG||D=VF1{##W~IU=fnX1`+s z$I@x{gjLe>i5u_OiH!W+Df>v-O^V4C9xrJ!#9~G5xibJCfH(0SRC2wyaU8QBsl*HE z*hO`KYcod>I%W9gm`sG%yZNrMYZDOF0le|xX%^J6BtbPtyV&67D*nj`}F6T(u2Ez}n3yh#JJ{Ha&d845+2x?I1pzG^~HM_ex+_~T3VXA{GWVfOG|boWo3RTsoNK( zc6PYU&CUA+8H9g;?%vh&>EZli^t0;I?>ajkcxONVg;|a*pa7ovPyG1M;N)}k6*QB| z$}!v+McEAZdweNJGlG~|AG2C^@Q+{8+4MUtS);w8Gmvt_P|cr}r=6HlfpuaJc zYcGHESphnIXu$h#bhh3bV8I+wcn5m7IQU6`Jq>kged*t7c8MEY6pV6c_&ro#Jp3aZ zNzB>hkp-rv?+x}E4!UHkAK9>t!}36wO)pFeFfF)|WOWk;s%q^p;w zt}}7|#@t@)31pR;-N8=Gga!(}F_f)ts<^z`&YIb96m+AHpZAQhPC=)dpwU)(@*~M0 zS)@e?K7lCSeJv|K9;~ID^ud*dfZj|(ce7hlLp!Pg#?4Yd*J)ZtjCfaDxzl<_+rEM^ z(lPelE5Htl4Li%(DkZ|f!2!Mfl(Ws*{&nYdkJP6~W=o)V+q5&!9od6#oq>}drGPJd z3g&vEDlCws<|M+@uIw9DVXBa_s-M%9%fBN`vqKR@};6GK)wmMcft z^X|UZv}I2SIoJV0)!Z{ri4mnA1IxJ&gKDvQ4%2p9nUW#rmn*P%g?uk|bbQQQR~Nt@ z{kbV5aM=H8@#iQQzR+nLybkiyJi}H=X-mAP<5VTKC0y?b=w^e8>6LwsIzwBaLBdKI z?8TIF0}fG|UOeL{HO+iR_-2SNDq7)kjcl<0XBu>Ihq2SwlX^sF_fnMa1`^EDDPa+{ z2kjqrC%UT5s$2Nl)-y(eiE9qC zTV{UhNbeo0THICJBJ&37&Rn~}EJlCDsHM+8;Yk`Wn}$?o5EWhx>2aVA8_ba#?v{)F zqfdBJ3tv`<`-sxYz*J2qZ_iA878WQ7F~Aw?PIomkunk&+n}#YGTC9phuLT;<+pXv2 zOQN*y8WJSESPc0E_@z=rm)HB1ez;iXWY8)uESLzLgJEykPn#YBK*eLTDYF5U2Y`yw zL~Mg<0Pv{=Rr7-@TUQq%KS{}jZ)Eyi+}{z!U#y|--kGz1ZfcAPY5d6Wy8NK}QyOU8*yC$)&)G^F1V-I@8UoPO;9iyt*OZkX4?Burb)?}8a zA~|Sc6FTUD9lqyugJdGLHW}P-f;kan)nDFUy2P@Pw!T2)`8PP-mg+v5<{Z3cQB%id zGr=z;B#C`rKvat>hh?A>5aTav;fmSaV#z8TyxUh$?lLVE_iAKI*crn90Mw<_eRwm( zxF#Ik)|wr~T(B^*?WVCq-d-Y8^}AsiLKeSZ9i+|MeNll+RsEQ10XZ3_gL=o0MCw=hnYQs?zeI(TpM(@`#HaK$x{yC*CGC#CbH zL+qk!xeb9{aH~3rnDKeDszL`o@fP%mn#d|Lc9$LgG%rgpc*~=5)xQEf+(}}iSeR+ zXvOF;gbk3cHC-BI)3ARiwI*9btk>d;2~Y!SuOMJjmYxbnn{wIr_bM?jNqrDQ_C;|> zt~VmG8c_(T+#X#P@Ic?1ZHm1Oi%N^z3afM4Wnf}rN=$b_NzxarOE@NMA83Fjy)<#1 z#Fc|T-<=^3wT%=x4rvbhP;B$q!7G4z>4vb5kC43|ox%1>2%MiK zN=Ql*eL_-{cvGt|1WM`ZV&pl%&4ZyhZjCcrai7(Iew))num88LE=Pj|?gP00Mk^(A z$m$O9cwmmQav}c({Yai?ghtsh<3If|MttJ>GxyXD1D$28g2(54?`{!)>@P_Gbh-Q` zm43T6{L}FHKq?JqKulyhZ+ss7KLFH^Tmn$Uyo}TQ-$ z{|`a|fG2^)`Tim&n_ovJe(Q8+%F5Q(B|a{-R=>;8!a;%lIiPzz_|b{N?K=R+S>_*`p+5{OpBxc4mwY1BZ}c)B18 z$h$vxBtT*(fV&z&z0#8_R(*0`=o=$F5c=m6Mm1mFcuhaILt*(2was5D|8d5OHhSHk%1JuGX) zL}X-XC^uDTRgCG_2E`0#>UZXj*+ z!d>{>AoBOmFVa5=t;oVS=Sw){edk}t+L((GlZ8TK;dN&@1OzX`R~ZAabqXjj0H+C_ z%LpEUbV6mPIH_e9lkmBSs>4!uPcSyCGQqM6ga=!YVA)_{gR;jM!D(Q-o`0uE|aqO7kD<8~h}^U4Lv*5Fv(!0r>Nd)|j$BNqkeu8VMOfSzTFBWl=My+lh(wjP5{2#M@0wgl6uSC=I0aQ)TQLa=GK+qs6{-$s|+OE>G z1e&yv)5dq0Pd1DjnqeqfTtpUt3c!%Ln5D3&bzOng7q<|z6zs#w6NXp3ub0i| z)PDSE>beNURX*=pnQ5|pd(2H=IeJ-KmlpZWBl-mdd%?%T1&X|?xo}0y_xA1KSr+Q2 z6r;;hg>}r?toT0--r5Vj${HuM9Hbcz!{HpR3V>{vL6pAHFci)SbTCEiVB$7J>TN_y zjc3KIO@PogP3G&9Bml4$ge6E_Xv_xQ6<2tA@(ru$J-58>G9{)?A-F`aFwe01Sexj& z5Jd#MLiqwDu!E6ph5$EH++5MnG%g0-m9b-5z|EY(e%~BVfcCnyM(4eu@q56E-KPUC zZIT3h-u!F`Mh+`h@b)02%GR@((<$t0OFcQPWs?I4zou5M=CK5$hphbVy&S%L4J;FN zUgs@9P~f5F#3oKw;IIaM^qGP4H_bMkO#5wgkp{PlXjm^~DvwK;>M4u_BxwoA&QO3v z9b^9BT!w1??D6uzP(I5OqM0yZ&-ueg>WyYY#bU-?{l~dI9K=tJVw|9l#b}9xxZ-`s z_Lkz}SFE%-Ue1@v&g@yJkgthlQ6#t&2=}OAbsv57=eC(mtY9Y;ee&%?Dox;Y6R^0( z5-FQFNy6N7u%vxTe(Do=^X#?NcRmHz3%y94LdGFVt!X}$-g5aDZg~3F7CFZ7mf=#)}Qb0#N%$0$V@#|$Vk4zl2f?H*CN`3u#qdg<*jsTsAaPE~RJ?@4z zDN7hba}Je9ISZrlQkSUA^uOy9tM(om#c84}3=0eU2MBDwfx!MCcZK8AM z{s_Gg;#DO4t9q2g;j}mYh*~**p~&Zh+Xwe*P+H1R*P*J*GZ71md>V}GIf51|;!i1} zc({jR6ohf`WnW1(jlR%j$4iO~Xa~i0S4d4a{QFNXewJj3rTEfpTBHa#{7YXX5hKAZ z4R2Q=xegr)91>>0t$STZ!>@&Nd?Tiqe>@aOmDXP+PQtCkdQ2fca4Z~u)b3=prw08H zD=)!{meAi>2Rq$}@JgG$ba%S`i1ueiZbN)SmgP174$VM|*}!mT`(ZM2Rqp5u2XdpT zZT^nmvI>4YHaq*d1xX=#S>z?C2jS1lrvm=@LfixI2(ow@*=KKG0_9)(b z{)c+_@n5M2@rT}CU^3Jxal?;OLeP9Rd>|zLwRgwdcgak>UaPo z>|`hDhnkV+a{DN91ra~0XCfEygJApJXI z_x~vZYZVn2|9w-(aCW@aVh+USEiW{B*e+S0R--3$yZFPz7(0L@PXM5`Rbcp^?_wV)ec|dRgv!|Zw_aM)$`uJA^WJ4+RlWH5n znSn4)&hlq@xj`nJ!3gzs_1BevN4)t~4VxG=Ms4fa#*i3p$+WFKbk9|l68*velZ6Dt z>!6P{I1ajyl>Q=!Gku6@{0kh|{Tm~qKg`pWxR6gWIgSROc0U`e%+tNmvi*bxNN_UN z;Er%Zpg5TtRYBT!XV0|}nxRFa@U5SN@j{eu<6^--om6@hD|Zvg@&M$9$8Y!PvWK`7AkDeQZm-R1^uyqrUS99sM5VpQ{kIm)2mSn>#Gh#_r-7ECG#(p4B z2iU52{vlB7Z|Xsu$_%0{!)N7HzJNRG z%o{#=vA0npt#`49BeG)Chlw{jb>@CblOuO+w2MbP^8$K`e8J1acSsUl+VuAIOyz#g z`KPj%!iqAxD}{Is!Fop*wh&8p471NmvW^Oc6oGXZKf1+_D7^PlUF?zf|jYE5AUFFOI(2@ znL1}RkUVcRl0{GQj97yF)kInz6O+j8X8IA0aitSE21m3!=Y6)y5nCyzbIqZhM0#8OUk+DN$9v5+vMAeC%IFrY9C+DuY^>$FHHAQt%0W6D?`gZWjnFcY1wj(8&@Au4 z+{H>x@#4AUFV#K_MgD$SLnAKtGtx`sQNb(x0{!Sr!Y2kp)a^I&?7U(=@>ZWH@&2FI zzA`GRuxne8?h=MBMH+@q5s=c6lr1^m)GZ zuJ_;fw|rK~Jl>>^G$n^Z!aV~!&8KA|({jtaJfi+&tbiWR!{PXi=@XZ@#b9sESq z!Y2tZ^VUHrA~s!FNZeG8gBufNb7xt#+l>#UItb-mlq!fHnI;x+TkZb}{N*wM@9Bbc z7bvl($J`WO!9bh6i|3&A_dRRZdP=t4uQ@^Ra-))>#JBL^t%(ab;su$sT^2&*{ig7^ zJ#{6c`);IpXiWAf=VmITY@1^sU5Z#*S~~r1Bg>u3b(S+f>LMN9;Hf~4jB*IBVieo2 zZ8PHrnpqtqvb|y@4aeID4p>pr9mzCZiS!$hk<78C7Rp4MJ;#uV-^!(L6=9@IB2aP1 zT5C|zO5MIch3+zm$<;(id-4Vr4XIrnmaV0?J7#&@ysxGX&c&e}N~~7`_(UJXHa|?4 z`g+I7I8(W4bWp-}1(U`{zYf>5btvY?fgQfz64GtEif`As#C3p|_-MrLr>>r&=o+j`jM2VqYN2=a`D>B;cus0%G!XuY)EDdaw zejBEm^57J-Q}#7OdA#5468Ey>7IojYrtrk;n9bKX+%m>5&1(m|Z5`Lhc#ktV>+$Gq zRGEkJpvhjB=(F?7nJd|8j~gF)J^o_*mZ#p_`6p_8>2URQ@9^*DoGxqL;8gOY-)gvZ zf5KS#bEry4n{*^qGlV_KrKP*C_jRS^8MZb>m>~u!pc2WSwi} z$oh=i`a)>-Dn(pYtYY+`aH%w7#}j1`otpUW+fdDCoNetMbIFvs_3&g}9aa2Q_QPpU zk?I=7Nx9ggWD}X*x5yg{YTufrf@_n3^z1z?IWW}|!hl$=LuGs{)Bf%lPn?D8XY4so zsLaY016@RwUto%yf!@o?`q*JF9(ZTg89(bz?~VaC=oGaz@{qW}{Ne`osjJRoa<`L( zXZc$oZmMA}w#H-mtFNy}f@?P5uJgOKzFaVYL$but_^W`xNn9X!%O5u@D`XiVqDe_Q zxS0Y-0$Gmj(j}>ZD!@|m-%_V00i=LlR65;1r(ko zqAy|zq}xX34ke|-tG>MSy+tU#i^dzxRR%)ge{%ied^N{I*Y#=Itc1j%!6)lUw-o3~ zr(W?dwrbsGf6XR=o^nsZgp7o>~pNHE$d^r2!bdcRZXw;JEc&w z0Lc*pQ%`|F#}O{gbU()6`OK&~FXlZ;e*pE2+f!}~N`e)Yz(1^8mmTTZ|!!8}ZzkHHYNto84W?tj#Kl=Vk$-q7%4)wlQe$40~J zXf{hdqw7V|L9{lDAe}oD#nXbR_2(Pol2|laVefEcKvH zvaMSUB=n(C34JChB-g~(Z`+lc_QB`!qw%Psjd~lIq7)(MFxGJr`_kH4_)h%G922QSvXGdLCk3`LXZs2MDS?&O1xn%TDXcjaDn5qib99%tY53+74eH@T1Bz z3G=a_nor3`mkolQ55|Lmp08{Qc~W7U%iaZG z@T8#kBC!_XRu0qJsZW6AD3WoOKgtYjKOZmKG{bpxs+yH=n!%i}X}G>`yRoxUjux8h zd?f!5rchGK-*6h#mvs;ZX#~Co$-+5{X55Pp??QVBF#YR>I(cRAv$FzLskNK%Zu`0HWFD1Xjl?u+HIiIZf7W_ zAy@fCB_3pw_HllQ@Z`+YtEAHG_stn|F;}x`1+*XEa?Qk9=3w-sFoV6X7XypvRMFLu zS?D56nrlivvfwSxumJa{BXqkXipWC-2uW(r-Q0*fjoWt5^`xzd=RW^|Ydgmo48%Ql zj}Bo7k+Z}#Ha0ft-JAwIJe&}`eIW78w&*!*IsLH0$??^cH<2y7JY2J%I9oyhSL4Jy zXYq`1b$vC*#?4zJ1o?vO?{inZ;^pa#TAf-tzm`Y!d`{@^S7@%nTzi2lM8Kn!5Qs4q zOA#X?qx_;wpq*c?HNhEL^y~gH>4jA&Il!MXmQpHa{8{UdUw5?Uff`g70AWTw5PqDV z@0*`X!GeWW$$W2Bk!t~xb<|stC+?)c{2?nX$%tYE;PN`0ZRK{on~J>Flbnex3|DDx zmeln%c%+CUF@uU^VX?1(tc-Sy?(u5@GA2L_0_AjAGf$Od`HuSYVC7+P6(hRFH1#{9 zm~&m$r##HfqtbM+47BkGRY8qm&UtiVB+wX~aTexn`B#dBFLWGH&|b^46Rz3ryZ1Sg zd{iA-tPD)Z8R`AHPXepk8uzO)7oc&ifQira>~6t7D2Kgk>@9@&M-6h6ff;Sp-%IRW z9Xd9zIG67wXkl?jhExbiwiYm6pCdM%Q2=nvFrxBaDw4lDasLltdix3MCTG~sl84hs z5qhv=5FKQw(FS1e_b}6wg1wd%YjSes4oUJxj-*q-POP&^X8wIlm5B8-{?Ve=y@ zQIgD<`rlXZg6)fINl~ln+sDT|Xt=UTW>rXUeu(w4e|YjWn4smB09$}?NbKtxVij;o z;h5i2D+r^P*5KRg2u%ax;kh9f?lvQ)(MZ9hS7+K8S8n}07-dLxs_q)aCB3L2vA0UV z>}q$jN6POzI)Nd5_s(TVuS0!I&!17eAa%g*p~rPSucj8#5Ei2t=tDEdObME4mLH(_g7Nl zWsvb;4F|ZBjN3C<2yxtwmo`;tIF|BgG*(rtwSCB!H>KaNXOp@R{$$0sct1M9$-!Yo zFh~Io4rWlBA1|Qd5<{Hwx8e6-X9;r9B8P^sK7nvn!IY2&!$*0|@vXw>yG65e27;+{ zg4Njlzq}k*Is^2xm6VgqlOel&hnIuua@*skc^sa&1DBx8!Y}jD;v=U z-2AeptC`%llvwhC#AruGhUK1(_Fq`?WVnC*+W-Hrk(u-JW}i}0aw{s1)0S6|@tiz7 zis-6jrO4IC*9A%t7H>=_RO0lFzP{r3QF*i~g7%zcC;MVWMMdcF%#0a`455$ygOQnq zg@ponm7^9LbA*${P?o5Sf1b72Bsn>`tg6oXCgi&6Tg92ff1UHacd>=hW5jCL%5ZFV z*ER6F;j~d*#v|Gn5OUrU7asBA=ijvyzl}ftRdJ#5(`K#WZ{idBf>EK!9IDanb{3B$ ztHqs-=)Rsm@O1MCjOc>CCP<727tuB~!oRMgqvK%?%|}hS>{+-z@EjkiuRumd7I)n# zYx@d2dA%ValPvVXFS6NQ-f)xgm`0idD`n!-7U)Xy#u|Oh3~jY$aaPC~OlvlZq`gmX zF4?9WkZgB&q=u<*b3?{J8*h{d4oG4YmL6$qGUQ3aF7jxqqwRc4Nq;nmWiVfS%Z<~W zux}X~AnIh?8~s0?{6Dl31#;wxCaBW;ylfN9bey@7TULc*1zRASC5`w@7|^^KfCe%$ z+<3*O`J!_~E&;}E*S*4lF849Bz9AA>A;h90zLXBJo9SwR^@X;RFk$W)tOD+r;b-_h zO*8_E0wtjf0#DtB;T|n%!*Pd=pQ(9>JyTY455x=z0e#T`4Ca~GAhrJ zcjptC#sFA*EK)0#gl>w{zfo#vPMjz<_;V366C+a7oJOhq`90i>LwR8c5Z0WrNgnsSu_;2gnR;0sLrSWV)t1PKLT5Qm z>(uL=WtdvVWWN~H;jJHe?pHG}uQN;KPJ*rnB_{3FjGH}E+#DSg4SiWl?&|mr5q8<1 zUJhv{M?Tfy5v;89Nu_+sQ9@!=;;kq#d+MM1=k(k^8f-NB_1h}zNefQL+y+Cb%njZa zn;^R1_saK-rFF)zA}Dh<6aV|)0_Sc=L<%o%EK^QEBExlkZ z)7L;`m*N(qnj(%6(N4lVD3I>%13?bLQw)Y)vqBB^R0F9;O8pk|QsLw4QVB_!5lDf; zp((>?4cSUfwQ%1%2}b4_n@Jwga*tuhsv&z3dbZ_!(Jlx67HwBGkkrzvIv{c_$%4+4 zvbk=1BbMH^3#jXk%YVlONG{CQ6F#`t#JF_P+N9N0_%>p>r|yJWfsgZ(Bp%o@I)viLGbQUOjNSpt3KIn!a9=I6GM&yD5t#*N}8( z_-L^^CTtOGiv`K#r;;O4Lahy{Z!MvAn zAib_U_cahhLVMaaXxCHy+jiVc{8$k%v+yMhjA^;gtw(SH$1`Q%9JfvUB6mfHv*w6+ zC2Rgo6lGM%*$1b=d#(pIU-=K6OD*808z!txC`2A(p|X(?9gW}5BGwFjg&`9NNLepj zl$|>M#HF=YsYK5+$1*f=pv%ej3*WU-^AjNSl}r1S+@sOxUFp28nH^TVdc%kAYm5@U zCLPEwl2a&+g#}`D5fJmSP!BIm=d!xb=`jZc-S8< z5bdNlsbB!=4|rrIlN53~Szc|A0@smr_; zhjIPNBZmwgB$V?P!n`{(Z9(YvnS|B7oawO>#qZzP{;d6NOp#P^hruB8Q@+?B)KWJh zMpX_Wv)zf61e}uUWVwEa?>L=b-W9b~+$=D14AasQelYiQR}czi@gB&bq#S?ao5FyX zX-CCm^GpGhK*?`Y>%QRJ-?@5Kt5cVj7>eMLcRQ*lOw5!lcf_vSw~a32>FMtE4Qo2? zcLF(0JIs>$gy|f(4rogQE#$U;?fww)|wI{9$8i_{88x%WE2eT)52MA{kY@ zUHs41IUoUe%2q$KPVCVKEj3@=H(|3ASvljcS=$ycSn=z-8}>-)jZe~;lZTDZAbkmD zL7T0<`n2rdyjEBBt&Ch9E3e_vTgDw9;Y5Alya?RoKxgj#*%o|(gQ9{gA-4(mEas@7 z-L$weNS%oWzG9c->P!EhrKthp<#F7{3<@x&#|m9BFtODjDoTjM6O_r_*f-d@Mv-8T zM!0;y6WX(dRY}NU%j-)|OGyExYR??cDr`v}HfPwox+35ElJxmY;shuem{^jGS!cQX zrq+1dW+WMm2`G%skk&g47rg?I69{$~wIN_QxuKZ0(#FHXqmgQB6h5Ac3uw^^!#1KZ z_3X^T+Jhjsa+>dq2c~sOjB+yt6AH8Cn?B8_9{-h0e+r;EcP8>Qyl#14*1G=bX?JloiA}$^&FDG6E6-uE7bh1obnW6MD&l&nBbAY~`*I z+W7VU8;ad=Yx{P}YL z`w87rQT&T*=mUvtvB{^GYd+ZM^-RL_m}7b9@0r}r4`ibX+pj=(Jv2Nzp7b`AaVac! z@xt^ueA7|{A~WMn!PhsuyB@W*QzRuD_9ZIEDz4pOY@AfEShyPM?rZFifyeo-rh~Qn z@oFGBuc=DZyvk$wU6uZH|d&ixxG zM_FG6!G=$10DPsbhlhvb_Ku?CCAimL_-OF33V!@p!s_gG-k+L@6{JbyLhab-_M4Bn znn$Q5jE;_y5rogy>|2Kf@6?rJT4Sq_seBHiK}!c_ldKWwoHs#f>Fd{R1MEpXEo7*8 zlA1m@zp3{4jTTBh7iTdWH6tu{s=sOhD?(eGMV{f2DhSIRPwT}u?XDLLhUccBYxrZJ=GxaB}gH1w($AI;`H(WBA(j}LztTqM~x=F>Q#YkrO^H*!R+qWNZ2 zE($%8CCW7tEkxM-QzwD~mF(;`x+;Ho^Pn5!X_}h$$Ns$FYawQUjOCYLZc$m0`g?iRtudkOeqitHa|O0W&hIY|Ez!jE)KLM9NeM*ckn;o z_Ui+jNb@yGw!Q?(?euxW>v27FTO3;E*Mf!sj9^uKk(ucmI{zPdiI%hpfSL&d8ewhl+sa!HYVN*(;{;H)J$(l%NZndf zLiPNfNxR{QSaX>5xWUG#&1Ac9;O92}-l7H~Eewf)b2P}Yhzrx$oMmk8+{G+6AvrIH zu=mu()^W>Iz$1>;3y5go_;H$lW}<59RKnAu!ce$kK~yfXB~IBwkMOV|{3eEAt_e&-KMHN+m;=wX+=beBSzv0pu8i zSxKV)ydX_hd%gBAq;-N{}pV3JR$`5?tFT#>5s2PyJui%*n#B@k`-t$;Fj79R zP-O@xEkZnC^|cmI6HF`k?xQ;`avH`Y z)QL&WA+;FHh8N4@kr!|WDJTv`4aTz$&*12?{fU741GrT3L@nec!z5aGj_Ixz9U0r5 zQz$$cN!z3=xj5$Bh(|_85;E_w1k6&Ni1>2B&L?^;*d zG{0<*SSK9zTEK<=Rs{7BeAz!-NS0DF2tcDodl1d**Fx8is$jm zfT!w!ZR=>o^yDu^#^P0So=O58L_Z&1K_j=R(OnI>C4eP`ol)zO%kqY#U(8Lh;(I0^ zUv-(&B&~4Ga#MXZpNu~;+)H+{%HJz(+>1Hf)!1|jjME7@7lc83;Fn8&WM0%jB$9m3 zYMK}beKr@q@dxAujTknZfZ=bE-MrmPG#ZY)`fhoNme7TYxwI~XLMgT6>mLXQt<8G* zU-1xiUvQgG8v}p5DLjBN^Kn2VdeKlcsrAukha`7v8@KL@HtyV&C_vYkgujbM@&8@= z$7!ap(Qpw;jg)hdbH$Y&cwZGs5z2EZ!N$;DuSx2QCgps)>!|^k2*_zx=|Vit7NKy#J`O(=3^`d9JQM zQw%oa&s=|$?Wfu@WkLEE(ucul>as}^PKPLP@Y2U_EQwHU*|04vD%jVrRD*<$tyX0{9ng>7XJKyK> z(Ont2tMdN={9RE7J@tItt3ACkQUYt?ELUQ^mlROzYukH%GLh;YW+jhP*zYK61O-{tBn2ZLs3`ns% ewTSdU_oVX=#visszqomT{-`ObzbI9(fczhqqBpnz literal 0 HcmV?d00001 diff --git a/docs/src/images/keycloak-create-client.png b/docs/src/images/keycloak-create-client.png new file mode 100644 index 0000000000000000000000000000000000000000..dac8d9131e6d390455a8538ead6818bb4ad399ea GIT binary patch literal 12802 zcmeIZWmH>VzcmWQCAhn5f#43sTS}2X1qwwHXtCn%6apkTEu{^`p-A!K4nf=E?odJ@ zxD*M_P0#b3^PKnV9pjGiez+g9#>m<$OMm+}=bm#X)=*yuK+Hspg@pxp@>ug9EG+CH z%m;2_Rv*&@?o5xq=n^jvkV7}Im2kG+4sME}Y%7g9f z&y@-$>1pYoi>^B&1mgF&zQADlO&qOU9K@n2mA@kMbN8-0a9>hTKBA!P?NIi1k8;6= zHws}Bodjl`Jo&Z4jK(h-kX?mgh2@QBbgg;Fv5=v9PDn-pEM(B0H4nNGF;aVBOeN+^ zNI)1i7Z=x+5h))fI>MlyQZjfFR??ye z&!UN~n$kSzEk>!u5EHj7w945pV_{py-!y?)TS4*(x8#7axl9kaOdzp)F+^Uf1hLFn zCS2_*>?iywV#5P*z-KZia5o3FvWu^#&k1T-_ zttc&rxo1JAgZ#>}VjxpLq|XfC)qr)(&xdZF>eClE`kGN|K7ROAI7^7V;M91#ckdoQ z*3*9Ob~3c@x)8=f@=SBTgsi)muxy@ClF173oER~rYDAdmkr{LZ7}10xeI}kIJe%kI zlvZ%0t?+(?kAt%i*G>WNpU4h`=_)SGbkay>4h*Kk8c$&SBJed2Ij4?d_=yj&c=g;j ziF-4P$PdDZ{Z-e8Swzmuo&w#wm|pDh&^(qBY6OdW;FXu0Wo6eiWgBS%t6I5>;4L=x(k@#K8Z+I?0(%qZ7%tMt1?x0g);r&;ZS zD|b&RGL_lEO?3rLXOy1iJtCk<12}|ilc5;;J-LM#M06!vges9cwQQ#{HafiJQ0mnBr&8?O*cXzVxKG7ycCN=~-JFvx1aA4RYp5}S?S;kxP-VbePf9B@!4e3}TSGKJ_D zQ=1NSgBqrjkoJvuwpT8uYn|GXAzlN~(&>thPqw+$fzRYpCE~!$mgM_!B-(+=Ogh#K z%3=yVPK~x-DyU3Eh#%Hd)C(MPb6yE1bcaF1C{}e)V8i*?qq)|x*km#T<$+i$R4zkP z{C6NY!-Feu7e~`!7=Kb>KE68T#L;Nm3O(pn+l^$@tK;lDWWfiJ$+41w`*uC>YIA39 zk(}aiR1~PeQZef3>!+$idYYOmuCbu*vyk6eQ0_TTC#+TCvhTY%4R{DCBH{iMJnp%4 zq29Afe5Oe*1dV#oVFPJvRnl7DFCMbjWC&S1-1&BI>%?^gSX$%j#6!lX#VPfT^gSN! zVOK1s&v~Jo?f)e1be|9sllH|<#>!EKP!gB1iPh?J$Vt(Ni?4F;SnOs}&*wS{XzN{2 zUR+%v=e8e`W}pbXACUQcLU9R6QDAcp7%hl5I06${qmLGu6WZe5&7V+Hj`Q1XgQzGe z$4T*1VX(&!{;w$`(?~WS3yN9`LC3e7?LJZ~DCxFE#(9n^hjT7dh*}E7EO;worY-S~ zMi1(Q>(Fqh6_z~gJv$NP?Qt>2su$>%(<8_u>g|pdw~-yZS*s^#cqzwzY{Ypq-$e_jj%+OufPbdcjtf*)uhz%K15M4GHg-VKq#*>p%(<20lPju9JZnuBz!o`DOixJ>M%T!$+7~}@{aub7 z`bHK>85AG`*AEqbuTI7#zD}l_^Jz^@jZeQXEV=1f*i-z;a>pJ-Xz|!JcXDJ9e(g!0 z)|Zdykv-W2e~z^PsmhhG1lf((0&SmRzqTGEv?o{Zo~t={oT=l!pKxD)GVlF?2K6i8 z5%6cwR3+$V@PeVoPkNNQirTYhfE|URDs9Yp5U5h=DYm%9bltm1KkU{iy>pPGF_Jq3 zoA34-bewA4RPm+7gvp|qKrdpBiBdr zXG7j657>!|jGIurKM(Q^Ty*jZ!G`T;xd$`S+?p;DpOS5$VOBt zk3Ao};RhS;oRM<(TCUcw_DK9)hCV(I?wwSlR14y*LWfFW1k_py}F~rBq0KD z$0z;w*NyJg=v?xU&0!VIuy6ur)b%2JTf)0^$(D4Tm){hTEpL29Y=N<`unrZ5A%{m;O$(Ez1ZWWlzcG`D!sO`bLcX`u#|uc zL*V|iA9D?8p$~MztMTyRm9qYZ6d7mG`W-K1dy1UfZImPxjRWt{MBPbhm_CU1_P>rlcj#&oyoc zl*W9#OVS_xH5@*kOsU1+u=Zzt-fTGNPus23D5zmsG4@pU)|;G@D?cYz3*t0O{_7%% zJD^4LyQLWZLKtBmHyYRQNH=YV(1zvxk+&}-}{Wjv?!=tQ2k$ncw}g$-kH?W*f!viLl7g zxs6dg3`&s^L|rImowv7b_xnq>)|znIn@-ezaUj0&yiFvx+Ht_mj)>4oUDy#+0z)E; zUmt@$pKWciWD@nM`z4&!gF!>L!}t2Fv_IFZrKG3w8}f)kd#J0`lgCx2+vu-e0GxgN zrJ6gAJHJ*CIJW~@-eD(*gyx}HfQ~dd+{)5Z)$2PAnd2+ zDJ2m8`;0-4%K|{k7pN=_x36WiPo2N@I~ZDSphGhQ;1kdT5+*MZWJJ_>2+{S$~V5y1-cu0u}DJc-9l&qMP+Y*`amYk{h8T|W*-^! z3dZH!rF!Zgt1?4jTaw>JD-mD9?!r8oh^ajC>bw!>ecG~92@5J`ZLU46Vw4mqu*Kx3 zh9N3!v%>ux!9HX<9pOB^9&7SN4kd*&t+I#OmYYvW$w+f+%NC_gs=~Um-SZ8dq=pqS z?9GCiVJL@ja#7P|E{IX6nC!OuXGw7Z-dUn6gGpj;do$&;eF%_PygmV4Q2z#twY%YC zr5fB-UvA&@`)k$`cgIJgM?6s?V`RSh?ml{Qm8=>)zJxbF>E+KV47Z1~KUs^L94;ZG z7)XJ{c@xb2hDV`T>Y^X>OOevqs&5cqeqi;1=y!IR$Hx^PWUajIe`yO;1i>f3)vY+b zzQbFrrmZ6dvjXGuOHm6k_TRO&kB=;Y-Blm*D#DlQS3!ma$TcmN*yiX>KgL&9u3PWk ztP-4X_9x;C5VRPXt#Vkp(->dPUpoNx@{y)gGVXKHU%G|#9DNx~O2n5;A|Sibpq8X` zm6yOjt=<%b&QBQSHmKDm)KL-8p(GX+4>R*&=l3LNow9CFbER&9Ci{t8efOX zg$BqHGii%27$w|x40wo$0aO-~em&{Lt1@u!p;t`eIPj5Kkyak)zh8=ZT9}sd#!L); zxYF*eqhAG8Bg|U?M(Ci_JuZC2@%yC?x&WP;dSl|hS2`dUuoutIN)5wzrQYi*0LRW5 z5Nq^Ch57$hJ-KYwF}edE2z;K5GSK^G4U5QiZbv{K1OjF zQ69iZ=C$AD(}#{qqj&BPoXb+6I-AN>2GpD#N1XY{D&r z=OHkhfg{I&t4yJ#Sdy;o;LutW{Yu01CYRDz%ekViENXS&A$dL;Va}2i_;x{&Z8msv zzonx8d1A`p%~8#3Tw@E~172JtdALP#1tmFf>KOmJk9P9|!uM`_6#AbAyx#z`tjbC;2!B?#O;KGfS`!RNiK1{?7C;i7l3;eyUq(; zY~5^?p49geb-q3vF5x_EfaX#8!VJLX(n;gYCdW;;FgRm^cBr-wvK7caq`)~=xl4q4 zDClWp(6;{tECZ+ZV*K;Z=z8xD$tIsxY{-6<6(U5Ex5L8k`~(b)ox#UHao7|i+U&gd zRQ$(Vg0jUsewWkyH42C2G`9cj#)oCjkAghtVSq9}T)&6H7VV<-!dbj!;Ec`8THY2| zypCi#qac=$eD^UFhcQ;2)PT;mgMl zHfm>EQ61UB@GGIHa@DsPyoX~;OFN&7HtD1q>yX2>t#fC?;%({FPw`V7_d&pL`^*K- zx`yPg2Y{fL3g9{{Z3s&i4$@*G!qZ$hh?Wjbd}2uEoUt>uB>yXDoSelp;oLvJqms=E zlG;74?})!RrfeG!?nl4}Z80=kjS_Fa;Mm^@xIP!Dv)JEroJ#SnBjl`5H*uAG^@nKo z{9Omxb2F2ImJZs2b!UO7WW|{0WBU5Jv%VqH&bz_66OQ6vnbhs904pZak7N{x>hsC# zr==Zt7_*v_bjRa2D=F7P*Mce%w;14Eb09^g1$^=5?`OS>CDEXms?{jp)%`C248M^K zz^ZFci>l6gy{YWszt1j}ju+oM_*ZE8io$UfKfZCnct}mpzwk)pr-W!2&Yhg;<@Wc^ zIEJAgKYkeaG3e#jM<95r20&Ef&i@5_i05U~IeS33<{KQy6YHv)8Y&zd91IRlPY3=F z4))Ub2UPx4DWnWHd_Wl*f;rWl0NaQweabV zB!*vo`A;?3*&$EZhiyGM@0uwpOi1}crQ(&9E@vH`oIG#aOfX8E`42E~yTH5>6sHeUWh!z%!ys=qPfN{*!)?l3*}aCROdqFxU=9Ba>q_UW zI=yIY<~eksMM0|GN3HNoZpw~= zgyWY&hhI~%N_FH7Es^sC1U^E%byW8v{T0K3;5p>_;}&pDt6E#SUr*Z)cE)b4txUIbkAU|A^}%MbHLq3;~sRZu#T*dpsJc(Q@Fk&)}tz!AEMI zydQoVeK*11y0+owmZVSA_&mQ@RUM6>>CoH~R68BV(qu7sYQvL3*(a!kd@`i~O%t?967{$n%F+Pe2OsNb&zSsP4uK_6ihLLzP2ntuu}>wAtb`HSGJIU`6Uj#S zsK+bna2rWE(suj-<{q7LO8w~fMEh`j-k@81ar{YQbR{hJjP3oQEOD9U@h2lidfeKR z*FteCfG)5x*M_0eiZhhLSAep@e(iL`+L9=4AfToOy9^vuaU0sP_I(w}NLf@s z-MkQjleM+)K-L=n(1kcr!IQ-2AYUfv%;n8nv154Z-9|byz{Ps$w z-F%8;ojD(?Jm6l?Ud;SV+IMWBjs)UlwzhOoMHyUxSKys}k;y!i_>$|mrGSw)+M)Vv z(JFvHqqfc}a#<>(xX~NK0fc*wx)gsC3y+lVHgYoGAM?L36NU4&>7D9aWjq0!$VGZ% zw~h&9^rpsR2dw1LH4v5Q?e3AJeo!9uRJu&}5GFVc8jJ*;&u*?yv|G$YOEOOxS2GgO zV|2`@p|=dAPr^$-{MRAGY)dbHSE7!A5Rh{T9jj>bUs2F!#8Yk^{o3@u<~S@xDVWSiV|1gs6=t z!gE9CY7eox^Tm)R#dH(dG2Y!Jjy*k#m*0mwV_)LQdT`&%tTYILK6yl@5f{bh;mjh{ zqfNk6Zc9IO?n@|*OYJQah_pdd=BEPoIXzL!Z%Y^HDYn?(erc^>*ca9@Zw%S2m@Mt7 zU6QQ4UA1`_J(AyBKU7T7`NadoVlYu@w?rzXEC;n)Gm92;#JCZ?pa}!Mu$MoZbg`rV z!Al(B5pOtfAG1lKZ+yt4C)Q7YOHuI*15fP+BJjcbKYaNceW3TV7}#-?{$I~?p;Cj# z%J6(ln*`qR1x%MZwU%OTYho#z9&%;kfAJz@>m96(2$TK+kSCfeQHU?e+RY*hXYKGo zwGgrsV+LXZE!T1LX!F2UymiyP zy2j1C#=)@?)VyhA>2^k|QqsRuj--;iJVrVBSZTnZeZ&^^%R}ys9fO_)W`%L&llr5! zRbOb$K(;)6Z)4s2Y!+PT@9Z)pjl0jACH-^cHo+Thx5xoizXa4>T2m5Ag$!EjhoR+F z5+_H5y*3pacTZjF6~UDKF4XnVVk|_Xx|NS}WFW?qoU{hGHO0@4q z+^AWc@Sr$kx&Hk_=PAxsFDgd&smFm|`!{NcEQ7=&;*mRli$RawXgFn(M4 z7Us}tEyd0~I^xOsO#IMX>61*?QaR5^k1^+6kTOa2o6kBiRH5ym77!hOt@^oKm4Zi1$tTAWj+kIapfydR#nv< zXOMQWlI&A^7AM5{aPgRIJ7W8-^?VG$<*diLDd)k8VAt83%Yenwx6gjEUXcs|iY{=7 zdqE99h6AQ+gUEj<87w@1ABLJMEN@Og_V{I))tP!S2G{uG}lBR7tgpq>vDBqTr5cs{{h zB9hwVZ7+6r+Zf)Ro%=>oSWz6F(U7G+I>pmM!d>lzmM(#^a~|)pzT%Bu_c9{qQ_!WP zM?!OtNJ`imy2dVX&OmudG_RaeINc1K%Ff;Tqd% zlraodvXdA6+a!FkMC+5p3^pO>FoOlp^xAI`MHVKqEkpZ(-_SvQg!U*48*y zW_t)dDme^N(Lp3o%9_=zL|H+K?~V7N*HwLDH{BQBr9eIOE+6PZ-bTLvTt2CMF>WG8 zlr!%7-Jg8+VcC%CyT@es+KZkclb^nqRD&#GhgZY&V^!%^Gcd$blOS%g&|nHn4Xc{N zPtd_gH~ZZgN#rhCM zZ2H_umZ}0jeoFPO@R!>oy5Yn#^)3PrKlJQ(!3r&{aXc5|rU;_T8<2BaMbo>qr1H!V zr51kz1L12RZ+UK2(d)b~=JMX*pL+g5gv{TrL0_>l^qiG|q{@ciTcJruPsKnKrhV6; zBe|=j@aq%LhkK$^x6v?Cv8WmQ{(`4q#}Cieo=jlVObh-3l5!~k0UBc*4&h2dEY#oS zm|_TUOy~HlxdK{8+dxTYX`DjMxO{6nbg4ME(z2d~C6xM$JjBTa1=W)YKjVWb(x(M) zhO5^DwSgA+q$HKHds7@@5>$QN^mtd5E}GgzHkxM}oLs-@_(b$EYQ?|Y)?imI3wg{P z9Kg^@`AJxXMbFoS>TaX)K6V)z^386S{-||{2lqq($>u}s3D-QxbEmjB#PQbRlG%UU z)0*Two~Wjm7hD`dJk&U)eo;07 zI}7eQViOP64b$k8#2WCqZeGaUgpmDGD|_(T{2fM@M>v@w^H=e}ueZtUH=e5|X>XE* zdm(Qn3V!|OJgrOPoSLOqdtxwdoE9I+G(QyT*g>ECM<2~zUMj_86nGmDj9`~fJh)A4 zv>P8QF!yMu%Ob^YMRPuGG5`;dA>3DAPtiniRXVzCE@8yZ9Wc=B5vqq_PyS=FS{hR- zwH?FkGZDg~hWi*{b=oW(hLkgnui``qQtg{{Q8S?XMTN!Adb#g2l|J8!fLLjkS0=_>lA0$AQtI=9X;2nLMV~PFssqgzq}B(#ed})GU#Vn zkE~~YGlib8R1=!qEJ+>Gv73jv5c}AOfM82zP0ge^qcRZxld3zaJYq%2KBln zGVt0-nQ1p?El68IqUO(-)WgkJmk()&%$Bdn@h6jHc-U1SY7iWI6Jhm^PMpLVR+Y8c zyn5nr(*+5hi=w>Nn+Hk7QawU9oQiugaxD5Etd#k1hm@$R`?qO}0&$BY#quV3%Kz+n zSrTR6vuuJ+lzCR&sqVt%!L^EGml6Oi6C7k&fng49jQ8k3Gzg!oaXK_4m0`$jIC57y zeM64*U8GDq0Gac4*csAtBb6^Y-FjF&9JBcb>hX0*CD@b8Li{frF~z+5L=iQdA!ECH zuKZYR--cpHu$Zmt#P(bGfZv`9MNDzKT|EAzxCsZ^)>~q+we8Upl1W6CkI~rT+_DVEID4k^AW4Nv0wtcKE%=(pN_4vP=Ylpg1A%ADxRI zQVDu5=x|;5;Oa31Hk%xB0)&*`P#zLOq@muCPLqT0KG7t;?ZV=rtLyr*tuvE;NJ+<* zutkCvxBvE(q{w4cXE&abniA{&ErsQE9<}MNzyVJT3F>^HdB`ilpQhX09r#D6cYqq^ z$SV#%P2HxkDharxDIB%VZ8nZgdvS;X-4VHKp?R<4MO}=U7TpWdwhr28PKRwl81()S z*CKpILfDHg7N4w~O29Si6#iCvLj>=>uosRbiKgMX_vM_P(EoJ;s}LB5<3TWaTh@L` zhdxB?G1unW{~E~d;o-7i!_eI5=FXKtN8d*kjbM-Nm`zGa3|lRW#PC24d?P*N>0!?l ze9zEvf6K)DXmQK>=CX~{hVL)U`}vpVffe9>&}*ro~kpxZzDBGFN>el*=Z3XEY zhj6A`?3f?F)bBl;}hq z7HGM@Bbj4l*(xd3%)|9{{+#}(w3B$ z|HkxB%Zh1lZDKeX`MI;Uo-A zobCbz5xoiKJl|i0h7yn=d!rwe)X9u$eEbbG$p2>NbJu@t_Bi23)Pz77Nb5E+^>HYW z6!n}|e{XTOML)ojsihwP6lkGj4ky{UbnuWYGc-QOif(k{QoW};P!5O)E9^Tdq3!#Z zSl1r4?aI|*5&pnEV`a_jyM5-v9g2^BetO@p$L0)^&sBKKEA3z8?Hy`{YTyLL@PWJ) z#*9627IBQnrxpO~J`Or=cB_q)44G8nk{9j^p&dIx`w?w=EAR} z>uQ2r7Q*P_$Z)~F8C@pX!8pFY1T_QKdg2}XWsA|?i3Q-7GvDvzfV=x>ZF^E zFU2%okL3!0W%cR&ZN2K~bk13|Ee@ok_U0jVHR2_^mA2?4tXIFS)W)!Fo|Bdu`QPKt z9Lgus(*njaR&$PU5K52s_{J3_ z8gAMO6O)skw+C+o>MiOz+W12hb*eBS2{XlkZ1Y+Q4~d}0%LE3-_k+iQwiJ=+ zZ*wINA%S8vFOw_>_ER$`;4?DRLroadLFv4w?L?kG=U3M(G%D@Og~njL0HGDs?h;Z7 zx(eHXHgGbEUb-O)$fn;Tq)LK~QnJ>FfA3Ey)Jc6<3o2q5b7|YoI+6?8z$mqKObmKr zocP{7GlLA?SOB~=xz9IL>)pg+pd8irCpDBE!OZ_;C#o&JhbSxJyz}Lz)pb5+kmwca`S)*qWF+Zk4wy%B+@Q5C7UHV>t*DzHXd4|X7xg&2>+GI_8hGjiH7po zh1;#0Wm|UQ-k!_U&!ws;-vvO4NPD~Mbu;{cLMxQFIE{$xP)J74=I)cIFuthAmE~ zI(wHRnZLINQuDqB7;foR(jGYfJ3RiEgZr-&dt#mZuL=GyQJ~l6u+X$j9+OS9{$`Wn zaYwqFK8zBOxq-rldP&UP(9=OEL~6L z?_q6X-$m;e&YRmnv|guJN7nV7!SelJ316FwQB$R0%una~%2>)n63PlCOoqy47+TF_ z?p@_=aX7e+cfEdtzL&`Nv<8aM4Sr_8cpkog^$nHlO2STg$+xRKq(hkgIxhSe&+jG5 z!PdZG`;-4PyVEAMZ*%EGxoe=RfY10Q|8qs<+LUnf$^4DHVw9>idtd*O(G90B#l7-~ z-Mx)?c(BhMv2jA$xML>Kr;83X$(Vz4(n83ePfDB?}@?Zw&k6Fo5_DlDcQH$p=m0e1n8clJjPDRfl}d2g(R2pqoNF9<>e%DKX2g zXT*nIuz%7XFQ2d*xKzt@m(J62L$rP*x;dD-KGA9&Jq~=d+y>e?3z}sh#h)duYwyBd z9o(8=vSM#Vzd1$*J&24{UtkFWE14Bz_8CuM~$b1(aFj@epHgIE~}c`LaORr!p<$c;b5+ zRaldoF!ABfBj&XbWOLu;4NM1ETmWaeQC060`$O zi7xifw44L^cii|mwmlVng5>`svG!lSSE1=6Kx&31@5(6LGQ{ZL9&HJ> z?H$lW2I5L*#o^7q{?GoV9Hd_sR)1c)hEQF5YggB5CW~^yIS+@nc0YHu=;8h3cOlf> z9;iY>4Mlsu*?)h#nzPe?-ysq65u26F9)MiL+zXd_vu!6~2a2k2g)Fy~1aevKKVx7X zOJOY&U+JGVCIy`jX%*4@c`b!(pCUt2u1Z-kKJL5IFMtI>d9OZ@3Y+@LzT6&}u9j9P ztI%pg-W`IGh44pr>!!>U@hp=OpgGcfx$=dF)&FepwFRNQ;q1_jL>+zWQ|W-2H{J2} z05U!_@)MEQt42m63)P6OYw9_#2Kr8w1(KXUf=fZ71FEn_z&0YrRyx}!!*D_>_8Gq- zDhquAkv@t1I_>PBP5SA=bj2cRu-J~0i4F357pYNd^<9}d9tNkOQM$n<-@O{M{tUT^ zV{`P;Rnk>WzL6z%l!i3rc#`Gii9c>NCZGQBVDwLF^a+d3Qla{(@P>e%eDX)@jiLIY zza}~BU-@;kqb1&kb=&gzG6eIoqqEd!X^gBozVp{`yiKGSvSHu(d&xi=^5j&qAEVG- z?>z-aZ!bpJTsQr9dJFIH4g>P@^UwBT5bwWJ2*U5iIXnZne}J}P?_8BRIW}0lzdZNH ztjx6RKMm3I^y)w~A$W0dCrr_Q)9pI;T6I-I6;4e}{l*q!NKIpC_3UCu3ri=ti23m) x*A8QNgw*&b1BN|o@?ih}<^N?nfVd|xyqc_dwfLZ;GP6%+}+)sMiLr#cXt{KKArn{ z?>yf(Ge2g{nziQ7S#UU~s?M%myRLohs|Zt3lE!#N`s&%UXBe_F5+9#ELxjSA9zl5t zzaG?R7Cd|Q`I)SQn7W6-VH&c!##~mgHX9e4=iVOkF0mV)5tjw(Ia52DiPo>*br3xgF;KzUT@{u0jCpY%<=~fb_gt8#-Z_8TcyeM=Y{-RekXLe0ty?&osgo0joV>gl zDper%2Nd`PcB~>26Jg;JY_6_F0-nlRM+2&>=iZRh{?Fe({PdO(D&cyU!yOOOCTDE_ zL`zGXydwS2yMHb|&q6KSUYgg^aRN$cn9&A$s=K?pVQe|hkDOcBihmz#73?{0+&&k| zONKv((3_x0fb%0vMZrZ_BmO)Y=ikRRm(Dnl%&n}fWMpN>bPQG$1_ydw4gC{s-MhmC zV;o_Vo>p^r2S`6F4>1U`5}BIa4mQaw{vBxOaZgW=rEu&_+>EPZ(ct2u-sa|JTnwq& zu{Z#aX{DRdkLbYK-cU`F4;&X4xHFPrvOAhZ%xQ{}F6>Rmh8O-|V}C9X@b#-HcS47f z6JX1!q?+eaH97tJ7p{Z{BfoVHoy4{*|C$7l5`HHC8pLAnqQj9iH1^`_s%u?-{TvY*$@`Sf={0<@4R?N z1dXFc@Uw`a;ZXCI**}m$J?l;Dlo;ye&VI*+ujNxtT|?MpV!NugC$AELp&FU{-XF17 zquox`o-Q4!&ze=U;0*~IrL2GH{wZvxUDy?}AnJ#cO}Wx{o;X8E!@N&mAZc#Js+l7- zQ}of=AZCA5$Z$?}4JF)vD4{Z4^~SY0s^Cq8a1(+ zqz`|b*}9OYrzhyw4_Xh{X4rK7)X~mm9JPj5=S7fbN-t(&eMPq=6Ib+i}?h+5i>DN512?A5bt#!w>PP1 z;{9mg(kv%Oy;j$c$ze5RXt6Y}#!Y>J{`>5Cbur(PhN@P7MfAtbb#N?Fw6M>TZ0zIi z@lse#MbDENlSK6e7EsKIv082YdmhZUXw0=ZkaQ-8lu6L=yQo*!onPdDj@;x)hgZP7 zYw`Qb`{|2Et%CG)wm+-xTMXivEg2~(Q;76>fk%CXjo_r>m5j?hm$*XcTuZU|OPXEz98(5+1T z`s4{q^e`o?oZK7KyX*JcCdJ<**9^Y2*m96Z^RRb|)qb~{)h>ABLsG^ZK z44Q*4-_14$w(9qPC{7u))O2O|eaqPt~Zs7F&?)oH#Rlj+X z34rJSEG`uFPNT!}`ea4=?&=6+Z5`h3dpBTcDlRVmX@62y6VbK!uJ{n?xrXY;3vB4i z=$_`X1$H|X@4mpH5JchhsDfiiACgV5d+Mq|Jy+*=QVdj<7kis*jfUvwO>T zo9|3dHDn)BZM-O0{klD?C>d#gk{y7D^;mQk#sDZU&;ICr+%nlHJf-^eO{3jP$SnHM zkNv`y4J~O!@#6bhmBVFM{IKyYCPp9B8U1t_R)#)1hu4Vx%oR!N0!!XY@M%t-&aq|^ zHPi?)gFQ4ftUs2p6&lcMOpZ7x9{aoQAKXy?363{F?6#bI<&5Z|6kh1s*#1<>_tezV zCY>^z36e7X7EQl$LUQ9CnX38B`L#T{@Mj8vAE+4?nq8S~?d;YzH{U)9IjsjS%I*xO z2|DuuQ_{_-O;b3{!hIg!LF2r4ZBs($4{2r#D+XJk*EtT5Zh~PbkY@MwCSk##yXeJ zXAbaZl+o>c&(?cwAcLeKX6wPTO)dpwaba&{YFf)RUYb+Kl|)dO*I0~b9$?Vp+B*ec zz2;G9Z>&Rws)s0ByRG@$|K>>Mwh8t;-v&(txd4k)po1q+c~nbW@H^N$&Z zofOQ>iAhI2zgor^-ec=EoWw)2$0IEdSJr|n$8GhN8^{lMbi2Gl(9(CdHjc%(Y-ii$ z=ZDGa?GwIf@}yVSzIiqbM$SEwKfOpr0I&e6Ci=*`!kQRZWqRv!lx9Kp(fT}sl_nv) zKf=|V%rlBwGswY$YirvtQ}i|2m#VPb#q;d2BOqXJ?0B8XT15ZDn%ZwO+%;}r{B5-> zdaAEdeUhRqEK)0dLxO4@)DB%wg9Fl{YTJ=oD?$buqRhP`Ug0_YntDkH)^xmRjhxIj znymn$s;TYO9B90K`*J-o7Na!Jjtom*T*r@N9FKD2a~;y)ko($wEMnbxQUYnDN%VT+ zw4>m5NL-u|nfo`96y3GYcSt>EL>lwaVarYhtzy# zsvkcUUzN;7d>p)|f{GVj+pvC9n zXi$5h_RkOY=SEhd6fIG$6DyD{o%t4A75W+nPP(2(9M84w1-F>{S06K0}VBGZT(bjF`LUOhRa1# zB{I$#ql|D1BDr9q7=N|QMZ2ScbtHMep=?J1#M2gSaET~%FQXvWNfL1e{B(-*;rOH{Gl^A3N%q5No$KzH8|jOh zI!N_r`4kSvWEN`NAJ{Gqf?jor5#J_XCoXppYZ=nLdAvcouogy+IqzmK@uffX{|FFH zYB4q{*+Us-k_1`5z0#qSid1h~f3fGawMk90#$)T=NF^fL7+yJu-cJz`Muw&exCYdG zzKeRhr%lAc-zLS2n61|{>SjjH~j1QwQVThYOwvLaHf^o zf5t}B%-2ZUD^vKSu{iCzaaLEGtkx~y$9#?bMsbd-xv+$zT0CaJ<^C4gFz^ObxyO8) zTI)7ICG<_BZH<_O^Yd5d6HdK;ygn3fsp$m~+C#3_rDQm9^SlmS{onYyyL>lP|LAm| zZL8;0`}M9)#x(p6z>JiTH$nm1K^JP;e|61_1G`E-`We%nsTq@Y@$+S|Bkq6@LOuW8z*>m=rQJ=8Cx##R_! zkQ3e9ZduosNL+dmn5}(nBM@@vliQ)k8!0CXoOvKV+UN}%Ena`byBlqe)(+>#JI{j! zW=70z_#rTo%>5krDfVe@M}5!5dDm)Kum!v}Gq+|H^%G#HAdi ziK#r++!swF1XR5Eq;ag3L(YMHsmX81e`bADTpY{vPg?8gGF@Sa#mmdf{{}B>q&o<0 zt5YG+3=9Tahx>>jnsbY7zNAjYonRv8b9_%BMUNf37BOga|N9J9)sy?gEqTuW=*mQY zOq8?QbKXwu#rT2c?4AuA;l@P&RG|o@!#~2wN(l)G(M(c7)S}MBC6TtaF4pwa@UsNX zMbH;GokZguFP(Sx%10A(7h%~`aEYx%i=RI1wp;$5@i$po&u?_{dex`A7peGKU}G%S zMdxlz(#f|+U7Pw4Uzq$qna=BXmMWen3>vOYGRf^nl?py#kx@}o-JE&UmjvRpHe6l) zt{`o*@b|}?zhe2^4il$K3`<@?{c4jwBMqBD@Ej@_v7q6w*tIq%fYh+NnA{hXVSLaW z_4Oyusc*=4W8TRrwGR>iM_;}6j*Fim#A9tpI;6xKaWkBsY^qRjjO~l7L5Zwv0UYg; z{*A?ho{#sdfOihdWaKS&-5MQB;qX|3%uDX~*(t+d*L;zJMyzgkAnSY60qP zB1o5~oQn%(Bf031cY~mhmp52?&SHyQ0SAns>-fPyGg2$jVwTW7``9YTO!Sy6q<6qA z9s?5TVHxCqf2(XcS1vs7ZW)5flD{tJ$5HNZ91Rj(41RjdBCgz|mgrCa{eCa&ZqsF~ z4TZ{G*(hen;Bej{>}cT-Ck5}LBvA8`eQ*^mn(s9>TUvsxUUf}hk;OP{9etC=Tvj-b)~%G zC+}$-J;qF@eDnR!TjsD+Li{QLvHbLnyD3fdp>px5qD8t}16;udY)%<4~=iLYsRpFJDeEeM5s_E{HGqC-&EX0H?=$&!&*1 z-8VGBDdDRM7n&szm4~Zc0Xk+MrYaSaJ^R&ytK zh;8`-$|8tX{X2HiClpOqz1>;pl&phU=%gC`TuP?>KB;m8@H%Y3B|00G3jFA5{JARd ze5DKuN~B&(<1mrR`&Akh6{VNo)Jmtxi>@{p#^e8Z4`WwN6+D{`;<20m$s4=NRNU;R z0b!@~9O~+cnjxT6@2*B>MdKLSWdAXcZ#a$)O;0!c0+JtwJe~SP>iTO`{Qy?vqG*$l z{QUXzgIFu_Ee~0s#hK(5D9J_U9r)9`&Z`50HGL;P9?tPz9#0g&fwpEU>;=MQ2!xA{ zV9&FFz3RIZP!l^qW9g#mu{Qf_-pzO5csoYDzS?2KJ5f22;Xe6rQSJb4Oax*eKh<|tfNs|>hF{SEX+;PW`CHTltEhVQ;gtZ=5I`wTlDawtIEec7%%R)rZ}Hwr>9Weh zaj{g)P-!upx3;CiILu3=l35nH$X*-(7Rog?<<18mzU=!pko$)->9*=h02>GX^xg0P z2#tCKWC4$jjq3KE&)35vt`@(~=a7JxKGpqzaBlW=Ii=8~oAp4Cd~hhg17inYBdMsjD9a1drOr< zu7<*BXZj&jL9@NSNv3_-3JVJqvtid{VQ5}UdF3!0v>__e!zieBc-0yZ!iM*)!Z~fS zySn-+ebmxn6bYH^98KI_jC1&~wh#rH>s4saR-O?bb$bbAP<7Cf`H+5lQ_TZON(evC zLxIBzVc&Vl)D86B91HeObiq#YZbowLuBkY= znyF2H9AkyB413Ti8CjuLPZ&GS{lZmB5YKf>?mCaA9x4F7ep%=50NA4?Z&qW)o= zCxx>c;)#P9^969(PVjP?dr}PsK#A-8+JRQav!;shjh))dJ`9vVorueJhDYw+BO>X* zre8F~u8>9if^ohQ1bKP$+t}Wn10$B0iIjqZwc<|_0AFhg{so{g~x$o!C@_f*GMTCndGgUC4GHzHsg@eIcASwueb7r7MjvM3F!}0#7DO)ai6NRon90AWCqWYt z=zDwdUCnOj(#F^o?>?g0{dlp-=Y#)NZZNer(U+Xx8(Ga2#dN=wqZ*^XNC{V&rXXo5 zb9@$)=Gq%OrQPPadsV`F5`6Srm+Vvgqzts{AYl&6eP`hDyMBs_0_U{efa`J`|Mw79&7U|K zXyU8pO_Ur~=;dJm$l&RC%nJlYq24Sv`JlGV=u?Y)0W9A^I-C zNl`?s=^f#j?>H=y+<;*0PpHVz%+V2_{AZJox_&V?I9}!A>9I*uv(daz8h8J>v1z8q z@2b*_to-H%15#bvts?0?9$`oc-pYd>xdLoX47iBiO0@Shxy(47-|~}HR>elgMl%m@ zI|{lz=d~A>7)>*&tgVF<$!j^?^pWS9B@_nHZtZaC2fm>V^>swo)WqcTKO?nY_6$Eo zrZ=jaAlYF}3x=dWcrZ*Az{3)kYz>9BhvOjOl|g~iV=l7WLb~`zS*rB3d92q7x<3I7 zoozHcm9B!(@>&(E13AP^7AqRwHe}~o_W`=eBmoHCN^jcV5%H4SIr6LHe1~&;@HDRU zG5ILU+vkP%p|9i7u2M)hR5t9}lHWOFP!PTtZ&gaZj2ftD94_9ucD*Y@d61hN(eeRHwd~u&^W{HWL3R|% z4~ZB~j-c3Gt9tVx(k7xfqpg8>*y8f~O)F36HyKmhY^~(PMgR4lutaVf@FKsI)~B>p zLqs#<-^*7=3tK7*k;Zrsj^5MJe8rS(5F{Ab>QU8dZ`aMah4{37Zv|#7W5c^I$dg*T zXT(Aj76}eatU3LZ5}K7#VWP)oQahRPRZ+8M)ujtlGg{Ed#5$5oMD$G_Cf#o$D$_Kj z(+c&sY1zEsMjXS2^LeJPfU~QC`9(dAA_f2W*sUv5l!-tLjATvba~4m zC)$Cv8R3`muUv3WcPYAmgnv)N52tik>AZa1aC)lE0F!%#eRKb%c8PoO$~W})Cl0#u zXE-DG*s0$t>Q;QL9%kS8@10{5+53gcjU+%gHyB&aHqufW53X?;Z)mX{>e0L|P_#X1 zf6Ai9&ePpa$G@QwLA>tuY;r{&`MR#cu>EyFv3mm9-ATN7BK9;~yOk?DeN8(tdq)4( zL>VZ!C>5QW5|{l(5NR!o3gs25q^TZmcUM(4ir>ODMw2_+^wV~`R^%7Zz+`z1$n3t zBpKcttE0mrh~nMvd*{C6Q_*Bt9cPu<{$WgamzdVIxY>nJNk@suWQD#jEhC zgDgK0$=S@8 z3s5?HnkM?Nn*4B4i6aaX)=Uot99(GYB*yH{s#_WM%7jHrN@<^#_@yxoR~XYAU4ij; zs92fM#k#0aFk*ht7idk>bntcD9u$m~PfE}4FtbqUh!_-34!zKs%Z;xmeb$YwzDco>%@v&N6B?`T?bvBNmN!<>4(ZeU(PGT~wGD{BD& zYSK9*N$VLlQDMTduqJ2avis_jFZ*W!5bAHqEJ{ATXYVRv-F%rvJ1Q0&ZhToEFN)GD zS_kGO|KawQ3A6jb@HJaVosOpN9TPS(@aw@QASZ_=7>knCy%MJ%dx9s7=r%0NwK@A{ z@KHdr{9AlH?TnZyHT4@@2Z(}#LSRBdg3w$?BA22NJPCT&;GbqJk%5xlBU8K4AEQ;w zfOoJfaN-4Dz)t8ma@Nz*=vhZ&IUzd0d>dbwvkMN+ZcO25tPGZ0zm=#o@#Ql0(TU)F z$+>lE(>zkum^lhsL!W9!UgOj0;%f^P>R23?1=QZQ9PRSrVUV`3N@`|u>q918@fFI% zp}wYC$-V~N&rpBN6n*pAwi!~jTN2|Vs?f~jd~Q13yFdO*6@jqD1JP9hKLx zgcMC1jB$NAGW%1H1YPR_?@`;()nwqcuMY-{YvVhE*E`MryGA8Bk;?PS_QEmOe+p4l zxwz^X9o$9b3ItW@9^e@t5&txJbulZE_#+ekd3@2M`XlRfk)lVg`k^$UV|)=b_pvy9 zuP`tfYO}sj7&E@O_{r3KrF%Rg`uB3*3p^=q2xa?g6bmfjR-ajf5roA&7;)Lray>e9 z|NT&B3Eh_@1}$H)Xa7(_e=f@+>m&x}Bx&u2YMi*IfK#TcqZ^7EE$#KiI;f0N`D0YP zgIcheYp(ez?GNiJg+Y^i^x+=m?*6@7yXC78W_RLv0o+cgaICVi~8rg{5ZFi+!GmC8~Ol|vRX=e(A2S`JiEg`f2C^_JA4qslTjDGHn3C6N8bST6BC_LczSzWlJ8QR6+99~~vA9uagME>u?nvvQo z$$!WFQEsIi9a?-91*skpeImQPz10ED9vqJ4Ap2dntXLhz{QaJ|c76C@k6wqEJw2>E zMJ=~^+XR8QtIVKaoxA_Vm=0R-j_;&ENxK@;^>z#4%f% z7d&_GN0y1aq`NtMXPzg@nR&6gXSq|q^qRrVa;HLvmbyVEC-HUbZM){{4%oe~$bQXL z2fW1S(=zl8Qq7V2b*Ck4013w6@Vc;UIy|W8kSftEV|U(^nOlQ|nEd=kRsC>x9rFD< zPW!`^?#+4U7lgWP>4W*YlGgM@K}Da(k*9lQVq#*W$NL*Nc;cW_Nc_y!abNB8(D88O z0Bg0SCc~%7+HS%QH~B*Yn)F5Y)>=)l+s?9k3JzVy@z^gCzZ`#Tzqwnf8}PeZSjlod zn1%^t+Yf9R`kVzV(p1RC(zO=-wSo>a+~{^?kZOnao0sk93>J^_JWYy`7aCdlx@jML6A`C_9JLIRs%Up%vx z<61Wo5C~j9@qZE=PUR1fPhf@DipRFTXcs7@Iqpv~l8N{bv*^@z8b009^4QKkV`Hmu zURqi*D^N(HUJ)`bEQ*cAn=aE0dAhB58W*7Se>~6lBak>;Y{HwK{$QgmFE9T`N+J4m zGcq;(1fhKV40o(wce+i^dD9~BoBqbXe&i(zc{<+=-OY475}a=jL9!(yHZzr7UjKIy zCjYD~wYDJ#+bz^%sH&bU%^40cN{{V@5cHJXYzEwc!~yw84HUJcZX`?f&N6 zdH3y$3RgNhV2xWD8E2TI?O-BDqsLh`nNL`yY@oXL^>J^NN&nWeq5u5`CCJ=dlj4UR zFL!UN=lPazgk5SX=75-fg+bc@gmY;10RB1L3b`BMnF@#U7p8b^ljcLoEXd~6lXz__ z>fWJy4YsJUmj_!9sDkdtrS&6!ngmuFJ%_7_-mbJ}OV7nIs%=#&T!8UFS=-!Me?vf7 z7(7JyUhzNPEBk)3eQ>yk6~xE+2TsE!9pV@e2n2_KfF;C?2P^%9Lj=|Znf?0{j~Apj zePCoWDk>@zbo9W58l&B`=#b?g#N8#ipyda|-@^W?RwLDC8~xC(L=@;FETQ8k)pP(d zmgD_zC;roq)8dG3ybjAf<@znP`-SR=Hpo~Mao#Cm&lE3U0{a^X($dmCms2y&SS-rQ z%KRu??3TLiX~foy9~f1M&RS5Q5;664HZ#RRX4K#CgxBK4wBf6EGJ2)W8>=Zjg~#rP z_Xgv2o5wF){4F2J$ptkr(dZSE^&XQPaQ;U6y%eApXl~|- zG*pe3V*M?*IxW|-r&EQAgY#Od|+4P z0;E&QMX8{srru09i~0H?%fg+)%bY&TUE=n^R!9jc5GtmvqJnLJgt*!ZO*r*`B=c%e z4>nm=2u?E0)H$i9S5EI78Y%+-yx(9oeR6X8E?yH`pp*rVK-R9R!DiXWiNth8`wo8R zuRY0e%rVFX)Q^3)T@DjlpK_#Q2HI*RENQ>;;DF0?>g39H5}b9n?oEZgEh-u*NpJqr%ypvi}+L z{g!)ko!U;NfCWT3Hy6^o9 zuVXQINPE$Sb~f6AkKzB+?<{k*xu+)Nbs>Y<--GygeY^x&s=oOd2$$6TemI#5pvD0+ zZZow|k7(r=qh!S$gpEQMyFXBP?H&0N-`Uwg5!i^VI;a6D0h(LM4>zG9E7qoPX2S<78$27P4RD<7da)8aRprB@w$Z ze&x(U+32}V?*H#bJJs6^FD9Lb_-`P~BR2}3ba&GEk27iAP8j+HOyzU2d(1Ks`(BIw zJ}l_y&zvi%G+WXEEN`OJphk^39AVm~3nE7-0*soSdA5fPBd6%j&l*)rRne?i73e&PeTu+MvcVt{KeRJ6-@bzvn5i& zSqa8`JBb`7TcE7$>%_wI@jUr0{>dIUW8(b6iLXd#Ta}6=iDAoD$qrY!e*@W6uK+6a zaif5pxboR_J*K0G$Fl`+bkB-k*0PPlo)$)Am0O%h`)xfNkAh{?if>VjV$H&a#nTx{ z`<{6=3RJ<(XmG~sw?kX|tfeqv$I~IbrQjeU%hOE?tkQOLJ{h2= z^*8@x#n!V#-4x@>$BeU@5pHOS7TmlH!3azc&aATscK-YE7BEA{k@H@TO4D5$N_QYN zpWw+3R-1l7riHJ;S^c032jAsskh|_F>xsB4aW1jn%~8Y3Kvq0T zfdtUp#`_sufU1H9l}PeX{fq+bk$*0s%r&*aV>3+#kCJ`cEV`%H9Z%xRBl}Y}n~n#j@pV4;11o8S2xr~ z8DE?|UZ89*+R!RUEhA^)!gtoLjy$Cy8FDwOQ+q3oW}Cc>nIV67Q+&Y04>+jt!;AFK z30XO~>?EB4{f?@&OA)gfy?yf2r2sSfKi)bhR+$_37Jjf3h4??Vx}`-~6M)0AKEnwk zPQbZ0$+I?S)u1qi@jhG06hqa>hao zZTv5>#LjzLM`eWIu+O$5>B6v6Ww>7DhW6RI9oS`OI2M7Oi^~Nbk)4kg8nEEoy_Eaj zHT>0qdYc)8<%YtHyu5fa4-yiREI4@vtCUmik9y5J@Fh9XK_So-_6?dPzC~d#CyB+S zN*8PyeklQ@3pR>=&1=x^*}K5j``F{x!#?#7Sf2^{i>zzu+O2?BbN=_F5MGLpXaj*- zF}fRu{wjvo30`)UKK(*IXD(+q1(qCcXqs5Jye~%#B8Cp8*F!GrXWwPXl82bpHE4p8 zD&g}9JN>0q@xs*oF68}5oX3J$>wP^m8Y_Ekrve2kbg~$->_)kJ)KZ+d`sAET=EE4k z$q#5z!vRMTb9^vxd$_%XZG`cD2+ur(>mFg)Qt<2=dw=ofoX=%Dk}Nqaqd++Wywu`e zF|SJ_`Fr#5@P(EZ@BM+{(?^>K;(3Oz9B0!CaK)-NhmcyJ3H3X?JNPa~4a7TOB5-KJ zcR0jC|bY!Qh}$ zAy9aI=o?&c;FTeS(6+$_PY)=(5_%m6B@P8#P(}KqH&T4*GaHWhE?RpxAIE^7c5@fG zj=trF^iz4JbHe5JpXDx^$7g2x!7RF)56m(2!F2WZOFEb-5)Ldl9%0L-)O^fZI_*-o zjKTcj@Mrrz+OvJlPvWuwT^-K-p2&DCouc3%O4*H~3l@Q+AtegfY_2R`lU51?(Q|O3 zt^&FkLEDG~7Q0gGW7WZPnQPTIwW?vZD$>Q?vC7@g(WI!y|Sao_&@r+ z&fh-ky?XibB|8rf*gxDCE-sfpcFiX(4I3T(S;!?oLJICUi~Z|T>O?E=03{>S0sy@5 zrj};+6INuQ4-c|6neKcqyeLNAo@8EE;=o`T%KFcQOU_3HNogL0qT3N(f*mF@#SqFK z{x%dh-arqjF80lVhd{cNlZ6Hc&)-wdFrnQM;0s7q>_Ech(F!FC{m@4MB}cwbQ{sZW zs{zMfK>qRe$_$?JI>7Z|RA2v8g0MQRixWHK(S0WuDgVwTqi+kGAeiUHga-Zrw_6ST z)-R2x=F0U2l~k1f2BuqzKiRV@E*SMSk1YeT5%(oL%C$|4{5Q+rTS$Os+|$}XslPAD ziDLf0&anSKO4P040MyVQ3FzXd15VTO7+92pb>#%689*buOD^ly1KgF{Hfdj zp+aOHXz4q?!AT4FSrU?exsi_@AfQwcCMV#Z^8TN@`@HZ`PKgDZ{rA0K_)7vmEB}%$ zMJw!p9Cc(8@ZX~&m#>4(;`ZM_IRE8oa{VB1;)2{1DD^MNGU-4JRxJ)dhyHux+bss1 zrf`Li=>Lzwz&8rl#N#i|R$?6mH-l&lL>yh`qlyB&vt&(jb9<%Nrq1a-eve|sP>4#ZSia*t58qr*D37R|rfs!ddLe-nOkJj8=KZe)0$aJQ z$-$nFqw;4>POobZIA0M`^m9~O(3iQ!yOHu?&_P11|1=L%gjD~yS0`KPAaw!Tekr+@ z)ob=bweZD+nBTqRTZ#TQqlyM~&Z%BvbgM>rv#ieqUGjFoFR^h||vna%OrGsPMcO77+C{ zmp5OtOuz6Zf`y=b47TnsQF2w~ubKZ3@D%8^j>fQezR>*nzy1abQ?meROK*@ku5fB& z(!iDG5;RuY0e$3EmM5wgyw*V!;$V}5+3#Biy!P|Y6;tZK-uW0hCjo6-Iz>knpqz5x z!FphmaMY%6m>C9XeQznbNaDMnGLMV78kD7V*B?Y_5H3hsar(=^?>mZs4Fpn(ivdZH z+svKJE%ppz*`mo3Uzb=6CiY&)g`?cSkd6 zn};0(ccRZM=W7X)Yl%M#OJ9MEHZ+MF9+-x;l_ewc_wBMyMkpY>UDv}LktgFikBjx^ z?^3-^QSxI6uD_MH%L2h?mZIh(OA(EW);~Bc1I>CFPnYduLXZfy^76D#h5eq10@6c} zO?Cp;A660A6^$aF) zRqe#Bd+=LPIX29Fr9JO$*XlH`Ku+&5Ejdj4*FlR`CbfanMJo!8wyAGJ6v_lhbDmaJ ziQhJj-Vz6Yaws4Gb6bs(wB5$U&5*QTAufX5#Z!g@IE+SRtQHXX&TO^& z-PIab{bL0I#!ZjA4}8`lA21I{xpz@ZAT47RVuC&=&d+&Vq<*?m3R%9WPv!fPM}EoS zc4q~Qnb8ttyLgz-bV63paVN&|i7^=ny>V=M#772eErvAfEL4`_Yx7l^<0_22wIQ_1 zA_d12_I5dslj{A_h$Gmc?Xu~Ab?Dd}+!!a6ON$7xJMc+7yp}Z3Hoh8nr3xN0h$!z%khAj{6<7~J5Y)m9dE}j_I{0Z`%jiw@<*`A2 zbw39MsyRnm5WsZ^_nujWmx8V|L^|c^P|-hIGu|j z@QEA?jh7Prc>Xy;usvj(5wclLS9WV0(NBPWw2Zlg%o2Qgp$dZ$t_*&LG_~!{meP{G zJNk@DsI}Hq8Zpm7%5xkY6|%@)QIG{s8d2)sS|odc#@}zT4=%&l6-;OXm>|_sLxP!3 zbCo~0hb)Xwz8~$L9^w{N3ghGL2r3%gO<_A)+(*vB*$GU*vt;T zPp^pt&4t(mNF5nX=DCTpX_UDF!LMnTG9sM#^=sazz@78Jd#C&Ax5n?gyArDwzn82? zZ^3p?!i2Lz0k?|}L5*hI1e!b0V0BGGFcnGp0a52(E*tg6gsR?yrRu+akZbVqpc2q| z{dQ3(x_jXcCDm?_quGJ?o%JFv3r2tE^aHWpk{+nR9c|jywf8f{(P$||WtJTFrR0be zeRmO!OEIQkB$}wBETkM)T9js=w?Lka&ye|37jd*ehr7W_qdvY4V-rj(3 zb4y30%U=Ve-SEZ6ad%hN!;2g|?cQH~COlRMvO!OApz#%Xp=CidYg-vo)4xv|Y{oK{ zsqH?f-Joop)1Z!}%%{x~EG8aomhrU?11cA>4&rxZ5)ywWUt7cq5N!3sKNK2X-;RH6 zz1+AR(SvR_QY{HQBGatrr44Fqad}lbqD~G*eN~O;_yogAOak z=meR;OD@wq00fwm)!n;NVdBUc*V$rt`)$52S=-ew-juw$Y_MV~(gtdZQDsx4efia` z%?D%9srmLdYBl@=b8INV&pp>f5(PoDUw>#=kWJ(_S04U4bDcdrQ!ul$v&ILT1slzw zqahYoEDgWn?E>#)mpqQ8#70kuuTk?;{ z#SgtDOeJX0>h*M^Ul)0Nj|?wJ_-`1Pm_8_(GZ`BjtEfEk43Y+#A)QsGwj+iFaH*>6 zMP_$XZ{|r+@lj)v%r!9p0N<`sBJXw%E2k81G@t!-jK_aqn9oFIX)Zev71l_n*SWQi zsEYuda$0zj3!(9mn|I*b?VktgMBuDiV%hJ0j_kStK`s`c+ei{%tEupnry;yd9#Tgd zWX2$MFRomg$cx8iJ`xZ#q5nsKzt>EgDD3+xUm+#fGeXm-U_(~ILURQ#`mYI<|1lvv zbNGude}?@33H$!c(u9lMfg9!x^6?wLG*w80}kf? zgPg19&x#r{{>N@V`w{*FjsMRr{0E4ay7}xM(f`jtU}v3wMtINAUA?Pb+7uThG_+Nh z$ABiLe6l`%m&;P*uE_WO-flyTM`xu2yYr@I6RS$F* zS@HU9n2yRfk^nSm{Z)@q@_wb%o-mV;wZq*|wV8CPmcs4tNvwbS|FBH|PfOPX;P_3V zwcR;j z;2$6P?blt6OXm0y13C_cR42i^W7Y+9SC0`9fV5{6zXRLp$x8Zp!vIk_Lf}2_Wo}GDMe~ zfYcrp1$s6_+MPGsdnEh-7ssm)Mm|cCd4>W1n_r#?0c;ja@gkeA#O~^kY6ln|Mt$K2 zl2k1@gLOkpDo2@KETj|s!n>#Br;MobraprNDTEgNLQE>+pWUYjs`55jB~zIe2M7?@ zu$qZ~!j5>Q)T_B76t_vxeI$WyF3@L`;y{X-t`JZE`?>`ewFd1hv4!^qSL=_gL!bQI zTn6MAhm3%N6fUyEqqS8J>|+CIrzc6Fa2o*-$tRnC0{}Kmq(}oeoe8ob#z+mPWrmY* zTM-HA*wrdX}oh zvdjl5O}{AmD=k=7{a-H}lYsNgh-Sg!M8;F=VU`ow+@uYMfnXN&(MoOaPlw~k`ps8T zC4_dT`myzvD^+OoZu_V-R!L&a2=A;xfgCOh?6xYRwLI(}PFSVJz3fJ(D~SZ$4?>QG z@3QO`u69R%pUH8Qmf?Nb!YHkoPP9nVA7c{f!~lji_c z{`0RVL?*BPg4g6-3NZIxWYPg)j{CW$HPjp9iNYipORVB?qtdZY=AG3v2Abluk>1Mk z?zjo=HcN8g^sN*z*9;=$<&|W}Fv96WBRn^7LNSk|46^r?hM3*}KJ8ATL5Y;a)T}b1 z+K-|bOSZEVW9!G2)dq>VUPuM&gM{pG^06pG{b-1cj!jM8mFK3%wPPIU0>SagF^xX@ zcVTVCw#2@T|jgBvp>vj#SfILoTUc~iQ zw3PmP#X-9(b!^SzL$B z7*ImC^k6~^$=xZ8SS`4OpWABc^$MGAbd$@rv3kd`WhVUC$7OG%E81qI?c&#|-BCG# z^mY^|VP*w!@LU;Nq=36vPT%}x?u8w^$o;?XDVgwErR|anlV-WzXR(KunHCk#Ef=a; znlaL1oFD_ipkXpvEJ&#=aJtmj5RfQBvE)shBcD)iIX{RUOXwRzwd9p^@OchcbL(*0yeW$ zBkJmeh)GHR8`eBr7#-a)ak*UVtY99%F`drAL&_a_L`>Rxy48H zh%2gaWn1&13;jQF!5)MXxSde_e_cf{STYYmAhlst?*Bv?xj^qNs#e61#?PeI|HK;K z27y5xet$2k{pYfTg^;D*`CpCt?mwX8brONJnzlpetGK0zYn@P20jqIZJ5l#{r<+K5 zwl-3v&PD99Hm5`+{#XNJJi;$v!b{d@ZV$K%-&I*TNM6M#ARMJJQ$4Vh{PZ3Zdw2G&4V9v#xeXN*tqD^~ zU!QVxY%D$<%K6CY2lXZE>y<3JGuFPITQr_>s$SF9cxc$y8+3wJrfvW%h5P>X@vzh| z2&@n~sHKPex8%hM@_iEKE{aLL>!>AxR?ARj6C?@v5y+EY%CDW27mMoGPz5QRlA6sA zKeZYKI*Zy-Rs9KX??ixlhIz=}SaHwR6nn;2(fa6ict3ELY-rD6a?DdFG7^bqbHSnJ1V4`hkee>&-RCLP?(ja@{n$fa${L=Y%EDPE>$2!q+6SHr%Q=z zp~h5sv3gNUd5t?f!g`I{dmf;a%KapoU9Zfp76z1b&cE|Ya0valk$!H)e!n>WlZ+Yf zu;g#Uk)D-~nwnZcES;phoE&P&J7mDwOE$K`$;DPblU<2_MX!wF8A(b?zIyelCt_If zTJ!wUUETL^QM{z#HA!>-q8V@!S6p7H{f~MAx9{rvD%(XgGYdf1uS}m2aF2Nul{mp# zZVckF@-kgr-S#l!@Uf-t&|)C%)?%`$-p1mfnE&!oa>YH1wvu*DIM-}YPrW}MJ;`|x z1ND`q96lq6*;36ZcRLkD8F#6%h_Za!UL7C%NNmfSRQq(JI~+QbHbWhcn*Ug&wByNTRKDf|GG=5o&qp!eg#`(nwxr(8=yta#HlEIgBz3ZTZQ1uq~{tBG@ zC?GZCCM%25ng)e-w{k7a&MIO*sdN4v4*uNSGUe!#mF?$vG;0! zkB`+?_4P`YS7g6Fhy!+pntzX_0lV+@^DhP?9|-z{d~Y`BruS!~ll>mTCA&GhJKnPM zIIK8f-`;Zp+#TCHUMC!-MY}*9K{LDG$k6d3q=3W9#W$9OAu7O#HYzWVr#rFgUdv>@ z)Qo=S5I?%ENJD2qli;-gYYWhfqFlovf~)^>_iQaJLMS+15~n?g0=MpoFFvOMMw3PFo5@G`)I2^Q{Z;ToY*QVZ5pTH>*_?}I6$VfK*OtGUhI zZT!BMFYP~`lp%AGFU??6Yq+_wR8JGB+_|0S>BuFzh!3~aoe9YIBAEQ5yJB0hE3bQ5 zj?a%?gD3=r?AmcaD&`DSL!g$Ifo9_HhM2v%9Db@{3(KtRaB%#^E;+JAd8i=T+gCDHJnW;%m|`_VQ?S!+$f=08 z_107<2~u9v0O*W_l^DC*i%sppKm}n#3{P(>dB3-P^uy0@z9ki(6tRu zg-XYC<_1Nm2Eti)n1+*4Lz-uQ)w#dpt3=BSFXV(zp@f6Y>3ILsOY&kz=VauP=2Ag+ zuuKtfYu#f4gtZf}lideqv>eWqnf_=ExxUO|JQDweDC;@FtBXoz3_V$TM>f%-Zq@Mh(HC@jvvV#(FW-C?v9U za}{0|>Yxj-32b9#C@;D)&Kr++* zI8)%JH&Khk7R+@diu1wAO^m4fKBoGX)9hqA9N37U5$4i$4`)<;)J9M+*YnG1%J|1O&$vz({iNx4sVX_3)@sS5-F|eC!HyhYg%xhDQmw2^=BVPNQe z(7)CKdKTAm@J~_Il8)PF1|Xyd)7|-nYI=V?%C7OEeb=7$OaQeMPyNR=_1kCF+I1Wa zDVhZnJN8|?2Pxo)=29OsTPib_gr=Yvrz=+9y2)ps3P zd1bgm>q9$YZPcaQb|qHF(>W{LlllWemk#xZ$0@ZzRYvztu4PsW0H;i1LUmzUqZ&k2*I-Z4BDk4yfQ=yjK*Uz-q) z`mMMrZhMy7g+EEaoon+H)FRu_zcACZljqA!^(RXLky0r29JwxM2|2?`ks8aYc-9$B zaKWK{VF1TV&~XV+8S~D&*_#tqT=M%1JP9413+Ag?~^gZi!|pj07(3#h(yqu6h?vhmk9o5uWGGUqjEWaz;vs$3V8_Gs}uNewKBE_ZP ziJha-0W|iSG=Zd`*Fq>Nl+??d))w;c57;9iVFdUw!$2#EiBv1K8ye?lef&q0CR zdPI$1)|r@bBzd1EL?zUInqAYM@q&$qLYd#IfA`0lW$Pp+4N?6FcRT`W_ACBt$_DW6ipVWhAh zhqOjIJQp}QB3UOfHe+IYM)%*^^^y zbvH%MNL~HM3`=Hg+t#yvpJKiJFU_Gbrc;xyj$)_SUw2%t3>$+25qO=0i)b@BpHS1C z+A75e&Q_lZIn0I)c*U3TiE<${~VH^GL119wY?8V%k_$pKx~c_0_V~Gjm}iuAgzLsxxlv-tZ!2 zg0XgZ3TGd;%?1M;Zb>iB@cdTyF`6Rs;-$%*ic}@>|J19l5tfOj?Cu2o6nvL`+P>sQ_C3l+D2x(2wkjit8 zX>@rQYf~>&CiVP@0?DU0Q#}yDRi^!n8>VLs;7!)@mkr&U%R<+LX(QBCAfg>GA6i;o>)!2;^-$+@LSOOW zTThca&V@nRif=3WhJw1#dIg@S8*K+p25Qf_QQw}uN{&WB14$nxA0|CzW3>akqUG~I zH2qCrnKYoNh+ngEMnLer=Z5&iXE4P~t<51(pRM{=!hnfN(hkY`-JOb9p zJl>f+(%o%6p7>p2ssJht&iwR(iQrPJnsD9>}OZ>+3%HS5O?lZanaUwoYPXM6&-j zs^me|_hfFihD82fR&Q$w_umB?I8-+6Omh=de0gJMH-}ld@i+_T?Yn`PD&|yJf@X&6 z4Y<9};KuFY;LudxYD|_ee|TO5xP~5P`%%v%68vAn9{JY?g9X*vUJ{0Rh*eh~C7-=A z@~K+P1eB4Ip!Z)=dVmlO`?tfbJfO(q!lB$mbe=7h{yE_*XuMC)djR9VdcA6s+lo|^ zQ_P~M09^Yeu|4NKKcq<0OEl@cBSx;~=P36KP>836xj8;c*td1!ht+w5I=nlB3!;CU zsbMWYCA%t8Q>kCEu$;Q|JG8)kL7*({1rhqc!T=(APJ>K4wjo*6{LBF*NjwUS|F=pH zpl^BAaHUjK-X0{Ax%}v6`3g!97|?Q)yT}p_eqQ;sw+83A?3P5U@_)RB|4jYu#3K(V zIa5&yM8R`(JqpsZx95zAjKm1A3hkDAl-d9AS0x6%XgDO28$z;YdpdwMUmVRhZZ=C{ z;P`{b?769V=6+x(k)SNW@@GmcOO@J_ODGQF4jDadTjUtPz4ajyJpZLitHCD?i zAald%*hAjF=IyA&{@6b!Uiy7ZefRBMmFjKPl51<@ccgJ{+N(2vbbl82XE66IxBeeI18iJ#O5IgVUifFMv<21_?>~Hi zZ_xBDuODO1O##}z8qo^X*cU{*)KKuaapI;5fB9`z>;rELByzr8ozj=o2J8krh|kq@ z^r${O-8|%Fw!M=4mVS3QV;?$u`Cr%n8NHg$p2vYJ(Zk!r<2h``$DfzGitN{T_wMx6 z&0TDz^Bek!LOVy~MNNRy;cl?Y@f-k=<*Tz1RGnxR0;csAw(#`{k9O)m9X3epETOM_ z!)YjHPxbfZdIz8q{>;fKJ}7IiL0&Uc(ru#ouyYoj#!D8tpNdau5^xrp$Ft3dS~TKU z*3n#rnrhIBZiZ0q_=7RV?R)o41!lX=sY-3oO5KT2LdX$%Y(Eu*d){k??piRa?@1)^!_y1ShIwQR*bUloM;N$KZ35myV-} z@MBi)XsRYVnz!VV?+3hG>c+*ZG$CLJYJKM z)zgg1Y=Zp@DJ^y12J+ZkO5!xLY8 z3!vihWiTuaS-IY|taI|f>W&A>H-)Ma^wqUrOH?T`WT9}jneJNbi8vBKa!4GFTJgz1 z;~vhJl-_iSdO4n4C*H4ewWKUbbGoYw9yP#{(nel@O0@-d`ka z#okLV=@wcp<{%dqZwfa=iaf-#c6e0iT;A6Aw`GKS9Cp^ZHKG?*J<$~2-G}_~e!?dA zsizlcKzA9~_K}h9AEEkL$^G&k_gjD%wew=g7ojmpHvP=+46k|UC~Hb4wiu2AUi2~v ztP;SVELTQ=p}=tPKB*U&M7{<=A-cQPRlzf+vu14}7E@{*#$(1}x}M=@IzRne2Q+iX zPv*Gf%=JKx7>x!)m{r=x)6{#goj3dTiTiTr7&w`pVbyscOH&SEByd}tP@;B&+wFfV z6`f!NtRXdBTB#3j;n?ZiT}upAF(ur?wi`3lIm{xB!#ENHCq|W7)zZY(M445jkg{ zWjjkIKHFPU=;9}B~@(l zC*T>(U;HEc(Q=Q5knxcJY8OR+x*s~%SL4;ygB&5s9~kP!&!EL3!A_+T0y~#Hh)WmZl5F zx^f|l@DnDlBYjrsD;QH!gem4Bf!uZ4b<$~jGiUA zZLehI%FKiK%}$355iH44v%KSX=euKk3HdaqSl7z=MMo!wo=!68su45+dI8tUpYhO( zN&b+!vf@s1Q8c*%PA}I4un%H(-6+3US#zejxUh`R!DrYjr_m8L4nmNS^(R*o79jD% z+I~b$nW9cakpUUQs~pshQLU?+o0ZA)Kwza{(te)l*yk5lL7{DPk4)cso$5sF+YYcK zgbwRy-!+f^Jm{&_1gt&qH^>6|LhiaK0EQ3@5)B3!)za;k(C?$Jp?{5|o1lY(TN*i| z`Jmw|OQC8AV@01fKD4PjcY+GCIDlfmqR``a5Se^h!*KW8Mym(3B7{iq8e2(5a0*e^ zW7o33Fx~ot;{S#$rf>7(+-pC%t35=6>pOL|2W-y1sdjO0Vt?PuYm!~)1`&J;PKFG% zj7pCgusrx_SS;(j+xScvW_!0337fg5OlP+2JnXlKs*X>mzai{%*g1##67*GZmAhVG z{vvBm`DNpZjFXknq2FBRwQcQ|xZr;I=8+j|e{YZK2E0P;&o`zf;O6n{E2#EHOIsEgp{A6ev&uKNkTksW7TwJ_ z3}cM+a3jxSe)`~>FLChk0DTrQZn zR>hjjpKj4MOGgZ;7z4aEEMo1*WC+OI=?1a3)-04b1qKa5MZwSWaVVx6&CxWl)^*OW zNw23bj4C`Bpffb`iUru13TV8V3HV-9bk2zxa=zLEZgVAF1ht|)tob2z^aLNa?9K6J zk3C411HkNMKQ$K89n|UfNQD?p%nADr=!`~RL!)W=&`vE?yNf4S)Lv=HCQ+$^DE{I! zXABDqm%`%_LVHQA=>7z%kS+*XQ^pqK?r#NK@%t)16<2#|(o)e#Ha9;Viu4Z1!EpIN8IV?D-EN{JB#p>dQPAlW+8+*z~LJlKqNOBq*S&?vU?`)6Z-Xt~SvPr|;s3zEp9<6&36i`%^A zi)~k-ZKCiz^@pU!vsa2ZlwoWqM{mxaL%AfvEQJ+-4UUXWdbe+v8YA<#co`i0$R{zg zK1y2hE$Ue5J;+=grpGo*pfv$&KF;Vhz4i$(VxMBje6RSgY2iUmOTB7+6L?4kYfq(Z zYKX2h@dC84^4?rb1TB<^OLT39v@ZhUwC~+CNa9A&m%8a7CC9`9Wda@Ek@T?Y$QUNa z=GAhw+wbG9f5VCTegkjYK&ofD&jb5qXrcs!DIxG_{ArG#4gmxiiCjomh=fGCXrd)? zyk`yA^huP{*u8VY`5=r)r(?qY=^z9&gOLEtE%CrcOi(#42$DbbU+LjYpx+x-L}e~- z%3+29SPf#oHX_PDZmZNSDev~TB4s1%435JFz*cxWm?{y^>^{P^E=}J;n=aswLjx5N zWr;$+Ue*3fd(Y1+Dq3t#GHK;bUgU5}yG9^1Bo#qcKDZ(#6JQ8C$k=9v?Oi$b>#d-= zANEa`kK%GU7sc;26Cn=Gvsn4C4Fu>yR2^CG<{cPKn7%x=^Orj}J>!~U1PVEzX`F2Q zvzU@DjGES-HHnp)ZT$e(ov(5{yWoOn_AMUOH<0m>rgqHr%3L{?)!$TH7Xi^eO+pQn1h{I{yAs5fhzgr)7AkgMwjRWR#7=dfc{!YgRd+DuD zYuV4#sw2VH$z01m3s{*9g5^G)DgvtNz==Tm7Uo|X+gn6^5{gmeqG;LMiOk_#Z7*&& z-;E}MlI63v=XKb@tR4vwS9MeGm45rH<;bbavtgoAqMwY#X}{WB(@sS4vFVWsIPps1 z5%VtJ!Z(KV!5%5SHDD1d673$vh<6|zYs&{-2*#)r+s}!f_>A^?zT4Gm+?);-`%T(oIJG=-Xkg(+_NM9kWI+$zTG4 zX$CQOir=;GC0)GNSz1f%3{3xF3?<3$XfAyWe4{lY_P)7jvCei*doSDRvqijB{I&%? z9(w!Qfjx60J)uIAAn{xGCWUs7!r&LJf?!1k8J*8BQ$&CL%-4Tjq8O4ot^~~pd3;e< z3vhsGec4+_ZCXvIz_ju;t+!*3x-*%4JP!w>9!HNQA|(U1g_Y77*Lcfc)P=|6=dFbF z88#AR&WgQ8f+Lz0k|g#Y4MNU?RFzfnecqZxV{{1oAK#6ljRZgPxZ$=u9@iSfyjSpa zOi_7;uSWmgnfU*@^FQCd_j?e1xP82PLh(2hU4~vO3)&3 zSF%v@lppO$5WIMOB+^>A-^UW1>KqNOFGCh1z|G3<*xa`{JOxvg_ zDfeIhi)P;>EviU)rNl(p{-lf!f8hUQ!u$8AXg8bpX>DoomFn<`GRmF=1{2l1M$%rG zjKwQA6S|C0pUX1StNU1mByKwjQh|l3}%zL+Q&a$su{$YjLFaL(jm5O77(IFYQT&#A^er4FNtk*1U>#UMb`C;|vk{oo+`G3-fjO?rvGfjXx{$o{NL!eDN*4FWG^=9-Y* zluX^M64&W1N|dAv8EdyE1G6H<&Ds2srWVDA+bJ9WNHQ-}d^m1?M$bp;MgFY|T2s(a_Y0{{f9@0uf+W*Nj<8Hbm(<$mwkF?tIGBwEF0+ z`as9_sCjP^4-`Q48=U_lT#4MkBn)!3F~vhJ@QE^aXz9z$*kcEt&avI;LaZNXV38di zJx1#NPeFm9j41H!JNqxFzd0(3`d*hCUh9;arw zScxe)`~CyAaYNb{H?wBtsV$wUFutM2R@S(9A6(@8x*$==THKDc182g%&!!92Wp=P) zoPx3q)}pRFBbINJLLoaJ%~L4#a8gaCkp34=hyBN}5m71PPz(?>dfrcn(R(SwoprzV+!LOplEJK{e3#~=$UHVYiIm>3w$<&oPII(m*V1Uas%JMlVw zAmK(%()EF?eCD%>uHX6i)@nDKPrsfNm7H8_X|a*Sf;~*kc0D-O&Ynlp<1LN^>#W?P zu|V}g&RDc`H=tXOs#&KVL>-YbiGxoYue z`qyb#E540=__5mJGe{0Dr?)r3ywglo&-9Zd82h_WrQ{vf*QpE9-+&Q@&eX$d@R9Zu@Ju&bRFH`*M06uAq+`>l-I+yEa@&row?aSSGDtfZX(- z!ND<2#we=*cF_YTw;6|tNwa$znjp`^(%t#%A6^Pk;twxvj|kUXZ>1?JUQJGcC! zvmfR!FEg||8?mRu6-^I+&%TliCY;=~!xYE8f>lkS2_?(~ZC+d?RJMAlYOgu|ikeUl z>${w($4vNW#?4+?Kw*kEa}D+ZnI%2fC~eEtPrX!^>W%PAVC%F{B`|yjPom5>BXMX? z$m6v=DeT5HA?d|d7IH5kPB6+jDzOS_`caL?ZbtLr^gW2vqDERHzuRr-Q?Xm(YIP}& zUV)RX>6SxaExL3Lhqn*Mp_gLvQ!hPV13fQPErIO(_ZVfE35DmIrX(?mPj<|CHgDM- zuWC@|n!AMVrEs_Cd1CVTf0pEQ6{Z=>hnKVK_`9|!I_4E*`)~}Av5=6NvrivaKF}0O z<-kZFTsh^Z`5|WCNP8QYgBFO6?Y8hsrPP&E25wwq@N`;@R@mGg?c11!oB*cxr?XU( zOLO}8g!Vh{kfJGY?B*?iPitj#^TT_=$?8xFhM~sTKv8$lQ2UtB=N^LXU`%s~rl2jo z0!L><|7cv!BR}ByTdi9yi4>rPYZE2Bk*4GP790ni?<%-&vE?NBqMQ=5wkqs)_ipHA zH4wL22v1P;ptE!#M(Fc3uADxKvB_*GW4hpS#T<&eY%FMn?S-LRBMNM?VZLRvG91hh zN|3?6S!?sj3E~-w74hqNlaDXfZ5j>iu}#k$ntX$ZO#PJ4X8Je4&wM;PC zZ~QcoPcY6zJNjzb+0n_VyPb%Z_3NLK-d&p(5t!|%PE0ef)s3S&iaON9Hzn8;88wKL zT#g;@i{M^2G3peBS0vkTwpouT-SK|nG!*AF9e6f*q>BP^IY+%~S;sVf*J!#ue+~RC zaydhTbuc{SM4Yzu=7@M+0juFr6xy$ zPx9WaI11374-|)1h7x{&DW!XpyfdtvHm@hyUdfA7J5i}1kD9c*bpp*)f4b_e-~GUT zCyUy{SH&PUUUgC+8?>Ta3|DmEnBI7P!iI9eJjuYK7j?RgWA&+3f`42o>AAAfPgi?n`4&nQd5~(w__SsWtCe$r9jSj!G#=17#H*Ys^N}KrIYi6X|v4rnLb@ zwJN%5V-am?W9M`9W&Za*;uRXd!UzRq^-B=#-#$mWnct`RZO;F z)Q%6A3y^&K3l2mbPtO*9)N<+0)3%jWzi4Bp=l2Ag@ z@WCk=_Ca)C(XOK}Uvclf^(x|v=dFHPS%`jWBg0#YsTXxB09~`lR!WgldT7Q?G_hOk zr)%%tNPeO3BUY8Z^H+|a`=W8_ACl1vPDs}`d+<5ll)SP@$S)V~_#*w_fQl3|vvs#E zBUXEwpU%G5m79|2*v|whr_?ggmtr$CUNAHPv9hJZ<1`K8bm#@c8D^fDOE&u_^Np|KJ)X9xnxV! z9OP}Wa@Bh$_wEhE-u3e6^HlWLAu(ME#IIKBOnzv4{@aEsUViTl9)tAWy7 z(mmAAJ5$@!2WKj;B_DWkAIo?&SN=QcjSm{MbRM@cnzQL+jr9aCHXT2X=GLs~wv(vR zt4gu@lbetDPc*Y4XKQ*Q2yK5Lq2H33v&@qB5V|6?w1 z`xzwnL6(nJ;ycLf3-)%Qd+#?H^R{?%V;(8K4_h9HZ=8Z}F^6m!jluc&b*wQwDB}Sb3%)fpx9dw2&MFj{-KkZxg zc)KIKH`QaOFJ#Oih^5JZxkVXNg&z$OV1ZE*@)XBALkt~HB>Jx#bRKK-Gj9BdeQY%0 zl?Ssmj}1bgr>{zgg0nOJJ;tO7Np=&A!hRp5hb5}&CIIJcoXef!4xSK69sDGf)KqZ( zn9j)m5;|`Puz9PW^$cO(cQpYPO`uXsXYAy)&zZA9nM7q3HBm0n9tW+V9F!vTu$u0I z_nOA_7cPhA1A@%aT0wi6<-5HVdtL!5u;Of^x~X@X8zpxU`>qV1v?2hruk*qW%M#^s zzb;jmonIFY@2+&jn9LY|_y#izFW$aWF_~azpw>|KQiR~RA*SYvGdw+$%jDiee}seW zN<2uII?i`4lz#X0JkNn#`q-~7C6c?4)KzW2)Ub)l^^qkp6in^5X|{eWNW>aF0rxEH zLbVwp#cRa}S!;W@G}T#&#h@!b(HqAh z9cJ*Nma%aaqvk=JPV_>kb7lr3v17^YB9?fG23YI*B2_4(QxA^O=Que+(^bu%Kj7Ha zKeG$-;yM+kk@~1nfRrW5?=p4JZ2>)+slbOSdLqRu1YrrHg(^D0&Cp%Y4g9awC;9e zHJL$wzOsjjHHJJ2P_jVL(qVpOz{&Qq&aKYV^cRz=M!{d4-O^T4kzwe!NjNY7`mRM% zOY+5*h^jHbz?YZs={w!3DKG(Z40-UOA8k0K7{zYZogzhc002!6LE>1 z$Bh&`5}bpkNWH=Q!OkwTz6uVFh`^SQ{xg%8(-QY2?kS9-?V{>l9$E5Q%2J(r~IJ_Y4o8KJardGVCg-t=^M zjStVT_CmM+J;89r@#uIPb7|lmmE}TB8L|h7M#Kj}+6A0uSrf?;}D?(Sk*XfT3b^rKm>-_y?ZH*6D?*kug-lWvzyg8o{cCC}bd~X>*V*-|4 z<^XS(qELCRpg!}Y(#=-7T$g`7NGe+~ltWhtiGfJ?KU=T#{wBj=acf;NsEZKn6M})#Uzo2~qdEG#MBO_UCA)BlH(Hg;yzD|NE4QBl2EI0W!)7wbtJA9Oy*z8Wi{MSHoEHiFwba zDTPRHhO;SKKK7^{b#@J-TH$=|3D&Tx+Ndi($Ns9tKm3}=v15f8Ge_TQLXmYszv$%V z2NyfkN03l6QTXF2R2}raeqpRZ3HzOiax@8(>#Tiol_+#_(k%#nDz!q5@x)0{k%=b(@=HnTi zCS-ArD5faUtbO!5xuh+1jnP?mI7bn;B@BhrvmUgXZwhpKN$G3pX3EKa3X&ps8+<@% zRar!H$iM=&?^JwTaf+f4!1Ut;#$F?{`p$DZg(~e@CLn|+f(t5Ajj)5U>g5q19)W8G zM;`T1;9Da7I)^s+`|_JybXukXTc_Eu6arB3`M2fxEVSy7&xV+m)^|A`Y#YoWcKN;9 z9q|J%W4a3oj^t3ZoyDJJyDm%Q86p+@1ooH>AA}1$O`I!7a>?L;^qgxhd6MC#U-)a;{3yiJ*{$)J z(|3F<#njtxEHmy?w?~zmzx+Av`X{)iQ_3(s6dGFmyObi^2pI_!z{$ju6B}h7yJM& zA{JTP4SmOEdr7$RW^T)vrD$Yw)%+*A&5SFji6!$F$Wq*Aw!r2@a@wKwy40?cQq!ps z1gO}#tUH+6K3cW0+STwyqp@Yi>mTach4NNW)G0o4LWzUgO*k_Ujk@p2I+4j+x?EFh ztdX_+hG>tvdpEylSGs(js)T%iB~NtL7AKpGxLqed<=;E37G)!bQB&FdDw>#DGej5V z#3QgLb;QKhEk#u++Z}<=7FE(DZ?jD})^*L^#N&=&6yNLBLXi#@w^D@jCCT*V==4Sd zC-EOg=JdW7YHXkIwPu6*wa_APa!E(FiX1sw&5Vc|9oCCr1A%E|4zEQIi_->UAOe`ncnh5ktQ|C z!2wlwN&gM(c4otoKp?pH`ogabT*$x58o8#^zmxJRxVennGJOYsxTT}8lailb=zt6U z3GekPwp5y0D2lI)Ev5#2vL@ER&J1KJ0i)XSI^@}$chK~s9G)u?3$wx0P*c-aTc(qV5?k-_6>ewRn28mgmYLnczxdAR z%{xGm{IIp9?R;JQmW-pon-f4CoqND(%jpr$v8-`2wdXOzdk$i0o#YU{7nf}uCpc9C zC%YU@^{EKnrZSk=qH{Y&o%_8=dg7#18JB#|cZ0oT1X)ULM(1{*ijd3K{Yd`dqArg= zp13N5oOKYEz>S=@@<$}|Lwv}9V{LvJF1oIGEdP9sCZg4w$RQ?ZbF`R$335lS+rfo8 z!Xs*pwAY5fBg{O!y`atm6ptj$i5bZ{x6jKyn4;9wj#sNb??1ii@!NZj@Y#w}=X|T6 zkBTcS{Bdg0`Mu>WbmN@gFyrO~MDdtb;WJ(C%Pt>F9Zcs-6${=?746h50Y+rD;&#TF zR`NK%yp;CYZ4x=XgAjM$H$QRS;_96D#Nu}9am1z(#FF);3t5s^>ii=qe?m7w$7{iC z0YR2FZ$$TtAs_Tx_S=C&ylEeAT&W*G8;-{hd?)lAAg;1ac#lu_HqplW49yR?Czqfz2 z4!P+-B1vBhT?h}~Bq*(UTNF&SCt%k9Cp_tm9TMDfY6}_gx<2~eM`{M|u}OE?Z)F(F7rHdh|ld0xQ9425`K zb~@e(+;J(TG$Komikhx81(S<|s2|REt+1)i@it55!*MR=xSd5V{U776N>lLe{Ptan z9V@X}o|87GeYp^zp_q~4rzE&l^>~85s#S zx=JiTmUCOa%?O!BX9+7y_ z8|j%8jv$%h>@Gj$z?WaL0A$l_JCRu}ra}Bj>R66$Y!1UpK>%zxV z=lcj`Ud|uczl~|}|DR;HF~G)(I?jq;pSI7jL!b$0!I=67b1Lue7bSxJ{)Xy*J?#Aa zTny(N=id)2(<#bSw~-)^y~}N_x#>Xs`iIR%#w6qWpofFS8M)qt2&qSF3rS;7@*LOy zsqu;C53A#Nt6`T`eMlvn#?l^6+^p@vB9GI=NQ`d~!sq1W>chM08-*UclU`%wFm@jG zJo6bM!zQzGWtU{3%0!ii)kWyS(2hg}8R_CJg5;q~OJnj{N*6z++}gA(kUbP5(pihg zTc#c}&**lyNZKD-NP+R*X$?FN9526WZM6^o(5^OKJSl zGSiM4WNS*GB#AL5ko`z8_&)q_;QTjw*%wUn*TuVrc_VAKRnI0|q1FwmmRP55RcCsp zZY`93uYe@EA(OngqFdW4?4RlZ_t#zW}ZcQSu|p-nFkmqQRn3bs zTnu}Q8Z-ZE-~GreQlcmpHGGR0Ry$lB$o9jwvN|mBVJE2$_03l zDzpmkFZ!b|*dqOwJyzRo#CC@rIC#t*$z>|k+K89+=rUfuwRQYjq&`f7wB9oO^T72h z63Ymp59u$ltT0E4owus6JyeP^H4D=l5PH8FE8GOQmS=!YrNhnJRaog>jmT;PlrjxY z1PZX;TZowI8q}%vIPq8s6+cPS!SAjN)p`xw0>wBPC4x(^2yv1clx9gV@puLH-jSh% zE2q7DrA&=eegdrW79mq&gs?)1z1~&W$kJ!YjnK(-xcpp(OSgsC=_g`k)}mga!NmXp z?A!&okz;_dwgwL$S7XD?Dx8YbKxKR(JO3GuAQD>;aP$cM%RN7jQYc#u`e zZ%Yz<;Cbg6j_y8&nCv2WMyB)OM%hdF-i(KaZD&5Xjy=xK@VK1`jnO*4!9=(1%ZX)I zt8wJH5F6a9a3Mv9svNMjZ?2o*AMQxbkgj))KULBLk%817Gb@o5ZA&h+Ql*h9zCeW z8n-H(VaLOj&&M|td(5x1+;ODDSL0k5tJf_7P9$53gI)8}R4Q?RT@$y4OL5Gb9I;NdPSZD^W22k&)RKEumt(4_PP{{ zvGKMFf@}%S#;M@wF2o}RYm;;-wy-|n6szX%A2lK+j^vsLi}}7}A1+4)S6-bS?!MK?SJy#MtiUcW0rs%_&_6*6 zmS+u~J`-VsXBBonQbVfL>k`ftQysBUqAVz|m>)Y1%J(XOGbHM`Gdsf4@s0PKXPe)an`fJ{6cJ9jRAY*N~ z|B)Prf~#@pz8pS}P3JYZkc(%uXeT3*pG)DHCc}C!3G&zjcSrkj-#;-wUj|kNcRzJv zbzC1LK_NRI#ko>!a<9bpJ8}s0Ms}YRVH0Z$7j_R9>uYV+j~gEsS(*ETs&SarL8jB; ztVbCZxmMwPI@jNgxPMoG1ha8M5X9i3+xvbmF&e6HY4atdoB!jQG9Ml%oU!ll6*l{dgvlExy z?m(=yF1N4gkG^1ww2;Gy&lSU1!|vTh(Kx>IGO9Fn5Es3`>4Q!L9G5tp$hBRWQh|tEZcw>TzqIuaF~l_RS%9}#Wr^clb}oO>0rm9+>A zG_8E9k!U)v6WmIr1(zf!Gt{9VOM;^*IwU<4AcS3~bOv^PVpJ6?ktC_%S2&%&CB!R! zr5L+TMOGn2YQ)pWRd^<-L1}>;XVTuS0yF6Jxa1|o16FoSSUFZdW!Dq#dK6WO=T(OG z>k@ZO;&@m8ic$r(-d91-9&qjim0?MYj=%n_y2puZMyR!Bf`OS>W_g`WeO85?ueJO? zPF!Yf;468zvZ+gcDm^Z`R>8lTYa0dZPn+5)GNhc(r$(iNqpJw%ES|lO48jhv%wU}^ zlUIY9OmrWAj6G*LGy*s#I75BfFmOy)HztUXLrc1SrtF zJ5QG9YtDHQiZr!&lP<=oH#+`4@*t!N*DLBEd@aBfS1C%FUnNq*K0t~}eGQ^RD{&;R z7Io}c3fTG3mb2@;rwmdz17F8x_?CcK!^w=EmAbR*%%E3LMNcO-|GrJ>)tg z^4>^rB-L!yAZwS)o&xw*7*VG){Ugi{cM;;%wNREzvCUJ8YMu3cFNfuGIYSE_%Tvkv zE4S}fXj*~QQ7+dm(*l%Gu(}2Lmf~7I>)&DxmIuqBv}mWpc}Bj!&IwoW;+A>)TAwci zZg`mPrv`SftYHu4PB&OM_m5Toe|zWl)Wj9X@xQH4zVyMFIvwqdL$xAAMMR)b1*ujg zRUvXwF5xC}ODIhX1rb!hNVe~!za*egjx)l#pCt2Y^_%=1_nZ@}m*bKv?9S{?`l5DWzO{oY~AQ|;~5X-Ru^u<P4&@scHY}ZxeOCoGoPcGj3-yB^u=K7I_5FGzqAv857om3{j0r^!^6!)9 z(?|mDB`xkgGwgf*qbkp23kNywzn1F-vFxL3GcN|UJP)(>RXi&zhLJqee0ZZo^N4B7 zK9+UQ@mM{Bn9qXS*Xv*kui;@~Gi)2g*1|f-uT^5z8^^TjJ~T#V-}4`~T(B!DmQg52 z$mbEul2|l)@ROQ27fL%GnPZqHPTcUe?Llg_oj9XOF`e9qqKY^uCgE@2kO%XcS{}c< zqZ!4r+hRn(I=pNcmqxz}xxUy&eZf?UIN=Wy7 zcOL@-appfImVMhD6a{V%dGN{kBLp`ef})ofBV+8_B2@h(KYutRM$-uML}|MR?!-DK zM>q`Hldzh+s5GX;k0d5uIq+mIDhgzyDh6_kbUjAB6D`*CdFgM?i|kqkXUIG>H}67| zSrQXGS`JyGk+k_oi14cF;qbMR$D9x;C5mO~^X$Xr7-rr&pxN9e_%zUZza*vAw`IdXjo_+!b4XA-DGZSXX0sMS*GJ=12I&{tvK7n;bNm5U$psP zA&y3+K8jg`6P?TPw0?~H!cb})C|5af;V}nuB)+d$mKv`Wn-6zpB8Zc@TGr!*ljI4a zP|j;15_lb}D?WVnh(S&phwMi-oF9uKu;z#27bnV8cATm8f=@-3C8YJlPL8u!ym%mv zV-lL@cGR;Q&>K8tyg5|sBVvE_cd=;XHpKO$n5-uba$HOhpHx0kBpRJ?qrA_7i+5I0 z&!vlHjKvSR%88;@7FVaDFc}==S{U3N4hj6?tHO~}dy5fr{>>BP-jFAySW&$1RjHcY^vx`*+3 zUaW&1Do1(S5+wHH|%BJ@ENfXOb2?~Wmp-?Ck3WY+UP$(1%<+$@NK&7s53EgaC00000NkvXX Hu0mjf7Jb}d literal 0 HcmV?d00001 diff --git a/docs/src/images/keycloak-master-admin-cli-client-scope.png b/docs/src/images/keycloak-master-admin-cli-client-scope.png new file mode 100644 index 0000000000000000000000000000000000000000..0a11a78469ed074dc09affd0215ad69e9efc19c8 GIT binary patch literal 57723 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/keycloak-realm-creation.png b/docs/src/images/keycloak-realm-creation.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc54ef80bef485ccf1d9d4eca7989f66027cd67 GIT binary patch literal 59714 zcma&NbyS;O)9_nCi&LaH6nA$kP%L?-2x}Q z@AiG3_xsj4>-+&Lxw7}{JzHmfGuLNzRe7vuq|Y8bdW5B@@EZ8&(No0VlNV?xh$q%D zOHGd+eR!n!T1v~ycrOjvi*UBR{jzQAhp*4U@#Mt{?fE1TvpNfb6LW2Ccep|HuaGC` z_&=7G((ny~Wt`EF5}43C@)VAw6;9w=g1)2iN%OwFik+7dqqnbmE|U3UE&TQDXLRr3 zhk1+*NcD2dnVh0edv0L=V$-fNMw<8!^J zQIz65rMZ6!+i9T#olIR!GtH3y)^}rfYQJUxlMBDql`>Jz5c3WS2|=P`V0g*QjNaeh zPaT3QjYbpF_|_X%*5G`)sUj1c+^#F(f1`z^`tMJ;hq}uFx9l=G$?ZZsuke1C#*Oh| zJ8V6z&vH>Q!L&r9LOek{raNbn+NF?rnz9){lKs1sG@7Dle8Y-W)KvIpjrHi+fULby)-}9Tup_751E#~V8qxcs5 z(~rLJpp8hu7rDkrOISNfYHAQ;r%~fSniy2J9^b$~DBQz_YrlRuZ;#QumX~i~gZf=p zH{Xq+P)N3?uesv;5{ZKRsnRZbo_-X@pU+3(zGxgkta zOCCsx>;rG8b*iN5@)MJR3-;2-V`tm|uz+-h!=_w<2FpP33K?S52`%)arfO{`xqL6) zb)9T&RGIf`oe$PbR912!At7bH-7f5V%b0Dz`3 znK@n^tqQsP3NcF)TsrsL5X$z=D$GMdk==RYwMlZ3eQQrNMSl7UI`HA$;=>A&7Nwsi zl*Kkjb6x^e=Am3uJyZO?Q_O>2H8%#u zV0d>Nu*a9M)*}+(C`m-7yzq5d0U#1g2={$^w>h=))Rx1E)cFl`hlX`9gA_l`wQOe* zS+k+w-daz8l)3EIn^t@)1}GLU9=bx7BX~S3*&YIg8a;_b zq3CpggOX%PpURA20K>T3ZxU=IN0pT1X$gB(nMdCenf+9otfV!DGHlTO5{iF+@P&k? zT)d(P9carT|BNi$ijL7p`snOf*LCa`p3OMCq4M-#S5Vx-@>)~Ir!(w672gHV0Ce%~ zNAQffxDX$nILdwpR)IefJb*;jXM{e_i_P~LRFG%w{$)sXDRozYWCoX65Gjh?ba7}n zF(>JaB&X9#dn96Hip_TWzS4rulXlKwv&UUHl9|$>&*o~Zqs;tmY?#{N7Y4qZ+H$DO zHLD&642gZQj5sVhrG>^VFJ-@ETJ*>4o~*zRJnb4?FY0EEdG|Q2zSjUhLph|@Ji~M5 zKXmIt9iDE!zkgAoyDWQ3xFhNDMZw&(&sK_M+TyC}Ic`feadV`^qZ$ugfXghdl6N#f zUKlv1JES|IvCQ|gJYZ6FCSH<1SKRN#DD%#$qw9e847#bc5ReRmLLWd0m#Ek|NN=2K79ov>HmxS}4{o zQN{pTiN8JoofER^C}U}o2|8*8$6$&>HV{+mP7=eUQv~d;geFd``YV3wh+mF7?q3Y- zQQ~Y1fwmo^v*;u~yFCOJfce-n{iY@K;ri*a`Zt=)ekb?#yX}EjN&%1xK@{J#05kjE zhqIM|?o z@wnD+13g^!dTQh^XzhJ&xL|TDv6gDTb93qUVACVgS`5M&tmrV`zp4i8sLhCuTVTDyzfXa|9pRj?ZR9|-Z3r7;FT5PnSM^-UIy%BZ*N6k@d zP(3=FqUy3nq6H?nY3gIsT+h?*;gA|E%2s@^Zsy=khuGAYG*!Grm1tE__6bL;Jcm|!nI237iqX4iEpzS^+# zEO@4whu2j9MQskOB2O>P`_|8MRx^;}M^!mC&YcKm*Jyz3J5vpse5GNHhKsBhr5 zZ*i0k?$S10ccVvHUen?mjCKHOhIC(lwL+gP?*hsVBHoDZY3M=8zdimhQV9hnd3B$>_BWitk02NCqgP{#+lgFM8-;%FI z_ac3)W1LZ?&e^@Lo#MtE?&XkB;St=!Cud*3ZH-Zn{2?(?&4Je&V}zGLGTvNz?d%bjbl*$c1Cq9>?@Nhl+E z8E4EroC42M*KGQVb1TVtyt_YNFO3!T@|8-w_{duzQ@L+#*(R=_C+Rd7q@Ma&N8<43 zlZfrTsPe9O^WI~$*P(~q*&PAf0-KdL#D3<{u6tBOMSFqop5~t)8!k@;{9bv^wuGR1 zs-XE6T9*SL`CD*WXK3%{`7P5m_sfJc`B%_uG>I~DRzJo~oHLn+c?~tug`QO10@WsXm#FLd~mcYIK@ zs~g8ihP8*G|CM^frE|JR>S0z$ZzFLa1~&|a)^v!{FdQ0omC}*8BT3GhbMSN7ya0-y zWT>3P)Em6ups3Hie&fmYoe zdgP{%1B@?(oxhg{12`h)E24K@+OP_pz9WxujFj^p%oz7-hvCX0PB|Tqno;L+#>sPm zRtiyW;~8kA02xK3G`iX$^DLJFDgJI#E6Pe;{W&B2xGIkvCB4Q89BzM;bF03_ODjDw z2~V7_qKXgmb*fD)BR-RoZLIMXYF@aQRInAcD4el?8edo&Yo&rXcO=zF<-@E!qz`w% za)-gupSO33KdoK##7ihN#S2P!h4h~6ULId0`J|5Gd_5hLw|(I>iPgR60Ar``%mbt_s%t!^x#ZhQZiF6a(0Udd$wT(eA)-lb2{Y zjh~mW8rGV)1;v1%(3k+^aDgRar}0skJ8hY`krGW_M#bulzd2DZ(50fWNCqpR+V^^S zY;FH`5i`M;pXPBCm1O-5UB_8-@ogg8=%?Gh(xD#exRd4!kF@h%+7JsI9*b)9GC`KS z0bZFKa|_5kyF&N(@3}$>4s(@K6q}5ZuJGnF8&E(Bgyr1#=eNi1y4e9~HO9hoMBeA4 z3yDa~=o&4)1f3~?HlMD!!5dbOUoog|Ka?Yzcz|`HGSp^mX6I(+j#Fn*p%h;xX@;j9 zvZq7YMBo_kbx*kp$ zX7$V91aqaN`%@Dl_rA={!vLoa)_A}RU$ycj?I9*}sd1F7-E!-&cJSp26~=yyQkQ8Y zAEtmUoDRLwnqCAOZPLw zA}=(c^!kJ&TB7rdday7ZXjCT3!_E0yu1oHN{?f9N_wdE>SyK84r!Y5>XwSumzy`d; zc00+Jhq_POY~kq!AptyoDj;^@`EE~<3kg2XgiKB-x%I1-DiEl6{)Eo36C@SzqUu-j z&T$%SVCnb#O89w+s=@AWd==u2y1-x4xTj~@QoY_KFJQQKw}-k&*0`PAUmsKy+#3#o z4|j}R48pkLmliKUTdv7JOjsN*gyTN(N*aVcE|z&5=OZ3SGv;ih-D>DmBcj;vZ=qwQ zCIvXsnq4lhnLkFO8O^{je38{E^1V@X46b{l=i}CUpo+5O zel0~SD$?ZIVk21#4vjB%A*Glfh1%r}smvEQQ-$uTgB{=8PPoSR6YO>vl=N0LY)#q5 zq%MJBT513>&51~*5zm0eV0Oy^^J}DbgO_-ec#eWE0VtEYO+s+ZR{NbV>}Xw|qT)t( z_Rlimp_VTemWw{RIS|UZZ-@0uS>t+Mi*S4|>s`Olmw{mtp}y}P65os@s*#H{ow3#*C@rr>uUuu)8(V+M2pPOJT57^g76^x#F z1xQbr8%op@hRa>PW`yGD5?y?64anmNApJ;h1}zLEpKNtGPrqrh+5nEvHk3Vfw0V@m zcDe9qW;U+>f2NgiNmxsT`>z*MK!U_ATuw``M#R)6DIm9xVYld=SMgZdXUwga_m)x()m!I-d#lKDZe zB^3dUA|SmCPBxO19wRT#z&c$x5xaDjbf|i*jYik~TL44K7|6c~lWY=k{gc6HDkh?- zJm!S_Tl5||QxaKIcw8_}B{@obA0crtNH7C|Xx=f4#s}DBV7j|W6cp%2FvP!ELSarN zE5&ZHCn+Rb*viLT9&o%M89BG8H||=js>=%GfVtarJ&=wbeEby;NL+{HaFoNM4rGG% zWwNHs*i^el(`g7KJu3y0r{qG)R&!QKb4N^NJtdXv#LvpjX0FBrV}ROV#BtgcVz`_X z+bsn!F|P3!^Up6hbz>kAtg@E2exE{~k7RBRFP3S7L~~gtcz2DI8U|(E;gq@Cy@;_a zfsy!9NUFt9dQryYRj^`hxm`~Wy3`$I8JRn6F>jA5_FON2F$8n(=bORe78|``JVA^e zJx7+@jRB`bj!Sg`fv`(nQAVeX!yO;ek?!@Y^aDccRG7Nmh`I;o3R(J~Z7UQ>?RLBW z)nzaHd-zDwLR%$G=T?+zz^(>2)@hBN(b!?C9q zq8!N4)gJ#~n%0;3FhMa#bjJ&t3BamYlylSA9(;xOxfdHb+`eYEici zMB+v#y>}03mhep92p?-p z!y%U1244$w{dRUz!l(M?&+bp(U0D3P`?JJ>$qV6@8kClQamL^FRK zCf;N13gab-JD0t=s_prj0yupePbp{3fYpo)U7Oa;eygzCrt;mUAhZg#)T|3cQja$b z!cYE64kcbA4aic(;+S*|JeM{0JydxTpIOB*_W4lvrg2E1K!u`Q71cN&V^dUxBK?l7 zM}+?p_K+h1Jsly#;+B=<8v!Y^w|kqi!}!*r-sn)@eIo+aX|fWB{g5egSVG*e#)(Ow z4kIX<+Qr5d%bq0AzC$VS8g+Tl1dkS5k(Lof$snOx^`X2pcBeb7fWZ@+Kd6Pri>0N( z=Y$}4szRLHz$n8D#a`5)pfch7fsj>6nL!J*#$>cU_?Uo?mrt27cUxmk>b ze#_StkTzCjzynbnkj$Sk;n)C08feyVeLqi{2{{cJlp=f zYNOP9tt;;m{5?(Cp?*#VsE?GGRF7VCTObW*jO|Gp{S2Yj{;<}x!?gGJYV z2zVjk!29x0AID4wIx@*$;y&)lMiipE6({g}@gU#2SS;9WEL`jgQr!l+uu;vizDgU> zA6QGQU-0tVYCd_-kq&14A%XXaTin+yfB%|6ZENQs)zOhM#BoinaF&|eMRL=9%E7!k zV_q0NU1DYOoFH|yd++xN)ZlId^yrwp1U0xOFPj)&jK^+Vehte|wCX+Anhh#|z4H*0 z?mO*00jC&;p0S8)o^B5FvIQCg6#7duV}SHp9EOeR6fX;~QeqQUuqnlaG<~vD`MZ{m zB-8*DfDg~m(YZ^X4@(;Wn)pljBr1{VowlDo;%01_nVdd{k6D^l4hCP22JZpEkeJDV z62^jrm5ADOP5sM3?Dh5P=h6ytZ@mxOE-@p`lCKAxp>uP(T_-~md%{JvX8>>B?lz4p zLdFD@Z=I2%Q#4MCZ_ee*Jnu2+lR?EYm#troo=SVriq6Esr4F@68j!d4B?DMV6GZTfsq) zs$57BM43zK9~!LSD1{EZ#*BQMEE;Fb>?w0dXR*_uR&?-$^k0tMM2J#EtgIF*rO*c$ zMk1&#YcZAkQxPP!i_AyUaTR1>ZGr!cq?l1k(Wj~wzWz|HJd>ni{IC4J@F&4j7{v`- zvj0MG7aYXb(gL%9KRo@COQG%OfU~$N(Vzb8nnq_A;}>52c>Zq(n7d*!q`rtSe*~mw z#2B-n>!ij-{R6sjY2)v%NphL~W&3>mt~OMEFn>N!31R$UV*HL6{0I^*g%1oon&r{8N8|pjd+TJZ0q$^hbj2 zGi~erNzVV&S6XiM#`>@JVkAu@x}^V=$DDUi=a(o6HKX|>4tS}5@}Jgogt zi33FcvUvU{fRRipJdgB0It_+V1=#2+{84JEE&d7ev;Wp9&$J^56#AzsU}=z&`LEEt z(G8NKD9QU5ajb7t8=P8?L926ro0w;jVzD>ryKR8NvEo>?c|x*lb=&>C?+!enynZNu z8n?>VyO&;`$WtPFqTw^Nd!qTTsR!1^WsTiP%5$z$>HF?w2H4s|H%`#})Ix@;>bK zn8BsL$iNv~AoCio$gm$HZF>^0J!>}q!U0QDv?9xI2?R2aW7b4@IF)=bzk2t#qc$}b zH>Zf(#B8-QltVQ|7$aVTwT#B@ibYo^<4rKcQiWiXQ#Vt@U5c6wR&J2s^rh+&K)CFM z>9#pkCF-!iH{PR=LAwuJ)Y* z?XTFXbjtK?W=flyhjAqDepVlBB$(((dJZLWCl*ajYi*E+VUb}|QBk?4oz_>ho;*ty za42?MX>?g5kmP09xY?^2ovOavpT&_0!|wj@@#%QIgMr^kKcnN|erd?;fOhfE+h>I8 zRY3aI(-BdSLuv!N$-RN!ZQ1l26aVST;gyGbPYAn#Dh0|-H?hfF#p8dM#=N3ZhjIXo zQ=8AZ^YJfP;?WG~2Zj_GiZ+cBuwGZp+lR_QAG4s%V>rq72&iBL@wDUmw+eb|tP{j+8#w#w*D7)BN@CzLp87#L6zIE*~d zfnhJR%}APNNV6H7f-ED;W^HHPzl(%2anmti$79t~*Qp$T;8)NCFTc*k&!&4N>K)v`^^Al_G@(2hpS%6Js3S5g%EB~o9`v_L3g^iZ>4!J zHopQg>Y;iUgkQlyBN(#Bi!h{oqhmgt&qD8~1H{l@;X_Jk>?j!2(n~L96h4uPyicTg zWy@jQI!CV@NiGab`!3@{ScgmR|)oJh()W<8S~#gSQ8w7 z$$QLs&r^GFvN6aZ#d2`x-P+*SkRxbkl*>(`5U~j( zMf^u_bI)i17{D|Ucc4s+go6LgP5~Oa@Tab@C?*)VxJ~uOIQlmPeD!(4mrmwb6n4;$Q z*wf`#z@m~M51hqfB(Qw35}gHJW>OKxf!AjvF$<#>38HK2N5{Bb9rschNkcGUlZR^m z9PqXt<0m7cPGm$@Av|<@KbnMOqV)q_lE>53)&%ipqUTrM6Fl97dPZ`<7Rs{g8dzD3>!ye*&i%@ZMq+HA*( zxbH&5U#AJVOyK2$g@UG1oZ3G}N8|c_irLDxh(UvUe;Sc0ep9iG&>NCjW7dVjuk5hg zs+Wc<69gj4e$9TSQ0a^fOb68|;($-prO@fzwnn;Bh25qj^JxTCSV*pR`la4v`?Qw==}P}H3W(%Khs|2dAn!(KD?I?BE(6J*p`CV{#p{i3w?y6V*zR2eqcgmH`!?<;L7Bt@0UqAR z$|L7nI{f&hH>J0IyqYwEX2Q+}`&s&K6LoI@7#jG6U<%QE1g`#OpQQ+~PL-CyDaisb zL>I1Peq0sftD~kLmFsr{s3h}opJzPW6_~Av0MylkA-|g4r7j-P4;%?Nv@mPtFUUZK zCJH3Mj2je2C(7|mQUgm!cswD_zrKIIpr*gwX>buRY4_uh8t?}7?oQhmimU14OqtxD z5WDwf`z^KkdTM(I3|@QT=UU{;2s@#xl^HZtU8;VLBokaExcEnDK6#t|3cmPrj7iw> zTJJv1?%nn2uA3_3%N#S3=N4ERCr;7q1P*PW${K4I0S1&+yssCl-d5#egd5v&&)QK2 zDHolgMhR2t1&E76KkQ;2bg3v@OHHro6GkZ!ArmHz=X#V$n$CB5xDpizhnav*$EJd& zM$%f|Jy%Y&pUI(rt}v9&i)uexrkU|szuiw*r`#~qasbMslvckK6pFEr@btZlA&CTNKTqM}15o zi$3XydtzTexlGuvQ-n?(%xzskt5qbm8?x)N2t5&lC{w#I@oA-rNt5p0UW#_c8k0C~ zl?%QOnxdpv*q<{?JNc1jB?W+Nl_L(VoaVRP0Dfg{M1a?l%-bm*0oYH;Vc_!A%%0A!=S#M%sfBkuU+t52}f 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 Date: Fri, 29 Dec 2023 12:25:19 +0530 Subject: [PATCH 34/53] Updated README.md file with /status and /on-status , updated images url --- docs/README.md | 866 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 579 insertions(+), 287 deletions(-) diff --git a/docs/README.md b/docs/README.md index ca9c155..7dec121 100644 --- a/docs/README.md +++ b/docs/README.md @@ -77,7 +77,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. 5. This requestString will get validated as per g2p specification. Please refer to the link mentioned and image below. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/search-endpoint-spec.png "a title") 6. Once requestString gets validated data provider should save that data in redis cache and transaction data in db and send acknowledgement back to data consumer. Refer below sequence diagram for reference - - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/search_sequence_diagram.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/search_sequence_diagram.png) - Implementation explained in below point when it act like data consumer - 1. When it acts like a consumer , it needs to define a scheduler. Scheduler is nothing but a framework that allows you to schedule and execute tasks at specific intervals or times. 2. In this scheduler , dp will check whether there is any data stored in pending status with a particular cache key corresponding to that data provider. @@ -159,7 +159,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. ```` 4. Create package structure shown below. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-package-strcuture.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-package-strcuture.png) 5. Add .p12 file for search and on-search. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/.p12-dp.png) 6. In the config package , create the ObjectMapperConfig.java class. @@ -189,6 +189,8 @@ public class Constants { public static final String CACHE_KEY_SEARCH_STRING = "request-farmer*"; public static final String CACHE_KEY_STRING = "request-farmer-"; public static final String CONFIGURATION_MISMATCH_ERROR = "Configurations are not matching "; + public static final String STATUS_CACHE_KEY_STRING = "status-request-farmer-"; + public static final String STATUS_CACHE_KEY_SEARCH_STRING = "status-request-farmer*"; } ```` @@ -353,9 +355,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dp1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: @@ -391,62 +393,63 @@ spring: exclude: static/**,public/** server: - port: 9001 + port: not_set error: include-message: always spring.data.redis: repositories.enabled: false - # host: 3.109.26.38 - # password: cdpi@99221 - # port: 6379 - host: localhost - password: 123456789 - port: 6376 + host: not_set + password: not_set + port: not_set client: api_urls: - client_search_api: "http://localhost:8000/private/api/v1/registry/on-search" + client_search_api: not_set + client_status_api: not_set keycloak: from_dc: - url: "https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token" - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + clientId: not_set + clientSecret: not_set dp: - url: https://g2pc-dp1-lab.cdpi.dev/auth - username: admin - password: cdpi@9923 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dp1-lab.cdpi.dev/auth/admin/realms/dp-farmer/clients - clientId: admin-cli - clientSecret: G7rVA27HI5UpzMJfomRvaQHubtbAcWcN + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token - realm: dp-farmer - clientId: dp-farmer-client - clientSecret: 55VuMuin1T8xbYSUu5zAJAebA05tSwkX + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set + realmClientId: not_set + realmClientSecret: not_set crypto: to_dc: - support_encryption: true - support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set from_dc: - support_encryption: true - support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set dashboard: - dp_dashboard_url: "http://3.109.26.38:3005/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + dp_dashboard_url: not_set ```` 14. Add below attributes as per your requirement - 1. Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. 2. client.api_urls.client_search_api -> change port as respective on-search api. + 3. client.api_urls.client_status_api -> change port as respective on-status api. 3. keycloak.from-dc.url -> create realm for data consumer and replace name with keycloak data-consumer realm name. 4. keycloak.from-dc.client-id -> respective client id of created realm. 5. keycloak.from-dc.client.secret -> respective client secret of created realm. @@ -785,86 +788,7 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque return messageDTO; ```` -28. Create a schema folder in the resource folder for respective data provider Query. -![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp_schema.png) -29. With reference to below Query of farmer data provider. Refer specification - - [specification](https://g2p-connect.github.io/specs/release/html/registry_core_api_v1.0.0.html#tag/Async/operation/post_reg_search) -![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-specs-json.png) -![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-specs.png) -```` -{ - "$schema": "https://json-schema.org/draft-04/schema#", - "$id": "https://example.com/message.schema.json", - "title": "Query schema", - "description": "", - "additionalProperties": false, - "type": "object", - "properties": { - "query_name" : { - "type": "string" - }, - "query_params": { - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/nonEmptyString", - "type": "string" - }, - "farmer_id": { - "type": "string", - "items": { - "$ref": "#/definitions/nonEmptyString", - "type": "string" - } - }, - "season": { - "$ref": "#/definitions/nonEmptyString", - "type": "string" - } - }, - "required": ["farmer_id","season"] - } - }, - "required": ["query_params"], - "definitions": { - "nonEmptyString": { - "type": "string", - "minLength": 1 - } - } -} -```` -30. Implement method from ValidationService and change schema name in path. -```` -@Override -public void validateQueryDto(QueryDTO queryFarmerDTO) throws JsonProcessingException, G2pcValidationException { - -ObjectMapper objectMapper = new ObjectMapper(); -log.info("Query object -> " + queryFarmerDTO); -String queryString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(queryFarmerDTO); -log.info("Query String" + queryString); -InputStream schemaStreamQuery = FarmerValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/farmerQuerySchema.json"); -JsonNode jsonNode = objectMapper.readTree(queryString); -JsonSchema schema = null; -if (schemaStreamQuery != null) { - schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStreamQuery); -} -Set errorMessage = schema.validate(jsonNode); -List errorcombinedMessage = new ArrayList<>(); -for (ValidationMessage error : errorMessage) { - log.info("Validation errors" + error); - errorcombinedMessage.add(new G2pcError("", error.getMessage())); -} -if (errorMessage.size() > 0) { - throw new G2pcValidationException(errorcombinedMessage); - } - -```` -31. Implement below method from ValidationService in ValidationServiceImpl. +Implement below method from ValidationService in ValidationServiceImpl. ```` @Override public void validateRequestDTO(RequestDTO requestDTO) throws G2pcValidationException, IOException { @@ -876,13 +800,6 @@ objectMapper.registerSubtypes(QueryDTO.class, QueryFarmerDTO.class, QueryParamsFarmerDTO.class); byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); RequestMessageDTO messageDTO = objectMapper.readValue(json, RequestMessageDTO.class); -List searchRequestList = messageDTO.getSearchRequest(); -for(SearchRequestDTO searchRequestDTO : searchRequestList){ - String queryString = objectMapper.writeValueAsString(searchRequestDTO.getSearchCriteria().getQuery()); - QueryDTO queryFarmerDTO = objectMapper.readerFor(QueryDTO.class). - readValue(queryString); - validateQueryDto(queryFarmerDTO); -} String headerString = new ObjectMapper() .writerWithDefaultPrettyPrinter() .writeValueAsString(requestDTO.getHeader()); @@ -1064,8 +981,218 @@ catch (Exception ex) { void testResponseScheduler() throws IOException { scheduler.responseScheduler(); } +```` +41. Create new method in RegistryController for /status +```` + @Operation(summary = "Receive status request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/txn/status") + public AcknowledgementDTO handleStatusRequest(@RequestBody String requestString) throws Exception { + } +```` +42. Add below code for authenticating user +```` +dpCommonUtils.handleToken(); +```` +43. Add below code for validating the statusRequest and updating cache. +```` + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); + + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class). + readValue(requestString); + StatusRequestMessageDTO statusRequestMessageDTO = null; + + Map metaData = (Map) statusRequestDTO.getHeader().getMeta().getData(); + + statusRequestMessageDTO = farmerValidationService.signatureValidation(metaData, statusRequestDTO); + statusRequestDTO.setMessage(statusRequestMessageDTO); + String cacheKey = Constants.STATUS_CACHE_KEY_STRING + statusRequestMessageDTO.getTransactionId(); + try { + farmerValidationService.validateStatusRequestDTO(statusRequestDTO); + return requestHandlerService.buildCacheStatusRequest( + objectMapper.writeValueAsString(statusRequestDTO), cacheKey); + } + catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + }catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + +```` +44. Add below overloaded method in FarmerValidationService +```` +StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception ; +```` +45. Override above method in FarmerValidationServiceImpl. +```` + @Override + public StatusRequestMessageDTO signatureValidation(Map metaData, StatusRequestDTO requestDTO) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + StatusRequestMessageDTO messageDTO; + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(farmer_key_path); + InputStream fis = resource.getInputStream(); + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = requestDTO.getMessage().toString(); + String data = requestHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ->"+e.getMessage())); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(requestDTO.getHeader().getIsMsgEncrypted()){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusRequestMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + String requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); + String requestSignature = requestDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = requestHeaderString+messageString; + log.info("Signature ->"+requestSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(requestSignature) , fis ,p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + catch(IOException e){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = requestDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + log.info("Rejecting the on-search request in farmer as signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusRequestMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(requestDTO.getHeader().getIsMsgEncrypted()){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(requestDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + } + } + requestDTO.setMessage(messageDTO); + return messageDTO; + } +```` +46. Add below method to validated statusRequestMessage in FarmerValidationService. +```` +void validateStatusRequestDTO (StatusRequestDTO requestDTO) throws IOException, G2pcValidationException; +```` +47. Override above method in FarmerValidationServiceImpl +```` +@Override + public void validateStatusRequestDTO(StatusRequestDTO statusRequestDTO) throws IOException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + byte[] json = objectMapper.writeValueAsBytes(statusRequestDTO.getMessage()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.readValue(json, StatusRequestMessageDTO.class); + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusRequestDTO.getHeader()); + RequestHeaderDTO headerDTO = objectMapper.readerFor(RequestHeaderDTO.class). + readValue(headerString); + requestHandlerService.validateRequestHeader(headerDTO); + requestHandlerService.validateStatusRequestMessage(statusRequestMessageDTO); + } ```` +48. To call on-status endpoint from dc add below snippet in scheduler class in try block. Refer application zip. + 1. Get cache key from redis stored with start of key "status-request-farmer*". + 2. Iterate list of cache. + 3. Get request data for particular cache key and convert it into cacheDTO. + 4. Check if status is pending for that data. + 5. Get StatusRequestDto from cacheDto and statusRequestMessageDTO from StatusRequestDto. + 6. Fetch the msgTrackerEntity from db using statusRequestDto and build responseHeaderDto + 7. Build StatusResponseMessageDto using StatusRequestMessageDto. + 8. Build StatusResponseString , create resource using farmer key path and send response to dc. +```` +List < String > statusCacheKeysList = txnTrackerRedisService.getCacheKeys(Constants.STATUS_CACHE_KEY_SEARCH_STRING); +for (String cacheKey: statusCacheKeysList) { + String requestData = txnTrackerRedisService.getRequestData(cacheKey); + CacheDTO cacheDTO = objectMapper.readerFor(CacheDTO.class).readValue(requestData); + if (cacheDTO.getStatus().equals(HeaderStatusENUM.PDNG.toValue())) { + StatusRequestDTO statusRequestDTO = objectMapper.readerFor(StatusRequestDTO.class).readValue(cacheDTO.getData()); + StatusRequestMessageDTO statusRequestMessageDTO = objectMapper.convertValue(statusRequestDTO.getMessage(), StatusRequestMessageDTO.class); + + MsgTrackerEntity msgTrackerEntity = txnTrackerDbService.saveStatusRequestDetails(statusRequestDTO); + ResponseHeaderDTO responseHeaderDTO = responseBuilderService.getResponseHeaderDTO(msgTrackerEntity); + + StatusResponseMessageDTO statusResponseMessageDTO = responseBuilderService.buildStatusResponseMessage(statusRequestMessageDTO); + + Map < String, Object > meta = (Map < String, Object > ) responseHeaderDTO.getMeta().getData(); + meta.put(CoreConstants.DP_ID, dp_id); + statusRequestDTO.getHeader().getMeta().setData(meta); + + String statusResponseString = responseBuilderService.buildStatusResponseString("signature", responseHeaderDTO, statusResponseMessageDTO); + statusResponseString = CommonUtils.formatString(statusResponseString); + log.info("on-status response = {}", statusResponseString); + Resource resource = resourceLoader.getResource(farmer_key_path); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = responseBuilderService.sendOnSearchResponse(statusResponseString, onStatusURL, dcClientId, dcClientSecret, keyClockClientTokenUrl, fis, encryptedSalt, CoreConstants.DP_STATUS_URL); + if (!g2pcError.getCode().equals(HttpStatus.OK.toString())) { + throw new G2pHttpException(g2pcError); + } else { + txnTrackerDbService.updateMessageTrackerStatusDb(statusRequestMessageDTO.getTransactionId()); + txnTrackerRedisService.updateRequestDetails(cacheKey, HeaderStatusENUM.SUCC.toValue(), cacheDTO); + } + } +} +```` # Data Consumer (DC) Implementation - In DC implementation , as explained in Overview of libraries , dependency of G2pc-dc-core-lib needs to be added. @@ -1089,7 +1216,7 @@ Implementation explained in below point when it act like data consumer - # How to create a Data Consumer ? 1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/spring_boot_dc_creation.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/spring_boot_dc_creation.png) 2. Extract the downloaded jar and open it in IDE. 3. Add below dependencies in tag in pom.xml ```` @@ -1163,9 +1290,9 @@ Implementation explained in below point when it act like data consumer - ```` 4. Create package structure shown below. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dc-package-structure.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dc-package-structure.png) 5. Add .p12 files for search received from dp and on-search -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/.p12-dc.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/.p12-dc.png) 5. In the config package , create the ObjectMapperConfig.java class. This class is used to avoid ambiguity between parent class and child class of Header. ```` import com.fasterxml.jackson.databind.ObjectMapper; @@ -1197,9 +1324,9 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://g2pc-spec-demo-rds.cs9zoco3zxkq.ap-south-1.rds.amazonaws.com:5432/dc1?currentSchema=g2pc - username: postgres - password: K6tnrCU0wqXOwPW + url: not_set + username: not_set + password: not_set hikari: data-source-properties: @@ -1241,73 +1368,74 @@ server: spring.data.redis: repositories.enabled: false - host: 3.109.26.38 - password: cdpi@99221 + host: localhost + password: 123456789 port: 6379 - keycloak: from_dp: farmer: - url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" - clientId: "dp-farmer-client" - clientSecret: "55VuMuin1T8xbYSUu5zAJAebA05tSwkX" + url: not_set + clientId: not_set + clientSecret: not_set mobile: - url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" - clientId: "dp-mobile-client" - clientSecret: "d9yPYp8G2nYLh1ztdeqvdvtxEYqx63Xg" + url: not_set + clientId: not_set + clientSecret: not_set dc: - url: https://g2pc-dc-lab.cdpi.dev/auth - username: admin - password: cdpi@9922 + url: not_set + username: not_set + password: not_set master: - url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - getClientUrl: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients - clientId: admin-cli - clientSecret: bCfUQy4z4NKiiz82zScJdKGtbKbchkhs + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set client: - url: https://g2pc-dc-lab.cdpi.dev/auth/realms/data-consumer/protocol/openid-connect/token - realm: data-consumer - clientId: dc-client - clientSecret: co0rJfm3mIq0OXysAt6DtDjibOHkcktY + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: to_dp_farmer: - support_encryption: true - support_signature: true - password: "farmer_search" - key_path: "classpath:farmer_search.p12" + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set to_dp_mobile: - support_encryption: true - support_signature: true - password: "mobile_search" - key_path: "classpath:mobile_search.p12" + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set from_dp_farmer: - support_encryption: true - support_signature: true - password: "farmer_on_search" - key_path: "classpath:farmer_on_search.p12" - id: FARMER + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set from_dp_mobile: - support_encryption: true - support_signature: true - password: "mobile_on_search" - key_path: "classpath:mobile_on_search.p12" - id: MOBILE + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set registry: api_urls: - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" + farmer_search_api: not_set + mobile_search_api: not_set + farmer_status_api: not_set + mobile_status_api: not_set dashboard: - left_panel_url: "http://3.109.26.38:3005/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - right_panel_url: "http://3.109.26.38:3005/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - bottom_panel_url: "http://3.109.26.38:3005/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - post_endpoint_url: "https://g2pc-dc-lab.cdpi.dev/dc-client/public/api/v1/consumer/search/csv" - clear_dc_db_endpoint_url: "http://localhost:8000/private/api/v1/registry/clear-db" - clear_dp1_db_endpoint_url: "http://localhost:9001/private/api/v1/registry/clear-db" - clear_dp2_db_endpoint_url: "http://localhost:9002/private/api/v1/registry/clear-db" + left_panel_url: not_set + right_panel_url: not_set + bottom_panel_url: not_set + post_endpoint_url: not_set + clear_dc_db_endpoint_url: not_set + clear_dp1_db_endpoint_url: not_set + clear_dp2_db_endpoint_url: not_set ```` 7. Add below attributes as per your requirement - @@ -1318,7 +1446,7 @@ dashboard: 5. keycloak.from_dp.{dp-name}.url -> url of token creation of particular dp , replace realm name with respective dp. 6. keycloak.from_dp.{dp-name}.clientId -> client id of particular dp client , check in below image. 7. keycloak.from_dp.{dp-name}.clientSecret -> client secret of particular dp client , check in below image. - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-dp-client-secret.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-dp-client-secret.png) 8. keycloak.dc.url -> hosting url of dc 9. keycloak.dc.username -> authentication username of hosting url of dc 10. keycloak.dc.password -> authentication password of hosting url of dc @@ -1330,7 +1458,7 @@ dashboard: 16. keycloak.dc.client.realm -> dc realm name 17. keycloak.dc.client.clientId -> dc client id 18. keycloak.dc.client.clientSecret -> dc client secret - ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-dc-client.png) + ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-dc-client.png) 19. crypto.to_dp_{dp-name}.support_encryption -> encryption flag of particular dp of search endpoint 20. crypto.to_dp_{dp-name}.support_signature -> signature flag of particular dp of search endpoint 21. crypto.to_dp_{dp-name}.password -> password of .p12 file particular dp of search endpoint @@ -1441,7 +1569,7 @@ public class RegistryConfig {} @Tag(name = "Data Consumer", description = "DC APIs") public class DcController {} ```` -13. Create below endpoint for triggering dc communication using only one data. +13. Create below entrypoint for triggering dc communication using only one data. ```` @Operation(summary = "Receive consumer search request") @ApiResponses(value = { @@ -1461,7 +1589,7 @@ public class DcController {} } return acknowledgementDTO; ```` -15. To run above endpoint refer below curl. +15. To run above entrypoint refer below curl. ```` curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ --header 'Content-Type: application/json' \ @@ -1474,7 +1602,7 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ }' ```` -16. Create below end point for triggering dc communication for multiple data using csv file +16. Create below entry point for triggering dc communication for multiple data using csv file ```` @Operation(summary = "Receive consumer search request") @ApiResponses(value = { @@ -1492,7 +1620,7 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ return acknowledgementDTO; } ```` -17. To run above endpoint refer below curl and create one payload.csv with multiple data. +17. To run above entrypoint refer below curl and create one payload.csv with multiple data. ```` curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ --form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' @@ -1605,7 +1733,7 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), fis, encryptedSalt, - registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString()); + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(),CoreConstants.DP_SEARCH_URL); g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); log.info("DP_SEARCH_URL = {}", registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); @@ -1623,7 +1751,7 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { return acknowledgementDTO; } ```` -24. To generate request from csv file overwrite below method and one private method to iterate csv file. +24. To generate request from csv file overwrite below method and one private method to iterate csv file. Used to convert csv multipart file to list of maps ```` @Override public AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { @@ -1636,7 +1764,10 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { } return acknowledgementDTO; } - +```` +Below method will convert csv file to list of map +* map -> key = column title/header , value = each row value +```` private static List> getPayloadMapList(CSVParser csvParser) { List csvRecordList = csvParser.getRecords(); CSVRecord headerRecord = csvRecordList.get(0); @@ -1936,100 +2067,10 @@ public class DcValidationServiceImpl implements DcValidationService { responseHandlerService.validateResponseHeader(headerDTO); byte[] json = objectMapper.writeValueAsBytes(responseDTO.getMessage()); ResponseMessageDTO messageDTO = objectMapper.readValue(json, ResponseMessageDTO.class); - validateRegRecords(messageDTO); responseHandlerService.validateResponseMessage(messageDTO); } - - /** - * Validate reg records. - * - * @param messageDTO the message dto - * @throws G2pcValidationException the g 2 pc validation exception - * @throws JsonProcessingException the json processing exception - */ - @Override - public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidationException, IOException { - - ObjectMapper objectMapper = new ObjectMapper(); - List searchResponseList = messageDTO.getSearchResponse(); - for(SearchResponseDTO searchResponseDTO : searchResponseList){ - DataDTO dataDTO = searchResponseDTO.getData(); - String regRecordString = new ObjectMapper() - .writerWithDefaultPrettyPrinter() - .writeValueAsString(dataDTO.getRegRecords()); - log.info("regRecordString -> " + regRecordString); - if(!regRecordString.equals("null")){ - InputStream schemaStream; - if(dataDTO.getRegType().toString().equals("ns:MOBILE_REGISTRY")){ - schemaStream = DcValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/RegRecordMobileSchema.json"); - } else { - schemaStream = DcValidationServiceImpl.class.getClassLoader() - .getResourceAsStream("schema/RegRecordFarmerSchema.json"); - } - JsonNode jsonNodeMessage = objectMapper.readTree(regRecordString); - JsonSchema schemaRegRecord = null; - if (schemaStream != null) { - schemaRegRecord = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4). - getSchema(schemaStream); - } - Set errorMessage = schemaRegRecord.validate(jsonNodeMessage); - List errorCombinedMessage = new ArrayList<>(); - for (ValidationMessage error : errorMessage) { - log.info("Validation errors in Reg records" + error); - errorCombinedMessage.add(new G2pcError("", error.getMessage())); - } - if (errorMessage.size() > 0) { - throw new G2pcValidationException(errorCombinedMessage); - } - } - } - - } ```` -33. Add schema to validate regRecord / respective query params , refer below schema -```` -{ - "$schema": "https://json-schema.org/draft-04/schema#", - "$id": "https://example.com/message.schema.json", - "title": "Message schema", - "description": "", - "additionalProperties": false, - "type": "object", - "properties": { - "farmer_id": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "farmer_name": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "season": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "payment_status": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "payment_date": { - "type": "string", - "$ref": "#/definitions/nonEmptyString" - }, - "payment_amount": { - "type": "number" - } - } , - "definitions": { - "nonEmptyString": { - "type": "string", - "minLength": 1 - } - } -} -```` -34. Create DcResponseHandlerService interface to handle the response +33. Create DcResponseHandlerService interface to handle the response ```` public interface DcResponseHandlerService { @@ -2058,28 +2099,279 @@ public class DcResponseHandlerServiceImpl implements DcResponseHandlerService { } } ```` +35. Create new entrypoint method for /status. +```` + @Operation(summary = "Receive consumer search request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/consumer/status/payload") + public AcknowledgementDTO createStatusRequest(@RequestParam String transactionId , @RequestParam String transactionType) throws Exception { + log.info("Payload received from csv file"); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + if (ObjectUtils.isNotEmpty(transactionId)) { + acknowledgementDTO = dcRequestBuilderService.generateStatusRequest(transactionId,transactionType); + } + return acknowledgementDTO; + } +```` +Refer below curl to execute above entrypoint. In which you can see we are sending transactionId for which we wanted to know status and action/transactionType for which we are searching status. +```` +curl --location \ + --request \ + POST 'localhost:8000/private/api/v1/consumer/status/payload?transactionId=T757-5372-9253-9725-4673&transactionType=search' +```` +36. Declare below method in DcRequestBuilderService interface to generate request for /status endpoint. +```` + AcknowledgementDTO generateStatusRequest(String transactionID,String transactionType) throws Exception; +```` +37. Override the above method in DcRequestBuilderServiceImpl class. + 1. Generate unique transaction id for status transaction. + 2. Find registryType from response_tracker and response using transactionId given from postman. + 3. Fetch registry specific map from registryConfig class , as it returns values specified for registry like url and credentials. + 4. Call buildTransactionRequest() method by passing transaction id and transactionType given by postman. + 5. Invoke buildStatusRequest() method by passing txnStatusRequestDTO,statusRequestTransactionId and type of transaction. + 6. Create resource and inputstream using keypath mentioned in application.yml. + 7. Call sendRequest() and pass dp status endpoint url, keycloak client id , client secret , url , encryption-signature flags , .p12 file password and status url flag to identify the method is called for status operation. + 8. Invoke saveInitialStatusTransaction() , to save initial transaction id dc redis. + 9. Call saveRequestTransaction() , to save requestString along transactionId. + 10. Call saveRequestInStatusDB() , to save the data in dc side tables. +```` + @Override + public AcknowledgementDTO generateStatusRequest(String transactionID,String transactionType) throws Exception { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + String statusRequestTransactionId= CommonUtils.generateUniqueId("T"); + Optional responseTrackerEntityOptional = responseTrackerRepository.findByTransactionId(transactionID); + ResponseTrackerEntity responseTrackerEntity = responseTrackerEntityOptional.get(); + String registryType = responseTrackerEntity.getRegistryType().substring(3).toLowerCase(); + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(registryType); + TxnStatusRequestDTO txnStatusRequestDTO = requestBuilderService.buildTransactionRequest(transactionID,transactionType); + String statusRequestString = requestBuilderService.buildStatusRequest(txnStatusRequestDTO,statusRequestTransactionId , ActionsENUM.STATUS); + G2pcError g2pcError = null; + try { + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + g2pcError = requestBuilderService.sendRequest(statusRequestString, + registrySpecificConfigMap.get(CoreConstants.DP_STATUS_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(),CoreConstants.DP_STATUS_URL); + log.info(""+g2pcError); + } catch (Exception e) { + log.error("Exception in generateRequest : ", e); + } + txnTrackerService.saveInitialStatusTransaction(transactionType , statusRequestTransactionId, HeaderStatusENUM.RCVD.toValue()); + txnTrackerService.saveRequestTransaction(statusRequestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), statusRequestTransactionId); + txnTrackerService.saveRequestInStatusDB(statusRequestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + acknowledgementDTO.setMessage(g2pcError); + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + + return acknowledgementDTO; + } +```` +38. Define the below method in DcController to create /on-status endpoint which will get call from dp side. + 1. call commonUtils.handleToken() method to authenticate user. + 2. Declare objectMapper and add neccessary dependencies. + 3. Get statusResponseDto by converting string to object using objectMapper. + 4. Get validated signature statusResponseMessageDto by call signatureValidation() method. + 5. Validated statusResponseDto as per g2p specification. + 6. call getStatusResponse(). +```` + @SuppressWarnings("unchecked") + @Operation(summary = "Listen to registry response") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.ON_SEARCH_RESPONSE_RECEIVED), + @ApiResponse(responseCode = "401", description = Constants.INVALID_AUTHORIZATION), + @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping("/private/api/v1/registry/on-status") + public AcknowledgementDTO handleOnStatusResponse(@RequestBody String responseString) throws Exception { + commonUtils.handleToken(); + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + StatusResponseDTO statusResponseDTO = objectMapper.readerFor(StatusResponseDTO.class). + readValue(responseString); + StatusResponseMessageDTO statusResponseMessageDTO; + Map metaData = (Map) statusResponseDTO.getHeader().getMeta().getData(); + statusResponseMessageDTO = dcValidationService.signatureValidation(metaData, statusResponseDTO); + statusResponseDTO.setMessage(statusResponseMessageDTO); + try { + dcValidationService.validateStatusResponseDTO(statusResponseDTO); + if (ObjectUtils.isNotEmpty(statusResponseDTO)) { + acknowledgementDTO = dcResponseHandlerService.getStatusResponse(statusResponseDTO); + } + } catch (JsonProcessingException | IllegalArgumentException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + return acknowledgementDTO; + } +```` +39. Add below 2 methods in DcValidationService for signatureValidation of statusResponseMessageDto and validate statusResponseDto. +```` + StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception; + + void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException; +```` +40. Override signatureValidation() method in DcValidationServiveImpl. +```` +@Override + public StatusResponseMessageDTO signatureValidation(Map metaData, StatusResponseDTO statusResponseDTO) throws Exception { + String p12Password =""; + boolean isEncrypt = false; + boolean isSign= false; + String keyPath=""; + if(metaData.get(CoreConstants.DP_ID).equals(farmerID)){ + p12Password = farmerp12Password; + isEncrypt = isFarmerEncrypt; + isSign = isFarmerSign; + keyPath = farmerKeyPath; + } else if(metaData.get(CoreConstants.DP_ID).equals(mobileID)){ + p12Password = mobilep12Password; + isEncrypt=isMobileEncrypt; + isSign = isMobileSign; + keyPath = mobileKeyPath; + } + log.info("Is encrypted ? -> "+isEncrypt); + log.info("Is signed ? -> "+isSign); + ObjectMapper objectMapper = new ObjectMapper(); + StatusResponseMessageDTO messageDTO; + + Boolean isMsgEncrypted = statusResponseDTO.getHeader().getIsMsgEncrypted(); + if(isSign){ + if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + Resource resource = resourceLoader.getResource(keyPath); + InputStream fis = resource.getInputStream(); + + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + + String responseHeaderString = objectMapper.writeValueAsString(statusResponseDTO.getHeader()); + String responseSignature = statusResponseDTO.getSignature(); + String messageString = statusResponseDTO.getMessage().toString(); + String data = responseHeaderString+messageString; + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + if(isMsgEncrypted){ + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString, G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(), "Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusResponseMessageDTO.class). + readValue(deprecatedMessageString); + } else { + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); + } + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(statusResponseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + String responseHeaderString = objectMapper.writeValueAsString(statusResponseDTO.getHeader()); + String responseSignature = statusResponseDTO.getSignature(); + String messageString = objectMapper.writeValueAsString(messageDTO); + String data = responseHeaderString+messageString; + log.info("Signature ->"+responseSignature); + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); + } + + } + } else { + if(!metaData.get(CoreConstants.IS_SIGN).equals(false)){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + if(isEncrypt){ + if(!isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + String messageString = statusResponseDTO.getMessage().toString(); + String deprecatedMessageString; + try{ + deprecatedMessageString= encryptDecrypt.g2pDecrypt(messageString,G2pSecurityConstants.SECRET_KEY); + } catch (RuntimeException e ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_ENCRYPTION_INVALID.toValue(),"Error in Encryption/Decryption")); + } + log.info("Decrypted Message string ->"+deprecatedMessageString); + messageDTO = objectMapper.readerFor(StatusResponseMessageDTO.class). + readValue(deprecatedMessageString); + + }else{ + if(isMsgEncrypted){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(),Constants.CONFIGURATION_MISMATCH_ERROR)); + } + byte[] json = objectMapper.writeValueAsBytes(statusResponseDTO.getMessage()); + messageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + } + } + return messageDTO; + } +```` +41. Override validateStatusResponseDTO() method in DcValidationServiveImpl. +```` +@Override + public void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException { + ObjectMapper objectMapper = new ObjectMapper(); + String headerString = new ObjectMapper() + .writerWithDefaultPrettyPrinter() + .writeValueAsString(statusResponseDTO.getHeader()); + ResponseHeaderDTO headerDTO = objectMapper.readerFor(ResponseHeaderDTO.class). + readValue(headerString); + byte[] json = objectMapper.writeValueAsBytes(statusResponseDTO.getMessage()); + StatusResponseMessageDTO statusResponseMessageDTO = objectMapper.readValue(json, StatusResponseMessageDTO.class); + responseHandlerService.validateStatusResponseMessage(statusResponseMessageDTO); + } +```` # Keycloak configuration ## Steps for DC and DP - 1. Create data-consumer/data-provider realm. Click on Add realm shown below. -![Alt-text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-realm-creation.png) +![Alt-text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-realm-creation.png) 2. Enter appropriate name in small cases. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-add-realm.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-add-realm.png) 3. Change token expiry time as per requirement of testing. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-token-expiry.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-token-expiry.png) 4. Create client. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-create-realm-button.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-create-realm-button.png) 5. Add appropriate client name. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-create-client.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-create-client.png) 6. Select Access type is confidential , enable service account and Enable Authorization. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-client-auth-setting.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-client-auth-setting.png) 7. Refer below image for client secret -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-dc-client.png) -8. Enable above all setting for admin-cli client and admin cli of master realm aswell. +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-dc-client.png) +8. Enable above all setting for admin-cli client and admin cli of master realm as well. 9. Add below scopes in client scope of admin-cli of master realm. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-master-admin-cli-client-scope.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-master-admin-cli-client-scope.png) 10. Add below scopes in scope of admin-cli of master realm. -![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/keycloak-master-admin-cli-scope.png) +![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-master-admin-cli-scope.png) From 0fafa333713d14293676ee8da36821124e268f38 Mon Sep 17 00:00:00 2001 From: ttpl-rt-119 Date: Thu, 4 Jan 2024 13:19:01 +0530 Subject: [PATCH 35/53] Updated sftp connection document and images. --- docs/README.md | 1171 ++++++++++++----- docs/src/images/dc-on-search-sequence-dia.png | Bin 0 -> 86417 bytes docs/src/images/dc-seq-diagram-2.png | Bin 0 -> 124342 bytes docs/src/images/dp-scheduler-seq-diagram.png | Bin 0 -> 138909 bytes .../src/images/dp-search-sequence-diagram.png | Bin 0 -> 90239 bytes docs/src/images/filezila-folder.png | Bin 0 -> 9452 bytes docs/src/images/filezilla-site-create.png | Bin 0 -> 60201 bytes docs/src/images/filezilla.png | Bin 0 -> 172691 bytes 8 files changed, 819 insertions(+), 352 deletions(-) create mode 100644 docs/src/images/dc-on-search-sequence-dia.png create mode 100644 docs/src/images/dc-seq-diagram-2.png create mode 100644 docs/src/images/dp-scheduler-seq-diagram.png create mode 100644 docs/src/images/dp-search-sequence-diagram.png create mode 100644 docs/src/images/filezila-folder.png create mode 100644 docs/src/images/filezilla-site-create.png create mode 100644 docs/src/images/filezilla.png diff --git a/docs/README.md b/docs/README.md index 7dec121..de7db43 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# Overview of G2p connect - Registry +# 1. Overview of G2p connect - Registry - Governments give money to people for various reasons like subsidies, pensions, scholarships, and emergency help. People can choose how they want to receive this money, like in cash, through their bank, on their phone, or with vouchers. - But, each government department has to set up its own system to check if people are eligible for the money, make sure transactions are real, and actually send the money. They have to talk to different departments to gather all the needed information, and this leads to a lot of duplicate work and problems. @@ -8,7 +8,7 @@ - G2P Connect is an open-source project that helps different government agencies in a country work together to deliver digital payments from start to finish. - The G2P transaction process involves an individual asking for money, providing their ID, and going through some security steps. The system checks if they qualify by looking at different government databases. -## G2p specifications +## 2. G2p specifications - G2P Connect API Specifications is a project that makes it easy for different systems to work together. It sets rules for how they should talk to each other - The main goals of G2P Connect Specifications are to make sure systems can work seamlessly together and follow the rules set by the country. It also aims to be flexible, meaning it can adapt to existing standards and use common methods like OAuth2 for security. - The message structure used in G2P Connect is like a package with a signature and a header. The header includes important information like the version, message ID, and what action is being taken. @@ -18,8 +18,8 @@ - In simple terms, G2P Connect API Specifications is like a rulebook that different systems follow to work together when giving money to people. It's flexible, secure, and makes sure everyone understands each other. -# Overview / List of libraries -### 1. G2pc-core-lib - +# 3. Overview / List of libraries +### 3.1 G2pc-core-lib - - The G2pc-core-lib serves as a central hub for managing shared, reusable elements within the G2p specification - This includes 80% of reusable features, DTOs, and configurations consistent across all data providers and consumers. - The core library encapsulates below essential components - @@ -47,7 +47,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. - Note - All these changes have been done on the basis of G2p specification. -### 2. G2pc-dp-core-lib +### 3.2 G2pc-dp-core-lib - The G2pc-dp-core-lib defines entities and services specific to data providers. - It incorporates methods for constructing responses and managing requests within custom data provider services. - This library encapsulates both an entity and repositories responsible for handling the data stored in tables dedicated to message tracking and transaction information. @@ -55,7 +55,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. - It also includes building cache requests, validating request headers and messages against JSON schemas, and handling transaction tracking in both Redis and a database. - It integrates with other services and provides detailed error handling and logging. Also constructing response DTOs, managing encryption and signatures, sending responses via HTTP, and handling tokens. -### 3. G2pc-dc-core-lib +### 3.3 G2pc-dc-core-lib - The G2pc-dc-core-lib defines entities and services specific to data consumers. - It incorporates methods for constructing requests and managing responses within custom data consumer services. - This library defines functionality for building and sending requests in a secure manner, involving encryption, digital signatures, token management, and caching. @@ -65,7 +65,7 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. ![Alt text]( https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/DC-DP-communication.png "a title") -# Data Provider (DP) Implementation +# 4. Data Provider (DP) Implementation - In DP implementation , as explained in Overview of libraries , dependency of G2pc-dp-core-lib needs to be added. - Data provider is going to act as provider as well as consumer as. - As shown in Figure 2 data provider needs to implement the end point and also make calls to the endpoint of the data consumer. @@ -77,86 +77,101 @@ E.g - HeaderDTO , RequestHeaderDTO and ResponseHeaderDTO , etc. 5. This requestString will get validated as per g2p specification. Please refer to the link mentioned and image below. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/search-endpoint-spec.png "a title") 6. Once requestString gets validated data provider should save that data in redis cache and transaction data in db and send acknowledgement back to data consumer. Refer below sequence diagram for reference - - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/search_sequence_diagram.png) + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-search-sequence-diagram.png) - Implementation explained in below point when it act like data consumer - 1. When it acts like a consumer , it needs to define a scheduler. Scheduler is nothing but a framework that allows you to schedule and execute tasks at specific intervals or times. 2. In this scheduler , dp will check whether there is any data stored in pending status with a particular cache key corresponding to that data provider. 3. If it gets data it will build the response data and the call /on-search endpoint is defined in the data consumer . Please refer to Image 2 for the same. 4. Refer below for understanding of flow from dp to parent libraries. - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-scheduler-sequence-dia.png) + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dp-scheduler-seq-diagram.png) -# How to create a Data Provider ? +# 5. How to create a Data Provider ? 1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/spring_boot_dp_creation.png) 2. Extract the downloaded jar and open it in IDE. 3. Add below dependencies in pom.xml ```` - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-web - - - org.projectlombok - lombok - true - -+ - g2pc.dp.core.lib - g2pc-dp-core-library - 0.0.1-SNAPSHOT - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.15.0 - - - org.postgresql - postgresql - 42.5.4 - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springdoc - springdoc-openapi-ui - 1.6.15 - - - org.springframework.security - spring-security-web - 6.1.2 - - - com.auth0 - java-jwt - 4.4.0 - - - jakarta.validation - jakarta.validation-api - 3.0.2 - - - org.springframework.security - spring-security-config - 6.1.2 - - - org.springframework.boot - spring-boot-starter-validation - + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + true + + + g2pc.dp.core.lib + g2pc-dp-core-library + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + org.postgresql + postgresql + 42.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springdoc + springdoc-openapi-ui + 1.6.15 + + + org.springframework.security + spring-security-web + 6.1.2 + + + com.auth0 + java-jwt + 4.4.0 + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.springframework.security + spring-security-config + 6.1.2 + + + org.springframework.boot + spring-boot-starter-validation + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + jstl + 1.2 + + + org.springframework.boot + spring-boot-devtools + true + ```` 4. Create package structure shown below. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/dp-package-strcuture.png) @@ -204,12 +219,7 @@ public class RegistryController { } ```` -9. Add below controller class for dashboard purpose. -```` -@Controller -public class DpDashboardController { -```` -10. In the service package , create the respective DP ResponseBuilderService.java interface. Refer below example. +9. In the service package , create the respective DP ResponseBuilderService.java interface. Refer below example. ```` public interface FarmerResponseBuilderService { RegRecordFarmerDTO getRegRecordFarmerDTO(FarmerInfoEntity farmerInfoEntity); @@ -219,8 +229,7 @@ public interface FarmerResponseBuilderService { ```` public interface FarmerValidationService { void validateRequestDTO (RequestDTO requestDTO) throws Exception; - void validateQueryDto (QueryDTO queryFarmerDTO) throws G2pcValidationException, JsonProcessingException; - RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; + RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO ) throws Exception; } ```` @@ -238,7 +247,7 @@ public class FarmerResponseBuilderServiceImpl implements FarmerResponseBuilderSe public class FarmerValidationServiceImpl implements FarmerValidationService { } ```` -12. Create DpCommonUtils for handling token. +12. Create DpCommonUtils for handling token and sftp request. ```` import com.fasterxml.jackson.core.JsonProcessingException; import g2pc.core.lib.enums.ExceptionsENUM; @@ -298,15 +307,27 @@ public class DpCommonUtils { @Autowired G2pTokenService g2pTokenService; -```` -14. Create handleToken() method. -```` - public void handleToken() throws G2pHttpException, JsonProcessingException { + @Value("${sftp.dc.host}") + private String sftpDcHost; + + @Value("${sftp.dc.port}") + private int sftpDcPort; + + @Value("${sftp.dc.user}") + private String sftpDcUser; + + @Value("${sftp.dc.password}") + private String sftpDcPassword; + + @Value("${sftp.dc.remote.outbound_directory}") + private String sftpDcRemoteOutboundDirectory; ```` -15. Add below code to introspect the token whether it is from valid keycloak dp or not in handleToken() method. +14. Create handleToken() method to introspect the token whether it is from valid keycloak dp or not in handleToken() method and validateToken using g2pc-core predefined methods. ```` - log.info("Is encrypted ? -> " + isEncrypt); + + public void handleToken() throws G2pHttpException, JsonProcessingException { + log.info("Is encrypted ? -> " + isEncrypt); log.info("Is signed ? -> " + isSign); String token = BearerTokenUtil.getBearerTokenHeader(); String introspectUrl = keycloakURL + "/introspect"; @@ -317,18 +338,30 @@ public class DpCommonUtils { if (introspectResponse.getStatusCode().value() == 401) { throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); } -```` -16. Add below code in handleToken() method for validateToken using g2pc-core predefined methods. -```` - if (!g2pTokenService.validateToken(masterUrl, getClientUrl, + + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, adminUsername, adminPassword)) { - //TODO:check this -> done + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); } + +```` +15. Add getSftpConfigForDp() in DpCommonUtils to fetch sftp configurations. +```` + public SftpServerConfigDTO getSftpConfigForDp() { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setHost(sftpDcHost); + sftpServerConfigDTO.setPort(sftpDcPort); + sftpServerConfigDTO.setUser(sftpDcUser); + sftpServerConfigDTO.setPassword(sftpDcPassword); + sftpServerConfigDTO.setAllowUnknownKeys(true); + sftpServerConfigDTO.setStrictHostKeyChecking("no"); + sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); + return sftpServerConfigDTO; + } ```` -17. -13. Add below autowired dependencies in the RegistryController class. +17. Add below autowired dependencies in the RegistryController class. ```` @Autowired private RequestHandlerService requestHandlerService; @@ -342,7 +375,7 @@ public class DpCommonUtils { @Autowired private MsgTrackerRepository msgTrackerRepository; ```` -13. Add below application.yml and update as per below instructions. +18. Add below application.yml and update as per below instructions. ```` spring: mvc: @@ -355,9 +388,10 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: not_set - username: not_set - password: not_set + # Add db name, schema name, username and password for db connection as per your postgres/mysql connection. + url: jdbc:postgresql://{Domain-id}}:{port}/{Database name}?currentSchema={Schema name} + username: {username of database connection} + password: {password of database connection} hikari: data-source-properties: @@ -393,90 +427,97 @@ spring: exclude: static/**,public/** server: - port: not_set + port: {add port as per requirement} error: include-message: always +# This is redis connection configuration spring.data.redis: repositories.enabled: false - host: not_set - password: not_set - port: not_set + host: {host domain of redis connection} + password: {password of redis port} + port: {port of redis connection} +# These are dc side url. Add respective host name e.g. - localhost and port e.g. - 8080 client: api_urls: - client_search_api: not_set - client_status_api: not_set + client_search_api: http://{host}:{port}/private/api/v1/registry/on-search + client_status_api: http://{host}:{port}/private/api/v1/registry/on-status +# Below are Keycloak configuration. keycloak: + # These configurations are given by dc that is the reason name is from_dc. from_dc: - url: not_set - clientId: not_set - clientSecret: not_set + # url is for creating token , add domain name e.g. - http://127.0.0.1:8081/ , it means 8081 is port on which keycloak is running. + url: "https://{domain of dc instance}/auth/realms/data-consumer/protocol/openid-connect/token" + clientId: {client name given to client created in dc instance} + clientSecret: {client secret of client from dc keycloak instance. Refer 8.7 for same} dp: - url: not_set - username: not_set - password: not_set + # These are dp keycloak instance url , admin name and password which is given while creating instance. + url: https://{domain of dp instance}/auth + username: {username of dp instance} + password: {password of dp instance} master: - url: not_set - getClientUrl: not_set - clientId: not_set - clientSecret: not_set + # master token url for particular dp keycloak, add domain name http://127.0.0.1:8081/ + url: https://{domain}/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://{domain}/auth/admin/realms/{client id of dp}/clients + clientId: {client name given to admin client created in dp instance} + clientSecret: {client secret of admin client from dp keycloak instance. Refer 8.7 for same} client: - url: not_set - realm: not_set - clientId: not_set - clientSecret: not_set - realmClientId: not_set - realmClientSecret: not_set + # dp client token url. Add domain name and realm-id + url: https://{domain}/auth/realms/{realm-id}/protocol/openid-connect/token + realm: {realm id} + clientId: {dp client id} + clientSecret: {client secret of client from dp keycloak instance. Refer 8.7 for same} +# Below configuration is for cryptography setting. crypto: + # to_dc means these configurations used for on-search communication. to_dc: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + # flag of encryption (use only small case) + support_encryption: true + # flag of signature (use only small case) + support_signature: true + password: {password of on-search .p12 file} + key_path: {keypath of on-search .p12 file} + id: {dp id which will be common between dc and dp} from_dc: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set + # flag of encryption (use only small case) + support_encryption: true + # flag of signature (use only small case) + support_signature: true + password: {password of search .p12 file which will be given from dc} + key_path: {keypath of search .p12 file which will be given from dc} dashboard: - dp_dashboard_url: not_set -```` -14. Add below attributes as per your requirement - - 1. Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. - 2. client.api_urls.client_search_api -> change port as respective on-search api. - 3. client.api_urls.client_status_api -> change port as respective on-status api. - 3. keycloak.from-dc.url -> create realm for data consumer and replace name with keycloak data-consumer realm name. - 4. keycloak.from-dc.client-id -> respective client id of created realm. - 5. keycloak.from-dc.client.secret -> respective client secret of created realm. - 6. keycloak.dp.url -> keycloak dp url - 7. keycloak.dp.username -> dp keycloak admin username - 8. keycloak.dp.password -> dp keycloak admin password - 9. keycloak.dp.master.url -> master token url for particular dp keycloak , change host name in case of change - 10. keycloak.dp.master.getClientUrl -> client details api , replace host and realm name in case. - 11. keycloak.dp.master.clientId -> client id of admin client of master realm - 12. keycloak.dp.master.clientSecret -> client secret of admin client of master realm - 13. keycloak.dp.client.url -> realm token url , replace realm id - 14. keycloak.dp.realm -> realm name of dp - 15. keycloak.dp.clientId -> client Id of client created in dp realm - 16. keycloak.dp.clientSecret -> client secret of client created in dp realm - 17. crypto.to_dc.support_encryption -> flag of encryption when scheduler will call /on-search api - 17. crypto.to_dc.support_signature -> flag of signature when scheduler will call /on-search api - 18. crypto.to_dc.password -> password of on_search.p12 password - 19. crypto.to_dc.key.path -> path of on_search.p12 file - 20. crypto.to_dc.id -> flag of Farmer id for data consumer. - 21. crypto.from_dc.support_encryption ->flag of encryption when the data consumer will call /search to validate the configuration. - 22. crypto.from_dc.support_signature -> flag of signature when the data consumer will call /search to validate the configuration. - 23. crypto.from_dc.password -> password of search.p12 password - 24. crypto.from_dc.key.path -> path of search.p12 file - 25. Dashboard.dp_dashboard_url -> + dp_dashboard_url: "http://{domain}:{port}/d-solo/e62ae08b-a6e1-4095-af79-c36f02b8fae2/dp1-dashboard?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + +# sftp connection configurations. +sftp: + listener: + # hostname of sftp connection of dp e.g. localhost + host: {host name} + port: {port of sftp connection} + user: {username of sftp connection} + password: {password of sftp connection} + remote: + # path of sftp client for inbound e.g. /inbound + inbound_directory: {path mentioned in sftp client for inbound} + outbound_directory: {path mentioned in sftp client for outbound} + local: + inbound_directory: {path created in local machine for inbound} + outbound_directory: {path created in local machine for inbound} - -16. Define below endpoint in RegistryController. + dc: + # hostname of sftp connection of dc e.g. localhost + host: {host name} + port: {port of sftp connection} + user: {username of sftp connection} + password: {password of sftp connection} + remote: + outbound_directory: {path mentioned in sftp client for outbound} +```` +20. Define below endpoint in RegistryController. ```` @Operation(summary = "Receive search request") @ApiResponses(value = { @@ -485,12 +526,10 @@ dashboard: @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/search") -public AcknowledgementDTO registerCandidateInformation(@RequestBody String requestString) throws Exception { +public AcknowledgementDTO handleRequest(@RequestBody String requestString) throws Exception { ```` - - -18. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO. +21. Add below code in the same method to add subtype in objectMapper to convert String in requestDTO. ```` ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -501,19 +540,19 @@ RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). readValue(requestString); RequestMessageDTO messageDTO = null; ```` -19. Add below code snippet to validate signature and encryption. +22. Add below code snippet to validate signature and encryption. ```` Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); requestDTO.setMessage(messageDTO); ```` -20. Add below code snippet to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. +23. Add below code snippet to validate requestDTO as per g2p specifications and build cache request for Request string. In this buildCacheRequest it has already been defined in parent libraries , just need to call. ```` String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); try { farmerValidationService.validateRequestDTO(requestDTO); - return requestHandlerService.buildCacheRequest( - objectMapper.writeValueAsString(requestDTO), cacheKey); + return requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_HTTPS); } catch (G2pcValidationException e) { throw new G2pcValidationException(e.getG2PcErrorList()); } @@ -525,7 +564,7 @@ catch (Exception e){ } ```` -21. Add below 2 methods Custom Exception handling using spring boot annotations in RegistryController. +24. Add below 2 methods Custom Exception handling using spring boot annotations in RegistryController. ```` @ExceptionHandler(value = G2pcValidationException.class) @@ -546,16 +585,114 @@ public ErrorResponse handleG2pHttpStatusException( } ```` -22. Create below endpoint for clearing the db. +25. Create below endpoint for clearing the db. ```` @GetMapping("/public/api/v1/registry/clear-db") public void clearDb() throws G2pHttpException, IOException { - //dpCommonUtils.handleToken(); msgTrackerRepository.deleteAll(); log.info("DP-1 DB cleared"); } ```` -22. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. +26. Create below class DcSftpListener for handing sftp request. +```` +package g2pc.ref.farmer.regsvc.controller.sftp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.RequestDTO; +import g2pc.core.lib.dto.search.message.request.RequestMessageDTO; +import g2pc.core.lib.exceptions.G2pcValidationException; +import g2pc.dp.core.lib.service.RequestHandlerService; +import g2pc.ref.farmer.regsvc.constants.Constants; +import g2pc.ref.farmer.regsvc.service.FarmerValidationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@Configuration +@Slf4j +public class DcSftpListener { + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpLocalDirectoryInbound; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpLocalDirectoryOutbound; + + @Autowired + private RequestHandlerService requestHandlerService; + + @Autowired + FarmerValidationService farmerValidationService; + + @SuppressWarnings("unchecked") + @ServiceActivator(inputChannel = "sftpInbound") + public void handleMessageInbound(Message message) { + try { + File file = message.getPayload(); + log.info("Received Message from inbound directory: {}", file.getName()); + String requestString = new String(Files.readAllBytes(file.toPath())); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + + RequestDTO requestDTO = objectMapper.readerFor(RequestDTO.class). + readValue(requestString); + RequestMessageDTO messageDTO; + + Map metaData = (Map) requestDTO.getHeader().getMeta().getData(); + + messageDTO = farmerValidationService.signatureValidation(metaData, requestDTO); + requestDTO.setMessage(messageDTO); + String cacheKey = Constants.CACHE_KEY_STRING + messageDTO.getTransactionId(); + try { + farmerValidationService.validateRequestDTO(requestDTO); + requestHandlerService.buildCacheRequest( + objectMapper.writeValueAsString(requestDTO), cacheKey, CoreConstants.SEND_PROTOCOL_SFTP); + } catch (G2pcValidationException e) { + throw new G2pcValidationException(e.getG2PcErrorList()); + } catch (JsonProcessingException e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); + } catch (Exception e) { + log.error("Error: ", e); + } + } + + @ServiceActivator(inputChannel = "sftpOutbound") + public void handleMessageOutbound(Message message) throws IOException { + File file = message.getPayload(); + log.info("Received Message from outbound directory: {}", file.getName()); + Files.deleteIfExists(Path.of(sftpLocalDirectoryOutbound + "/" + file.getName())); + } + + @ServiceActivator(inputChannel = "errorChannel") + public void handleError(Message message) { + Throwable error = (Throwable) message.getPayload(); + log.error("Handling ERROR: {}", error.getMessage()); + } +} + +```` +27. Create Query and Query param dto for data provider requirement in dto.request package. Below are examples. ```` @Getter @Setter @@ -585,7 +722,7 @@ public class QueryParamsFarmerDTO { } ```` -23. Create regRecordDTO for data provider as per on search endpoint requirement in dto.response package shown below. +27. Create regRecordDTO for data provider as per on search endpoint requirement in dto.response package shown below. ```` @Getter @Setter @@ -625,7 +762,7 @@ public class RegRecordFarmerDTO { } ```` -24. To get data provider information create a data-provider info table in db , entity and repository as shown below. +28. To get data provider information create a data-provider info table in db , entity and repository as shown below. ```` @Builder @Data @@ -659,12 +796,12 @@ public interface FarmerInfoRepository extends JpaRepository findBySeasonAndFarmerId(String season, String farmerId); } ```` -25. Define below method in ValidationServiceImpl as it has implemented from ValidationService interface. +29. Define below method in ValidationServiceImpl as it has implemented from ValidationService interface. ```` @Override public RequestMessageDTO signatureValidation(Map metaData, RequestDTO requestDTO) throws Exception { ```` -26. Define below autowired beans and configurations in ValidationServiceImpl. +30. Define below autowired beans and configurations in ValidationServiceImpl. ```` @Autowired RequestHandlerService requestHandlerService; @@ -690,7 +827,13 @@ public RequestMessageDTO signatureValidation(Map metaData, Reque @Value("${crypto.from_dc.key.path}") private String farmer_key_path; ```` -27. Add below code snippet in signatureValidation method. This is the validation for signature and encryption to check whether this is transferred correctly. +31. Add below code snippet in signatureValidation method. This is the validation for signature and encryption to check whether this is transferred correctly. + 1. Check isSign flag , if yes check metadata given from controller is true or false , if not true throw error that configurations are not valid. + 2. Take farmerKeyPath for dp .p12 file. + 3. Check isEncrypt flag , if true check isMsgEncrypt flag from header is true or false , if not true throw error that configurations are not valid. + 4. Recreate signature using header and message. Verify both signature if error from verifySignature throw error signature invalid. + 5. Decrypt message , if successfully decrypted add in requestDto. + 6. Above logic is same in 4 cases of signature and encryption mentioned "Overview / List of libraries". ```` ObjectMapper objectMapper = new ObjectMapper(); RequestMessageDTO messageDTO; @@ -852,7 +995,7 @@ requestHandlerService.validateRequestMessage(messageDTO); @Value("${crypto.to_dc.key.path}") private String farmer_key_path; ```` -32. Define below method in Scheduler. +Define below method in Scheduler. ```` @Scheduled(cron = "0 */1 * ? * *")// runs every 1 min. @Transactional @@ -1014,8 +1157,8 @@ dpCommonUtils.handleToken(); String cacheKey = Constants.STATUS_CACHE_KEY_STRING + statusRequestMessageDTO.getTransactionId(); try { farmerValidationService.validateStatusRequestDTO(statusRequestDTO); - return requestHandlerService.buildCacheStatusRequest( - objectMapper.writeValueAsString(statusRequestDTO), cacheKey); + return requestHandlerService.buildCacheStatusRequest( + objectMapper.writeValueAsString(statusRequestDTO), cacheKey,CoreConstants.SEND_PROTOCOL_HTTPS); } catch (G2pcValidationException e) { throw new G2pcValidationException(e.getG2PcErrorList()); @@ -1193,8 +1336,24 @@ for (String cacheKey: statusCacheKeysList) { } } ```` +49. Create DpDashboardController for creating dashboard endpoints for Grafana. +```` +@Controller +public class DpDashboardController { -# Data Consumer (DC) Implementation + @Value("${dashboard.dp_dashboard_url}") + private String dpDashboardUrl; + + @GetMapping("/dashboard") + public String showDashboardPage(Model model) { + model.addAttribute("dp_dashboard_url", dpDashboardUrl); + return "dashboard"; + } +} +```` +50. + +# 6. Data Consumer (DC) Implementation - In DC implementation , as explained in Overview of libraries , dependency of G2pc-dc-core-lib needs to be added. - Data consumers are going to act as consumers as well as providers. Implementation explained in below point when it act like data consumer - @@ -1202,7 +1361,7 @@ Implementation explained in below point when it act like data consumer - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/patload_postman.png) 2. Using this data consumer will decide which data provider’s endpoint it needs to call and which request it needs to build. 3. Once a request is created /search endpoint it will call and once positive acknowledgement is there it will save pending status in cache for particular transaction id. Refer below for more understanding. - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/payload_sequence_diagram.png) + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dc-seq-diagram-2.png) - Implementation explained in below point when it act like data provider - 1. As shown in Figure 2 data provider needs to implement the end point and also make calls to the endpoint of the data provider. 2. At first Data consumer service needs to write the /on-search end-point. @@ -1212,15 +1371,15 @@ Implementation explained in below point when it act like data consumer - 6. This responseString will get validated as per g2p specification. Please refer to the link mentioned and image below. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/on_search_spec.png) 7. Once responseString gets validated data consumers should update that data in redis cache and send acknowledgement back to the data consumer. Refer below for more understanding. - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/on_search_seqeunce_dia.png) + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/dc-on-search-sequence-dia.png) -# How to create a Data Consumer ? +# 7. How to create a Data Consumer ? 1. Create a spring boot application with the latest spring-boot version , maven and Java 17. And Click on generate to download. ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/spring_boot_dc_creation.png) 2. Extract the downloaded jar and open it in IDE. 3. Add below dependencies in tag in pom.xml ```` - + org.springframework.boot spring-boot-starter @@ -1263,11 +1422,6 @@ Implementation explained in below point when it act like data consumer - json-schema-validator 1.0.82 - - org.apache.commons - commons-csv - 1.10.0 - jakarta.validation jakarta.validation-api @@ -1324,9 +1478,10 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: not_set - username: not_set - password: not_set + # Add db name, schema name, username and password for db connection as per your postgres/mysql connection. + url: jdbc:postgresql://{Domain-id}}:{port}/{Database name}?currentSchema={Schema name} + username: {username of database connection} + password: {password of database connection} hikari: data-source-properties: @@ -1362,115 +1517,99 @@ spring: exclude: static/**,public/** server: - port: 8000 + port: {add port as per requirement} error: include-message: always +# This is redis connection configuration spring.data.redis: repositories.enabled: false - host: localhost - password: 123456789 - port: 6379 + host: {host domain of redis connection} + password: {password of redis port} + port: {port of redis connection} keycloak: + # These configurations are given by dc that is the reason name is from_dp. from_dp: - farmer: - url: not_set - clientId: not_set - clientSecret: not_set - mobile: - url: not_set - clientId: not_set - clientSecret: not_set + # this will be many dp , add url , clientid and client secret for every dp. + {dp-name}: + url: "https://{domain of dc instance}/auth/realms/{realm name of dp}/protocol/openid-connect/token" + clientId: {client id of dp given by dp} + clientSecret: {client secret of dp client} dc: - url: not_set - username: not_set - password: not_set + url: https://{domain of dc instance}/auth + username: {username of dc instance} + password: {password of dc instance} master: - url: not_set - getClientUrl: not_set - clientId: not_set - clientSecret: not_set + # master token url for particular dc keycloak, add domain name http://127.0.0.1:8081/ + url: https://{domain}/auth/realms/master/protocol/openid-connect/token + getClientUrl: https://{domain}/auth/admin/realms/{client id of dp}/clients + clientId: {client name given to admin client created in dc instance} + clientSecret: {client secret of admin client from dc keycloak instance. Refer 8.7 for same} client: - url: not_set - realm: not_set - clientId: not_set - clientSecret: not_set + # dc client token url. Add domain name and realm-id + url: https://{domain}/auth/realms/{realm-id}/protocol/openid-connect/token + realm: {realm id} + clientId: {dc client id} + clientSecret: {client secret of client from dc keycloak instance. Refer 8.7 for same} crypto: - to_dp_farmer: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - to_dp_mobile: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - from_dp_farmer: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set - from_dp_mobile: - support_encryption: not_set - support_signature: not_set - password: not_set - key_path: not_set - id: not_set + to_dp_{dp_name}: + # flag of encryption (use only small case) + support_encryption: true + # flag of signature (use only small case) + support_signature: true + password: {password of search .p12 file of particuler dp} + key_path: {keypath of search .p12 file of particuler dp} + from_dp_{dp_name}: + # flag of encryption (use only small case) + support_encryption: true + # flag of signature (use only small case) + support_signature: true + password: {password of on-search .p12 file} + key_path: {keypath of on-search .p12 file} + id: {dp id which will be common between dc and dp} registry: api_urls: - farmer_search_api: not_set - mobile_search_api: not_set - farmer_status_api: not_set - mobile_status_api: not_set + {dp-name}_search_api: "http://{host}:{port}/private/api/v1/registry/search" + {dp-name}_status_api: "http://{host}:{port}/private/api/v1/registry/txn/status" dashboard: - left_panel_url: not_set - right_panel_url: not_set - bottom_panel_url: not_set - post_endpoint_url: not_set - clear_dc_db_endpoint_url: not_set - clear_dp1_db_endpoint_url: not_set - clear_dp2_db_endpoint_url: not_set -```` - -7. Add below attributes as per your requirement - - 1. Change db name (gtwop) , schema name (farmer) , username and password for db connection as per your postgres/mysql connection. - 2. spring.data.redis.host -> add redis host - 3. spring.data.redis.password -> password of dp redis port - 4. spring.data.redis.port -> port assigned for redis of particular dp - 5. keycloak.from_dp.{dp-name}.url -> url of token creation of particular dp , replace realm name with respective dp. - 6. keycloak.from_dp.{dp-name}.clientId -> client id of particular dp client , check in below image. - 7. keycloak.from_dp.{dp-name}.clientSecret -> client secret of particular dp client , check in below image. - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-dp-client-secret.png) - 8. keycloak.dc.url -> hosting url of dc - 9. keycloak.dc.username -> authentication username of hosting url of dc - 10. keycloak.dc.password -> authentication password of hosting url of dc - 11. keycloak.dc.master.url -> token endpoint url of master realm of data-consumer. - 12. keycloak.dc.master.getClientUrl -> get client by realm id endpoint of particular dc. replace host and realm id for particular data consumer - 13. keycloak.dc.master.clientId -> admin cli client id of master realm of dc - 14. keycloak.dc.master.clientSecret -> admin cli client secret of master realm of dc - 15. keycloak.dc.client.url -> token endpoint url of data-consumer realm of data-consumer. - 16. keycloak.dc.client.realm -> dc realm name - 17. keycloak.dc.client.clientId -> dc client id - 18. keycloak.dc.client.clientSecret -> dc client secret - ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-dc-client.png) - 19. crypto.to_dp_{dp-name}.support_encryption -> encryption flag of particular dp of search endpoint - 20. crypto.to_dp_{dp-name}.support_signature -> signature flag of particular dp of search endpoint - 21. crypto.to_dp_{dp-name}.password -> password of .p12 file particular dp of search endpoint - 22. crypto.to_dp_{dp-name}.key_path -> key path of .p12 file particular dp of search endpoint - 23. crypto.from_dp_{dp-name}.support_encryption -> encryption flag of particular dp of on-search endpoint - 24. crypto.from_dp_{dp-name}.support_signature -> signature flag of particular dp of on-search endpoint - 25. crypto.from_dp_{dp-name}.password -> password of .p12 file particular dp of on-search endpoint - 26. crypto.from_dp_{dp-name}.key_path -> key path of .p12 file particular dp of on-search endpoint - 27. crypto.from_dp_{dp-name}.id -> id flag for dp - 28. registry.api_urls.{dp-name}_search_api -> search end point of dp - 29. dashboard.clear_dc_db_endpoint_url -> clear db endpoint of dc - 30. dashboard.clear_dp_db_endpoint_url -> clear db endpoints of multiple dps + left_panel_url: "https://{domain of dc}/grafana/d-solo/cb26f39f-97f3-43ea-9f42-68d49d9822a3/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + right_panel_url: "https://{domain of dc}/grafana/d-solo/d9f9c625-934b-4a65-995f-c742daad6387/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + bottom_panel_url: "https://{domain of dc}/grafana/d-solo/a25a6c65-fda7-4fdd-80a7-80442aed17e8/bottom-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + post_endpoint_url: "http://localhost:8000/public/api/v1/consumer/search/csv" + clear_dc_db_endpoint_url: "http://{host}:{port of dc}/private/api/v1/registry/clear-db" + clear_dp1_db_endpoint_url: "http://{host}:{port of dp1}/private/api/v1/registry/clear-db" + +# sftp connection configurations. +sftp: + listener: + # hostname of sftp connection of dp e.g. localhost + host: {host name} + port: {port of sftp connection of dc } + user: {username of sftp connection of dc } + password: {password of sftp connection of dc } + remote: + # path of sftp client for inbound e.g. /inbound + inbound_directory: {path mentioned in sftp client for inbound} + outbound_directory: {path mentioned in sftp client for outbound} + local: + inbound_directory: {path created in local machine for inbound} + outbound_directory: {path created in local machine for inbound} + + # add dp as per requirement and add all these fields + dp1: + # hostname of sftp connection of dp e.g. localhost + host: {host name} + port: {port of sftp connection of dp } + user: {username of sftp connection of dp } + password: {password of sftp connection of dp } + remote: + # path of sftp client for inbound e.g. /inbound + inbound_directory: {path mentioned in sftp client for inbound} +```` 8. Add RegistryConfig.java class in config package ```` @Service @@ -1579,15 +1718,13 @@ public class DcController {} @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/public/api/v1/consumer/search/payload") public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { -```` -14. Add below code snippet to request from payload -```` log.info("Payload received from postman"); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); if (ObjectUtils.isNotEmpty(payloadMap)) { acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); } return acknowledgementDTO; + } ```` 15. To run above entrypoint refer below curl. ```` @@ -1615,7 +1752,12 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ log.info("Payload received from csv file"); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); if (ObjectUtils.isNotEmpty(payloadFile)) { - acknowledgementDTO = dcRequestBuilderService.generatePayloadFromCsv(payloadFile); + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), "payload.csv"); + Files.createFile(tempFile); + payloadFile.transferTo(tempFile.toFile()); + acknowledgementDTO = dcRequestBuilderService.generateRequest( + requestBuilderService.generatePayloadFromCsv(tempFile.toFile()), CoreConstants.SEND_PROTOCOL_HTTPS); + Files.delete(tempFile); } return acknowledgementDTO; } @@ -1625,7 +1767,34 @@ curl --location 'http://localhost:8000/public/api/v1/consumer/search/payload' \ curl --location 'localhost:8000/private/api/v1/consumer/search/csv' \ --form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' ```` -18. Add below exception handling in the DC controller. +18. Create below endpoint for clearing db in dc. +```` + @GetMapping("/public/api/v1/registry/clear-db") + public void clearDb() throws G2pHttpException, IOException { + commonUtils.handleToken(); + responseDataRepository.deleteAll(); + log.info("DC DB cleared"); + + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + try { + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + String jwtToken = requestBuilderService.getValidatedToken(registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString()); + log.info("jwtToken: {}", jwtToken); + log.info("url: {}", registrySpecificConfigMap.get(CoreConstants.DP_CLEAR_DB_URL).toString()); + HttpResponse response = g2pUnirestHelper.g2pGet(registrySpecificConfigMap.get(CoreConstants.DP_CLEAR_DB_URL).toString()) + .header("Content-Type", "application/json") + .header("Authorization", jwtToken) + .asString(); + log.info("DP " + registrySpecificConfigMap.get(CoreConstants.REG_TYPE) + " DB cleared with response " + response.getStatus()); + } catch (Exception e) { + log.error("Exception in clearDb: ", e); + } + } + } +```` +19. Add below exception handling in the DC controller. ```` @ExceptionHandler(value = G2pcValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @@ -1676,9 +1845,9 @@ public class RegRecordFarmerDTO { ```` public interface DcRequestBuilderService { - AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception; + AcknowledgementDTO generateRequest(List> payloadMapList, String protocol) throws Exception; - AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception; + AcknowledgementDTO generateStatusRequest(String transactionID, String transactionType,String protocol) throws Exception; } ```` 21. Create DcRequestBuilderServiceImpl.java class and implement it from DcRequestBuilderService interface. @@ -1702,12 +1871,29 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { private ResourceLoader resourceLoader; ```` 23. Override below method generateRequest() from interface. -```` - public AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception { + 1. Fetch queryMapList using payload list provided and registryconfig. + 2. In for loop iterate configEntryMap fetching from registryConfig. + 3. Filter out queryMapFilteredList using registryType. + 4. Build searchCriteriaDto by calling dc-core method by passing queryParams and registrySpecificationConfig. + 5. Generate transaction id. + 6. Build searchString using method buildRequest from dc-core. + 7. Check if request is http or sftp. + 8. In http call sendRequest method by passing all arguments. + 9. Put returned g2pcError in g2pcErrorMap. + 10. If request is sftp , create SftpServerConfigDTO. + 11. Invoke sendRequestSftp method and put returned g2pcError in g2pcErrorMap. + 12. Save initial transaction and requestString in redis. + 13. Save data in db. +```` + @SuppressWarnings("unchecked") + @Override + public AcknowledgementDTO generateRequest(List> payloadMapList, String protocol) throws Exception { Map g2pcErrorMap = new HashMap<>(); List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + List> queryMapFilteredList = queryMapList.stream() .map(map -> map.entrySet().stream() .filter(entry -> entry.getKey().equals(configEntryMap.getKey())) @@ -1720,29 +1906,53 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { searchCriteriaDTOList.add(searchCriteriaDTO); } String transactionId = CommonUtils.generateUniqueId("T"); - String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId); + String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId, ActionsENUM.SEARCH); try { - Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); - String encryptedSalt = ""; - InputStream fis = resource.getInputStream(); - G2pcError g2pcError = requestBuilderService.sendRequest(requestString, - registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), - Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), - Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), - fis, encryptedSalt, - registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(),CoreConstants.DP_SEARCH_URL); - g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); - log.info("DP_SEARCH_URL = {}", registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); - - txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); + if (protocol.equals(CoreConstants.SEND_PROTOCOL_HTTPS)) { + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = requestBuilderService.sendRequest(requestString, + registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), CoreConstants.SEARCH_TXN_TYPE); + g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); + log.info("DP_SEARCH_URL = {}", registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); + } else if (protocol.equals(CoreConstants.SEND_PROTOCOL_SFTP)) { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setUser(registrySpecificConfigMap.get(SftpConstants.SFTP_USER).toString()); + sftpServerConfigDTO.setHost(registrySpecificConfigMap.get(SftpConstants.SFTP_HOST).toString()); + sftpServerConfigDTO.setPort(Integer.parseInt(registrySpecificConfigMap.get(SftpConstants.SFTP_PORT).toString())); + sftpServerConfigDTO.setPassword(registrySpecificConfigMap.get(SftpConstants.SFTP_PASSWORD).toString()); + sftpServerConfigDTO.setStrictHostKeyChecking(registrySpecificConfigMap.get(SftpConstants.SFTP_SESSION_CONFIG).toString()); + sftpServerConfigDTO.setRemoteInboundDirectory(registrySpecificConfigMap.get(SftpConstants.SFTP_REMOTE_INBOUND_DIRECTORY).toString()); + + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = requestBuilderService.sendRequestSftp(requestString, + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString(), CoreConstants.SEARCH_TXN_TYPE, + sftpServerConfigDTO); + log.info("g2pcError = {}", g2pcError); + g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); + + log.info("Uploaded to inbound of : {}", registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue(),protocol); txnTrackerService.saveRequestTransaction(requestString, - registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); - txnTrackerService.saveRequestInDB(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId,protocol); + txnTrackerService.saveRequestInDB(requestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), protocol); } catch (Exception e) { - log.error("Exception in generateRequest : ", e); + log.error(Constants.GENERATE_REQUEST_ERROR_MESSAGE, e); } } AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); @@ -1751,41 +1961,114 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { return acknowledgementDTO; } ```` -24. To generate request from csv file overwrite below method and one private method to iterate csv file. Used to convert csv multipart file to list of maps +24. Create DcSftpListener.java to handle inbound and outbound messages in package g2pc.ref.dc.client.controller.sftp + 1. Values added in this class are inbound and outbound file path mentioned in application.yml. + 2. handleMessageInbound() method written to handle inbound message. ```` - @Override - public AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { - Reader reader = new BufferedReader(new InputStreamReader(payloadFile.getInputStream())); - CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); - List> payloadMapList = getPayloadMapList(csvParser); - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - if (ObjectUtils.isNotEmpty(payloadMapList)) { - acknowledgementDTO = generateRequest(payloadMapList); +package g2pc.ref.dc.client.controller.sftp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.constants.CoreConstants; +import g2pc.core.lib.dto.common.AcknowledgementDTO; +import g2pc.core.lib.dto.common.header.HeaderDTO; +import g2pc.core.lib.dto.common.header.RequestHeaderDTO; +import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; +import g2pc.core.lib.dto.search.message.request.ResponseMessageDTO; +import g2pc.core.lib.dto.search.message.response.ResponseDTO; +import g2pc.dc.core.lib.service.RequestBuilderService; +import g2pc.ref.dc.client.service.DcRequestBuilderService; +import g2pc.ref.dc.client.service.DcResponseHandlerService; +import g2pc.ref.dc.client.service.DcValidationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.messaging.Message; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@Configuration +@Slf4j +public class DcSftpListener { + + @Value("${sftp.listener.local.inbound_directory}") + private String sftpLocalDirectoryInbound; + + @Value("${sftp.listener.local.outbound_directory}") + private String sftpLocalDirectoryOutbound; + + @Autowired + private DcRequestBuilderService dcRequestBuilderService; + + @Autowired + private RequestBuilderService requestBuilderService; + + @Autowired + private DcResponseHandlerService dcResponseHandlerService; + + @Autowired + DcValidationService dcValidationService; + + @ServiceActivator(inputChannel = "sftpInbound") + public void handleMessageInbound(Message message) { + try { + File file = message.getPayload(); + log.info("Received Message from inbound directory: {}", file.getName()); + if (ObjectUtils.isNotEmpty(file)) { + AcknowledgementDTO acknowledgementDTO = dcRequestBuilderService.generateRequest( + requestBuilderService.generatePayloadFromCsv(file), CoreConstants.SEND_PROTOCOL_SFTP); + log.info("AcknowledgementDTO: {}", acknowledgementDTO); + } + Files.deleteIfExists(Path.of(sftpLocalDirectoryInbound + "/" + file.getName())); + } catch (Exception e) { + log.error("Error: ", e); } - return acknowledgementDTO; } -```` -Below method will convert csv file to list of map -* map -> key = column title/header , value = each row value -```` - private static List> getPayloadMapList(CSVParser csvParser) { - List csvRecordList = csvParser.getRecords(); - CSVRecord headerRecord = csvRecordList.get(0); - List headerList = new ArrayList<>(); - for (int i = 0; i < headerRecord.size(); i++) { - headerList.add(headerRecord.get(i)); - } - List> payloadMapList = new ArrayList<>(); - for (int i = 1; i < csvRecordList.size(); i++) { - CSVRecord csvRecord = csvRecordList.get(i); - Map payloadMap = new HashMap<>(); - for (int j = 0; j < headerRecord.size(); j++) { - payloadMap.put(headerList.get(j), csvRecord.get(j)); + + @SuppressWarnings("unchecked") + @ServiceActivator(inputChannel = "sftpOutbound") + public void handleMessageOutbound(Message message) throws Exception { + File file = message.getPayload(); + log.info("Received Message from outbound directory: {}", file.getName()); + String responseString = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerSubtypes(RequestHeaderDTO.class, + ResponseHeaderDTO.class, HeaderDTO.class); + ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). + readValue(responseString); + ResponseMessageDTO messageDTO; + Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); + messageDTO = dcValidationService.signatureValidation(metaData, responseDTO); + responseDTO.setMessage(messageDTO); + try { + dcValidationService.validateResponseDto(responseDTO); + if (ObjectUtils.isNotEmpty(responseDTO)) { + dcResponseHandlerService.getResponse(responseDTO); } - payloadMapList.add(payloadMap); + log.info("on search response handled successfully"); + } catch (JsonProcessingException | IllegalArgumentException e) { + log.info("on search response handled error : ", e); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } - return payloadMapList; + Files.deleteIfExists(Path.of(sftpLocalDirectoryOutbound + "/" + file.getName())); } + + @ServiceActivator(inputChannel = "errorChannel") + public void handleError(Message message) { + Throwable error = (Throwable) message.getPayload(); + log.error("Handling ERROR: {}", error.getMessage()); + } +} + ```` 25. Create on-search endpoint , refer below snippet , there are methods called in this methods refer code after this point. ```` @@ -1876,7 +2159,7 @@ public class DcCommonUtils { ```` -27. Add below method in DcCommonUtils.java to handle and validate token. +27. Add below methods in DcCommonUtils.java to handle and validate token and to get sftp configuration for dc. ```` public void handleToken() throws G2pHttpException, JsonProcessingException { String token = BearerTokenUtil.getBearerTokenHeader(); @@ -1890,6 +2173,21 @@ public class DcCommonUtils { throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); } } + + public SftpServerConfigDTO getSftpConfigForDc() { + SftpServerConfigDTO sftpServerConfigDTO = new SftpServerConfigDTO(); + sftpServerConfigDTO.setHost(sftpDcHost); + sftpServerConfigDTO.setPort(sftpDcPort); + sftpServerConfigDTO.setUser(sftpDcUser); + sftpServerConfigDTO.setPassword(sftpDcPassword); + sftpServerConfigDTO.setAllowUnknownKeys(true); + sftpServerConfigDTO.setStrictHostKeyChecking("no"); + sftpServerConfigDTO.setRemoteInboundDirectory(sftpDcRemoteInboundDirectory); + sftpServerConfigDTO.setRemoteOutboundDirectory(sftpDcRemoteOutboundDirectory); + sftpServerConfigDTO.setLocalInboundDirectory(sftpDcLocalInboundDirectory); + sftpServerConfigDTO.setLocalOutboundDirectory(sftpDcLocalOutboundDirectory); + return sftpServerConfigDTO; + } ```` 28. Create DcValidationService interface with 3 methods , validateRegRecords() this method is dc specific , to validate query params. ```` @@ -1940,6 +2238,14 @@ public class DcValidationServiceImpl implements DcValidationService { private String farmerID; ```` 31. Override below signatureValidation() method and add required if conditions for required dps. + 1. Check get string from CoreConstants.DP_ID for respective farmer ids , and add respective attributes. + 2. Check isSign flag , if yes check metadata given from controller is true or false , if not true throw error that configurations are not valid. + 2. Take farmerKeyPath for dp .p12 file. + 3. Check isEncrypt flag , if true check isMsgEncrypt flag from header is true or false , if not true throw error that configurations are not valid. + 4. Recreate signature using header and message. Verify both signature if error from verifySignature throw error signature invalid. + 5. Decrypt message , if successfully decrypted add in requestDto. + 6. Above logic is same in 4 cases of signature and encryption mentioned "Overview / List of libraries". + ```` @Override public ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception { @@ -2054,7 +2360,7 @@ public class DcValidationServiceImpl implements DcValidationService { return messageDTO; } ```` -32. Override validateResponse() and validateRegRecord() method , in this for another dp validateRegRecord Method will be different as per query params. +32. Override validateResponse() to validate ResponseDTO ```` @Override public void validateResponseDto(ResponseDTO responseDTO) throws Exception { @@ -2336,7 +2642,7 @@ curl --location \ return messageDTO; } ```` -41. Override validateStatusResponseDTO() method in DcValidationServiveImpl. +41. Override validateStatusResponseDTO() method in DcValidationServiceImpl. ```` @Override public void validateStatusResponseDTO(StatusResponseDTO statusResponseDTO) throws IOException, G2pcValidationException { @@ -2351,8 +2657,124 @@ curl --location \ responseHandlerService.validateStatusResponseMessage(statusResponseMessageDTO); } ```` -# Keycloak configuration -## Steps for DC and DP - +42. Add below method in DcResponseHandlerService interface. +```` +AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws JsonProcessingException; + +```` +43. Override above method in DcResponseHandlerServiceImpl class. +```` +@Override + public AcknowledgementDTO getStatusResponse(StatusResponseDTO statusResponseDTO) throws JsonProcessingException { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + ObjectMapper objectMapper = new ObjectMapper(); + + txnTrackerService.updateStatusTransactionDbAndCache(statusResponseDTO); + log.info("on-status response received from registry : {}", objectMapper.writeValueAsString(statusResponseDTO)); + acknowledgementDTO.setMessage(Constants.ON_SEARCH_RESPONSE_RECEIVED.toString()); + acknowledgementDTO.setStatus(Constants.COMPLETED); + return acknowledgementDTO; + } +```` +43. Create DcDashboardController.java for creating endpoints for dashboard of Grafana. +```` +@Controller +@Slf4j +public class DcDashboardController { +} +```` +43. Add below configuration values which added in application.yml +```` + @Value("${dashboard.left_panel_url}") + private String leftPanelUrl; + + @Value("${dashboard.right_panel_url}") + private String rightPanelUrl; + + @Value("${dashboard.bottom_panel_url}") + private String bottomPanelUrl; + + @Value("${dashboard.post_endpoint_url}") + private String postEndpointUrl; + + @Value("${dashboard.clear_dc_db_endpoint_url}") + private String clearDcDbEndpointUrl; + + @Value("${keycloak.dc.client.url}") + private String dcKeyCloakUrl; + + @Value("${keycloak.dc.client.clientId}") + private String dcClientId; + + @Value("${keycloak.dc.client.clientSecret}") + private String dcClientSecret; + + @Autowired + private RequestBuilderService requestBuilderService; +```` +44. Create below endpoint method +```` +@GetMapping("/dashboard") + public String showDashboardPage(Model model) throws IOException, ParseException { + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + + model.addAttribute("left_panel_url", leftPanelUrl); + model.addAttribute("right_panel_url", rightPanelUrl); + model.addAttribute("bottom_panel_url", bottomPanelUrl); + model.addAttribute("post_endpoint_url", postEndpointUrl); + model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); + model.addAttribute("jwtToken", jwtToken); + return "dashboard"; + } +```` + +45. Create method createStatusRequestSftp() for creating endpoint to listen to CSV file payload to handle using SFTP. +```` + @Operation(summary = "Listen to CSV file payload to handle using SFTP") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = Constants.SEARCH_REQUEST_RECEIVED), + @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) + @PostMapping(value = "/public/api/v1/consumer/search/sftp/csv") + public AcknowledgementDTO createStatusRequestSftp(@RequestParam("file") MultipartFile file) { + AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + try { + if (!Objects.equals(file.getContentType(), "text/csv")) { + acknowledgementDTO.setStatus(HeaderStatusENUM.RJCT.toValue()); + acknowledgementDTO.setMessage("Invalid file type"); + return acknowledgementDTO; + } + String originalFilename = UUID.randomUUID() + ".csv"; + Path tempFile = Paths.get(System.getProperty("java.io.tmpdir"), originalFilename); + Files.createFile(tempFile); + file.transferTo(tempFile.toFile()); + SftpServerConfigDTO sftpServerConfigDTO = commonUtils.getSftpServerConfigDTO(); + Boolean status = sftpHandlerService.uploadFileToSftp(sftpServerConfigDTO, tempFile.toString(), + sftpServerConfigDTO.getRemoteInboundDirectory()); + Files.delete(tempFile); + if (Boolean.FALSE.equals(status)) { + acknowledgementDTO.setStatus(HeaderStatusENUM.RJCT.toValue()); + acknowledgementDTO.setMessage(Constants.UPLOAD_ERROR); + return acknowledgementDTO; + } + acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + acknowledgementDTO.setMessage("File uploaded successfully"); + } catch (IOException e) { + log.error(Constants.UPLOAD_ERROR, e); + acknowledgementDTO.setStatus(HeaderStatusENUM.RJCT.toValue()); + acknowledgementDTO.setMessage(Constants.UPLOAD_ERROR); + } + return acknowledgementDTO; + } +```` +47. Import below curl to run the endpoint and add payload.csv +```` +curl --location 'localhost:8000/public/api/v1/consumer/search/sftp/csv' \ +--form 'file=@"/home/ttpl-rt-119/Downloads/payload.csv"' +```` +48. + +# 8. Keycloak configuration +### Steps for DC and DP - 1. Create data-consumer/data-provider realm. Click on Add realm shown below. ![Alt-text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-realm-creation.png) 2. Enter appropriate name in small cases. @@ -2374,6 +2796,51 @@ curl --location \ ![Alt text](https://github.com/G2P-Connect/g2pc-registry/blob/alpha-1.0/docs/src/images/keycloak-master-admin-cli-scope.png) +# SFTP configuration - +### Below are the steps to configure SFTP - +1. Run the below docker-compose.yml using below command. +```` +sudo docker compose up +```` +docker-compose.yml +```` +version: '3' +services: + dc1_sftp: + image: atmoz/sftp + container_name: dc1_sftp_server + restart: unless-stopped + ports: + - "2224:22" + command: cdpi:1234:::inbound,outbound + + dp1_sftp: + image: atmoz/sftp + container_name: dp1_sftp_server + restart: unless-stopped + ports: + - "2225:22" + command: cdpi:1234:::inbound,outbound + + dp2_sftp: + image: atmoz/sftp + container_name: dp2_sftp_server + restart: unless-stopped + ports: + - "2226:22" + command: cdpi:1234:::inbound,outbound +```` +2. Install any FTP client viewer. Refer below image. Add sites using below steps - + 1. Open client and select tab shown in below image. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/filezilla.png) + 2. Create site , select SFTP , add local host and port. Add username and password added in docker-compose file. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/filezilla-site-create.png) + 3. Create folder structure shown below. + ![Alt text](/home/ttpl-rt-119/Documents/CDPI/G2P-Code/Git_hub/g2pc-registry/docs/src/images/filezila-folder.png) + 4. +3. + + diff --git a/docs/src/images/dc-on-search-sequence-dia.png b/docs/src/images/dc-on-search-sequence-dia.png new file mode 100644 index 0000000000000000000000000000000000000000..6de88b9eea88a6945fce606f442d020d2287fe57 GIT binary patch literal 86417 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�IgAHWO7Kv#&Tp>6zNp@ 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�D1> zL$J=w>K-Jj+(U&03>J{J3MXLkHZ${;<4dlBI*Shn+iv&c>R4aC6Tz~Sl(JX_~)&q^$JpMa;j3y#z3zsr&K{(r~d zl68vUr9yorUNEQ^AOM*riBzY2@9Dn79VmuSO2Rlqqlc4(aqNYiJ{^I^mj| zfxKo?I$eudgdoeqhm{E51eUHXobWZ&Q{Z9hVEv+7nvnQ0FPz0Zoq3-@6YCCB=tIqG%Ixmd{jzqxe~U(}6YIwGcg zC*Y6|G=KojHH^9xI=pgb^z4#w1aeRZ#3gpSz>@yB0U~@a*Exm{%`P@BhHd5XY^0Z% zeBf=<35)Zp%p$n1PXx4SSv$Re?@=faxGGz+a)X?*0d#xc+nvI9eI-oX@=*WKb&_Kd zYx&Kc2Rng8<}}q1MGX8&|M_25k|bOkP$8Jq@H6doD|%3$84X(?va7*};0~%|wCYT)h&5nm^AYisjc=(NBNqyK=@x;Le+ynBq)M_|s(n>Mf_ z+-4>0MdgI;i-6f)0}jk6@dfc4W2z!PXS{qAzPhnYZ58Cy>}#yRla}F2Y`>eO90o#DyWXqYx@qpKIJd^aTK^*vp;>j^B`(4DW&LcQ0$%+(P!vBtHct@2O@cROCP}%=3t? zWjF~kU-2Jf%xPghOiF(D(uX?oR<&bNmxlkcMaElHYX#SA5aD7a2wd%a;uOGq<9nvM$+QQDRPA3S%jWre}>jG3I(S0I>4 zB$$G;QCh5>XYvY+QLMOJgf&o_;o-Dms-Fa#=T6J27GIEgK7Z|Fae`{#!G~SBat;;B z)jmZq`&rjS^I5|cexmLO6M%nQk=wXjI0s&-k^fVehILXpGrZP{v7;zFHHLrCW2-;) z&92930JBETL%DXh$ccWm?1ZPZC5E;c?gg9mQnc|GCS=S=_C=Pf`EL6w zCq5F5rdW2l9>-GGo&$w+sMd{dV;5gdf*-7p)1Nvlq8G6eq@4~?Dm!s;pRjf`+V)`4 z_#DtAv1Q$2JoHw>JXXff7Ya(-9@{gDWhHg8mP|aRcAXC{B7A1BUL4F!fp2TP=Wf1S zeM-!!DHe0LG}_kC)2sCadLYdZeFh6&@`}_MI$z+>r9fN!uqOIz&sG zpCmUC#XxA%Ipw zJCJsgHpvao^ws<0-qvv6(96xHeyYiV>)#gc77;ordEBL5}A3!#6xFU z;l=p#egQ`F(>F6H`CZYdxU{eR&NO8cO>l*2jrLNy)Z2G08$e)? z)`J^^qk@(%`@ME&9;W`*6euwUFXLIAYo&o02h7C+x^Hd*hv{5b9tmQLSv<13wMswC z2X>VMaMgJG`%s$qcS8DqFq8Br;-vOkLbl$!k|%~l1XGbk)2xr}A`7qUF1OFcaupp` zNqAv?9R0BRUdyR#NwqX((^{mm9a)k|H7Cx@gkR@?&7M?Dx#-@x#(t;3DVTa-+Cex< zf)Q5}^d*FPV74N+PsNJW``q>TAWVO{AVx}D2`{4VcS4AZAjz`ZM7YsJ;}vBZ6@5ie zsYdMfrlQqck|xUqK=v+t)ZNi5g;Pa?^J!OYT!THyQkUhQJ#y-837lGSa+UtIk*;Xu z+M`;6OcPako$BN6wo12Z*#(dNpNKMlZrbkzAC!fpvNsEq-Mh z+#}8t*PVK+sl`o5RNv^99h0JB_C`IANvlcfBMT^-jWQb=@~;@F^KmRp*-6aj(iWX+M+7 z3>}P_coW3knuHVSaL3lS?q;1)`{b)1NhG4`pP`=AOU;HmbeZaV?NT1o;QELhpliQ) zzHK3f`f7U3Ju4cjgpjx&{qLW}@w$_U>ntv4*7kUH$cv<``nU5Y-^)+F1{=$rbWj+AD@@UMh1A$L9}_ z)Lo2IiO-yS-yPi1_H`oJkVH|M!Z^PxN+q{UypOTM5|)!_)xq!(W5SRGQwRmiE@38f zv4U$~-GOC!ZmcfIP3PFjwq1&7Rc`3OO&hXfqBkq7aW8oDO%n{oE>}s5a&t<3VI^K^ zg}WZuw1K()NMMc9O?a=R;CQlUyE8`u%}J7N9g9Qy*KZn8BK7QC?Hw`Yoi$o1B+9{+ zF`JFE&aGT0%%$5YG5!S`g{8#S$HxU}DJX&{4Ds4W$d*Q__aljX3=||z7o(MANa%U; z3yUpE`h2Sn5ZY}ibLIxU$LY>f)8^7=FS4@P>8#HwWFI{FiGE)=M^kQ;M!9;G!jjQi zc-g5ZGavVJu`V4vy{&(Td2a#Tyx>0ZTTZp?&P@4-1=P(GMkSc0DC)cfgz#<7 zN;A_wO{Erbw~H`}>YZU40{7RPOq+vO-u8&iY1c&+=!S0f5u(waQzXZ1YsIV2N(s`0t;;Gkr@7gj}iSd z&9!kYP%_pkFD<~hGZgbp6sb>tl8%`6_Z7aY%xQ`)4_sH1E~pvb%M*2ZQVd#Dj$+h_ zLf5$!C>Cf!f*qg7C$l_Hj&qG)+GICr-STXpZG@oIrWw&T&`$9eZ38_UTQkqjuvIZ^ zmn3|!JQ%Q?`GX7oKK@@0+Wr>}seHPyT$+B`yeolY-uzFMY*V5-&0T=E``?AyKV?T9 zqMslPd*6xQ!J9w5e&wf*Y%B(DaGG)m8~l*l{>B&lP#OP)-`#nVv;^u+pJC|%#o=-2 zKXd6WQckhjD{<-G`q3|xNB`o52X6e341vS1Pt!F#DzY>3IqXi^r>d_{g8Rw%A=~qd z;G(m*9K)|J?1)3gz!jgJ4NxxC3CS8^{kSBsXYHCV1d$?EJxmXb_5FsWbs%<4WTkBh zzS(+YmFKCb^k1wycm5C7ee?G040gH#PRiFvmI8?|vl-Rdg8nmz-0oiBjnl0sAiMq~ zbWI-g*C%*Go~;w+#Qrn&Nh7FSZ-qs3eJOpc88kKz7)m_NvrMFfEe{QYLe=g#ljVPBul3n)W~@W+CgE&gaKOJG#kU2Il>!gq z?D0c&(@6)_%`{yvsu|DJ0BO-nwUEP7^$|2d_A}mPFs>CMX(luTsNeQ!yr=h4Bn8-X zkF`z%Klx&_PYbac5DT7w+53Er(65pZWC#rf5L^RJ$Paw(?`0=}B705@NBwqg1G&im z2iCcKC|pFPOpBmey#_#oM70DmYJ==3fpXYaM5iWDitjL^-fEqZi?@gN~TKORLAO{0xvAyTD)u9=CCfC_+& zW+Z*1@c!n=3)1lG7xgOd7OHibh$f7Wp~*rQM{$ZqhjMThrL{qUW8W@qDJVz+t#s(6 z&cg$@$5l^&x@cU4+6X81+v3>agi*S32cS%kE&`^;-4>gue8mMrw1=g}Lt{n7?MMS> z`fnQefmb>7eLARCv0sk-qydt)NdYv$h3>tCak0=O&V}9E{q-yrynLg)Jq;cU& zQ7R)$AV9`(19hqT6Sv~^F?McHg?xg$4_dhJw9MJYm!WcSTv~^kiYiU_2w^I%dOxfB z=_W3I8kRIAHNS;EW{>L;9AHk&`JEQ%N8WSv4ZuL2^1y3BOvHaT-|GCVuA$5B0PVjo zNk0*C`#7bLa&&@lYa*1*Bu5@knfk^P=+MmMN);}=FdTip>*XP_F>Rwn`L`OSpqyRk zF7okAe|f&G8PrmVog9LZf`>byFa(({@4WGWPOwYB(dX4NvxYEW-nv1NLkEd1Gki*M z5R_LF)o2M`gnD(gfi>aG)6)acC*^8W4~h6zg-K8(NNTn)((Tfcc^XP&?QX>+xPcb@ z3Z%zZ;QVcq{S>bDm~-jkP{jYP>7TZYVt2e(Eq1*2Fgt(3`P~70|GGe!LCTamLLN8! zUare$IN1@){@3!^hIF$NbOHQvy27Sv(|Dw_-BXkWqplK|m|fbpy_nu3`~Alc!~n27 zDzXNG;P^33i-8d*M6`4~#8=8o`;4&8?SKq}9ariq=2J^N4<;KFnTxl5pX&6(hZ|PDVN;0l4 zZ~8y+vj1w<`2Ho~E;Q$S(>kW{+$weO+#?`sM4wRvCEen4*#D|%p`2?JIbd6<+>|70M!^QQ|Z`DQCL zUkG^IwYnItI-XOB`g9MEv{<=Ey@nEj^F21}Gj~b;DL`FjX@K`bbVBT6|DNY;At$;k z2`_IJ8Eo6RLoAd5`yUk<=w_4@w&cYT%GOcQT-e9%m5ItBV57*ooHef=p4c@SZ zQAlUMeL(Mn|0J)$WsR#=4A0ae*{*F7-gzIK(34P-?gbhh$pxw%9=oG06s3ry`A=!~`U{DO=<4H82!D2zt^+nC zDZXi;#1eQWNV8`eQd~l4Ys!I5G@|V+@_B*!^*1SdKnpHVbidLGtJ?SyEJJ!PB#t4V z65jWQQRrp}GAI4r8MG;viJdjCuz~D=b3_uEQyYW9pMmmukT_O!Hzf|I&bsd_D$VO5 zNlT(k0`yc$-S>en`8qvXrA`5S-V|5yK)}2SSUUGeg}EK>sTy`T@YH6`nBzmnaE~MG>r7tCf}aj-}D-_O8<+;%I)5a zc_z}oAUjdW-^dbz1aM&u6NXrMWK{T%jN`q=%H98|RSG@P+r0^T|6Qy6zG~n# ze%R@tOyi;w78*ZA{{_8X5*LDf`dj!&^2QI$dG=NRCJIvnpk%`jb zUg@vmba;1Z@>wouH?OmoCZLAqpqe4Rwgo4Ie}EByxkLYdSV>S9R;ZC8!eU)0mjjmk zFW}FMJAmQ>oyZ^K162;`{!1Bq{5|9x$jkP-}y z+?#eZkb4Xs1_Cv|z-e~HAE`SN{Yg-0)S8Ikig2(-H$x}bt<3%bqvD^Yh_m?kU(=30nB!M1c2uD zVQAleN9S|&W+|%e;U;Z&NKy65PF_L1Wk6~l_qTg{bLY`|fU{!1EWoS$wmuA8^lwIW z5a;Sb zbcNGiN}PLa;GsF9LEK6D2>MI!4L&k!TuK%??U3ns4mk zubp$S|COF)FO>l}D2*Zk&qB81y|WEeub*p5yUJIofRQ46LA?xtV6<^|*!8?ZwF-gB z=Tmw7LRZGnNl7$!(Z~QQ{|SGe%9yfXiz`7CxVZpLGfG$xiUv(ZDKOeW;v~-r%RQWj zJXxEz5x1MstM9fvbO$wzXv~tMW&zLJCMdAOpi}h;9>uNQgj~orO985osLCQ z*bo+8DJeLOPeYJOumA;nqRV|8cN@X+*{^~|wi6lgHkhVZMm*Cf4T;Yo?L1!dP!Q!M=S5ohk`538B0WHiipdCl&`n=ewH|r{Vav8xxf;Y%o&r|3weQ6X@D5iGg5982Z z09Z+quVUQLOIF}dbD#C|iNC28M;k47$g`A17x3Hp-CA%`)OBL&#R@Mx6U~nCgniH%i_G!VOGoNy*MeLNp< zrG-kpW`w&i_f#g2SmTvPWpS*Eyc(q#F%xNXDky^fij1jas+ekR5+%m1EN%-8HSaIo z^}h5i`Zj_XnWw?|U&qBPDF}XRQE%m`@yyu`mRJqGFdoNGn&#q@)n=0s#r+4A;Q={j(FeNfaY!Cz=(A*t4g%-AYrPqau+yF_DjyAqyP zI7ZL*Z9V7Om>Cp*^+SOt$wuf@taOjE0P&1aloL<8pvu#$20Nq=Q~F1ZsGsNksw=%L z62gJO4zws@OK)uMzfMB+75o2*LHs)6^398G537K-eY|{?dGwhXH){^wrYfr^PKn4n z{n7V1EySXG%uZ_OXxUQJ)6NSUp5_*VhKxao367#7 z^%5vBTe_g+)`wpi`DC?2`C`TjSb|VJzZI+|U7?I@ePLWc*_9w`r7u(@bfN??;H9x{ zpeL5>cAf(MO1WnkD8GyJEW8E>QB=KsQ5^6>wAdhCz7+P!d&S5nln=>>TCG?`v0S^ zl@vjNa0W6rjei+ewh$oBHXoF3>j4i(C9s21d5b-VzQ$-Qi>D-QhcmnKRsP4zKeBxM$LVQZ@yetG%p~@%GV%2c=nPZWPE}W4nH< z0C>rjRUlsyDQ<)VCg`*Vm<7~Ri97hZU#4ED);7G`H5zkCCEww}f# zQkun(yJZ^c8a3y?f$>O5>;k+jlTepz`cS|80i0)IX36cs8MUwMWOr^D3MDs_0W_0T z&YS|WD;t8eN9piEzy{)8N}n_xe-6(Hi3CU5WNEm9<88Do#2#9tP1~^dPJ&r=lP>4u z*=eo`czyqEHZRgw)t$pd><>)JXX6j1?IiPco_91&#{j8KGRi}3-_4T zDEBCgQ3FE5kVkBvcE_^f{p=>CWa8D6?@^f5#ZU6m!1!hcC?Tb7OVm&5#3ucOYsI+% zPm={PSVp9sLeq>nwuwsrHj#lmu>^tu^1=@8|>k{Fu~$T!h(S=~pmQ(7K;My#Yb));$zY^(9u3@ISOmH zXP0cWLa9TTou3siB!(YDL#Q=<7NAl?%T~FRhzukj&=R z`?<|9Y6j^ityr6=ny!!8iqMGDV9!VU`?FK6-JKN|nAhbctN3NsLwJ3kV~QfVq`%~T zq?W*JA2StpEh?`!B|XM#?DGdjvZ}|_@Wl_{V@C&MWsY`0H_&g=4r}lb8d;F}g;+=N zG;c#_nVvjV4lB80p!M3!wVg9~TL(c3&v{=XF2ap2_ev3N9SnG~Lf5As!fg1N?wFO^ zRYtbCouhS2x;;AV%4>5UrEK6#Qr7KzV2BJi&BnkxN=onJKXsr-PT4 znb=teouJNvZeOsrL8VryAM_7{HwPUv_JWD!Gi09}ok`f5l~?*AQb=k_ZQB4r?zq2` zW0EDWU)h5>mfhq_a2jF%1;=@OA;Oz?nR9a9LVae+aUXlo`5?}G1jMIq_t0#1n+^gK)A*k$fMo|>D{ zUp(O8ki<1VdF=3VE&XW7mx|mn_39<4!lb3d(^P5VHJ7FGXhRu7X|okBeK~|q0)9>B zAHQ1A;{>PK%R~`N80Y-!_ml2_ppd-+2eHRrtDIghxvpn<8WQkoey8aM^@+lPqoY}^ z_zubC19y0{LBzFv4@1WYnJs@PMrY{o(i=tiUa8|L`hJFZ;e z;j~aA(Js)`MR;Ffi@9Q`j*TI31fLTTo}%M->jzh*(QWEj>eyf>zA{SvCxWh{IG*Y*8g*YEeZANS)v|1=()b3W_+e!ZUS^`_zWEQek$ zjzBcdb{LVljmztD!0n6 zznHhg&^`-tD_amLWyTww>k=xN;=}R0Z=!j@$E~$8LySKN@LT>U@9GZ!*wS02G)nVo zlb*mE#;>^Q(-zBxEUM=cO-f|#;?cF-^B%vJ62x_$70(U&r^;;D9QAXl-Wm^TzF)Qm z>QeJ3;EtTSh&l|@8zeHslk0^|z95VdBiDf0){F+xLegHUCNR%1MKfhB~0G-+$n@5o_3=!Nwz8T>Cra;T8E&oOZ`xvWRZMu(MkCTX9l70A?OK3-|j5^ z_VU9=Oa9&HrkPRgraRu|H3U$I+81^e2Tq~b#l8s{Lu#)tEJT!CE7+%QA13>YHrYF< zUyxegT^xQYug^V!*+)oi=0Zi;^NnveezScB_YUry>#~M4WF)zK8=-p{q_D4|CX8eJ zq8>2SmUY_mveL3TD%+aZYONV+uVxCd+G3S*@Xz9v(@hw(-~-7^H}t_Zl)p?w@D7R4 zO_<~3NIiVsgYYbY{sBX_P-T#}d_2a9VNAAtrtNz#rb!&b*|*FPrP^C@My|^jO&jx3 z6Ry~7+T;R_mFP?prY%B-U{-bAYL%c~Ixk&m^uzqY)b~mnLv*hEaY5s>3s@ulZegy8 z5I@ZWP1!G&47CBbAYM@S8dT(Zf$5z)duINOCinCJy*H)W4*bxmc~^-^N9D8l>bo3! z9iLk;WDUau|6tFAUkd6gtqAfm&3Q|><+#RW@iY9fFydUTof zZT_~G{U%5uj>KwLIkP`Fq%`#S1L5xBth%AHNU}=mO_+1LG=w5iZ;Jub&x5@Ff00(2 zgc@z%N*k%9&ZYg;nq?vb6Ia=L6jz1uXsY^ir}H>6O5OUI2P(1Q;t3&g{mjD^Jnzbe zp1xw%$e?gKdZMRm(53&n)JShg?I8EcyLGbh_jK+8ih@mUzjD+SXAbs@t(S3M?{TsdHU+6UMcQ&r?Q=R@ zYjDnAg)<~|-G;Cuv7sqw4_3B8c{iSob#&%sxw~XCa>eoF)^tL2)=eW=KK*$@0upM0 zBeKce!nq++5#fO-qhSEfHUzASy ziKvG1mwZef$?mB1oe18ndC_)z6St*XvbGt@tlbFxfwKxiy;iR7>owTl0s`U#UxKy#ffa#KE`-70@62oQnElF#4 zJ+b@M*EikRxN{rpBN=eV1bsyZc2ArdwZX3{$IiSiFjtNP<4m)(X*x_XWmJ=?qlWpN z2jlad{HMKoM!9wedNCZvvDKTe?ezOZVy%2T^^hnv5fc$z&rxmW3{jD{`xnPO&r#I# z643h=tNO_er+k_vXcAX`#9y>onLceAy>jN`v{6(oVTqoA!0v(Jqz4*WzWg49oolx0 z=QVSmzPyw_8=Tx-j^7_=qmz-FVe|zhN6apMyhZhU6I{y48%H!q6rC?~>0q~;Q8XEY z!RHe`vKhMvY&&$VjXIfHgN>blXnBL+=B!bv;qXz46}&(51NdpOv}wE3fZ+#)yzQPR zQ8pSrJ0C;zb>5702`1&{z5%_^m+HTIaDKejTb5K0kRFRE=R(xY(FS1(L(bn<+|>oz zznh_h2umGXz^Ju$JX7vxuEnqDi5XMzT0zXIZMzlXc0soFlhF&b-kyI6`2_ziY5GNE zJr4=}M?~N!0|+I-|44;7E;2YlV;?HSOxf$ZRSbhkwH4*b00j7gY-I-G$M=2VQUJH1 z81qv2{{%chT{ngbzNI(48}%@*^1#ha2)it-pgth_x-MB>`pOAIW3nfJ!ETS|g;{YL zFY4<-a@-Zfd}b7qAhl4+gsji{@-ol;S^#XmKvt-2>Jk`{pxer7qbpT{ra8l|EhqhT z4Uo_dw!lh9ax75rX-B&N?{iwQ5Yrcltmdx)LV67em?%x_)+Wp`FkU@=^7Jsk6o0gx z+WG?R#x($2_a0CIFZ@jRsb=V?^S#%u0)Ha6ge*kYvX`N&?uPN%gP`OZfqrK7vA49Y z*T*T~(a%k#I|+|`1eHxk?A}@FATj|$5=uwVjb))350^l@C2Q)~U_r5Y;_iC zX2SIOnd123=LG{hPJ&PAz*5opGiL;KcyE3#-5eG#=p$JBSZJsWW9hB|i`6y)@nVJ0 zcbND<@Rf?ua37~VFc)h`>mfJU1csP`Q?D*?PCBLu9@?t;wz z;*F6gZ3Iq<<#!DzgNbfwu0h4LB$PY;_M$tGrQLw#enF1i4}`Jz0OS9%@6+Af{q76E z-6dB+6W;C=DrZ=N41ExATmbONsba_7PMj#0e+lt)!26*ri2_go81JfKqqU~x6c(yt z&hO(NxgHZT$}lZcDUO62-ghA~b=bhYP_Xg?1q$RqlTO0KQ55UG4q8)?6?nHY8DeXF z1sysz;SP2LMcr!!l=5|f`J0By+*_asTZ7GdJIP0da`)toeY$_>QS$uZ*|d^*D^*4t zaO|`rX_n4w>AbYVIb2uyc?m})+9HaeEi|}b=S#i74%2zb8K6Bmbd%IZ!k#(E&LBxf zOe|_#^OBm1hMQ^*-XwFNtSXvGcx}v>W~mC?djmDW%wmWYdqwEz@QK8*;5N+S9rRhB z(Ox^mrRs)BCRimK*?iK`F0+Wm-x|>d-gS_N#3BxtsK+wn9#}^o}J|g77kzqo>!LKlM><7iKt1umCE*`^9Qi?e|OK3GrPC7HcAw zj=c(EgD{cvD(GNdHM9}n2Pz!VimrJ09NLyYh^x53=w~5=dm(T(uz|h8Jy&R=T#D0N zCBsV@9M~V2@k2eh>yL=5viC`^(G}R(ZNBHx4YYdnmU5)^N1ZKCz-cQqv28HzLI14 z=7E?ZP;|DCP+0+h*vDCd`eG0#O_Kj* z>mqcRI^x$-eb!7JbDRzh1^>_p{|+vcv6-_w^>_{9t;xbEo6007lLwS%0Iy!yNFOiz z?n4?}QW}~r7HU(*W>HDT?#-^5d<;fGY+!G&2MuNrkk|9^ERr$!(W=E0TxX6xaV!_t zKX~}1N-G6XPr9Z}+TFL^INN)op+tfGuX+gdCD%mQcoya2hia0%lU>voVUm`8wA|lFjGr$-)%gj$Av4431@n{vIwoH#2xMvM+C?EGX=<&B=mD1 zfFS8KVW=Cfa8X~ji7q)}0;{lGO^%`8%1fE04^ne?x@Z82!jBo)nh51Oa!up)K<(&P z&2M5MkzrcE)Wp&v(j$RqdPI=>EJ;--g+br*!(7@!WnZ=Z!o!&`tK9aMM)kzKi!Dt{ z(!SJ}FpQ+p-nr%;Z3arTx}!9Syka(<4iq%oL$CEJtvaqd%-QpSVW?SmeLhAnmWh+g zaxmRs%~K0;v*g=}67f6Ul8+o-gOr17&aB=xO4L&7gw4A*K{Ne1S)RsW&Pg9Xn<|7V zo4Sg2q+Nq^^YA{P`K*sY4u?|urktjp?R5HeIVa4?z8pG;uuKSL8tUiiz}K!%4WaGt zB^h)Xu%8obHxQU^##?`}Syyx|t=I;uihVU%wlZJOtFGPhaD-Lvg&yB@^S)*M1HAfu zZ;es`+DWic^Q1e~H+TOGK$)+mytIy}3TNo~oWB0Rj}H1Bg0L*Q4gVXu_i8}Ra*ZIB!r?xPElm&Wp?co7$oc49cOSOHa%@U3S z51&iqGYi|022xEAM}#w-8JhLRTRZBDn>0)+hiAmjkRcJ=bvi9f$y_O%>btxTT-akm zJAo|-am1z>(>M8-R246q>eJAHH96t8e1dIm29xFFUrkiX&Ba!)$Sv0=40$XyU;0*D zY4EZm^GDwrT6+St)D6tt;d9eGBN*E2q(-uG!TG&ib|4#&(Wc9ed_a_S9&d^FGYrLG zYZj!X3B~nMsl;;F_h3nSp1dmYyEZAGYD*=D#&qTH>v5UmkP3J$?MQD48A~Ek6%rm{ zR<))+-2Ox%DRz9HXk+`vKkTC)i+ojmt&$_zC)qAqTM-5%2a&6LW!;r4l8X*y-&EZE zB!~%`zRESyBH>ldNL3rf$9X!wSprf8zNNUN29NsdR35`)H>-?+lBJW0SSTr!@LARV zgj@d72A%y0Rx0e5(x^~I}?_mJrqhh zuaw1MXgxiwkucL78%jN(5bBK~Zko~|RK6jx5tL{3i1N}w)@F&(hD}a2A{^8F!RYfE z@fs~N8;!T|XQD+;3{@4Lp(Nr>psn1C9mN)q&Z~@7DaEi1e!yNI4GX^!$z0Mc@}?pC zNzJ~?VV~4~OOg9kZKnsxX~qfHIT2)2hR}?(NrdhqZSo%t^}?w*_f#`{nhMi0fQQ)0 zsnq^xN1qu3RoQ!1wv}LE+R9AeoA`F7l&Qxj_SAFjmirVcU%E3?DpU;<7f-@EN5c^) zAE>d_zO}>P6pR4*k+e`A^gu0xAhRjx?kBGcYfnLehN3#Q2}1Y|{$wOsnB^cA>9S7q z()SqKicXtKztQKnaySVc=08wiB$mxZW{lWo$zzH1RQfJ?+sY?k@-WO_MeUZ)Gr#}( ziFaPA6v~T-WIJRs9CmR05}jIV@N-A-NneT+*5M*Hxzk`@y7NCeCVgegk&aveSF3R1 zX2pT89ko;MU9#QoBj=(7X;auyxinMOrWJ7(So-GY2D(Hbd1i7GjO}yonGms%{-!75jX> zmh|sm=~rmcFCB7uP->O9_#@tJVdJ(o0A+_XGM{JQ4M(XK|ChqScR}f)<(kmWZg_?7}GC4R`~XRTPHj%VEFnoSi(>?nBMn10w8$`LGW1w3Deo1Run;o zxwa0q@EhP8J9o6bn*k2%4T@^Hai|iaI*`v(H`T0T7Nz2rqQHkW2t4?- zIOBW?DdP^!iJmUXZBv$nN_*V5=@JMHXDUQvU|g}lT{B*EJ`&%H87lnlZu%8LlPjF0 zL!bCQ3<$Ua$c-!9+ML?j<}>d%B+EB%%*6P*z_g<@x1bJ5$+CSr0OZS|CK?OPo1gTvkb`Z@B z(%ctQf{!=YV~tX*C5C4|bNhB|hc=+5^uAN^=zbKioeu_+mZb;Y54aXf!5ZBSES7yL zSZr+9P=#xEC;U8Bn# z8$&3~t^s@U26b2{bgei=YjhMwOAMn{g3*Z|E{sjnF2q@R!?}4Ve;3qO&TwS8&cJ2a ztWCb1(??E`Q!Z+-d5T^7bnhSlXP9;wbvi_L+8os8wN4~kg&=Qx2Bv)! zj)P>%`+0)L`^bRGEd{Zq=xV@~0XJu{S{)O{O4eEaI3jCSeQ*VQ$cN_eG8#4l?AkL# zI$i}0Wzs9NY<-RcQ(&v!i^WN-yFt`+QlB%B_=y3&AkRW7@H^6z;~|a%n#38qY}lgt zB;?>gp6bI)^`}U;ELztLc6pSU{+QG?5T;Y~j%l^$kT%h6ni%q2obD^T45ZPbJM37K zC}m3e!p9sPwlNi8E;>g#llF?5mvn&?9akH%0ae7g4C2Avy;BfEX=jRwfw3T+-H_IC z88SuQb%ZDf7?Y40x5QRcukw21BACwDXguJa^_Lki0oOxd3W~`wISnF!L9H0hlv*xy zum7n#k#B>31&k8E(k_4YD+3T5Fyp)N**9^}2Pltw?CK!Ps_g9;w1@Po?!zex`-j;9 z*|s6rYF$UnXerF!De0Rl-{lQgysm%2wrL!2)&fC)ycTFol3lYn%wXICnv?u+a61?JB784mBH zlxvO93f&VbkE!K;#O;X0R>*%u6a^W>^`8(K58-@=Q{-c00&VVTIxT{&9nB^sr<+FO zUQGE+xXBD%B=_nF%;Xr z#^$;zQ8+~JnK_kmN+KSI3`6RKl+SxSOs9 z*qm}oPJ+O)FI}G5tb1?vrcRAhx8bcj$xVb30VxQXmwano08m9d!qqG-RAIH{s?SMt zzwC$1U`N!fO_5QxBz^RVHa?gU#Lo$#RNfz>rj(;#b1X2>e1vGBwfj3SSBOQ z*kQcRkiCqCZ+_kJwx)C-e2=|v%U2=YOxV|Dy74V}YaxP0@46+vVg0+6I(`!zfk8mBYUMv!*7?PW38o^RpSKSSWs_mk7!OCFde<)&aa`BF8Z&8#+zn)bgpA+k_$Pq-_$EyMo50 z;ZMhd8|yC7av0XWgtd3V8?K>g7U5)#oa}=^F>a?I$3|Wj^hPb$k}vvj@U7a34XL@{ zN4w2?wOWKeQ|e!OaZAh0B~594Ae(GQJe>_6udr7l8`qk?OVvKbMsFL6REZiW}|E za%ftd`@DqqoV`(|87}{vLF+T_G=Qv*lD@6_7A9Ks7ChA;)JUlAD+JFapLXa=4Du-a zDljEio~E|zLV@yY<08-Ca?4C@5GxwJPdiM70OjBEyQU}LitMfH-We42gBv8z63+3_ zP?l}oJ#C~NDC~f~;=I?5Usemm03qesEIx(G%d6OJ8L^J|_g3}Lk2JK!5@S=a02bZi6BA zY>+mdbl}=$uahU>78~C6*TdVZ^lB&T?`0)&hR|^c!FYzR;42zHKE;z@iOdJtSaobW z7-8bA(LUl)Qu7mW-~NVB`sr|Dj#IMkt3K`V^*$PS;JkD9F-LH=#y(uZn8d$t1zP?` zQAn=6y&}-RI{$$QybnSV&mMr62IX6I3*0svfMBdq6#cck6FS9i5S>A~+jcQY&ik7I zeA~5_F<1#J$|$Vf{jgaMU2J{w@Va{}*?;?nKa1h-=MWmz(Fl+#eX2{87AiRu+SmA3 zY2x;I`4QUvzspDa|KY;}I~ZJ)qie7Ikd5rqGCAhc`upcQhHAl9$7Qga4)PVRp>SR_ ziE0fB=jO!?h^Lzb`@+FP5z>(Ol@Fpiw<|luYHu9?oIk}B27)NAoNNb&Qj(JARe=4G z&GRy{?LhBw8E`<`wb28BNFF|PNI`%DtaRH?$A8i-0x!Nk&F>k5Np8Lcz*2m?)Rx0@ z)92Gcp+bmfEP}}zZ6%*rh(JYyz9ow7M&=V_%0^SXn->jBz)}H-EcM7H$nj=|7(-_9 zU^vN!+tGpaZtql6m~t<6`Q%YrF)-;cf~N&cR?$zVZ5+r|so38COIJMD`we8GIYFhFckTfCJ zE8-i9kbf8f>5J}7XMs3CxsJEqyV9Nk7UU!*m)qb|d-XM_0MT#|L62PU6269E&q&F} z#H$FQV{2ZZ{d@zhP{#@a!>t~SBi*^fIGSjHYJcZ?F1J2x%j@+K`1C?lGJ%<;2+F6n zLNgTJ_0S4`CUCVWn4Ka%HSa4Ry&W%L%D58>wDPLH@qpP^JfIC4&`E`Bli#!=Sz;AN za9F};o=cNgDTmu(+8n}Z#G+(|C7zg<_H_880&)VVtP3kM8;RC;UpV_!Gj-U2EOrux zXS|fD>e-&-(uy4PuV!fzD;7`zZ)emi(-yfJy96%i_}zPGGY+TX&4SbZ4GQRd+WocR z%rvk6S*VAZj%ys~1_M7D6qMHx!Sw1v-Af%F-Vg=Xv2*7y!N@gC1j~0H=Z1&Qe;68h zFc9P7khTe<{e%RB-9Th`^NzTGMR{EFOFGSkE5(EwFdn50Eu0y``N6SHMm>F|cQ)$dFQMr;g9w7LhsvkIQ2U z>Q~Gb!Z(<0Msv^I7dr|M6AGBmRm2;Ww4DYa%h zi4lc?rn7UwI2A8DHGede=IyDN2qqW_q*r{tiYF?9Db;H>OjBt37RLRqLcKs`PVQHhbX!B9vD(g~;yk)1Vk;ZeXuKufxBA%*PD%G^lySnY!C zHPz%kB-k2X+pkA^*ha&C5n9VrtaP+b3&k5I^9N1o>d=v-sFKS}`wv;v=Ok?c~SKhJhtl? zA^F_>_^}^vTDACDJQ#0$@w|tHBu39K{ z)C7Ahu$#2y7^+A(JnjdI!c?#KjVs&3PPaQ( zH59vF1eqtj?U@EJZT|HOFaYg!K;%-n#7XYZvUrv!gMKf|3oTAlyR_B0XUs+Im&nN? zR`_V_i=SGh$hG3$cuGr~gTqCdm!H0#V*Id0IAPx_{#IcQ`70Pg0fv%3X^cK!n5Y4R z2_C@?BAtYzSi^lRQrI+77=)8yNmWacX?El`89szzePt%e+DtIyeZUuKp_*t_M}yO0 z3uF?`t`3r;WAB^mFUE7d;PVHeE7q4!FgMgJv^kzab z`C9O_5FBNE)XEap_rUz)dmZE%IeqHMWfhtv8x=2K6OK3O%b2~A%1tBcat*In%+j%d zc&+2~r&S1e@Uqy`H0-7cWLRqnhMQq?wPsVNU^sCkr?3;=#4?FX@!R#4kn?t=t(htI z1sCSf^Nufu+gMVVKWJV8w>4Kr%&;UVK{WrN4j7 zC6yqK&h>$pb$AAdi}|G)g*xK}x(KFY5k0AzJ85#=1@S@Apu`@Jsk^!FeL*>LXiGy|H) zb&Yci%Ey<0PmDN;A|p(f#EB=ymC`g>0ESK;E+sF0YY1E7(icV%c~{-ifed>_L3<(L zZ*5;lX_$A-(|l;9exiM#-LY=5S|b9VkVGfXzWv_0nz;H!@XXbM9*769%j;e%vj@}F zgOW!E1_y_MrM$vP!!CCuAOf5*wE(saL9KNitPbPO1#So`JqKaER|elxbwkRy_{i%5 z-{IPwHL-jlPmEuvL$LDG^mouP?61b<3p$RsiX}p{siFW5O83W zKgjYWc&_l;)ZNJdXPgvXO)I#i&Ou@@xq1ZT|J1p4k1|zWpVu*ZjA3qt(TU$In+_{_fK$&u?u~ z98?5y@4>P>46>fjjp)ncIPaC z)M*Z4e23DFJeR4S(+GDU4|bu(I9x@9t%k}p~adnFVsD1z}K?k9#XZD0`XG!B2>p?d5|_T7Eb}y z$+dZjauP2F|A!(h>3Ff}8t4456UN6V_~h^^l<6*V=Ry?d$8l(Ho@a7LLWKXt>a!M) zw1al+oLAO5Tc8mE9l=;Ui>jPuIYT5XGH*ve!yVhTG~HZ1C&at`dUKMm zA_v9L$yB@El45BSB0iz%9^<#o941itpUKYKcd6r;b})A?uFxv>DA9os8m?O)lo(}X zU}sPxS>`a|&)5IDGTMEQtOGb=%VSk8h z$UNgWuq?moUmktQXF8{Q_UF=hm{Y)p%3Hcy({9RKljSlOtN=jdBi{L4Z_!FGc0 z{`R~4%Q4da_?9c!bmAX>y|075!Ee8N<6cJ?^rSV=*bhNqyJ08D3i%*CJ03i5+LsI{ zHxJyAvI_Q;HViL^l_Bn8e)WOhw`dvBZ)#0w2Sl4i*lPwLt8PR5#wj$qWd-C-DHB%j zBV$W97oHz|W+uP#!F)3V|NTZ~Rn;LN?z)r0jxzprPYpZ$ikM1+Lf6lgmF*1fXhPTX z6jevMT_!^x6O9lYv}Eq_RlpWFrnxRKBBH4`N8en%gq}fhdzM`$kWp|70@OrPeK!kz z9!ELk+qFDMU%LiVe&nf}GT)=Mjc4x1)##qOBPOgE!X)!VOLzMabY~d_m+XYP(lr4z z7Xb5I(L{{zdZrh+bnBhrblir;Wpo3aiw6PCrrl|Rv{;5lx*m()L?G_x(;Ar=%n_MX z)SefH+x70+YttdBA6w@3b?4>H(3xx;fc9)JFrizzeNq?(ul!!?0DuCs1Z+(50nQ{yo6Lgd7t$H1`~@hG*x66db^`wklMK5 z35?t)5Ih!ot-SPccAV(gKVpnZt8-j`t3eK)od_fs`&bvq1WQ?K6{OiT_LWiD5xPut z9ETjBY$LUg&qKI#Srl9zxQw?R1|DztTg+Npa1$H*U4v3)xYjp}-)Pd1{(fHm-#-sG z01(mzHM3W7f3~62aGad;t%vX1lN+r{;mOM*pLG|9W4?4BV-6pcE6vheFT| z|Bz+eu>(%4^S1I%1A(5-dOzR!&zn=gwT_AZ?~iKr)d4Wx?FHaZ#3c8@TNsb}5-NgF zC9A5RA2`nTt6W(alSem-BLD;OxflP?g|b%wCWzexdFDyDPsQPG^Ayg966X5>?~va< ze0%GLyEh>hL>C6+@QaJzFXVoxoqKtHzg~JwyMUI%gZpii`IcqhR&;w!zmBe%Tg;uBBj z@Fi2`90U{HG6WaqJs3q;UxGsSCl1EX3S_5nzie`Ny%Im0^WP3wOx`v8{RFx>0Bxj_ z06sZWw#z)n0_z3WZWgM3?I7AVW#=z?wje_2dUMSH@@vGQu=-;w%@IFA%v8n}bkwMt zUV2AsmLPNK&SnZY#Y#}5m&Y>z@Zw-us)0HrWl0j&K5n=fZi2F*1}10UhdapQnV;Wz zFw?AdKz%rt2k(0j`jC5|#l8YdNqdp&W9697rwbg_;CBQ-VE$^t)gPiYH!v7Gm7VjPW+;W%(YgNTR>`ldfVgS*`r zW|)tC^BkxoFE)Sc=Cl#vX9W&#ePe%p3Oi{akq2iAKth%I``MpY`%p?TNT(E$zcSRgCYX!H z%%Npx%Dl+sAQJFf`+k4WBxir%1Bdie&N7xk+nA2U%Q|aOiLZ6YLMZ}t=?7uy9)jGi zX6jP9T^Nfo5`R40C}HGXRn;75y1Ij>@%UII)d|$_xO3&4zeM(E+erCtdKE*qoF~UK zwi+T1G7;Jns;SD}=fG92kb)rAA}WZrA|S<;pt#jJx5`jDg@r-bBL9#a1m)2z7tc(@NG6D5uLxvXj&z%XPVK7|!xM}6P;yDyS;waox;1gl`n2t# zrJVGN_W8f+(w4^~jpe~XO2Q^r#uzj4XTQ{qcS)MyLnhu+JEOGBNr>|v-*4efhC4>4 zH-)ny>Uwsj_)*mZut(@imnkp;Nz?pQ2xQrxKU;JJCX)E$2*;1$Mw>EI=vcJ?OkR*^ z&A`pz*;!c}Bh2#TR+XBHvLK!cxWqDnr8r8&106%-C9>U25@$q#I%qNEAUkORzvWTF zg2mzywmvtP01B_s`Ta2#Vbi=P(Ty-PnMjdL)&em5RgXX=7(V`Kjm34~?wERf96wrc z=$Hp6IhPj5loWsLb`|^Cpf3Nugg?ji%6CUNHq?jV%i%ocW5#b~TGzgEWxUw2wbVs7R^99tTKO6Kj z&n>tzyaWAc(ksF3@;x^7=k~=r&PZX3YJBa}+y#BQUk?tVQV@L)1buXh{bf(hfeEnT zw~SNH&c6d+!SckIH!9?BR05zTiLfxUbB#Ztr0=KeXDOSLl9prjPhgo<%KP9wHAV?P zf9P*F)lX2Unubzrdm(_`SM%qC^gsOW_Z#x8E6Q&JXduF6tmPmGYrfd`La*ordiqbW zjD!|*b-_fRbFk!ja8+F$Z+nsHd85d4>DjlQ;?ClBr-iZOO4&e~jTXXCC@{+Geq>*= zK6^+h1VmRgGlNyGoj~F}@8op=`2N!s6nvNK(0c{|q5<|nSmtLz6et&C6b@YBfe{s0 zQ%*-t!fr*b+zCC0>lVZ+>i`yUA6zR4G3m~O+}l%m_NRs{MO5kS;EoEUa3c9D2KBzt zpI+yQKHw2zXpGfRUD8Qp?4B zz8+gJvaH?*0s))qy@1SHkSeJwAHUpHkRJ2gyi^>~+>0SFu5-}iMY6*%_$!^Zc{ojR4HD#DCq z65&(se2Fik0+%GKi02C7PCb8djB3kBqqyIT?vdAY8L3H@TV1+F2tQ=Dd2mg=1|aLH zwjzYAMERUZX0Ck#a9sV2i1GKc_)pK}gpX@D^=^V4E+?YkS_-k63^k;h=l5Tj1iz0j+Dd>Rq-0=lEUkj!!jR_J~9!Y7_2Tt?!^l4pPdDwLlEj3y4Axb9$TXdzzT z2jGqMTv|m8^;KBd7G5J*14k_RC=-@==r@eS<^Zk6Lch7Z!`XFy0wL#A9GtKi)u|vW7~8ahOv>$hyuVOvB(~!T%oq zkuq{*6Yc49lBrwJYsHD5RuE$Y#K?0%8cG3e)aLi&Exxd;a=He@zLdd5y z-3A*q?GcP}G^Lni)DRr0%Uz9xfY@DRytHz7>J*Vzu$}o1+TPTWAKO4kaH|u_ngw?^ z&{8f)DAOz=IvJ97%=3Ac)hzrPGoVZ@q8gF3EGMF}$f?tFEgL~P*AixRE}lk;G6e3 z+`R%~^ySKlySN~PG7^TOso7v93DgMtl+`J`n#~^^;o*4dx4eVl01y9n=H~7B6~nyn|YUEQTsYJR^rRXwDM#l0pF2nc{mS!-jWKH zs(1j5l#Y5w_DC98eqYVIer`qdkDC!dxH+G#auCe83&9J_cq#RumeYVwCUU%{D)khn zw}!j3Gr`}lUP@sC;6gt~B)$Fwkte7tjApM+IgnpMoykE?J z@aHNVxnu)(vh4Dot5hB>um;p(ijNVwQRb5)s~ZWb-V2jQS}X$uvG1E4G_=arN+M6e0SAlQckh#Y_^+j7k2x;to>sZ zcGfjK`q4@jhwLjnel2#)%6fD6jw>UHD9W_FBrkJ$B;JVFs)jPfgypw8r zNFwSNHn*4J6oEL)3tHAi+2>`lt8#)_i59wH*d@uWva@GCL&ca9r1IiAE0q0v(}@?~ zt@X3lt@J0fIY2eD%IUT>{NeVv@D7(2CviE0$XZ;GXkMnC9v+=72yqNcZT#1St&&fk z69RunYXWZWf(t3Pz0{i37{!<4gHl;03Ga010@ElRl}VzbatJa{;H}+RIC$jR@>?=& z4vlhlA$Qd%^;_m1LLQ1*(p0J#KIR?7$)-ckAK>+nT9RowCeXKJqf6-@m z_FuM7GP{%v=PzfT^U1Fl_-Wc$sNWcR1f{5_QMkV!|19$%;bFj0qVEB;UVBUIvD%4y zIe842V6IEbD-nOAMZP172?JP%_wI*!sJ6r`a{WCIIJ_lZ34W*!M%Y{WvgPwaPtN=M zZBu=|O8kH47>L_R?Kt~BE2F^=J=4xOSmWXf3gl2O^+L7_)CM2wSrLS(M2`v}VM8*( z9I`z=GAt?;2LRs>-ia2Hc^yE7?Tw@2GY}SYbErmUIf!+Aul`U*9?4H~?r*%?HqZAH zx%mk+i|-x%6HJ5bR9oYTncf&z;2xxdPZXp%4g7u&3k=GquSTYK${G(-r? z`)7d!dPtbPMv)oL_l4|}yCJ~Wy)psca=BcNasZTt+Xa@|rfb0`8C zbOEQ;hRA?DASJsArPKHdmYfs}Fha0Md~cU#M#pGtX| z0#1=gE|oJMCSa-uiofzUuqTWcP!@^~x>6Ce0Rv%jML|9GbPa(Y2s`lZ1_;E3ZER;g zTzc5m54m)LT<);VO1Z;pxfeckdleI&r`ymX;O?aIQeF zV$G%y!g3zF<=5Cx;^Jp;SMlKzOtu;G@{Ee!S5Mk&!@T>LPJSYkymVd%s0DK+eWm;d zp*m#6yakQ1;d2dZG=-#b?Og=(=Tbn6$fRlA(wuj=i79+q_JUM}mP=%KlJ|+R3)tabr8A|WQ-MZ3CVkV9Yb0z*_0-7+N7OzLl+mkG zB(se%q;5eAS6@vw=YY5MY}#F_mb9O0v8r@eAQPGJ^vXwT^(vx~h-tzc3FT&3uoh-u z?b&~CN0Kqfcao<6i+txuL3(qjEeABC&D`ax+6*bvh%zdjiH#B2+yBf`VqcfHoK8EUb9tW zyOvx)wEUSEhefF?eUNlKWG3dg$3~u~n#~ez9fH~jgws(UhH?N2%DLOck$4I4vaDgPFAAO;Dt9@#wlF(Qg$i(v1uaU5-a;?9>Shn&B`GB&7WIC}jvsNBUTRwou z3Iv3y6zP;%=Pg<51LUHHl)>#W|VB4(adI8OW02j z)C=PR^9a^iHM^T~T)T>`SrNQqnd3S*SZfxNt|1-6*%t>R`y|JuEyRckdC0S5`qpXn zElL5Oxk$Q;wld6D{I{P6^@n<1bd!1@67TbHpXmN#9rqKT|<6s{F|tzv+r( z<}3r%A@_8IQ9N6cVkI64UIJ((#78`kk&J5EBT(4bl<<6jtJp`Exax*;wH=XX@dF7i z#FMI%5lQsL>{bWh|AH~g)3dF2 zB^N@k;HcJk&y~5SQ9Iq0^Tj=2pL1ZsUVmuXa#KWBdM9xiS#;1>HREQYVUVRQ+mqn{ z+<-;ic*t5^*s^sMPY)}lMqj%(a<1dXzoMvj-sry&$5V(mHpSb~05Gh_zqF6V;5m{RfZiA5j6*7`^z1XVZ1{v}$Wjb?q zRPhDX6vmNWIN8Pdtx}SJufpe;^EMc-Qi}QiUHS99#5LyY2a{h@8ZY3FpVe{GK9mz9 zVqcR^v9uJY$oAO3O(fO}D-TEg$lD7Ge_peNf{8@_A zkVN}-o19nLK`Ay`GCj^_%s2z;`%kW{1*0tAX?vyJKdC@a7igKI(dosByhz^gC+_$+ z#Bdw+V;$coV-_D(9ymd?EwJn74fs`*K5GZokpOfE(*(DE{eWf2$3MZ#Bm`#vp(C>W zE(h=a`9pt`>HR2YF+LW*s$7vc(7(t(j=ihmdj<)OKx7gR_{D+e#lRk-T?!Eipx2KF zW~A^;;>(Gy!sgp#IRz5kK=*m|5Jc zF$o(%{&7FhBF{I?geIqYOU;4F`1;Lx@!Kh?qaVkom*BfY_fM6~)$;-8IoX+S&`X#P z`cdQ5D)(Jbz}bMJ!wgKzV(+1CT^VAB|7(vi5gW`D zi(Gsn#~xgtW8qiN6NtfqObjQ%CsRRAFr=+G53=D|u!S4~ z)apD`jrDerj?9mgW#9=m(NC63UwZKrkhwcDAb&FeLk&M1;cYl!AK-oOL%!D)V41wV z5d=B~NqF~w9i9bqqz+^*{`;5X~n_;L~KhYC&=Yd}_! z)`&~ip~ozM&8Fo+Y_zy-SAmp9gx}!5C?YjQAIE0g*n|Pd)lfwzUm9&30X^C#Xr0DG zDF}^3XGa<&AT6me+6VgS{kaf7AD|qAN6_!bMi2xY>DUI04$%USPm~hS}cE8;TZ;s1|g~9`bLOB)$+;W*R^q#(f$cgZ24Fy5yX;HrOT- zL@=Wi%Q%@!CKCmO1bz_pJsuhY!h3Z{MI3)iqDHEvWO3(xf|S(|ipz0;0*gH%927O| z%3j9Wb^u5zS%K$%uj|+`4p(dy(%J?y0Ze}zr`SSJMLJ2snxYbEo!rcS^mAPdg^To)M2 zoNZ!TF5WZl#UEF~l)LN@HMYHcfhrW@ZK|QeaotDA)a9b#_yRhWVjeR|3MT?BF>X=G zoB~00!dJA}SVJl~pP_ihKxW8+j~5w?ENaWOy}>Sjh5sT5xTA4)Gk4uepV7p%JBc+goPBNy&8Irj=JhDRoxu=$CjP05VUT6#z_Qx zd^rUIS|^C`eF_7aIZIz!zPQy(h*ZHp*QVcO3c+@HA@n8|l^A$9U3u!jQZjlh27Jr`pUK;-5s{TJPUZd${j_&LJEwF%f8z|)aA?pDfQnCH6j=V-KtgHz0AL8t6GtelB?~j0qs*0d@Q(Li)+qLC&fR zu@Ek$IzYFB$O6g9ion={Xe#Kz$Al>X>33X*o?MI5e-w)}lz&27y5H`I+5A=k4Ean{ zBK_`671wR(Hpd2$z#9me{TRm(Cb*YxKuJ2b0cd{>sDqFYReB5-!Ui5PEC`EgK#aYg z9cdz*+2sHnf}{Y`DQ1Z?UF~PmRPjePp?EwG8sT~?==R$%HD&*^4)5S=1}F_~Ld>2% zATDV!QeK?R>aY(A004BebbVN2FW^wtTQKhEAdpM@8%Ke#6or@tFoLLOBH~FOG$WX$ zhIo-1lMzX~YApGgO!_teoEiQ03PuzpDtYG36~L;kz#1T22rRF`(FrE^pNJ8n@quwA zlhhqR0zk+IJg7k-8W`4?2Ff}Ql%PM{Zk_r^TDdWp7D-tByrtfOP{8;)1I0yk5Z&(s zgdd5!y1(4bOfQLnzX_;z;)D83bExy^-}y^B)iH8IScbV^;NN^L;EOIA6OEE z>+oqsA}(zF7%kBbGJy*p6L^FS?#9EBe|-URz3eq!7u+!h`>Na&n6iN$ZsB1Ns1}rd&FK5d5)(a4!4+=8%|BUJ^o7KqBEvWb z)Qa4%P1Sy?Yk#R2{@3LrQS*KvD+b4@N_YD^yb3$Yi^Olg`5;PC{`L$G8RjwoE^!Nx z!2(%^`(z-sA2G}|wS4yjv-@!ce$m}Xzj|NiJNI4vH=)+7a0fYX{&7fjsoiurXy-qP z3z5_n?KA+}btPGq|J2O=Y3Miz;#2b9#HR(FNE&+(?*3sIvlS0To|Y2xo*$p}n;v=6 zE|2@~WPDl*?m;*#zPeCe4TJjp79gBx$W8Z~_mSzu_s=17!DrcD3;z#@xZfG*Z>rs! z8&kfA%*>MCE)PV+Nn7hfWmc#0Wv=qm_I74F|)xP>&(z%i%}g@E9#2|l&q z1X^gQyKLXXx;bR`?$PGT46J5}1guji989mmrhwVj(uEVud?BC{=`Y`NL{`zhS%#jA zowfNc<@~xvk&#SfU2x4!Tfxk-hyb zp#Z6tMqo~M5eC8A#*4-ii!gfei?C)Q0utFCdGWxZiHOr?6-~UYzJoKQ%p4?RWj8-L z{IEL!;g=zJfY-rlvMAPH3C^L;G&#aI24!Fva;Kx!Hi1w{sFSKYV5WB5qOjUs)f;KU z-~=CtLzp|r<`C&c1wj&iQXiuITDAr7)BQ6DwtmRpgmqD;)K;C}C_Yb3B5v@8aYU}1 zfKdYEA%?(-?U%qB@^x@+&L6?+X(C6nVov5W3xzi| zK*jl50(}&l*2a$#q2&-I3Oq4s3z8+?-}A#0)NxbuJH=iA72k)fU=0t%XnopAfy`v?dG0|1&pJ@ z{lvhzD&(-l4xei$0gq7A!$LHnsoi>7+9O!xZqh)R3%4srnYU^YB~1b`AReSh9RN=G zXtom6&O)M?jHp5rbJFNS&2!fvZ+L7^fWDz>?lb`GGAO7RK7OFeF_ZUdy((1h#MO+l z5(hQmM>lg})gq5fI`B?HOwfNIaJswojU=)1SX#+g7^j4prBsd)QD6kI*T$FGC0Gs` z4a>iMy^#LGc2`6&36!>Gd4^#x*>2L6dK{I#N>I(yl{g9(x-{*7srmp3kI)R5`G}J) z8%FaM6`3LJy76RhGT2>X@Krrc;qJXrZ}Qx6)LHHg>4f&`3K~xg`s@q!ky0sK5o6f` z=_h2nj;j#k**8F#ms=WGbLR}-cYml}x9AWGbW=sQ@C$5(On+GgjEbO{=J=2`Cz^jN zZ^hMy;wy!ArwWuCEDm5|vW)QOgc z*f=HSvn9YO+D-hILKx8Z!0kVoadOf~4lH?&;IAcJcPSh*^R843TDh z2Z5kCpE;TyrqD9;v6d$y1!fo4N8dJRozCu;b=&fgG!`pn?mY(cLIx`KU_>r@-VVp(s&G;Ul zT?u_lzq_Nma4M+*gLAno7-Tp?P_}!XD^JmxdPlHjED_f-?DC|s5XshE>O>@ebol=$ z;Yeu{n%K=49p)K9=TVx1HZ3O4vz+7ATpxe**DT-((X!xrO^eLF5f z6rJ2+!PdRt8Vn zJ8fv1q2Sp6|cu(v*{2B$I{whwkLLU%d!&+ZcOKci=B@j^_! zwA@{!GX6sW@h>y*FZ;&tHU}}-{_prZj$Lqmd#?W>>wt^?`R5;YY8e|f=Fsr)I8a!B zg$;j*JY2PH1WGwP<>x>}7YF~56OHQx5`47$Is{O20QK%@S&<9@7mAG{e-eFn0{I1mEdfVev&Aii_%Wjuz)%hoayGGUtG zI37t@$pXy;n6QJVy3Zdy{cquwA!d9okg84$bAwuas`aJT3X}^S@RJ%sgA(Mhx#k9_ z!E2~6HdBqn1E51gPJBd7$1L!ajOzkeGh(1`6wa+)c?UWYEvaW5_j2Yc5U(kaRiGSm zG=%^A*1J3BN1oVL7lBw3LO-gG^I&Ag*RV>2B0X=6Y|AcuJa7wqZ*ON00f)W;Rd@>4 z3?krgl?NhU-|<2wA8@Ru13~)%B&j0;;7bv80NE&oCI-!a($eT_gpy+JASevnuPt7* z065;RBQq2v$v-3G(iA+P6LwHUe+qjJA6@H!@~nZ709p5LCN;2!0yH6XKn<8kB!v4e z9$8P;H-RE_ivmJoK$tos5BL0~DJ|0ZAo7KpAi#1L1rl+mzQ)~zh_UosnUZ{XzVT^^ z{3x!67E%a1WRI=@u%I054GLuwU2iY66%FWOMk+i{j!8*0Yj3@&1^iI^7pVV_2R=d@ z8F3K^1BsC_rp^cwfsMO1E2E;c)?RO!uw)- zss~%A>{$G+VqM6Qk6>!8C<#YFm3Xq(TPtz`%OkGlB7so$)RKfDwN>&2@)rsbEMk-% zl4B||5Fj7oL=aCWKOW*+38O;|S(QKk%LS^cuqrPV49`LnER$f>_+Z#U1PWq;GIyAP z*58O9U5B-P7XW0>2>Z-K#Dqwz9hUl2(uni8C2{@GKnw{@a_ezCna^7~9Ih5pypZ;) zJNSxY{UBkvxH<4%>&kOM>(s}Ch1Xws9y{Z6K`Id^#A3FqOaQaq*9XFR=-uqoF^iIf z>=nYjJIDpOt8nlXuaw#^B2%(#4SAvtyqa60h=U4b+7X~kS)8r%NkBS)J~HT0i$f`R zeg67wFu?&?Ng1CnbR zc*zL6`Of)3w9~BA$9gAph|TcVG5b$9hWCl7!4E7>^`IoKx*wppYsb9oj5~ub&b>Ga z{Nz|34~NHI{U4O(d+pNBqe}Etm5JN{jBtF8xi@$I?tR2NBxNUSuSJVOoNN4#x*PxI zjdnFj@%Of*MR2PL8OZ+{zDe>2pGhoA)Rt0rq{ck`J+|7eQs{O1fAXCFzXF|qmXQBy zvta2}Mozs*Aez9b^d8ZSAr7t~SS!T>2c*3PlEG1aNfX$yXfCbZ^5^U_h%Y4Qqt>R? z--nK$MiK-Q`I#@0{ZhTm?rk=tUceR}7G$pM0Fb&&>No;2Re*?aN7)*hpJr=NZIdghQuy{fV z;ITsy*be2Jep}8|xgf;o z{+BbcZ0xd@iqpS{G0!VTK4K%1Ouw1*bK+$5^;SIt>#d`qS(9DjLLAX=Av6$)fRMt) zppHn90iNzP%&V7Rv8lT@1P~+6#bq4`;|7pKX=);njuPqI?!Y9M|7SqQpOc&qBA9|S z(wj|5;+4u1NND25FjR&q1lopCL;AC%A+Sf6U;%li`SN8h!1gn$>%ook8WbLQ7m!a~ z(hoUYL`iYz^KjmjBC8~vbHFJer0@kFRR#zxJATYp4V%wEC%>49%W0!tYGZ^1ULbT} z!1p9!M`h5=zZuFqfzN9sfCXV8g8z9p{+pWdzt7MhS8BqT;q9jg`|vmL zt-bhH8qY8IH(R^(7Cd27i|-v*Mn4!d5bS9EfxrG6G;%5cs1ClLl;fZF;QveR=S zAt%G3@-lLE`Qu2zuE(cMP%nS-<*+zM9uSmiD7P{2_*{2$JCrINsc;`Uiv5YfUit^I z7S-s0Ht_|d1MI*{N5gsD1ivWrHp>SRz$GTOLVvlPf5%+?V%u1Hlxd*zL0qb%iiWiR zYA^pMa{bFj_HTokemx4mzUUvyk^ghAnazS7=$twsG8=hmv$cv_2P&L59-0Dx2z)3D zfAIcK%iL5UiL?M>4c9BB+Y|kDH2$0dyr;TNf!9HLWyDbH47#={aBbPZ;j_1>b8-Pe zb%E|{qCeS{L)_B`uX~XUrt3dF!N|Gy&#;i6679NMZvfAaK@8b&8{dT&7&b9Mm`?;y zTg`w(ut4h@?BVT?C;+Z=f|^+A-`M{c20>K?X;jw%hf6dE%rq7WX^G^xck#S-2@m(u z&!Q2?4&s_;Y!tFj-ZDV?pLLO5p$zD%4FRG-`ttHh$omPr_h&6U`V|sY2){Jg?x&jA zlA_`8UoeLw&}O!k_9Yz>xzhyxoeKnQ!58(a)SGz?7x+kc5wap|S-4+P`M_k`BrN z)#XPl$axJ2I4cg<+`XS@|Ip1p@`C=#)c70P|AhDqWK>1~0)8DLp3d1Xbnka%0MH%< zUEOMItvTY&U9bJ4$1n%_b;ORy|1{zZaQe3JOMd@H3Rn^%hXHj*rrE9WdlJwLvLW*d zOit?v!#y85#)GsK9chrA61U(5&^N49$}-~ZzS33QwMlS!D#>4LRwO^_Hp z4F=$8h54OoL<2DV14Fg{|Np7=KTMBsuKe-m-=z|8y%iD#z*@V+s=8bQ5ilWj+Ont> z?!s_FDu&TH=WYW-TBP*kXqTSJ$L_Gafh3j%Cl~H*a{Oa1`B`JQ@`c<%1~-L_RpLkU zH*Z0h6bx-)7T|%bus9hSJ7(2!Tk~JMOPB%EajQGcCn$ioQl(Epk1JREpfVQ$+5vhV zNk}`S3;ysPkmOO9MJ}WaLf4~)^!zZ|bTyP5Z6uv^SAy8H$LcK%GRUd$_RiZ##2X&& z-45B_WI4&Ls>jRL{}h%n}QAB zQIL^&HTSCXdV2SJt$Q%avbgVWc9_pGTrusGhIXC}83sT+aN`LcWjgdnF7zpN@b20H zI=NH1Pd;Yo4*tS@s?XxtnjaPDVc(s)96<|+rmzUsEpdZe+XXp3eL+0&;FHk7(5ZC%rufz`0;9LbVs$mrRu45UxJAqKTFQer8i0z=^&- zsPkF?iE{xn9|e!`yftF-Z<7GNcN+L98#+&Lgti`mm9ZI4ki-VTByfVyx%*Mx7Skzy=57?n?fxmE2v?(Bz!bAf0E z#BVaL4O0LnO=W*`}70!f@Bo!k>Lkm}?o0{-J1 zvLg|XM$z-ZW$O{Gm;l(;jOkO;p$HTCWBxn;l0Ui+s$D%Gy-G-?O93r2C!pbNj6lH* zUc(+?S4s9dh7&I<>|GT;GiAFsM8F}=8De6JZzvR)-YOWm64B z?O#Hut{HoPtyWb+3J$Z&{DE%ViB}Nu9awMPGjlDEmUF<3aBR&7w6=^8?Np5)jn`Ys zhhxxHF9-}bthLNVHg2$ksxT2o?Kh86MhT64+rKDANl@xT$|!jnFV7G3By6M5Wb6jH z0VEht!h{!6LfAfgH@2t3-Lsi%XPjMwWc|Wylr#~Q45X)#mOc+8xeLSGxcI?R!C-X~ zkDgB=VskbqL;KJ<+% zfiZ;;E^QdCXE_BQjj~3j??F%WZs&L}o_(qL(nub~eUyLuqOP5;K5rHqpT;YA8 za$qRTgk5#;zLL$9^IpJAIaSe9!+-X+nv>Y2!m2y5gyM%P;MBUqYWzk77#uTK7)+M79wggRPlKH5n@k4&&>hM4*aQ0xxjta*D|^fVb~ z<`7Q|8m-~d7(_geJUM?Ze%sp%eNjAK`2_k+Bj8%8{KhGq@P&3MG`u@7oI;2xaL|b~ zbu}@Bj#^ zD}wk-zI_u;6ip~NA}xE6F^eJUgx^Zfd|{Qc1;ivY6dL&sKQw9Gz5AZ4vLH%z<&J7i zMnU=|vjq%$z1|icCb|1nP!;VJaK8|4I1}mXQjc$C(x;ArTuFkI^>FLif63?k8U975 z_EJCPhFv7@Ba{@sBq9pkZXX>UeI^^?ueB^6*|nykqoZ+9yI)*3gYZ0CY2_0}h31+^ zY=O5fDJDqGh1vTt$(@nBWiIww`@hJ@#`Sv6XDE!PE~|1L`>4|G_Ac&3cVlwOh7b+R z=s~}7p4MY}{$t;RD8vY!DW&M_mp5=4#V+J(OMA@UF!lqoO;xzJDdl)E~X znAoT@{>Akoi;)XJjXxEP1c$sU zk_n0+u=5aGfQ-V$le04r-M~ECmJ|oGK@%cK?u$3L|Jg~EezJV+0Sw78e8#PFMF>t_ z1gbj3^k+xiO0sg+xf=QvJ0CObh#^U+rf)?`5tMobvR zHDlTxYinaDjkNF~{mil}LK=-pNHKU64`e6wbzi}U_4dH9HBn>5U=!;BA&g*A@L`)a zxWj3VfDLnVgr8Tyyq-NF10F~I5^(bf?NiVqp`?g9oesO<2sU_b(h!}w2Isz=H2N|0 z{G;Sw7}vkRKewY5ORdzJ+S#k%es1fN#ZbQdLS0 zE;$g~opWBF>bUkAymFnXGPwAhR*M4#Z|JBY!767O4aW|+j%ksAHhRirJW%2c#5LY* zGG_~X4EfQ22SR?UkYb00WaLC4-7gZhXq0=^1fpY{rEvE;PgfG2Tvf#%h-l}5FgXYF zdG~p00Q5oyQ%y`%0rqE0L@6MYJ>L%F(+7 z@;sr5+j6%Cj^xF1A4I6qmU^SqDCR^z>>gmlwMM}`7Ys)Z&A>n(P{}!iNk(jU9DT1r{Fo7x`alf6rR|eJ;V$;KPYc35 z8w1>05~f(zl=R8uYo%4Kr9x$cZurPwQ)OZZre@FV$h2XI=#da{x@t9Wzkz8PRCo;~H?CJEh& z%=bp=f&wc7Nt{a09iaQVXuYi#6G)y_@9F7zV8~mdrsmb}Oe9!R56z`>M9sKoPLc@f z&@p5>H*5DH4-S6GHoUTXF=SQD{6*Xi#X9_Yo;h(n97UIy-Mrsc2TMpbwt=9o$Enz# zXqq)HiHJ#Qu1|`JeqC!?$~gh=PFNGmscFp*CJ}z*u7NN7jG8Z?jEYv@5YVuHmZ9NB zXFoBT54@XnWve8&5s9f}g-$x{-eG;fgG=gS(Zx;IrsEYOi3+Y45^1dNVuYKn&dGw* zy4Iv&W5v_NGICdNTB={%dn$CLxPvp!=6#CQxtB z!H_zov(FAIwSMkd?8ZJ9Om^Lg`fX-VbD`iSt8)bF6r1#5M4P2sqvh`_MSg(HnFmXZ zk5Vsj5tLtlkbY|KRD{FS1~t7)lkT3%C@Xr5!KLexEvy}qhAHqRn0ZV}jW+`enUltL z+r6^g*e#>Hn|YtJFh1naD3t#)Brl_MC!;POrX1v(Vg34M|NMwx^fhAP3r{lak> z282Z&g2=T^4rG`4lwx>|ZJQ{$(uE-4E?QunI?s#5I#6M(z(zh2rJQ?EYGyVzVcvFR z{Q>dV+omZ4mwJJ3-aol4#q#GjPmMgUQOG`Jo{n&j2QybH=eNf^J$F9lx_?8~t z+!t_@?FW>Yu%)x%0Aou>DWI((ZupKV&Y8BP6)ZdoBBhJMpb7Mt*#KeWd`|hcW|AY+ z?CrJzrtelqfc<3)Ln!mn?83sMD`0y0xeR^C;?uX6cAeL+*1eK!zT@UU%EI00cjzZC zyuKGoxc7cN<+Bpjg$Bb&KN~LO0_*4?U)DLp*O=!U%221{QvXEQ)tUIj)H+ZJFJX@W zTlnb!uF-7}UX=x?6f@Y1x~X*lRUkCPFjVJZ*vvK;?g&{I`zz`}?GP~FSdB>zQoH&+ z{vz*FB&;Q{K&O}fBqf0E9iyfQBoQFXT$X`#+R38Qgyr;iLO?mAD5NHLLc{OqgA*Kc_S=OAgZMd3d)`Vvlz+{ zBpZw%+ZLBWj|OVT_qNzHR$k|RXpmFb&_-z(;+)6r>wbre<8~PkYEdNS&03}E@-s@--H98 zC3essz0xVI+J@$AlPM^OEb$Xy@E%od3K0P3cH?4#ng~0PEbx?rQXmHo{o_73;**{T zu0bB=C!l7GYl)=EG5p~hmE685+VjSKd3ZpawPvqXa1t-67otl~1hFq@K=0_LS@MB} zlxrOPWZT~{{Zi|opSusjRlcEWxZ3zFdF2$N3)`tEYQJB`zn?C(kKxt!uLzBHjq!VlTWtVH-7@qcJ7cJ53MHb_GQqG5 z??Mz|ipd)A8@qCnk_6(1R>hE{L}lt9`?f#$KffP-P!vkxMQh0|5}Zr+9gdKroA1)# zs`jdc1Jz@u^4+@&1%wgQ3jEp$;T44clJ;9&N1V*BnwH~$F+jQt+sK(g%5C?s4& zycD$sA5s84w16kmBkyFA{-hszwv3=F{YBmmpGm%UIT6&+N*HekTZGR!ZDMrj z19F}N?ffw@k(vR6CWHK{Ja%|(o{f(Iljf=k?$jABqK7zsOWG5TL{G_ZsB}{?eTaN; z!qX^HOi*zcGEzV6`qn(Da5GkAW!uzVmdp@$S+y{$ffl`qYw$A#4%iX5)iJP1UorBIWzQeur~c(O{-XcTvU=3cz+ z_WF$q2-gTcdE6Jidu`Mz2j+v~dPgr5^v`#q4#o#duyS&tTp?RiFF(Mme@Fb4Z(Nk@ z%CMC$xlrRh47y)ucvi_NNSR=k&;3%} z(ngvzF6R^EN!Cf2el*Tr5!;Nc{#``kl~Vm1CE-g>>;AsN6jY7BRd2X85q`xHPhVsz z21{8+lAB8YI$00Zet%hMET`u0i;w>MlIBeLKQ6gb!SKhReNLH+o^L-Cpn}eYub>|w z{`NGH-|%><07Ow`n79ub6)a8Q$ui?VkjBh++qkKjU+q+S9f$!<$y8SJO(&bKS8hc3KP8IJooSa=p#D z@Fue~!w!9j5$j-4W!jjn9Dks_`@(hhz31!ZqQ`?1^&v0DkrfFhvE2;s_ikr@OFaGz zsJ1&lwG1YY>GwOu`#s+@j9596>bCfp=-$=L^Y82Kf7i|ozc?J7v}Mq2tU)t$FOkP) zSH0Nu2wUTzX`#2#V11BX^Jn}lvTiG_DU~38d$T3m=SJ{*oue>VGiSId*U)LaOSg$(;2)s>K#vzxP ztA=yPXV$h9lMa0tj&cBW=rB~pR!rJ|td{b+?t#wHE3FbM)n7k?Me`0T)#~Mty0t&9 z|MDMK-}>w7zml`9^7VBky^8x-o-HE#VCZJBaS$0`qB)CTVuvFyLHI{u*Cp!h#lE(W zrd{>0GP&oZ7j`?i--ljT8#u^eq{l)^MX}{b*++z&7@LK)WL7Za?=%bo>EJ}AifihS zxR)p`|6=2fdX$OkbCaq*95Glq=uSMrjB+^197yJiO-SHK!Yf}e*)q!_ z*_kL__d+_jSsK0KNxqf zpcq`wVu#6o8Gw!%UxfUUg-N!pSP^U3QuFnx$zI-g?@JP*tCV0|VT14S}jhW8St-KWroV4=xq`{NT zs~q3oRzLR$FoLE?TgH1=oyOQ?G z%zXByWXM6?4oUcT-!%ALw(eSf3nZ^ue}AWD>=Hp>M#4k^miVPF_>}z3apz5I0>XAY z1k22&23zKF-R{5LUkr(?zgXKftivxFHXTgzh=i-~a$u8TQQaO66qMPEd z^RB4DeJg1?&-C`IjoDY1td*~e<>zFpb?8IzH&WX?dZeKz`)pEOFGpi7Ju|CBe4gms z?&$adB(Jt1QLD?acitYh5qo|%;e`Ox76B%@Qkq%(zLAC2MYjsq%G=yd|141ijUpZ zT+AI5zv}E&UU}f?`aX;=Y1;M-qyNm!?zvh|&w7pJ$X9Wn^ukYij^>*e`Kk@1hGfXK zH4PXXY_i5&B%Y$Y**SObVdp8EJ=d-+$66{Ag~Cil8~j}p+iv5*b;>uzJ6l(E#`lK0 z21{mVIKLf^&30zFxrSQ=tO+blhKW{tz7lfI(JFpq7P>-d;;`bP+UxOf_c@PBp^$o@ zb@`PCo(Y77+>1wV;K|^{iog))*cANvqDN~6oC*s&>AAjK;Ss)jym0JF|7|eiOi0D| zW{%^;y_0?Moh=CyyH6$cUQr$iZRD>!gAzn-^?C(LAK&WX+{v0$-t60m8(Oiw8TKmj zqY|T6To_hPXGb_OYNiJp5!keB(y1(d&kf zlVL~pp{Px5o({<;!Fk;~8J{lA$Bq>b?~-%V1vId;h!eAF(m14lnk@1V+fEDRRr&tu z;>)rF*B|z4Mq+Jg2QPR9^*#|8zgi4!OP49GA<}bs(9OmnnnO%@oY7-iF*-V0oRr(p zVSYM)|6MVK`G(fmei8kWT%{R~0mD#+{ikfZ5DAwG)XQ5N?NS6g9>EofPS5SI-n@P) z-~aVSA8Zg2HhfL~$Z@oH>}6Lhol;>ju6hzjJ;7RekWzpwmE^dp99`7F6K^t4)2>2G zAl)r6N^sZ=$n+NBd3 zu5zUlM?d4n6N#7j@cH{NPPCqfKGH|XrPx!PnApd=JT;McR_}tR@iB3v1*F(j3#(nl3t!_ zsLP>*EGMpTP#QO^?l`fnRp#V++1A@4diS&FVOhsc_myD~vawk1z^X3vz2SO);@C== zyz->nk+6lPEu@-tN9MthE~Jfy$LTC?BpmAYoSWZV5s3fja9BU+x$Dhs{-iR!qo`DE z9o3t~rBxEkQ;%LL$ta4WK2RRoP zxnvL0a$nPGP?#-gfB)&nW73LpIR^q=!)~!*Gn=Qr^>T*+uWVxpN1N~NLnZb_y1I^(Y!x)q&c`fHb1ncGc0a&^HbFdBoix@b4zsNtyHRYNb-x6(iq#R zv?`uI8zcLKImqJX>xTlYFT!r?>q$#D}!PwUnz9o2RhmN-}lSD!Pq?J?62eIBV1=b&$sp@73O_bj+ht<=GyQpl;wxw(f71 zyfNviX4wBR{^xMu->tWBN-VU@#K?$)i``)VQtbQ&Evkq&dD8q1oMz&uO5=GBRmn|m z%Y8a0xk`!gmt054ZYj5%BeLxY| zugF|oxW$Lzzxfu=B3r!HQceap=Nr~Wp$4@RNy`B9*-cS7di!j0qEpxA!yDUbv}mRe z&WJ@NZEm?)N}{+|Y*FKpawUIsP^>iM%Owl3aTnldOzvi-Z8=SO*inB~9Gw!L0aAkc+(l+IK}WBbj@P$MvCuDkO*h3PBG*FY;*_{V;U2A9 z;J5WlM6*JrVp#FJ!hV9nmSP=DWu0G|lkar+>>G=PAFvfAhxrfjq)2|6;LGB9+#cFU z{ZvR5{!;1F6dyXncfvz1`xkCJj6UlA@&0XXZTDj-iEb;`a!v7ONyw)i-;!%`9pv1K zaB--ekUQ``Gp$4;eROX7Qa*-``4O3~MetWndP=8(E%ox(N!eL0H-^N%uf`tp9oK}p zP7ya4F0k)h&o<-b-HI=(E&^OMx$@#5O+NcU`;LZqgD4;SWQFHuT9Ole^;#o&1snYw z^InSq_HGG0Aq#my@;Gd}@?q82_?O5_$+3365sI}OP?9Js8YUmzHkSXi_e7Y_>rJ}b6&*2TGg>@YdAn?f?0vfP!)#(9+kTb#F~o^cC-dYb{n=vktjUQlCubzC6qq zy4$S~EV&C{;s{7ShC!b>L61#rFLe=;O{4j)rF7{%^cp``CW_y6LDIx-CZ(oot;-AN zMRrZi^-ky6GhEsE9TP9kv^dy(cME$;cIOsV+U08r=8g8>)c-gc^4t;!hOD%x@CmNE z;B{+aP>4;od^>-JVNhV3a#n9gp@xlO*0z5RryhsLI<@F=RBP8dzeo-(ZU;-EXv)U4 z=Jq+w@#sdaX2!l29~e$qTD`)PqpAtLKVjOW@Gi=&-Y_;?2}-UYV&TbrQ^4H+^_-^F zLk~pY+@JT}t9`$ps!Up#@h;8e+jSc3oEn zK3Fd>`DT}IeJAt~2+$4jN<;~9k$fhdkY^mbRS@1h1nH7EuOS7 znP^?g%R8$_D<=#E^gpT}_DB*ZRy=cM3@jG=I>>u;-)_LhNwJxgDwF)uF7AQJa!;)C z&}qpKZwkIJpj^v_#l4Ha(F~0fr@5Wy+cs!7^sTa@E>J0~B7b6E!)mcg9C$vKn}VZe zX)v>^TU8pDT9DaAW!p`b{WLwd-gxU6fZoy6YnVx~?KhkG)#Zkcq_7c6^BN~%lg@XpoF=t1OoO&Zh4X|frVS_6t!>TphW3?%y0cu_gjLv=uId^2>zde8O}=Q<@iW5us+VN3*Zfg~ zc8p`Y@GzI6xARSci9`s^`{26zQ>D_l3$OQVNx0@56)953QWC#3IM?t}Wszobm#S%6 zT_kyamF_sq+&n~|?DPml1tg9L8GNC$X4-2uUf}uRp_7Vv#A;3B`8GWEWOIJko+Rd5 z>W{cXW9{0b+XUS^?Kh(=)8mv1$Kxbq!zeahZzQe1?s_mmJsxH7%I5A}k`ZzpH{ua( zQh9hA9d>cB?Vgc^j5E|2e=;8+ZRK$ji?DmBOljC&Vr_$g$G%U~TcJ@Ag7*);Y%Y6!~|tC#=6g zN%eQ94|O4#>P>&mi=1C43M9)!p*$+tXiV9EWlhO^IF#CCDg6DwALC}VUW#Pd#J zV^3>M49i6FGjqx%zN%vW4OBOQ4xf{>Bc_9aG_~53Y=!RyL1eY>dda(rTFCvUO;a)Z zEVEvh7-PMy#hplJAL{J0`6j-1Ju1XRFYD@R)-KnA_XxFTO_}5?rVt`IKS`yWvL~wa zYew6u?rxrvXvVhI<7mVkzb?NGZ{(^Pu=a8D{gHK3q`lWJ~Xpw_Lo#k z;}*THJpDD!c8K|wgHrY&L-^y5SIvhG38sTR^%Ea*)aL>ADBLPF+V6_;L%4IUK$4gu zxJV}_Nyx^cVdL;5kW3M;|1`&Xs2P64t&|uRO*cq>wrCvJdyaL6N}*|0FD>;`AX?M3 zpazpnb!SkgmFmh5VIg@R=?j>i?>8ilUQ1|}iL&{Tj0^HJ*>3Y&(VR~jpg$YahH*QP z0E6=I*wd6r+=%itt)z+MMK9~s zEO7Q!Usg4y)O{w4Vt}rE_!N_9mw6E7M+Bv?5a%Fv66I$rF0y19cMK+$f;cieqQvJs zlCHDy+9VPzrWj$`B<5eRK0f?&sE$AO+LwGOH7==6XzZ;1{+b3!JCWjTZgwfG?p@Ob z%!c%nl;cZYUy5x-YSFbWnwk4o4b-~E<_~^)nvLI1-)U^6n8lVDVD<)I<;`m8ChbJX z)gzXzFb5f717V*l38qu8zKHcad_}}(f{j41OE+2GQ$a`W zC1IRwRBO4FXgY6y@ya)f^ni*lgS<@mdkvyPkA&uu39@mj9G5xW&D@YTX7Xik5{nU{k_{4GZ}Z*wB0<#!5UHYWUO z#Ro%?j`|qEHM`WuKDcc=Lwv^+`gl7&1H!GV#oK}eXr%U0b_@AE_p%4ZCnIz;c*c~fm@;Xhpl_*J$K8e`#XZruxdA*3(u}cRtl&F zw&^#COR}Ea4p7XK`~0z**4fhi+_BGxDcc-%63;q;Xl}{q9?C}bZVsLy(6HLNHUAxwGgz3}iXkT9R_yk5dW$UXO!Drt%Y6&_h2@iS&bd$OCWhmt zg^x}*uvq?3Ez$q(r=5yfFM6B4gnohUh+!v${-M$NAH)R>OT?;hvfflHo;M&8G1mg6 zGZ{1ss>ZYz2>DUhDm9Vh)D|^JTlo`Ig?xeP;B4EF-jH`Bg%B3`Ipi?I6 zzWvshIg3=3)G$6JLz>iP6?5LwLi9Mq)j!=E@Uza6b?A^UV3hGCu7aCH*rs~A3U z%Bm+EgPo73n~kxICQirJKg83ee0)jJ=o9x0E1-x3&RYxLR7(!Ix zi;myO`Z9H za=boFw7mt?k;=VPnHaur|pm<9HTR<>}<(rGL36QiQvmkC1mBA>`iE_*U{K{N|1P%Fb{ZuEDoL zdm^5{<7j7A(82t%GnKS+NpRz8&@=wooB6CAsh2vua`k?ecNJRYT+`Ki#Tkd$^tZ*n zeg`;LB30%{^LlDsltG0{wcbU8fri_6qtZSFp#fVsO0J&KpN%FBpq;z7VgY5?6wa z^D`y_PfzR4@D+><4{$$>d7ka|lZgesu4~RWj!TQ~JptWThyP!<jiS`z1h-#8JH`U-+9|mtpGoOCv{gHb@%x zuP?mAk}n;Mq_4bt_b$<)@4jOxEF?92opATwJvl^{&TalPRDeFbOE1(DIat$HUw@MzY%cQz1D7UuD{E93{w z48k#kOLIGkn3L$@U;mVG;c2RLFd|X{UD$4AeI=|5xdQGsU5>x*QsDRV3Wd7IM_ zQgQ$S-5!EY16hCHMpDJcpP-_XX$&p3@{f$T>agUY#5A*Jm$Ds=(QS zB%F4ntFJCH)dkZ2{+RRs_9+MM%clPRSr^U({l2hFd6wT2hg7CL&rVOryLe5xI4JPv Nx{R`Pj-;;7{{k7%bGL9H#QIvqy`XBiYO3zXG=%Apnx4BPJsl6VEO~ zwD@AOjt=^=9$A8Gw;`oqsKW29FE6(gaB(Q`XFPi%@Rlo-?%aa{Oy#6gL7r;m22+)^ z;36z_XRNH70 zAw}(^^lui7J{#-l{gz^HGHg= zlq6ZJ;V~*|_Y{*us2=EU?CAQimqaAS(+m35xZ(WGhFvmhR9vUk89PMxa^A5dX&oL< z~je_3$dxYaAr^b_o)PO@_{NbjK;eb(@Of!?;uC3t!0|`CqFzWo1 zlPqbcZ1VY}qqAN4#b@svgapa(kM6zM8a(ZC>>jq^} zr%8Psr2*|W{j&<$X0k6vjvKg0_f_d@9!cwUX^mF8Iht38sco}v>{y9!YlK63+zPd7 z+uUmjuP{4`JdO4vn4REszX7qcv)wL6^Y>Y}O9p3GzrjW=Ipy6b>cF3!@C1Fksm(kQ zFm!yP$g@o&|M40l+ziU-jz#3wlf>c0Eo;F}=?UUZ*@)0KUTGY@^T;yQkGE8vHQ4_k zjHU6W{Bi}a^wj)FIiAD79whpl1=0xGkQJza>@?Tuc*1InPKj5Gs+xLGy%YE^R8vP2 zxMh5gfaE$XL3CUx@ezvc#T5V#N= z6e+tCjXr(v#1IJt10YTOZ3Km&62f`qY7@NOd^T}AoS4>lY=%xg+KlO`&y(A=4^7k+ zE!&7o+#l%CovQFyCV})%i@3Bd|d#Y?BlRGVK@*rjD?tdi&B{mK%E`_M(F(Z+5YasjI{5>Z>{c|9GkRZ*O!DE;SNRq}*tw0I18-HbFN{vL>;@}ZnbOR(`dJ}w z(MgsaqI9T)1#pLGgc^{nA1l(K+!ZPRwQu=M=sWQ1rhybQ!uxUy)KPE+p=*St0k0>> zWFNb>-hyR?3)>PB*9%bs>$Q{b%hlV&<4qUiFIz&AZ#cAlCa_|(4x9KONT@s>+N+au zq(6AIsjBoIOXTN{(V}!H4dbI=CNtK0(KYtQNsq2;_r>_a&Ig;*ey1x@@9`v8l*ny4 zJ6wTKbQ#@z67mj^^&TSsj`Eju9;hdYOfj;KTr4goVF|bJvDcLDUA!y{gj9l+hA29D z_Wka4whP!?B{t4g5+rr;KnV3)v7!x`(B$X$hUKW=Chul@P(zb@#P(5m!mdSB$|v9$ zv_{1!JQJvY!p_O_>LEI5zC$z!4*73hZf;+(xGPoFDZrZQ>!yfkBw-Yj#JrHRemhTcUgFbU#5qs#mtXI`A3 zrr;=Wn%}em9FTUR|6zHPe?WcO!A_<{CKA4ba^H&>gMbzV5w9p>zy;@)Azxtq)x6Je zl-vwTPDp|viTwr*ijYo07R>pqV7LZ;nTH|rVdr1-|NH3v0=sS^$7n0JI4-jkSb(&{ z^RIU0nFX8-C8;LxNaK8O+pYDU7L%dvJJH-|c2nC84?Ky?hj+I3>_&MobWc2|ciz=* z<|iF{6~X=tP5dRJGAx(bUsE>Q0`a^Z4EQF6OkGHIS!aWC)wL-fABb({dQ$DrxU|@8 z+-1bP5pPbs*Zg(WsWwOSJACxr6?4!NMC2unV3ucNOi3&Y9t7zW)s08angux+svaog#+ORTxut=VRXm6#3dKr3c9B%LcpBnIIpcK}JZDJYsu+#^;L*5R z<{u)H71d@QOCf0&j1C|M=h+@y-Fn_g3p;}PbzO5@=e2XSTVOZ+^o$t7>{=7~(r+e! z)VxlX@a^e@QSHH&Q|0*jt;%Ih!V&h=dnJuBi7iFXV)~wPgZb#&;T%ilry*Pzp=)Q0 z{D)qUQZOyw)Z)vsn27%;i{HKoS!~re!+$rJ!5S!V++a<(Aeb}lO$_vkyPm!q_ zkkxGEnJ3$Go*j$#4qWcbIcg1$Q&@<-J}>XBn-%t-@xRF+&%qJ5wlB76BfGF9b6?9D zmZs^b-t#LM+AbE)2?DA2q{6vw}@1BJH-^Cvx%OE)PzsStH{Q|CF#YWgFNN1DVt zDhtxx%dXil6_Py7@n1b>DcA`&T+OZP)D)Y2o9qxbUNoGCk&t!sZ-IGQisB~*Wo3VG zZvQ)EdDODo)a;jYm^3`nQabr2bDNR*FiyCvJK1P)l_q}Sp4p3(qc!DKuEGba=tOt_ zl+Bdptb675{p+vK#x`T|2{T*hOwsae^gq;)GP>{|dH>CIHVRqUBMT(@ct2}(3QcK- z3=b7YI}P|lsd{V|i`(&{omcNE{TGI`y}9~El^*LgtdBvKDzZPw0*}mn&bCIDD*W+Z zK6~XEvP6ZphWFDMK+V64tum@~bUbn+hu{Aj&s}2e2qf!p`yTQ6U8&CF)q>Mt}{z!6afRbFi7R&Ha6yrCbktlFS^Qn$G`h##M>|iEDd_+IG z;}ffVaFy*qc57wob>ZK=U)Eh;q{utYwk8KbEm?itK~JT_)}_yVN6 zpu3Nnh+5%13f`rJWK+=1W>YYH5H5T&AsQ=u7RDO7 zx;E{*adJRIox$;Anv>!k?d&mY7xU03n1II)b`tfOWr5jyb2)kM4tMNI$?nc-abwNu z#B|oN@g0T|IWesP|c~S;NzoRD8 z*c#B1Z7rRAd7fmFr?~Eh77g3`I^rn-cW7{C%n zEvoY4b>Z`P|74b|vEoPTRP?AvkMfw+_&leE1Q&f&hggdoz+9_X_pI&E1`F_DLwmjK z)x(m**q+A1rS!XW(XZ3Fuk=+qncN-IuNeXXIWbv+iqv`QovW zvdGdmD&+rXBY*Mxd&m-~)JZw3Y4EscHQxHnom%NpWYSI7sZ|f=6Q2t1i{N=KC*hx)(4ge;Vg3X7(*%oY+52-n*Um?oV?J0X3OrZF^AU z3zr1O`q`O8;?S9$f6lx^%&P5jaB40^45yX{lcWcn^J>(UxAZ78KdB~i9|r@aw18dr zZh`E`kO+5*J)aI~XABlMRVmY%NNm4|oouF%T3>A3LFS0>Fv>d=HHf2)HJKs`E!dm_;gj|F-$W>HOEJ2 z;$kYSgmJI6C3(KLP}JRn#0(~D?PDT5x84IgloO9FCc7Kr*pLn}EKZY{%y_~*T;gNb zi%Q(K8hsZfzO(wd7U$P1y?4+lK42XpbMk{{gph6xOWZ|%qi|gqQQl}2oHTKn8N#6l zdOX=>2*?!JLXdc8?4!JWRd8nBhm{fv3`+{YgrF3r-i8yvY|?!5w1{5fgSdrmT)F;q zXQHWo(B7z|Q+H~`*Oij-bZ!4^2bzTt?YZ|}hc!#36sSe}&fO;8OqV#PP%eT}z?Mn( z?onvn@e+H_p&$OZG|*)sUVfL}u?km_ysY|I6YDj&7&93YSwGiPixE78y?l7xl4H+7 zEP_yIKpy~0+HXi{+F!(f$%xiHb9)|_jx^4pSdU2z~$kp?ke@NAQoKaJX&-R!&L5+=JaJ8`3ynFZdQ%FR) zfYsK6i)GHD73=Rx)nR)15RQH|FxD%FDXE4-+@Bt;$H*yBv6;}AxIqI< zY-I0O&pis0oOFF?WZw|MxVilfO~mi{4R*;Aj}7-mbqW!khJ0yDOS+Y##KDjY@-3aL z<85m^siw406Y(P02bRr7AA5TIj}xuAdn4LRu&kAmr@(<)mL($U-G)W! zJ~P!#mSc|9a4hFh!&027k0A!PP-lxD5CsZ8F5O>kM=B=P5(WY|3$TQHfI{T5tT;jKuyzHpTObpckJXx&|ADt=b&@RNUuBDpTH$rr-pG}?aPf7 zK#V+fokA1!xM)*7gGAqj@mq~Enc(ojqZ5I$?e~<=t7MTkZd8A+H1ps5M)9>`r!BDO z`DxDf#stCJENbh*V0M7Oni^3E!9~jgqg1#{C9LqOlS*~97 z2&DWq3A8ZAwXtCD$~?()%9A-k|A`nkHu=yu74!Th*iiq4Qru_0`Xu2LzlRr_DmsM_ zQxc!&?jtS(MQY)Z?gEs2Qse1faiY6e+!rd1hVrEw8R5-eSw#?I7oFHPMda;4MnVdh z>65P#m)1HnE{l2epQ9o+5z^521U*{thKDpzX)fCDqMQdBN1O53TaUyM+p~V5yI9ek z=Jn|5NZE0Wlc-1w0q@()Vpej=!M#ec@KHX*a^d&+sYYV`I{>N37$qe)c01zOzO(PiGe#}ht+&NH+{K!b^B_!AwtYsCwK(%joowykx=Eq~Ci@a5 z`5ANPPHj(f&mk5Dls3OEgiw4HD?%OB{pdtEOF{|X*)py#J}`Q_#}zVaHJNRm7ONPm zlcm)V@&U*xL2!;Y(8<|%>;lAr!|B51RKw2aDMoscL5r;$Uzjsx%I9Z&NOVX`9TSS$ z#$TBG=*V9G*1I~564;}&!sUN1N;Ql;F_$qE5<^+6KMg1#+65e84^O1NW5XgK`%R~L zBo^4wm9b=>wSc{>a;Ln&sts&N4s=CXMgQlLLDNUK+g=~eBt1`u+RruTjDnYZ0&ZEJPs*Cm}1Hs#d@ORN8{daaZJ#lLcr+i*41dGdyqxu?o4&_ z+PM-h;-FcqT}hPDHEhDy@Y9|3_0mOMk2eJD%njJy=FwA@=A!hW#^au^<_snGyVDv6 zFMFiMhGh}e`05f}?LD+wo!qx&CuS13K7YsFvjn+1Iepz)I2~PM7=^WMjEYr2J{HVR zU#YZvkqb4>he2R6xX)fE@r1_t#nuOYLAazZT2`GwCrY(SbB&uHdK;d}mU*vyO*7c~ zi5eeMBB5zAuw!ki`Pu5lR96TJ5dnCm&22D3L+X8*oKI4r=O!g;OCUSNbG&eAHI~{y zD$z=ufsamaIPu3#e1zyYn)X1!SG($w0XR}6D&QE)(h#Ve-Xr8cV1uz~?wa%`faY>c zybs-{N3EFqz%wN@0tnk%&}qr_-oYea5N4Gm_{jTSGu5*DuqkGP9oO>lCq9d&Lxm!* zrGcK3}$%SXr zIlt6`)xz(Dredq?C3~c$dkZVZ84`)MWcN$aE~lrQm>aBJZg>50#Ozv;ot@C>9PZQNWU;)poLodDHoD@c_( zY%)%*Qr=F=E!VBz^}X3&R;oUE3VkeFzg}K>WXX-?4sRQYb)sW)D0YY+<4lq9C3)-e zCilldvU&oG0-kcFVyZ&&(4|KLsvzl9)x^=z)$C!jGfGlIg^(PIPxHl0_OL`p$zU$ZHME zdFUXaty+Zy_oneePc+lans2pnor#)H76dDgj1S%wsk&VF7pm+5QN61xQ`jjydZPuAk^a~o^zfV zpO&a2bt#=}vB6qq-C`n=9u-=#Wqu#A9ejIMg92wX?L6O5E)eVTvO;L$In%zzkBbiI z1J?L3pI3gJr+KvB1zzuDpJ<$B7iu-`CYSPdpgEm%;-TuhCfpMa%qJEdHWg|n&kj>% zxE(yE#XhRfO_nlm$2N&&r^!`QX^BSB>}us+tq#tjKYt(Hd~dEwRlXlDOFS&bFk4=k zZ;`ut!a0CImF3{FWSgX$%YQWdm`+b-JNZ5%sbY7em30}<=m1MXdF1I=Jpt*DZUE(~ znEgr2@W_|Suw0&{#w|&}!UIe4CHL@@4iV0^*)H@bE=P>p#{Nq!p8gnL4tSi*!M8XW zqO;#5%WhACrdfDdCYpn#1pa&b8<@ZxAwu#v(SPvfWb@$pC(6CWrG!)o)b!w8_kiGk zz(G*1#|i|bYnT94bh9|lxaZDQFkrRTFs1tw<15_+8C(!cVEOuPQ%5vmaohC#1;(rZ3?k!7f4dP?3|qQCgh7f*S6)G>t3RM*^Uc|+m}62>9{F*O%O3AJJBvm%C}DV z;`E2ajx;l&5d`4K=df|Vn)PAFhJa_yoZ2+nX4{O|go>IW;lbZyL*h6G`!-7$M3>;7QWnoWbZeu;rt%Zr32 z_|M6%Wc$e|=Me<;`~r}F-tw{iK8O;_?t8Yt8>BW`6`?4kFlro zz}9O*>Ix6f+7)`#Oh}d2RBCRkGd-Jt7@t3_0KzcjVetCcz%#)ygW$F(=cJOKH3L+?Xjt1bW~&D_bEkcD_Z>hn zW;QQJMb^1xC|qo=JEsk>euOgnqdC){>M|9P;=SD2Gmy@+xz4mvxhEl1SOgK`q>QfJ zsa^vQHYXa7C5Yd+jcKPOAC!og|8VN*&0K z9F|Uy+nmdxJL zlQcWq^eDOu_7|z*T?WbEa!BaG3(gGntdl=N;xD=lw`T-0CGn?o5t>D)bet4WTZ{wj zAhr0fA|O=NJE*!cpUB=4d>AE$b0;u4PQH^3vA?8f9ba)hz&3nK5;XnFg_dfn=<56( zN((eQ!(G4iIETSjvFy_(p-aW0?ibe|hKpr4w0tBFfdJV>uxkaJ!36fVPduVSDd?1v zCn2g%tw63D78YaBfy5jc3ucGM*+HvppHVh~oBV@lvwndBROoDG>T_mW^Qi9}IPe55 zg@D&N2f!2tdj{^hW>~r{QqqMK>5u$6Nb?r0_d#`{UHy@OpoyMAGPIa7U&|6bX zb5Ti@)w0`pQ{mYxC(HGxVue$_Wv>zTpNZlpT?yVaP>|Ert<4ZA)oRU&7~8DN4hvt_ z(ls39S)=i`*x-p7DSj1iF{MQ7c{Ll5D_X%GiTZ=-b&1B&_^AzT2nD>u90bV?)+s3Q zdPnac5-Ne%%|^87%@USWszfIVW{BZQ3YDCJhR@_V`s>h|reTHqV zl-;5fT$hAnxLC6o?_Lxon|1*xJw{z#dNM8|C=9B8QOY=8g|UN%joFKd0)koy0>0uC z?sZ8K_4|&5CjOs9wDwrD;{n`|QIMJad?tUlnL?Nj=>TildT-yI=zD2UxQ+7YrtW@v z*_r=*r0An>cH`*aycd};yxq%Q8b{vBlQm;x5c^>N}$eG@2 zmt4zbkXt!+EI1{HSYjr0OZ%qOC`D$Ds-61UW=+D3W~}pQ$)_nBB-laVD%~H(c9JX$ zIytQ)6Twm$%+0Yg>G$>$*4Y6|Bt4O$F0)Zw8RWMJw<&yt1P?&i--^7XM}bI2M5P>k zu@UDMa5e1^!()rHY(&PD;l5d2(%?~_RLZf6SjRX;N;Qyb7{=Fg4{79c@VUZ=b$ z+GZ~0Blj7jo0FXG!U(L007cw)_v*;?bxCxgoRibD=U=Z6=1eFM^S=Y9?v2tfJ4GwU@j1RAiA=yYG_c0@u)Sw2#jQ^-)d^RHX^LqI$HeG5AK z;EJy&T8v4Aa7=9CR4P2}lt|q5_Fa#; z#z3y;4|zDCQW5NDJPI7FiH}>%{stT(h~=d|u8QxmBY_4e(lEZ~AYJqRA0;Dh?_ezt zkWlxS7O`w>t30(YQ|Q|igq?x65s9D(!ZxAz<5vXB!Y64abTxj2Ppfs(%mg%n}gG~^K6@YJx`t2oh>5&aOU*bb_qOW&&hkyUXj?l4~ zSAj)9{HY8b<~gHAl09K?EPuxRC-xxMdxH=WDJmyO(5v5!seI2plyY$ap1OH?f$x(;9@@qgx%XS7H8`+HDp&&w%18Kz*mU1As-+ zCv?Z@05;1lhN>sF*GEJ>lp$x=>$C+y(BpwZY!A653kz;*S;&naG*1$S`AUIM-O}P* z=8}zdn1W$=F|ec_F=Q`!h}X>dt0zkP?SYCbG5q;-JN;xMen>DOqb>tRrz9T&pi)-@ z4&#w+rg=@eR~_OZEFz2}KkuDb>zOkgfP=6M@;0TwHS2gecyAQ`>M@v+K6F-ja59n~VIBA@bLYXfxqW>L- zB>wtUzW`Ez%mg1*0^;CMYV8iO1#b2QI#OyZ`OXzJDPF4gGM(DqJ0F;%c+J`0XwpYG z-gUjgXNoELE#kM3-&jiDzP$n&y^;%*DA~q?9Jc0Si4ZKEIa%M>SN)G@kUXIBNX_p% zZ$Wo$7+baW*pD7Hq`{3%K!(p>WS|yVhzsMK&^tn7g-r=x$}Z4xYjl;xeGjM6YJ%s&>lGl-e&erK!Paz#5DI?eG=-2 z|HwSA*f1;A&N64lbJ1pVGoP7INy1c=dCgggv_H8ZQ>9Nsfz-JX2x<#cZo2|6FosDDbV}IX&rO_dRzOg{5 zqZl%&jyPgKg}Q`^Ty?*Q3Q}cY7|#Z(6+jqcdo&@Vt*#v7imfYPULBl?l(?yTNibnm z(NL?}4hjDLZSEj1a)~U2axj6?7zLCpGdoFs0=oAq1GaFcJxiT(%)J)qRr6^4o`%U^*`d z<^cr{(G!6V$?%26^Y5oBckdUfcLBR_&~#c&6t~_MSeFB|n5`9U6R{?1s17*bSQ*9F z{k^DKSx}{s@~-e*MTfL9BUScnaHK%gHsAEcw-MC|00YS|d*BUOZZUhUho;Vn(7?QM z&7S;f{?aNQ3SUYKIoaEkpa$Zsthk3H?Ge{7QrYXuBzEwkNSPVnjjpUGg(iRaey^a) zPewu~8)o?2N#_Ne^bL<(nT@FsQ56&eg4opoM?(lx8t-83Zu8o!tA}qK`WBwa9Hu7# zFR&gvQ5|z~3KpF^g#)+n4?jVr+*kBQ%4`P;Y<<_u;|YM&_hiGg-lw`tWMU0}wWiL@ z5m1QuAI-JLOqHOR3BH*S#maV$q^g`$pSz`}TPT(dpZ&GvifJFa%EeS)tL&>$qyXVk zRlIG{@NiH8)r3wqp!V6Kdp&<6h=Wj0!94g_@2G%Q>BXSpCkZRIdSI=qcNyMKiUy^A zr=jwG>5kQJC^3}By&w4fHuY1V+@B%b_=l@3*Bwb2x^OTGllXfNs$`e8a9B(NTZ;Q`0 z5>JW=3rS9svD~_o0jE1GqCUGevL}Epz&(aTW4qCF%3a@c`BQuiAmgZ}0BTD2>v#Kc z;d~97JN(h$2;~Td_u!8~DpvuA_$2%1y`eZ9J67n2EYVB+d1E)fWH3*qlW_l<`1klI zh3OUuOPoABQ>~1P(z#V4-t`eY#18B0Vr<^>I2Ukz7eG!3q+x@6^)pSu`)43E_mN!k zr?onaCtFHp_UKlCrNyn}h?|4-YyP@0orOcxXM$)=N8Q&(!Bn?@0Be--MszwWB~+l!Nb1lZj$Hn z$>&~un^+NV(7Y=!$u_Z67=Y)Ni^;bHtn>-l(SI5KCIm!f35Swx)Zq(P8@38N_M}qlHp`CH}O(L14jdvEhU_xLd z7(zqk;D>?Xt9^0RsKT0+eB5$Q+WxAs`_N;@_3{x9&uMQ!!ZYnVrOKHyHx0v^$Z7#< zCBgqea>4PZK4gAzB^r)(Q$a#)0Wf@~vUm}JZI9usWpu1+G64#VjxC{-Mf%02{(g|% zZzL2|oqUaxJR*U>Nts92HNXfp%L6%tWirDkJ`2N8p>NXNRbJ5G_?6;rrj5`TL%mC$ z(@_1N0+>5z7kL?o$JkswLYC)-@)RcvywmV6A&!oJ5pUvycH#MPn!v9zre=2|mw1BP z#JZm}{ArbPKtEUBaweA_H}%Y96nXw!6B!@f86cF?GV(;x=Ww%`!X#c(dUA=cp|iRo zT2E7YI48|_CC_@+NIUeyOW!o9>x)-y_w#tyse&5;rTUT)OA_dd&Ixnjc6Q$FNcgp)Mui!0mbyjiVV0;LxJ(jUwH(;LbNznA zvSC|fSB~FlA+Q_;w%DFGDx53YS6N>Ky>JYNPaTVidX!?Z?ceiKso1&(UjtQy_w04~ z5ig2#83To~tyyJ7(zXvP9}DX#GVr~-J`}v2czs!c=>@Aop>~%;6qpf&WgoXf90;U7 z-pK`qs0W5fu4o%}(dlx30eOeqJxhbpfPhlv&A)%M*vNDt0vl#s=_;t)E_F>uniG(` z|Bf9^*u(ai4#^3RjyHx(S;aJFT$PHe35kAO~zxYOluY4C;_d?$Ng|{li=dZe_+-3TeXytAJ#;!ju? z9r9uXYxYHx+3B)IJDe-2$#^YIp)OMTK*I)X?=owZpPlmK_wl0m9WCF6my~(eg zysHZz?0E6rk{d1I&l800;*4N>LsP|#rwl!JJC^z~v8wJ?k<$ClB&1}aoQw0onsyf- znayG+p9PXn;xPef!uOx|etb}5QtJWeZ15hUQ*OFa6T)`GXxMI_^48qj#R$=9=Ma#K zij;SSZikpZV1u)0Os(~=?i4_!Dt{JXyXa6zK*49+FNXZ)FqHqE2>X7SvkFj2PCL}^ z4Nn)q780$G-H;pNSh(QvKK~(yX7UodOS;2E#q^|Ls?Q3yCpJR#2&3@Dv0}{1KP8;E zrcKLt{=NQWzZ&1V$Ap7uBc{0Fr~?M%ZQRGeYc4=Wucqy!qmVuRBDqnwbG)I(TixOS z#4X1d4UsLK+<|P3oq;g6hW&9nnA={>YEhEq!8r6gt(o79$CxFp6Gw^0dG8!H6mdL_nRrtn9ck>l0` zmTu&!-F;7>kK<@LnPj=FmB#3x>; z%2%rdEIvO?GwTQl!K*!XfUWjNSj70c)7D**`RaG08D;#c0&Jd*YyxxKU#vPkTCD{R z=~!`N^EJr>Les4X!rtm)0M&BDR?n%&Bve@zNU)|msE$){-u{c&G*{6h{KiI6^}+`< z@$Z%!pf$1Pr^FVJ1@i`KkF<)(5Y|df&ZG!VWc8VmGUG(#bz}RI*1E*EQ3QM=af+Nt zVz8&#rkwRws{fH~<%p^8Nl6K&>AFa!6OeFE`I^a}?VA610D?}|dAp93+vyV-U7Cy%95fql51n@c0ND(uBPD4QNSms-m%IH3>f+YJvjb!(TykT zPPoF#>Foh9vU1FdF(73W2dM4VGRwfv^$jg=iuxaWHG)vzH}AY`db@=#iHUp3sy4p2 zGSta95X?joHGGC+h!N*8S*lb4CXGNmQNMt1i(;R0>Ab%fNfmR#7jPI}3&?Cwr1k@s z{Q*F9NUUx+L)$3v9;uz@!4xx3O`)wf6WxamAmoi{KT&rnTEKBISI3`unVb&k5#V|< z#>XLS>+tLuetkl8C*a0PbkCdXuFNnYl}kE2x%VF>{Y+X*Sg-ftD}&q7f9I>@i)bmd z$Tu;O7#e;3`3HFuKgWyVZ<8&47vYfbhr#zNC;OAqn}f-B7CXU4)O-N5 z3(FTNYB+k4W<^TMZ?)!7IknK`rl#9}C6JyU(PeNkyd@Ywi9Jb;@g^RI3&vo_ML+~z zVD~1hGw=f3Iins3bOC7%SMPm!3A9|o8*^xkotw}y{CB|YK3N6zX9(VT%TomOXM%IWoYTM%r829jTs0XgIkflxl9j|> zm-sZNgMf?LRkCnqq}a9iz}|P#z(vi8HW`_#xlW<|McohP)co=Ej;}3m0VRYxi%Cv0 zED`EixnY-{s;@#2;z+)b^PyXz;^Bs4rPe)YDL@R$$$v1)@|&b)22@-&P{kN+pJ5+w z?4l~GAg=;c9xiGsEsCM_UQ#tQrCE1le+USnuGbD%X>`$ za<>3WICfKNyv#O+-tjm7BH5E&-GH@(YV!}{`(jE(;gm`iB&7C>-6{2y>I^Z{K}D1! zNv@@remvGU8Bz|0fMSY*6{Art0EwZuZw@u~+iI2hjR63V5&>!RB+X3N6ua-eH(yqF zHIoF7|2WO%b8okn3l)$2A9M5zq?kW`%CjDdOsE7d*ODw&kk2H7b-vI=i%^sFH3NAe zSJWOTZza5g?{%K?-+u+RqGvltC-Lf%xgdg`M-&tYfNkYS=0xI{=(A~=trn_NQBwAa zTOX*Y;?t?*un7+QOzaHo{lTqYuyw0NwE|XOU%i^jP`oN@FRyM7YCPWVOjI!dj^n71 zeZmE{Kv}|?plk#bPM<^vT*twg;H+eHMPz*ELHc;y)QOY&(;~V#CfWu-AnA1F^}%0U1nvVaL*08UjBNVtvqZ+oE2bS*bc>i zz|`aSP-8M=*PRa{)EvfSEH?>9tHpvuh>C@?K*|gin(KClC@6&X`^=4JfL(h8;=>+U zs$5oopspO8@CEDwmOB(m)LWL`oUECf2yCDXMhY>*9|OGr^z5{RHcl}MAB1i$AOWRUP! zTgnV1Mk7N)5V@UK zlHKvH^O-)z%H4^VaL%>gNXUohQw@Mh6l_p@f_#IKZPMH8!5bGToRCf*yb!&&czV7& z$PQhOaUU@$g}X2tw@my3wT8}qcHj)rxW8)a>V0_uF2nY8;2hG*{t4}GSTVQ(g7p!t zd?~9C(0ooc|5W3dRnl?_2d;sDLk9+royUt$a59+wtCdquPvp<8GPQRcSh35W0FZAH z>S9fIjcwKYkEUMcU(&6>Sr^V3iD-N2jdlhBRTVF*OVo<2|1|jP{p?Wtr+KTR+D{xf zaTV`x=b6_4Q0IiMD*+V>E*vp-?NX<9VYui^E4P&SKrVFrA56@G`fRM16Bqvr>#+m@ zy*xt!RqD}MN1tJY{nNm=R-m19IhhL2p~w2wTUb=i*IU$bx$^hlapf06!`~s}lOqV)To-WBJe%SGD`)v5^9f!v%@Hr-zGE1%{;po&RTm ze&!GexyC>moV@$FAUceA{iS z<4}<3Qx1Idt4GQp^#4EPEoWVqH-5fXC*%i5#(S6{y=PD~hB)ylx_4ZFd6QIY9rG?D3|2oxzL4mCwb;;;|M$9~BOW z-rE_E=)e{Vbt);W^^R3D!Co%);&bohCnIG~mjW;W#FIp<0!zR#w9L)>aCboKq}q9% z1xTTI01vc;4~Aj<30+K{lTh~>8ddTAQOJoq@5K`W-V^8wD@Lj&c&-k2Ndq6N@G(_FfR>A@X5aopdvy2!($fL7!pHs&ZwJ6>=lzC*Sx2C_x8JC5 zr}uR^J88Jx7zTv5sf=*m74m+7OJUMh0W!wK_k@$5CQ^JM0a%4CP+bGi z*T(S%@pw@dS5Vr$jS8Fg!baFEZ})vtqph5M%X1V$N|yulWPgyHc|$eZx}s2QT0iN% zm_nX35lBza!PWC{gNP2nz{KlW7u(*EzsVr>sM-%Q-yyTdzmq!a+nJ+tk9A2S7uzj&NnC=z?tGj|5;i5g`~AMA>jyRh+Q{^C#j4{Zj|m=hDg z!f-$(b2AvbyF74GcUHpsTvXEm6a_ny-XV3hq~}coPTZZ)OhEY-2O$4$GeR)@=c4F?$*qVR|@mbZTQQ6t3ymA4O)-ui&ckdFV^OCz7LixJxN z+1^G`0w^J-7BIO$XY-UNW<664 zf=vk3J4J~xi8>lvNl+GWrnp7Z+CDq`u??prH$TAdUEx^2zH(rJ!@SvHKHF zcw6O`&A~j~6PJW|pf47i)dY~S)du3JuO|TL3D_?xt7q8#gn%J>K#=guo-tv53liqH zT}^3VYFR~H7jdruhS5cw_E{~Q0yx}KfGg8@x!vMGwCkL4z{#Gztkdzq)+zIB6<_Vz zSG2e64qDKVLwwl_fT#x;KqWkPa3|h)79p|1#ggLSY^rYtE63n~gs@ng2DTsNccMwU z3}Qgpb7m(%kAW)Tfvd$4Ks%BWH5tqg{uPqR|86SJSbK&mY&crM6$hMr7FB_0iqBI! zq3aJ9lBV^JQL?8q{}*>}9TwHv_6>sy=m3J!ZEO{X7DP}&K}-}-hE7L98VQk>Mqn@z z5kWA38M+$*VNe?Bl8|m;=y=bC+a33__x-%@aeRM${v|cD=2}<$>O9xA%ZiGJPIcv2 z&(xMcuayRl3{TBG*s}|cMLCsmxFHPX)Ef~eAUb^30qJ`wdl9tUR{CAX(3i>8p4X4h z_~6k)#z(t8npQ+|N;_xD_C`s&IKQPExUbM3ZQ@>{DtFs=DBK zUNNMRPz#oCM^t5)je{O`G~vzR?cj+#&YjMmaAx2*{9?DBsI7Iulz6mS_y}@pudUD5 zZ!Fl=NglHcepe4)L>qBtxYbd0H0%SK8hvIU;PASbBBthUcByT&(?For*2dCOb(&Ci zsia}0U{>>uP3KvTw;ma_;`7ypUNdkoZr-E0b9EKl_tXm5%bVyrNq;(u+~MHB%7&v* zzN2A$+7CVQX`@W?Rs-ag^W3?+u$O3(k5JBBKQr|`E+a+vUeC!4+G+A2GV#H1Drz9z zQ6(n1Lb)EfBcA>|urcL+6I?UvNPizKV_3E;r{+X;E=-7a(nMQ*Qu-IPX62k!!Bp5~ z^B^6QSK*!X>_pCbDE6G1p=-M5O)Fj{HQBe^3T%E$``1}uy8HLzB+i!J>>xhm#tO|> z*7-0~tmx#e0Obl^N*YyBoXB^ODmbjg563FEX_|0B=rBIyIQ1)w^uRSZy1GH58KPC! z_K$BcdZF7Yzb|Gy@(<+Hk`8L-{(|QjNh(v~z3NK3e4xvIF9;Z^I1G z4*v>wk!;M)0aUv~29MwTkPG|#Df3smzp@R6 zo8F|6-8?01{1T^Gm#v*?+FvX8aru+M(OBl4oqkojL$I+{9ogqph0B@gX+w_RN3^S{ zC&iHr9|Ri94}uhR`*RFhSc5j1<*3u{F{Se$D8~46N4%`Fz4eg8BHDi!Yo|_-+${Oo zV57Qdb5jlWyKYnLa|aHH*+2%vUR+`X5gj&9EwK!X9!+8+imy}b8(`73f9wLXoanx0 z7~Tuwl!fQMpS(kLo-hMdAu>~JA9i|k<|Z5O*g1cd7W24XVtDhIhmBjgOAy^8`4Y<5 z-A#ELJTRIE1&k~FgF3p6GE{h@4YREZ=PG5I!eb~Vv)p2hu)<1#jFgJ1!D7MKSYfsk zWtC~PkbT15-ESBFwo&tKIt8Uu8}zE>@?aC^U_G>^ z&0&w=bI^O@puk>09g6P+d>{!^%AA0raS*ZTJ1gs>AhGvExo~*n!Tu^1VtE|;uM%Q ze-pLX`E>+J*J|!Junu_#rB(|T+^%%tKI52J;$I_C`H4t&>{e&85NzrdEc(lrObWlfiPN>Mk|Q$>g{cTi$`_HP7|2foa0y27_IX4++$L#l>SW)TaK z3vBnoL!%5gA*@AtYWV1d)P3y4NNP$6d2{-?FRZzqjSus_WCz?bcA^DN9LIL}`TS9@ zFndKaPSIe>v9l4WmNJj1ASCx7w98hS?F+eRxOusVp_!fAN=W0ElP5C5E}8k=-sW6g zyXwlh(FUA$*A=lGtV6--wC4@5i=su~{#t?h{bjX}yUlar;?liZiuO9j|Va_S~8+GL(7>zSCD%1BkydzAPh0`VX{P$7C zU=SCe(%X=Z|BHlbaRBy!+)LBJK!#(hw&wEQp(5TQyH^Zeg=nvJm_!>@;wdU~jG`&K zZgIuhvz=lK;51_OqNnMWaa&#H#Vlek@EI82t;tq8WFvAVY4^suO?lL4$}}*SWw#5CRL$<~mlP?aY)s|k;n5bwh8aqhxv>Qc zamw!!JZjHsSi4xZC0RGC)!E)a+2$3x<5qBnw6~?rBx^=kGG*I~3Hma53enq_>1foN zaCS6bQMr48Ws&H(+SOPS>K-vH8Y+129uPk9n#5*ySiJrI$B#~5`8htYEHTkB2G|ex z_C@VVR&urY=rG_+zOjbHBSJhV_q@s=_5z!B zI9&)vG`#VA1z<(e9RFi~zU17pS2f0*#~x7acTEi?O?rDi-*MDVLMO0vCqrypVCvCM z^tV+0KM*U@_5rbzK5-IbpRUBeq&=llYmn8?lQ)cK^s_p6>kl5xd;a%VIe*A0Da!WNk;@So1O4Uf=865x`mVM@?$&ujVGreg z`3g|32mX&WXDGL9BVUx}r6HYSNUE??#W2TqN(?=_xz3vT^1x1euStNs2w108`doq}CRE$V}xNgogf0*cvU2@%INQ4EkeaRrEI zk?b^(O#yS~{Q$f9PPwfvIHe;x`rzdwFC}baLH%hP_)P1y=4p4kE2IL12qC}LvnDdzK7cqP_4?KmY~gzwF01UlJi^8 zMQ>y{+QYr1KC4x$I;mhf-)ecbzm(l!B=A_2jX#&8hE!g!jnrUe$o#fSo%7=hp9x!d z?WpKA|v6SXd6suzVJ9!4JBeJ*1r2y%^0$91-GOdESS8BR7 zNxLxIKcEBRR)P$V&j35CDTJ;W1;8F!-2ho`dMLInPIVP93urC#wgUHb8FYgCQJ-iB zKP*7B=`AF;)dReLGRJUu5^TruC8jd>KpI>Kw$-rbRIk%vjn0*gTLw@sKaW^in)4Te zS^MivKxmMxoC7E8RiJ5A3Lo8Bd!&dsd=?c?0KBz!o zEmM>~q~p|DpAORf;2!AvtMqcQG1-uX!|0b&FVEFW z)Vo|`&|)LWB3>F+SrxpON+R&hjFcbQ9{ z2j@M%0U|-SXuZ^6sP9xtPE@DI&#kb=>KAo~0p_hiI<+|V-ZxolE7@$J<&+SPn(B35 zv&`wfTb+jp5F9TFiRlkdp~-JDWXyn9J3l-R-p0VSQYfvjx6Ew3faoww)s>YZ)oC=G z&Z9BzT_&#`!nE(ae_m)PW!}n<=n)Wy2yS3f(2EJv`U zM)H7ecUe>2WI?2XXy%Jt4$qJb(y4bM=VKn(7B+X-Qt%*`FN*&(o%Y z`(*5gmw=HQ?BK4E;Rs^cd2eOt#geh`zEohK;i2plvC4>zV$F$FKJm`$fo)A8tm5h( zkcWB`D_q6enTHP_9ilze-4~||JB$1B4upLb?W2jY?Z#vHczP@hr2NYH4gAt|Yji{} z2Yxv6kU8L16j(~>0Gmxk+ya-6xCQ(G-j*uzO5}V>-Y~l??bk)HvcK@#eSds0|N86G zD^T--RcyL>UGhl8gln_-h?PMy_BKa&S~i{cg1&vDf?eekcTm4{z}()jZt|TltTxzI zZaG@RTO!qWk6c-Oxu-Vka8k%c?)#s3uF_D7KW?aO)wZ5O(o@!)La85jNS1q~(q>aY zil>(CgZ$5(qR|G)df>lETpv8pET_r=r{lY)H+X0dMT8sf5?&Av6`{il?`Cr^J&?`< z)aV=J0;Ao#Z!xGO&}crYm(aWYSiyA{f90_?ibFwxK1WWk1RTaeSCZnq(6bbNfd_W5 zWjKRGc2`N7^eh^vAHVfcVFUMj)7>2$1?MkX!%o(#Q%Ppkm7S#ThczxPl>wI~bY|a* z%j#HM0Zg$rrBm>(#@Qysf)+LHJkdIdav^V9i#5-^T&I^U&mJFSwR6ttah%^*bDmF| z(awUCL9=Fs?fk~A)JlV5hQK0ld5nWKnj$XU^7F|BHNy3K<^x;TMg^7?6023&M@HhM zN?}By4pjsPGT7GJpHj-8FJ?#A5iRyj5T91X(${v%M@?ZlGkRE?)JTExLizX1p56co z6+72JoJ0R(66JEt-jkIHA^y}fTrii~%SBitj5~J>^8oG14=?S{&8q|r=n+#XCmqI+ zhFhxL<5}&;RHM_Rp+sE0xHU2j$(@GndA>8rwlqUJj z9vpcYuLTf&uJt_Tf!b5w7AVt0WNSqp@1@yiSRQg#Le;l)OXnLX#>-ngpg{5uO6dEX zo)C_tEVm*nSM{OeqG@WYRM~s*P`-#v%`yB=z&TIlwnyV%<2xva_`uUgeuUAL8s0de zQw}!LpucE9Y3`@ppZGtq>Vk79`r+`twDVE7;(v%_gL2ZMWD-Dr=t`jRu)IAa>kpZW zh_BeNuN^!j4y}!qIf=%U{0M{0oTSuZ@9ZEd z8bmBB5FqPCa9AiEk*pj(@`X*5OCHLlU;00;rdnCH93(*nk)Dve!vT)f@?fkCeH%2R zvX0+RGJjkb%%aFBXpCMnDPdiq{h!DJ1>!kMgG-yu(kP*ZLl5M z74XnlgW=#f6i#Pdj&Z)eyK(uSXytaG&G&Tkc}~%1J9r+Hhu>DzR&13sSnu$k4xq2x zwd;^0+vx|E^hxW0X^mPB?r(4H-|0Fj#KW2ze6A+_{Lrm@_VB>-yRh~g;ZF^piL8B> z61K4npmKudY#X+7G2%3~!IlQESfA9CCsuT4rnv_e;)p#I*`*RSo@{lS!VfRAY*KBS zm{P@lSeZpSgq@f`r2FXT&x(_C$p7a*{+H|N1QoJgIF z$S^UKC;BCECpj+Upad(**CllR@DnK?``c{Z*bzfddItpr$tfd9&h_s|4!oM}AYF6x z<)=sQ&(qyx-uY~ItLss0o*^&Iq3|hhi#t?<66-cSlU}er_oeyGH`{+6=;04g6q+1%})f9G?F z>EA#%V(S4YCwGmvgqXq|)v4S;91=mDi}}^$+jJMQ3;&r>dv3RKmz{Wr(?=^{n`YQQ z@e8G#Q(=@@Kk7rD!GNqfv1U8Dfc3wmI{uU7yn8BUACH(~!@H0$-ktanANrNQtWr65 z3z?yLYl2z)LovMr2l2E>i0(4`>1Lz}rrnM|-{W7WT!XBUJ;Vj04o67czw2rWkytfktU<0rTIBt*VE@k5o~Fugz`36X>8~0( z%((>-l`0S%Xv3K00=^*Y<~)qf|4Ta3;{-&o5Kk)yl4WaYI7Ib#+V}gUr^j6RS#StW zD=k#l?*CmmP~i}3E=j`2p#MT#>E)i?iGNu6?2miCZM*XdAvYe}+n;j#_ACsPVgJo1 z{!bGAPcra-Oluz-M?>QM17q`BCB5HB9VKqEbw$(3Uvu2*?#r~U@{a(@lyUK>lU~eA zS(`5UDiLkC_mw?8=gvz$^=yx>uglTCQJ&R(Cu|$Fvni-g;-2mG+GMdhlkoDRT)T*5 z`}^%E-!YUZ@8o54WjU@GIpVo+NTNg9`i6hwHi$M!!jEE$>d72vi> z6W>ZokQ8K@|BVUop6n1?D|eX(Vp%L_#6!q;QnqhohD*C-uL0o*;^z>z2M5?N&;wID8P4}g@K zWy@Qx5{<#6CG*^214=+X`$@B0 zC`L;B)Thyc2eO;?r3K8^jt#K4vRzy7m}y94W)Pi8<4l+a_w_j<^MLgFZ29z)tAfXG zC=eXD-4;|_uFv!qz5UV;EV;`WF|rDPfcJnG-oG7S?J#_%3HH6C`$vuy=~G zPAcDIylW9KHQgMmp6;giS1olZ9))n6!s%&u<&EPBv1@ppVleM4c)o#^X_~i#gcL+L z!2aR}aukv)$r!@>ge-kk4M-6F>H_RnXKit+2jL>_FdwwDjceJvP6?F2((L&Z$k5ZC zhV6l<<{+4%Cd4H25o5i(0Mo7a3 z6mBVaVM}BqJh)y&6O5|E@*p+btqZs&duz_XeRF+fG0Jorc)ZGQ5}-#EZ_unvb%VL+ z$SEVynl|Rvnl`S1^suy4pJ@Tf#%exsrM2+t9WQJx@!F}L+cU4Tg`7in?v?mBya3QM z(`fv7T;7ibZolWf+c#|w+_ZA9!Lf|tuH8KRR=4doEGUV0EaYgvbI6#SJ^kFi@qllE zwndsRh3%@fZRs5F#StW$NWF(tzj?3Skc!&(JzO+%rI|J=rYqahyc(I~)ED$!=MJMYA43MY&pS#D<4FNlkcR*g4ELD;QU-^%x=N}Ur3vrfJ{^u=oB#}J}j zs3{~0YadLP8w8%IWtwPdcIH^LYr3TY_KLfB9bSok&ifceT&tB{-#GR+}o`bfGp z-@%;Ph^adYa345(*%?F8Zi_F?J2Mk{m?^SQ-b29WZE4xXYIkFCw7h)l;klcrs~gYs-A8r$i{;cjC%axV+J3 z04Xf(zI$t8Ho2Yp9yp%i=7fD@%8>?;{Nti~*CNEyhB}XNd1oW3gic4dc>NSTY3lnY zOCsu>Y+Ml@3Ssb0J27IXN$p!Y9MkMLXN&`rVjCV)9TtRs>JS-Wvhm;})kp;eF~Xb& zXTGK9M#8*OpUN@SLsW)WH@Qj_qh)%gO13r|#bGm0eb>aX;yrW7H{snprzdSt(BP0@ zT|&q6$`yXW&|0w*Lpi@T$D6h#%6y(lT3L9E@s-P^b095QV?OkPZDv-QhCWsOoX_N3 za;1{&#&<+?p{8TvBod_RB?f6g5}MI`pT0wRi<4B@Vt4*Q%8tHVU@scWi8ktn@5`TO|}k zd_=3G9%vEqO`0dN@XBwJqphVc#!J-cBPUG7>k%>wr(MLnAUiY1_qH-Y)mYIG#T84v zEc~TGj$J>O6VnoPmK+f>{_S2jv-sN)84grA^BDb0&(lzJ(iC2((dJtQZMyfeMu`4$ zk`zP~pEPC)&*bBEfg-oxj&b_9#55?23!J{0ct%VC1qBYN6|gVObh4TB?y-55Z=+o} zpwa&x{XFYiY=aKD>AEByk9m|}c1LOLL*8Ry2{qqTI^0PXxa;V2>8w0uKH98rh)5%l zxRfMN*0&6?69+_hvez$7)4Jj0%tcX(=WTTqBO*iG^#U?3nn#ia_m7m@1yT_a#bN4c z-2E1Jvl2#k0x2MyU^XOzt<)eTr(9vioboSC8+EeRt{x~&b8lTBQer~Q3s!gfrckPR z-1cpY;=WlO;z5m5u#1yyb-_ntF&9lxxGWv=lRtRADWGoJj!2SmaF^On4@5@j-ifRi z_P=&|H;(+bvJsj~aRcr#YQk}79a$vlp93P=N`O6t1>klf7T7L<=DkN zwy>w0`2+rTlQup{oYtfNWlmww?w+YjC_#l%pZhzz|19aSDKw{0^X~g zJg#3EtCEd8>{4jlJj=h0-Vd|{Jj?6|UjoKgP6pG5$9R=p z^|isvF;Rq#YZAtFb)}|Ylgb`~A=?z8DA|-OPO)U=dn-^Jw{%rpaBPi0kyAc!t8khGE~uLTV?GCoZZOzSEKwgX$J7!{rfz zrMIAc9Cww&L~Do_);DonByqF0GYW5yAx}gbXZ1C>^3srq@s@;pnYeSMNzRAO^`&6V zEw(N~*;I=R-?Hi>f?kDIGP>W%CQ-RW@#P5>Z915Rs3@i~^JII&GeuOf%35~uD^}nu z)fV9{+moNJeU7_2A9Y*;7X-iOupk;cWhR(410Q3?l%gmi2+%q|UZQR7Lm2zqxMkMH zY-!r2X-rI5+c-y1Z z9fWUFP*%kM?q(O264UEdzI-ofX+ivr7KP7Jq$P~R_}Gdw&iUBdNIc0WiY1PkkZ?t# z3+P2UX^{l>3J0~P8)4@+I!#QGyEH|h;rk$G5#o|Qi|ftY1ZL*B>^%{5m@oJ%j*ydk z7$&5ZS%tU4WD&CWbwJ@fILp_D^U|%kD5&) zH8py-n$S0==ZT$?qXV9uQNsdymPz*mmwsce)pxaO8PqT=&{F+0mKR}dsXzY#yVU$e zj`-fpc1x30_@GFVzmDv^7YrJm95c*cj&76i$ z-h-se*+Jb6LQky`cALQ?|_$4HZ9{6A3K0d zr9aG+Zo1O3{E5FqkKE)NHa9`Vf z*sD&t)_L0BkZp&Dh*`~+R#cHCS|b~{4kz6uAwk@W0*N?db@mZa_>;Xr3<75*cQ##S zIcwgQ+(XHgq@9x;R7?JG_0zq_W#?^Z-m(141|_yGm!!{n+F_hgkkT`8px~0>mGb+0 z&l>-EK$QRd6pwaCiynC0G)sN5RK#g!N(9)Laqc zCRL999|IRIchwu)z`%-gBK(Dn>{6&ht@R!pIoVk?U`g|CIR5qEpgxIhU0%4U|yeD3w~(0?zQ&q*A|B*C}&GP!q72B zjgY;g`scAbwnd$V4+1PX0ibIdFwJ*hNh9N}Nd>*~*n_`b;lB+&Vm(EiG|vYd{`I+l zwm3T++Aq$Kns4etNp#!Vw(&d~<6ow`^XIKOBtiL~6!Jb3454xraDg*`E86nB_nxl8 zB;2U*!fvQDRRu8%+znz8(wo78&(d#2j*P6h=dbBqB}!0By6a6v=NQ6%GChoGsQSgd zsji%R=BqJ^NoER6id0VQ8ai>7mM)6SABGo@=? zM!rdgl1H{4W=^H(liw~Z8`vIxHDMz9mm3!tL`nW9;T;%o`VTZB0CVj8awPfKC!uCC z6a^d<3?q^Uigx4dSVo#BuMD~}zaSd3zx>fvXoh)Qd2091wKBkBM&|1Um@;sl^O4@a z%;05T_Omg4FwY01sQz}f|Qajb)+~c9k z>rmM?U3ECr{%RP%E!>avF1Yv9>d3nAOhn%6aEjNUn0O534_agaUdcCYI35={kPT^2 z>RTJ(GH%Yfoz9pNcef>YA^)H)L!i7?V8A&@J1^M4y>~>a*bv%L0BUR*rY#7WAxM$m zH1M~~HMrNd2vG-a{&o$XG~ktWHN|6VQ(?y#;tazCCAN|&YyI!*=`w(HiDcD;C$COB zm<2wsB&er2f=NdpUZv(t@G&q4pSo{)iR$m|dV&gTMvYW4b_ zh9%}hj#boo5JzG#;-vjr{+%TOE2Ptf`SJYfsD0ULi3A5_S?}+4bnM`9A=i^_@ zv>5ml*yvV7N5yG#)}H6=*c`|_&+VgjrW)?!9QmEj(EOY(XAdE}IeLu6jDl$N9l&!*DWki5;IUjj~J2N?QcnCx<&_cXAQ z?5>eWJ6sRTR6QaDgbC?SgW=0X9fc`MYzseO^pK$P_CjU|qOe5B0w8@t(x)hc+_(cg z*+pnS=fuhnpzisQFK;P=fO4ZFBmEZ8F(k&9Ea!`whHJ&pDJpW*0e;-moo5%s zzjJnGS@1^qmDBa)?Wun*VZWdI#({m?{C$C%1QpQ&>ycplp8=*VAf;AebII)9hfu*; z(Wu~Wpj+^s#ZeKF=PH0Xo`Fr&&Pk>ii|tzmS{|r3h!jdFNzM6S)L-za3u;Z@;XZaP z0#49bp?~|NhNW{0n)8-Ebj+tJ6(h_J>*#g!-dXFQL5{)C_K#0f5CJr4EkE*o13G|a zZoAi@)5io9mLdancSCuC!g4Jfi%a{W=Ua|ab1;+yI0;0$07FKMPKg;e$Z~J+og2t> zMRWu-=!GUx_l+0AJLLh2pPYfFEz^E`xwLM-GSiRg)38JjP|~vBK9}N5s}ILb<6Rz3 zLVlh_N4iQiiieZ0TaC^*t5rEG3!k}hdhUyn%2n#DiH1!A=Hewxs~HahY{(|c^M?_7 zPWD}r#NYcQd;)T_J&k-k60%u6*^cuPwXxx#c9ePToqkuURD|}WD2FYQ3 zlVl_onTqvbSS>xIzNMclodo@380t5u3sTN%q_lxBQjlWCob^&!n=L_ z;bxU4$RGumA|q`p*%Zn!B%#(5zKIt(ZGCikugAoDX=OcAuBN8Q&ySDnlnz)Wn}~JT z9B}H4@<)HT5cvUh_eUoHj+z<{?+q{m?Vh64dWWIv80# zgVx35!XIBoSE6?4Pboz@3lW5DFFhzgM=>!++m!9euHPiN=^IfH$0?EqZ}28j(>m+s z+aGT#Sb;779>}?O?0Jy;k}mK&79>NRce2eM_$|S!Hq+{ZQN{5)y1vDr_C@+ujVo}f zH_cyh=D&`q8vk4Ou!20VQ*cEr>z6a8&sb$jLl?{vZfsWES-D zel%&ll~BU-5FZ4af}_vxxC(A;`+>?}HhBjC!xNW(aXRFNlRYv)ULv*6X*F?0yo@S{ zqeDmbRjDoeE9IRleu(>^Bewf5XorPZ{kyZEsl)y1&h0M<4Br+ij=9HBD)SWU#t;en`|QhH@KviHAt0@I~dkB zX%o-{133%JEB4i1bJa&)`asC9aenjhxT}D6cH(v`C2}gxzU`P+jcC0O!BQBh#_t00 z@8Lp@>pk{!&idwoXx z$P6@jiXH07cd*lUBRwyGX=mwzKY#GhCTD45dak9X(>LUJ{Un*-vN0v-hz~Gk<9Mad2W|Uk%B4jL8Q49SXy29 z7fiQ^-dMzINHkl4!e|?Z@#Zum6mt7nlIEd=y8v8;3ac$`o5a*L$O>f8pTVgyee|pN zAJ_cV?UN*s2(ruP&#C^WE{R+}v_Hss;{+kM)Wy9(KGN3k`}eja>z=_ghZ)yK7}4P3 zuDxOgFcE3%=bHk?SfJn5Rsj0J;DS`v1C3(WJU;#6LX3P69nL|&OB&#K(xKDboH>?V ztvhrnh{)CnY$ifL-NqH-S{GBmvC_{yjFTwazH~<>q&UmGJvD}Kd?O8;L`Rw!h)(B& zxe1v5wwn1K9!>yhb#kUE7{K$;A4hG1F7h%f=T)#W1$MR0y4vt}Ms?o4Bqh-cHGYU| zXPDEHeux_cMg0upke->gl<2JVN1y-?9#srCuoOSP5k_K1*8d~piHL9e5&qrRJYWa~ zQ8@?Hrw%t4Xs~!uyMblH%uLYrK?h=dC%my)Vaj~eSux66hCH10fMHfq6J_=&S0OO< z%~=kDno3cGN+xOPN&VCSv$V6!j8YmQR&uCx4#>SZOB2H1x{>NM=#XO?dR#*0N7dmV zRe2%pY z#4qSD%Bkhl@^f{)K(xQ8%9%Y`-mru6@GFwQR(XW+T|->40_rTE+0FHNC2AW?(xkjL zQhOcS6~(=@5hz{sVfC?bZ zh0G-%z~>s8}WyT!2Q*?M|FyD#n2-y&jMg>hp^ME zfjaDT)F~MiRNGGYPhqW!&?gacE&hRPIij;KD|oBwCZaWcQ^%_bi!>D^Ha!-6?(380 zYPn8H$0fKyc*}Kq9e?Ql!e}FG-jczHN)R+iK2}jNclUQq;ctWlh?>5xPLZ=i4lt!$ zz^_^!-yqQ3y+f7KyR^!`vEbf}2S4`vlJeGlFYHfcqU4*&f9i1wP|lnTW<=|gTL8Z@ z8#La!uKZ}lBi)T?D7_!EytN&y+|sD5nwf*zCXgJCBWl_|A(2g%aijwg>;<2>>>JJ{ zUwMvZJ~VO54&T1R(-je_{y}E?i9oiHPJyRP8xFLA)2^OyKH4 zB8TAkGJj;QkljcB`oZ>>Rigi(fc#}a{-+mf+j!tFGR$9)p6z{$508(R9Fd^>DJK4# z6f^LnUGebEZAGwYnUee`lKKBSNAXAj9u1mR^2`IG>;3ZY#qWNmuNsEIz3L8j!!@?g`P3bOYkvAk5hKiI4CD_;|j7EE^fjamaVmi}s#5e*+-p**KYnyat z--A|M$l+QE;^DbBCiPKiC%5h(2?$7yXG9<7GAVKxf{ zRX^ZWy4)v3C_F1x0QK}hgNMl#d@yEqYRpNMYYp^}3o%3MEAu_jzF_==;>%kp2O~lO ziPu|AXdpe!&WP8~48lpVPk;fFhW@A;PH@PyRCzxIBHmjPvfC4ruiRb%-2_oHrn1&u zP4zRk@EM7aH!cN<{b_7w#5WP2OSA{cAW-{ZcXy!Xvsmw>*9BsK7sT8d53t`5hSGEgFs-A{=Xw^NYAOCLR&q>VYZY?TVVtq zkg9w83gLuZ1Ab-T9UNe%S7km@L@|pZyeR^0(F$-@mSpSQMmMgDP#e^P)LetcPfxLgieVC)*vRkmqPBQFTf|Un z3$v~p){6Do+ZE=l?1pU8D)`#3Ye%3Q!XPx^wAM^u7!t9chJ!L~2$4xQmZIPpT5e}R zN$W;B6X-dQ9chb^r*cu8`Gy?T#H7JOC`Gc&bI*QQAc1rLu;2{4N#4ff=*G=uAtbX@ zo0k1P`s7CNm;}i##?q{~5)@6lSrBSE?I33~1X@);l-gdn=VD%lyOr=Jc|aQ641x3Ign|5-Dci0V8Nv;rlvy zp7Kg8mKNibEKf0dBE>n$AQLw_<4>zE6Z&*#Mxb@{TcQ*XXAm040#qsoX3((W8graD zr}K=fgYBjIB7|nGId6dqW|jjM#-M5Pg54Y0qXCO4?i;I(Fpm?Xl{WT$+OjyV+FBTZ zgQBe^T+|?jpjNxX`nwEbbv1$2Wx@I{mcr%~iy1}>FQ?kCRoHV@M@phqc1?ZAJQ95x zLr!SBam3md-l0LMKOrvl#=!(RSyE9*PM$Io-nu*$x&xlCRxq?Kbo1dQ!ziT1QOB(G zX~bYbwABF$XA!@;3>+YpcmkM2Zp$L$7bw{1X~g0eFs5TUZ^zYq!#cxKQkf{S0YPSM zD*IYOf3zfLZDRCsiNyzTaf=+zxY3ht6O>Blra_~#JhQp=8HF3PlmC%E zY)6x2!{BpKS; zmZsrzFlo~X=~^v~v0se0s2)mSK7SpNkVUeN8(sJJB39nttifL<<=8g}MU<#xyzwW` z+YXdM;3237j$9rR9&<85>(Xek-qmy4L$=5=w@8NK%etmgb;hRe8hmz}vL=YuGnRVbc9FEwc({ zRTW_7Ei$U%-FyUv;|6cAaN*;I>>CyXCQ>bzhf$X=mkpiri8QN5~d>Y(V3fP z_SF)+T2n{To*k7?6HZz>@XkiULLl~|j{MXK*CSc+Ci5WITjvbgDM$3Rt7ojq?ucja zqMCJ9Z23`43XEG4U*EawE&~`?UrP`_QsVDZ=r76*_UN^&eS$((&oYr@8>kkd6fdA< z#O1&(RatJ%tKj7E+ph!t6JRB=u<%*h@Z$$CErjewkSBO^xgk=9Ng6M6L@{7`2xdAe zE%+T*C<4s5Vn%!!UGZC~pgJ~~IY7)qm;V}59Lk4j2jNHC)`_=^yOH9Xq{9&UfMa%F zQB>eM25{$RllW8G(U>hv#;O0-Vk*|iL_qEa4Ot27it50?(9(QIe&36rzrSz4~6lKH|> zTBfGbJl~Jf;@$&v>g0TrUn{y9Spl*LjO!BU9UPZI&8ZGsO(`Fa@T@Fm# z-@IiTYt{lqVtn{awX+lQc15@p8w`DdMHA`}z~D}uBy?p%FHJ)yO6 zE#`n$=IcjVBJuv#h5EA)))eq*TWm(3k4cQ{(^$&Vk$%m@>f)^5f|IYmDQnCVKVGDq zn>4g4@99A|;1=Le2Duy$ZQ}Tq4QBp#M|Bl|&A%)9~oX~FT zuufzf0;6V%ylgQ_#5F5EM)jcrt;qPTG%KQu_TksY^-)vfEd&#f;FzS{VE^a;3g?o& zyOLpCTc*I4kgkbIGE=9w9-A$BGj55Im>^-EM_xmnLn`G6?t%iCIsECIN4%Np8i`B# z7~))SOOx37tsM%o)3pI)QYH&rgn6 z^_Tf_EVRS?w3Iz2YFx8>+xoh3?AI9m=!Tb=zAm$J8{yR3J$w;)KtMNNOhYb&{H8HkYyYz_7>%XD%0M z<^^QUk8ryOVJ={{Vs2}~v2MvD22Bu6&n@OlKuh9o5IN}uajw8#kp^FTSzbIuKfHc= z?6+52@_Qve^}znxkDjoAjw{=?c^4?HFKX){BQv!M4S%(uam|GgcFe+_2Y_<&5aAIX ze(-fM1nlL`HltNJ`JALCQ_COnZx??fPn&>NU;;WofEj|MqMA!EL+A#bE_yq# zNo|n^$31$5PH7w`ZeL2R%nwrMREiM$j2QHd*eyqkrgWpL_W-_L=voGG7J0^iSJssj zhyx}Ab#2^eQstb;c+-anp`l^G^k?*C5g+Yr_Al$@Y*ZJ(tsc;54NK$kicFmM#+#4p z)PjRd#Oq=@G;^$UwHLuY(0FeIDoZz;Nu(iD#R(ApsVg8~VAR#*VN~&uX&S0tQ@da{ zyNd)4eBWs;0ie!!q|y?Bu)FbV7;cz}q-j46D@5zHMWS0~nwU?{sHtWl3(%|7_9YHh z!8Rt7W;qFWidhWp^Bn0gfD{p(1WJd5Igkbu%;VC)&a6OavPa~7q}i8mW9P9MXd81E z5fO--cEJ=iNb$x(c$}LGnfCiDOfJdzJgC{(cz_%1-%x=0f}s0d-2^7c|M+B)5f@IEbZp9MWL zoKSAxjxIQ;NN*pht~org?DTJ0oD3Fx=`n} z=uf*5IqHO&S_f|3$)lg?;jC!)CR$&KsSR(|T8wz*)3`I!sY9JahU*dswUm$o6-eeT zF=-+yPNLBNI9_gEW}$V415&{0rt*i6H_mCd%c?>4o}7AJRRN8nW*S{t1I(@69Co(a zpO(WohiWh9sCcgvwt&kT6EoEV6%3Brt}el;0~!xZVD|98W5}{q_7s4A5B`=)2Pcv8 z_&NlI+_#0WP4>r|!Z#zTBgm{CF- zpLVtdM-3cX(klgHV1L};!zEI#p3sVn^gx^@3+_z;Rtn^es=Tv@pPKc zf5vwH?ZiK_A(!;V46#c3u0QAmS)CnIQh9u|2+Dlkt|3kxb4|r1sQXULQ>z?n`Dp+R`X3mXGv#~&6Uo9l4X`MF`^VxSk^z_h2@IF>tOaw zx}2RTf!vK-CT+-T$Ij1-n3?xpmz3)1ys?-bw3vFAA7ZC^X?|wp^q&%8Y%-5x6rYeL zk*Ap!pG?8?w%Lvq-LOft>Aty~_(fk5vO)r`i$og`6t&-H_|cUNUSZ&zAgS)-b_OeF zY8ft?8q-I#gg$=aLNMqfrBHlSJ}}@lI6E=`wpyqc)-7y7lq1f4u=o&29{*ha zhE#Ycy%Mk0y2(4sq-%d|X;6So%2y}=g)oUWO6dJNrajL@(=iT?{oV! zp2tFW)6l55Ho`Y;kdu?;PQe?`Mk*ryhuWI=^7?T_0YQnyBs8P)>eAKcxsayOer#!K zTGv8Zp7Vj8BFPX)SGd~8M6?w}K|V0-R$t0N)#piOW>ZRqJ5FDpC*0?eyn8zk?31*t z*UKXD>{>0Q<{X?`&t7i>^w zPQnWtUK(5rQDaKLRaQGoK$%_uW$zaM!4rBjf~Lphyt>GMs&Spz?b~R!uMf<{mddj! zTyzhacHxuGLOZ^w2MxTBL!?<;U)N33y^LpFkhVM<>gPV-eMm^V zQtfaaN+N!*x(3==y6BYIiphzh;`uNl<_o(K@vYe`h^xu-uszx&45osrn2teZ8im+d6!_O@mTbqY}r7%N) zt#swb!A-dS)gzkoZ-dbI3SW0-R~xBYp8`BoWz4S+$TDc0L2&nf7RDgC=l{ab`=V8! zDnkge-nE}jYOx$LG2r2wujz2uP@>CIjABG;=jp6&@$#t{MtbA+0`h?c>?c| zCa0;ZSm)o3*HJ6cRO-*8+uL%Xd?}4q2p1>w`LqmX5x=8z$y{wM7&_YVYbm(}XGICB z#wq86f}iaQIF`A=d^%Gfm5xagaMsOonr+EG&5XJ1f->2_q8>tBD5Eafb(UfViW<}> zQm2;s4Dro5?@194TdCRyy>wX*8|u#ja+*KqVKb=rDhbqCJWh@lOI|Sgz-HeL7EV!O})GH9DgeoDa1QGe)Em_Y`t|>Yyy9s21uQJ_8 zl4{HOG0Rxh7JS)f#s2JN5+e@HyL4`Gby%qCSi~`k;o`fewAU$SugF7*$fVrhHxG&m zv+{wR#wxR$;COaULBjye+|Yr9TRP7-%#e7$r$bKicF`2P=@tk6mfANhOemVac34(P zH7f~y(ycE$hIdzU+VMvr`Z_}Im^sbvy&>7#7H%(I_iAg}jdZ!juWBbLDNAMDRiQJ@5>j!4Ex{ zmLWTf=9NO)l<4*;8y0Hy#BWexs^F?VBc3WGZT^x5%0Iw?aMp5VSzD5tka}_?9I}a+ z0|e`ALFagnDa1$oR59nd@RvaL-_rlht%mTt4#c^fm!C32N(~>Rd0}RD-JNPsmLnoqS?P)Yyd+<&+IA!q$FIEzHP^Q@rA zv^7?a#bu$T;^qHFdGToO)Apv1$C0E?V$rV{JA47sTKzA8zx^fI{iDBF-S*c%{Z0)V zDYsTvj(+co5R2sK+P|31#{}7T{iUS)-zyjYt*rZ3_zH9t1@>c^8aqW&+2 z;olPOVE#aq>bD%Qdy#(3c}TOA|HIsS$79|1|HG+BRLHCjdnB0^A=_ajGNO=?QFhtc zva-smBoz{6k3zPr4pKz+$lfb^^LxE@c8=?NUf+u;7~K?zgG&zbg>Zm(P1tvq~wzs1h?c$wgC08CLlxNuwg_46x09Qma_ z?gW8eH|z?xGk1X1C`6?_UteB0uah1C!(uCvb)j;dfJn0oFhPHSTIchI7D!qu|0nZI zgtCl!K!Pv@TBj+;PP3C}#{`pGv2`vMc`Wq2n=Fy#nG?f(86H_&ipu6dCftVI;2p0c zWUQeKjTuP5A_8g?Hrb4l$QPd5YcsHp=X4QS!oW?XzeGER z`PRBc4JjRT5H|$>Zo1UF7dQue3(6%VzyTDzUKa|L8qa)v8ma5l@0ZPwoE0ocCJE2w zJ$vvify~GN=NIDs9m|ws#T?6E>y){+xg)kVL=L0pY=;rJci-Jip(z0}O$Q$Ks1VBP z*VQOgERHyys5cQXyKHo`Z{v%M-S-_>}f?p95XO$03f~`#EF; zXf(ut$kY+z@(_mcbJ6=W#yOHS5;IYK%?iqjcqQ#$2%v#n-5|m#h}|H%>Xs{)Rm1f{ z`{W|n8mtPoQwtVyJOPo-RrrAzZg@ttcuZuYh$dvs$(uqF5u=}Ct9|?6tGduN18s}O zb(evhCe9l?mw9|1=J&337kYP@Ebl(#J3x|2I`lbrBRNh>3(7xB)h){V`egfOoWd>r z^mK719k8BffaoQU2e8;;&>5lfAcUgGQcfTaiWY3jpkTVy^4&#rtNUv-|LM-B$bNuw zKpNdpK+p27^bom=vW|ej(nxX+2rAt&Lwnq*sP~Zzn0Qa_rY2O$~E^ zh`846IKVhiSK!qa1RfLtuZiWN#6I~NA4!Dxy<9exfOKAKB>C8*-c^kJ_s~5=F4}Ot zz&3GLd6&F#~xV3eIs_VW*s-gtAP5XeEt7tuZ_0;)n~7JRX}DjNN7Lgrveu>~bwj&;lA zkCGyE(EF7U)qqaV((8SaA-V(|kOr{`@&!>3N->_ZoeD zQZWjId@tB6FPJ}hRnXLp#CRZrsH~U;^=2nMRnDx7zWs~%t*r*nZ6iutVS1dF_=R;N zZ<3Iu?TM{#j+C;KQ8Hd@ptUu7_3p=euq4FE51)bCOl9nUQr)>FreS+a;Z7}jD9~B6 zw*WSUM(}QaWW>k8rRQv~j|cD#gi79&Zb=1d?H#V-DkUqBn3o5^3hS-0*bAg)Sw5y@?Bn_eJ#3<=Mw3Hlj(TR_X+p0=fON6)H`___wWV90@LBtuY9fAwzJ z0q)5_ZGJb{^)moKLiWs0Yd3J7k#P3T^yY zBF~lyDPS_SsopwY8P3*b^$m4S;RXZB%jvukZOWpUPSd0cZ386c-wHKSAx;0O(^B!O zX+I7$m4VvVtCDTk0b@Nx{k)PyT%!x|0 zktpFN<>6c9SUncOQaBH~q0hkb$)HQW`hehf#~hjzo@F|m2%ME+g~6mOdT zKlwRuZ~l@hwGM-E$Jd-JPDSNOB5dZY$6;SKS5eL!cV+fhXH2Eq7t=}3&P$Oi*bY%0 z(~fy?^`#c>z@za~>pIU;^^)lG7Bxv~-#QA6tF{ReCs=XhJXpa4)9XFK%U&5$${L&- z3gfJoBC$`h;wOx9omBFZ`!pFd@1pP!!Svv!WW%dCjG9KKwxlm?#6E`uDLt?icYzXthM+Q3@vh-bd}{V$gGM{eTdTF;!9Z>{ zBu&1~n$b%QuIBCm3W0SFP0_Y%k3Wsu&?|gOHpyI;@w&6=Z6EKhs%(tkxov3gVx@m9 z?doxt84$=1D!*TwqsE=SeWY1IlKS9-?+X-t=3ua{e-L>Z{5bDZOpv8YW59(7nlO>( zqrzEv(ro+OW_I<8e#1iDl77i(Ox|l1t;|trXJy=*w(l>hQbTiNTCeRG@VJTRC~up{ zPU2D%J>mJ3)cbyn@38JF3d701>2mo^tkBn&>sMS;)Y=-7^pvS@HhWAsQOa8APJ7yj z_rlpN@k+aGGZJO*h$LDoc|~&=I5hHW*ibRym!#akT6RN&Hg$skdNWQ-G^B-GkghPY z3fkVWl(>l#?vUuJ$+`^5Ig}X@Q6ylS+Ox+v@2?dso%`x_Uw}m0y>sFDqWwHj>dn1t zjOzdfSNOLEa+v1oYLrAqawHO`3txTGSt#*pMB&4G$tSH77Kr9#c>jArsM$9+F#r#e z>wY#J?{22kr*j{9yG)E$o=Iuy=>6~(#?kWd&%Mgr^BZz4g&}#a)hpluKexh|a~H(z zXK4<})`!BZ8#T*>AN0QoPS^C493-d!O#@i_Ga3g|@vm@XLLqx^o-Na!Jt>t`?{h8_7YXPUygU*|Q(r#VOV4E@?W4F1zi z1J+X*j(+|4)3L!o`g_dX{SQOWet&ek|M%B!^*6WMy8;gZpT8Mc^G!+bNtL5b)A`8X zeeA!TCZMPO--EOMP?3I3y-I23=5udX|NGzUEB`RZBi+e1oXCh!>VZc33jYU@p~v|@ zBWb1^d+OCpy=k$XO*>bZvu^p3WKsjJOH56f4U$gOb-n_=ivk$SeM#g{Aw6iK8?0@Z zK(LvP)|ZNE8K|)O^y9QROoo)A%fKp7I&+E*Y;40q)PVsj$p!5ZrDtcVkuTwP8At$j z-ml=*P$6xR=LHaRS^v`*`O6`*`W6V44l77rK+D8!_=h`8?k;j7!91sR z@5MzpIk{eFIJwm@`umbR{VYfCJ_MS_QfnJB*jjQ|UW3a+V7ce6{#}%hl$6QP&XWn2 zQ$Wa7IE5oOug{c`J;aha4y8rhrh z@gw(!OdVoWkhD3~4MkLOC6s`&FCHgDnG=(D?6x!@491xkjBCP}CQtin)T~2kY{#<9 z#;dYLfFZh2hEty6F9i}LS6b>9qg@9@l>uBSs{Ll#jBc?PWwAr$N;L}0y80w-@M`tq z>fB%tG|(7#sV(KKz+90Jbe!C)w4}mo1~WhVE2s0%`D?RvwjwpE5WUB$!6K$)`POqQ zs-CAPzzY<$bz5+YH@-uwj^lJ^b*t?RV8kE9DV(BxP#yrxtQ|9{N3~`zc1s?hHQqq< zJVV)U&_cpX#m}4u(`#LMmuHA2e?v@~g%LwYKj{G+}p23(|a2Npyc0#El<* z&cTOs7kr^_Gzx>H47tjH#Op>R3EmV0DINDKwn1B6&O^lABL8tDa^_^fMu*^lyeVJu z%KxlliQ4ABDw&}EAd3J@B=pDc`8_$e)eaHm&`m>%=Fh*=Q79sssuBr4VEH8Yx{@W2%Lh zfoJ_13D9>Uu$@;$XW%`qpWLvjdWaEfT2@~4SXJqAg%F7NZ^&~Zv5j1E{}Bmb*BA#E zF{m4EOU?n?XS$;CGG&9vX%ONs2beUHQ|2|S(?~VS(`MhN+CbT`=Fj5fT5kDyU~A;B2MWUSc6o0Jy-Kxs>tNp ztu)9}i3G`~%Utkrl++j!jNrGK4R>+KXi9&l4O`Wd`_%B>FTVzu7>Rq|8+925bEUn@ z2O4G}rL<9x29wI+2Jx7jP4&`?a$cx5`1S|W#tM#?gX;Y0pG_F9A%ySA2%CO1NE{g8{lri@QCaYn$$}CjTkuT2zd(KB2 zxLzpVEw^2X9Ygy^-pG!sH74`t!vi?9xaYQqfeEa`^)-XY73;L^&(WiB5y{k5^l4IH z{WV}kYk4DmE*7c?PR-|Y#0Oh+;3bfu{3A1P+zA}|FLO7d9ZRd8aA>^aReP0ndn35{ z6pJXZbLr-qj2V!L6lc(q(kZQ;q*9LlEfv%0k3h}1jHEOz!}0Qg_~Tk6RSL&d{UMax zzgJD;>?B|A^>0bnKiqEqB-y0~^v~DMPe=vII05fy3Kf$TkwS=wu1UW&X!@EjOBHrC!7yYBOTQBgUs{ED6kZ=*&GG9l$r&{h%fDj=U=9$IuUT3EkV*7YY-KmPsS|UZnw0(zSUan zPtD$3J?}<&K8*Ok+36v0%MTq#xE8ZNPc$;o(gB1X`Js99Xk0-sI$`<(_bAf>He z>(9_Wu}VA|bEQzD*#X?mFL*jXS9dAwszY|ClasLCQt0yR9I9}~AD760;m#v4 z_JD#~6B61Y|MK6`fc}>Zo>qGNYKT1aL(qgqE{8pKMs+pb{Fds+wBS*^kN4xnqsR4M zjCIF*;)6y0or&(kbu~o^C{I7Z;$5n^=o8MP8Yu=spj5!0h(FCT5&W3 zWDu0+ENYu}>)FKW87mR6282oOz-*%#~1I_gLz^uS@4Xnh_ zH$Pz%=2xCgpCZ-jcYW;|Hz4;f|A)o)cXmvzcw(|391jW9{{_nUogFiO$;=gF_kT}c zt>r=8m!OjQQnq|{_YeMe^XKnjGeRHS9pv1f1O8{)%r7h+eF%Fbe&rd#&j^1%z<2-s z|8H1_^DhvB3{SZ`{R34n1iyST{RIq);MQT%IW%IMrSTWJgdQf7I5gftLq^SynexcMA`u$+g9@`mq5$8ut1j=_tFtzIxOgEu)!J)`s zO4l|Q1AM^@1if1$9TcsweT|dI$MuT;wy*#4$<;cHH&r7~^KLpVB*NINX5qht=7&He zMB|j$JP-3Or|k~BCU)C0P|Z=jVI#Y32gBTLz?Nkq{VLj)BDkOaTpFDG4PpF@y7c~AA=MUJwthPl(chM5b-gJ4+cg6Z zSUh`Rj0{eDYu$d}^E+5THja%!;BQacoDRwqf;^!{VRR8S{W(kD01HnvpWw0p_`MB% z29dfh7apd7gW6>1mAD`^BceX+570TqT>#l#4@`X%@S04{J5FitS{#u&Pr$0P{ftHt zdaaFlXaa`^GShjJdW>tvUUe%wC_SG6M8pA<+36Z$$W#jfrR;gsJGnQ?_jY}PoLI9b zr68fr9ZuQJ$h9iit2!NQ5#=FN$zC=E!A~x`Wh@kudmX~^|eEcNbFZh9UA06 z!@Y!vj%@zz6LiSCqJA7f3a7|_FSoptM=@wSM0xDDA;{Bl2ayR*_BpbVM;)u9X>wFX~mN&!SAQYCTFTwTi2wu072{6?{4 zexH;mP)6UisDAZXN2B{TDBWg&qj;yH`2m&0X(eG`LilX1{3>1BGeD{bo@>I8NwPb| z()I9ww#FreW_csy^USJ+X=yP)alj*Lx4chD19Gd+^f@w2ckc>xZW>s-Ra0Kmw0IQu zW8>ViedUhPtY};Ud+Uz-AirLw7WQBeo=nvXyQ%=8le}?;ROImtP;rZ`_8nV0MWezJ zNT!12^_1j&72utE505Wwa>sKb=EEH;zV}66k2IZKaIQ~&f0i~iwdjpWjK>PsZnC** zeHER6)u~f9Y4n^@oG#4`zBO!qH0P4?2BH=Z66~8H^9xeXm7+Mq5W+Q2tr@!X5Jsx= zWVnDj(yBSsYikkjeeqg2F_v%qxcphk1a4K_>9j2G4Y(6HS`G>+n)^di7Te_QceQLD zKgCAK_$(RH!gKg6Phv24`aXIg)!JNU$N6zuTQ9-98sj7d%p+BawVnf+Acx76BnKe&OoIT>l#V6;7sFde~^i7d9!Yxa8OE^2Y8G_iP}*0)hyHS zMaCH@4Uq96z@65!SLWi>vTSIte8Y7VO7_%~Vh@HZnJ@T6Q|!ZLXFzl6?ON8Si=T^=tE70^E0*Q~Ee(S0fum3Ldq)Lp9;sqPC4{mwDS`61DE3qa9^`Z}Zze+G>`? zFuMacH==)*T^E|XHz!|*2!EEV>x^?5x=GN)WBuTs_064vD)>6uZ5hbt6wO0Ys^(x_ z9ef?nRf2DJ3N*cRvHJ=*Y!vHFm>vkp(g@opg__5lGmG$4KA|F4b26lsTuGhZ^I0gx zk!n7nXUP{%2?(-#p#E8#e-1;SfE^4LM#5BcvX1cO&AqqBh>p~{Bp5!C3J z@7^{Ql0F{LKL*7Q)Ug?ckCX)FghJpJT)trjAB^7a2h-;yWlViI6@{P$}a@@ zVxV6{#=kW9@yV7RX!!^x`ES1kE1u*>I_!;pWnsg?aNU1?wU;gi?v|(gYKDT32@Zxo ze-1xmG9-DPCl8Gxf0a651IggykQvbY`>#`YfKC;VZ>B|!ToO5}e`;vnj{>KqD8+?M ze&xZ26=;l;lnStw?3MXU4zIU{`#2`ksZlgH%Q#zfVXa+<^=a7?xI7Z1wiE*_-Wf-@JPZt1tPuK==0yj4C#?7aZ+jc90h7 zDnXu7z`AB0oUONp+F~zM=3Xq%wgz`Z54cMQYl)GP4^?+4e1|z{6jJ?@6~5crAjds? zl>#w37ASyo1%d-{k3`WH0jdDc`E8g_P+3Blr>Joa=}x63I=xY-H;RrE&$Mxd&;Vpv z?}j^Z9PAjwsEw;^8^DPfNKbDu?vkQlUguVeQAb}jr`|umkAL23%)Su&p|OJ6o4KH8 zd(IN|aUd^#ArfJ5z5jCNHnagcf{i4Yx(>739tcKncw_LA#q*&(E4?WPx={1MJCe|p zqq8{M+l%1Txgl+z7l3h357x#&0rl4&ke6;R6+16;-7xaZ8v4BzbdD$ZjMbYL%){9o7t_2Cg&~|AapVMMHj<+dxB$M^}JK6qH`XhNRtk z3Goc;<5)RzN{dNA*sJAw=^&gE)ydt3k#V7p%<91u;-^b!Ji!yZxd`;kuB#o9%cyBW zIrTDz+^gO$QWb;KBqYsfe-JlJ57BC2PkJTSS$%2jc6_kL-kZqRnso;IokAU*0 zw)x4rYHWVpYS{6=?lN}r`M|+26iFX!)WqydT3!eS<9tA{of>r@$d1RUA0|;dP))^b zfc~WibYC+oX92n3QNE^a#<~K5nfajk|8c1U$w@MOFrwgb-xN-`a)XxT^eIYc9(RtF z9e}IV25{!Zc?#!CE(h|w#*BqGpcwju`%4rQvjQJ_5vsADC9W;1Wqa9}4H)6-MH z)4yy;4HiVE>qic#A%G>q8SFNWJg|7atB0rm7>dv2xnmR_xM{3EG$XUjWXO0~LsFe5 zq9xB6fbK%zQgUPmq6YcGN@{NN+B_KRF$xupxEa>d5@=++5Og|Y>}Fw0pxr$Nm{X4S zbRLDy#U?BUg$E*!Qf;x@&{5nPLpnZL-LKO&po#6TrxV){IW8fZp=V!H6yY@{LnG}E zx2M6ZlszOm-)el)Is9TE>5=;TQ!*HTXWwIehdYQ-9nsU8eqln5Dn8Xpg2s{)mRVlP zMfaRgPv(|{2Wjvi((lOe`FF8I-|fsX)9brW+UN#&?`m6sQPV{lKoT8~+E@)l<&Y+n z@rr?6L}7qN%Th(I3PDp7p=asxLjw9$Fz^MhJt6qaEP6lCNbMT(rNCQBAj4tquowPO zJezoN%HVrs#L>T>{mJIv7@CeV=w9@?Cph9Dc<&4Lft6A^2EAI$&UW>vRxc=h1h8M${#z%l^AkNi(aQE!ckCjts!;3r`6h zM2$iE3V4~aj;IMx;f6$i0I5_Ql~{jh>ec{FayRe~vGLwsKWoN|;DE|UZ1~&fNRQ&4 zVFWwRy#`gcii0Ot@z%mhvHPW!V7xvNg{ijm89ZaN38W2b!TBP`d)A3z?~f|J@$;y% zpzO%B+!g}`{H@$YQv)Ar=ceJA7ASa&R03;{K8K-{Ti+yUAik>$cAVF&zBK~>PaasF z>j(G;qKZEekX3>nbt&RWz+g0KhEaHafrK{my}*$4gEg(d1hO`k6U-iK55-%cq9Pb$ zK8%B_FEYSL;_-ry8R+%On|A!xncK8@t{o@6AzU{k{0Wf=B+=IhYrbMevSyl-ABnZe zG`_OP6IpvZ3lExTW}6pED7HuZp1sDuj`*A8;z<`@NwCbr@jT0HK^8x(z!^f#3T&Wz zdDF!(@2dERc;z7sx@#fpJpODEaTU{$mdTA7w|g8rpX9~Gi%E6w;zwu^Edk(lCT2AHi+B2 z@g*W9K1{|;^}6SEwPaMb6=3VvT>{aes(<@&k!YXvbHO*fNoRfJ#Q_;YVZL(>M5=~p z#|{78t%fGJ_^NyulypH8i=rvq66W9;rWV#d$-Xfd&mC1)y<$58XYA07FB|D*r0>?tVx zV}SJy`}_NsA|(6kNxNH5hW_zup8e-A{OiM;8*nnLKBqtMY)ygjDeN@$hDXgA6%Oyd zT!O<#r5+Qbdu2@QB-XL9&*?t~*Yv+Osm|@V7y+wj@VEw?07<(@9nujX>sbaEJkUgA z2Zyamae$^g5wc)CQ`Uk-9yLl0R+}qA{(TRG{2W#0 zW(OS9Zeg?`^dyfX2#;r>X6k7%GC%bIfJM(49)e=Fb#1CC=ZqVWNDP1+AE$SuGNEYW z$Spv(5}*ygKZk#Qzv#7)D~VXP^8(jj`C`Bc;Ds3&vhM=M^uYq)z^6DwK_%XV8)A2y zhDVb$8aj(W_g)BrTrR3Es2$qsBM8n%A?e{fPio)|R8zfG3enAvEmL7k6H9SMG2@_f zpPTH4U#EBVy=c6t)(W75b;q`lv++Zd!s!Au1fuy!+33R5B!JqEFnVO8tf|d&6S5_T zm*?Q4N1>E!gR7t+k8lPMaH(qwAef`iw%{8n05!$hG^r3YIj0#rPwapsVQo6=%T&kv z9+)&ofg0n{_v|#EAF6#4UyS0g?t+eZ)#bev%D{GePO9}KZ+AOb)5Khv1ru#s+w}p> z#~kQdY90|g31G%QFMjlhMYDsZ6PQZ}UkuQYzJL(K$S(L0N*=SS3HY9)hB>el#ZdJ~ zlZX!a-G+y*4iKm%Ea(Mog>LH%tRg%giUKa@wd9yxS%L4<)nI+rbEk_IVt({inKWRg zb>+d*gBGJK6WL>vsy%C2LjohjAo|_So~I(xSzdD@3*wrbjva#GDV}UrTeF{HG8+K|%$Lh5m3faj?LRVVQZ2x`|Iq-?q~^~Lq&62vx!qjnJk_#oKz{~7@+ zt~?R^0imxE>XeXkDVtcMErDU?I?VI{=`M%1U{M-QJewuv-3llha=xRs`O71WAy!&U zaqu6PjP+{ZcaIFGhhK_6>Y!MHywp5-UM}b{S$hT$TaaJ_HPnwV{)3vv;8XVLAFF+$sdL+ zcesHm8IOVEZAdh}RgH)A)Fo{Kavb*S{bu#uDc$2b!GPcw|1WOyy!(VgESN$LH(8uB z{r2%g9o*TokS*8u_R%A!CmL7h2587a@*nmKl2ypEhnR_9O2?~NhPuO6P0V}ox~C7$ zopcC*6L)vU9=`N1gXz~eNr*tb*rcLqKq?~9H&1u)Vn4sL|FIj~EL1o+^zK3jz`dsl zh?&AJB;%Kp*0yRb>FINuM?qN=Uq%MuwsX?{8+JPDwN^v8pO6u2RNS#M@rq{TO{ z<~y?ryV(Lx%EK_;_2R{Hw~*)MlVJtT{x^@&gsIaaSYq<~+fe-(xDXtSK0UC3FyB`V zMwn4S*&FYBKuq%)rK+I}b1_jFB|teVgyo&%qXRu61*sAP{&vLv4NpPy34(0Yc7d1V zS`u^Hy`$c4RW;wT!d_F8#=&FW7RH6SWE#|R$m41Rr)%-*p=V$knd~Y0pbwK7q`UOJ zB|m&A=)egKNmvXjz%`^i7(B#k7}b1)acg7cE`(nw|L`X(WQ4OUxcALhlM9$&$sfkP z)?fuGiOOw01`L@!peT;-Fyh{yLTD#L@y*=8UbHH?6jmugOV%6EpEV=ldNzs{r>x8A zK+u&_J|~Z7s9Nz8OcTz>9%v%KWb@p9;V%C6^JG0A^M1fH!I*CJ;g-#z9KLaOulr@f?A35V?tcfx2k4s- zrPEN>m2i zO!yAwk|y3qSR+esoFtWBHzZQF!8PcVM=C`@iylWqMd+C>{$DS+MiO&IY(X4w4}pLK z8B_ni11A3h!pOJcsQx}x{l}Na`uhR!$L#h`u+ldm;LpzhO}o2C0jum^N9^x^$JDDt;Y|9`-zYAqxaXqrKG8Gg(5Z&vyJ z_vb80q)Y+$SgPv=o8Fw|d4MkO0sw&C2`pWRP2OHYO`>4=JI*zy55)xvE6`uF+6FnA z*#HtwySan*G+I%=n?W9AshVb;D8vO`)eNM1>zl2M%b-I-VG=^YzD>hub=&8#fHkz9 zI>L2!NiVyu7#=rkZ8*nVDQ_@0n(l8839sx z&RQ&&r zJb?zpVl*K7<-ZNu39nxMGHPV^L}xoMK~2MWZa*BByQBqR6+O8Y*X{!HR+!tW{RgN_ z(qsgf?A=S8*}fK#=^=7~3@0A~=%RXrIzPaZh*vIIJyeZsbVI}Vq+EsIZCoFg0uk=x zvq2QE7Jz!Jew6CFWhau&$yyOT&UOoQpQm|RrQupC#IF)?_{us3>DKuYJz;87gD`4k zoa#EC zxJG7~+Q-!BWtQH(p>-H|HZ6STknC(&qkS77a632&rRV9w?$@YX@W?9y=}Qr?W#7u= zwviFCpF^l0Pr_-7X^qr~zaJuIZ0|r8r$|Tawcz5MZL(MkpS%G^r5YIe``ZpS@LSUk z7e?NjJoR8%$L=dXb^d+nB<1eVLnGB>J3H%bWt%?qMzD^Ta;6E6L^%gknd}Ll=fe=U zy|#gQkCQ=K8c9ETZy|q%$(q@hVEQrZY2F^=p8zQL9T8pprcg&iI0LazEF_es*E{zo zos3OnY!F#^0L{jJp6CE^HE@Me;*$&E*@}bjxko^aY!6GpU0bF5?h|0EynDXa%`V@G z)#=7ZJ`AWqp1k?qI*1Eh$?Oor88SRX0megKho=`|Q(12l!I;7Q^p4Y-d+cO^;8>hb zaq;%sq`$72b$9)Lp+$!d@RQjURL#K_IB-?d03URKZ#e*fd(1Aff1d5RWkf&>!mW2K zid7*niHCkc26NUOAU0bhMhOk-fz!=4$Vd}Tz(-ch9-1UtlImv~vp=DhE13ZV!~kW$ zOaF>cJ`8@PfL4gG3iS>!LJjoX>i{z(#5!{GCsU?vXa6a(#1Pub4a9Uh&qmlCeg zHO9vxWvIyf?n2nd#<;v1(_6G9^2pyQnide>0tkKuckicxhZ83}=TWnoc`zULM%mgWi(yVrsQ+0-7}Nj}fM4Z7EZ&dVZ#{ZsR?Rox94`{iT2rKgd9S;l+>`7+n>yMwScA*U4BzV~ z&~@d>OBriAz~3jgKV8O|X{w#>oO!dU7ki)JK$Ii#FxiG6Fe4Ew1bgeYr!i~3AEn3q z$hw+OILn`7QOW$-J0EUfAdDZJfZf$am$41+9Uvo3iu}PyP@A*Zbm^Kx0qL6*Lgo;% zD}<-W!?0S)izF+^5=(QWX_Kq#nnjVnZWx9xVB%DaMTnjF52YaHQf}- zEY20jOlw2_m=flZ zxEn}&l~{Nqp7|fWCs+y=@=c3+^0xO&2gq?cD=*Go2!ZJDTd%74+6Zv90?8z*t-JN; z!0fTqb<9@vfF&&SHg-IKHnMZv*URxDA4~|!1-fUuH_aHTvP`qO=HWazGpZ$-BVHa zu?XEFyQp~Xy=c?`9`%-Z$R`0(+%iKo(bDIWA!f{UH};H1ZnME;7GU`o4#!Sz8))Ra zR}H`19_|I#^6D~iTrvYGj?mBuqSM6UNt%L;;%&nP2nN_GaPs`|3t^uO=&mQ6HpCZ_ zloVu+3M6IYXp)FjKN%93AQRM?7j_Q_+Cu&N#qsj8t$;%$#ZmLuZfDmaYoVdZ@Y$YsJlfd1@>LN%@ z^^zt+8S{wznLu{ol>kLKCZ_5N`2iKomAfHU;>9p9;RA8&W^a z*y;N0X5?H%y0Fay_Sct(!dRu6LO$dT&^Vif>Fse2=uYI8zZg6-j5&Vjj9Gz4tjokH z&(iRGDpe(2XD4Y2?s@WKpZV+Uhi+y_&dvuK(Gl2m6EyZ=Ii>TJOs^SZ;vmv5K6Sr~jpSS?_S=c2EvwB#yP2g)2 zGF*S33Fs;1vAj&DzFc3A-PwGrVDsRMbQld`zD_d7>Ul?&w-@EMmoBuGY4$>HnwbB4 zNe;NI<;#EeK3`k8o98c|EnlF+;23^;GgWwRpy$RB{DWkV*rKJwyc6DKjopk4eEqZ^ zb{>T$tvz+jig@8PBQ-L`BcB~kDCqfS!N<3UVbr$V)@H~`%;rH7tBfQ!DXw2a|COMu zqn5%EiJ#xONrR1oztDni;0za>XZmYgvDseoIqZ`YRoPyPgW9i^gm?(Ed1a(E7(%3L zYaK2vW|cddy80}bGDV6Re4}!7-y>uds31eZEH32HbValIRD9P^KSyd>#Da^IF`Nb1 z1poM_N%}=0WH{haFO7toSH~BE$}8JKib7K?T~IcXDV59n$smh1ZT{s-e|ds;XD~YT zReTOi>)r7|YegW>+Grr^L{a{iTKms3D!TgY9}%Y#T;bFww>e}v3fPNs6(3mzFoZgZ z%WM^~lX!}leQjK35^=&dmevm$LmAg{BnrPRoR0kKwv#g}y zKBG&pet_1d8t&h#H3)PLTnv_w7ACX$WD)!mYslz#3cJSiFmTVq*HX`v=`<1u49T9| z7{?EoVwO6>S6+6B9AEp1plmxsXu#*2JJ?(B&Oa{KTp|jfo_};yHy}qci_A;@$PE_S zcwct)cm*X4^OHcA#M{_wOrA-^W;CQpHS9y=UFS{(a7vG4A5wKMew(owEPAG8{y1}| zVm+ys)jXw;$$SyA^+12utOW;09u-1hg%n52YIKT!2y)+xl@;8&TsRJ!2#nMDL2Mc z98hxz(9dI!AIdu}E0+Z9fBTY?Mn@Dc&?D!DE8#ftcC71KD`Cs9osY(LwWf) zS^bHf#v2S(&J5UI#(`+NhlGjHlLfQEEsIHB4gBQmtDHB4M}1bplri7+!#_?v-M+DQ zZw)h0F75R7XDmKxkWmJ0NO={XH6WfZw&iHozVPAE1sT=*=U!wFDTuj9vR~vJ1Md7r zfAsJ|1wpGc=Fz*8Y0S8f4pg$&_E{Nxm$EtJc3+!=c%s&1Pt#L@1fF-w>l)f{kodMQ zCH%rG1P~aZrZylLd@;$7oqv}R(tygs0YOd!^F8~7s*0UR&q&;06b_3byn)Rgrjs>h z5GM79pPw@oE6lh7`heP_f$}8-OrJ-WDu|Es4XNsQnzyP zWndGF@I1tB%2)9j`eO>450`scXOHRnl5CjJo%zxkCd^D8?%Kb{1XUH?p(huUH+!xe zV!W(W5}=riXJ~q0Kl%3#wys;pr9!RvXd6GT@g5A|Ji~aXhE~l+MY+kleUHNn;$RnS zXFG$&y)jue#*cy_pl5LAtG2aiX14Oi;@3@@&l86GRT=dr^h5y1mg}*)l$wN+bfjN&=<&D3kSocdT>~|UvZX(IQHEr-f@|8s0l0Rx~{>)-~Jf5(8u-g5&lha4( zMBX!^)c>NI`v*9+O~1L}E~ptp8lMBuVw1P2SyOtL)O|v?_#L~$eQyDu?2sAA|1Ea= zCph~jR+e~RK1!r(sG04+Ss$WLsx|-imr~=%Prq3^+VJI?Y)DMsB@t2mWb+MB*&#Hr zKuS-^b}a=Iq@W=+GO$*+eQ-9889z{Q#&Vwb(OWG9bBzOfh(vb4Zr%f-P37n@ja|n0 z)TgF5N+-lW(lt3D&<0{T=Jq*!+K0ERvK;wKak-0~Raa|V(;Y^1v>ol)F;h@%Z0$)v z|0Rj`&&R_qrp8G%Q?}y~8zI$Ljb32?zuf-E&&d<2as!NO#s`gS$7QG||ARICCtLXc z5gYuM^mpt)?myVfS;0G zw#FeK?GHRXUnx(oSkp1j%mj^Y!E7pqYR(TDUo~Iz z&zm(1FxhGwWDnT_-LaTi(2#5P{SzvQD{qnt;EXGu3wo-vqMrTp-&C$@PXtH&cTg4@ zf>%B?t6i#ozeTbIO1U3^+VY-)L}0mMP4lpqP)v)Rrz6W6iMCiD2YAx)!gS6fYPIUB zvBuXWeVi=vrYx>vdal#_cL98=dSrnG?R__5s=DYpXzCiJegb&vatfbt2u63d1!zi) zDXz!&Miqh9fLrE*vAn@el1Uu?cP^g)R(fvhywnlF+s4-%zVQe^?iE%~Efd(j0*$L$ ztlIOjH$m%cgLHcHG0?L)`ts3=d_Bmo*Omt~dqFkV27JMJ&+Sd)XPqBQ&$r*$x6?Nc z@jmu|lO|Ko6K^mu)9+${Aty&!+wPKy4T6gLt&^>ulWA_%(M$Qj=@&wP)p!0oKe}}t zJ8pW=x%(|N*l72!I@e@Fvaquj39u-3h$Y(qon@ucN$XOWUNy?LK_}V`QnAVEtU162 z*NsMB90MJfmp+o~F0{o?(`&5*uC~#zKuSj_3e`2+OS_r@AcPeb9s4iUY`n41Y zXQtmW>v9(i9pyQNRK`4FHxN1=A7%Ak9xxsPd@blSjCgG;cBS)J@cfhll*as z=q%mkhc~j5Dv)?#*VY7}kt2!7Pl2!46sPx_>OYM z>A#vEU4dph#ds{)MbM?Z4Q=MH^|>K;zbOR!4NNYr#N0%ZD{!n$+^ zHWA|(uef1N^yzkGD}3NQzNF=R1xULB*m#y6DInwGt^$%+jMT^s2=bh=LHkjwq z^+Uklr7V{aYgaEcgBb5o8b*a)IfFpFaA_aLFjw+i{z|MLB~{vy@D)ub;<# zwrTv}HElbC;9UB0#h3XW&ek#;9iw<{XSeQI_-&hSJ!`X-h`4g-JMV$%8p15u?GDxS+XzloC; zf~PNO@!lRmWjM`T63t)%xv}AvQLQoxnnm0Pk{#APSY^8zLFMIwPq|s0=BdLEj^8Rk zdWJhb4ZO>h{8IFNI&<^~++^Q#$7bqWdcm`fiOt+>JNV5JrSg>t2%TT#7i$CNg*(Y! zTzYE3+SkES_+88bfyftP@z-EbIDmM-ve|$0lvHJ_!gd&^5(%BxD2#;b=bga-r3Ab) z_nHr4S($yjJQq#|9nq=`bsA!fKhS?d4#Uj7dg(D&1W7n?jY%u2E7@`dCEJmt^Jgel zFZBRb?9RhZcD9BHYfW6vI}cFk`j2M$zTZdIB}1~a6^Yaa)jx= zBisBO5RZ^LRgOIF)7E03a`ls(^}+Pa~?BL;pse5P@fle6PvMj)Nhi=re@B% zp3~tX#^!-#t~y?`J3y0Pp2zefDRo4oOE$?m-O|9O%?#P3&e^H6Q{vDbw2Sq7KBSSc z9PxJf`4OK{xu4(t5HkP1t|<@;yqfsqqfZbVUTba5)%zVVHi{RTh+G zjmwX9rI~E8JP>>n+IVtmb40=F#z0g<89Gy%MC$pct4E_L$!9sd~giQ#l#fHVe_T*a8>HTbkdL~>|&|JUAa0Ruw9JULsi)au@YAqt*mtSD?(|u zySwzOpqhu(e577-IXn)z@^d-^QS4o$@s3I?-*2;hBNfCZFmZ%iVmFyANb_G<_|!<& z$vqg8O6cI@rM)Qc9_aKiIS?VhLbN_{=Z;}2^GF{O=vPhaH=A7xw)95@ecN~7EvB)$ zw5e(S-0t1kzRPO!=~|Y^{#x@U__yP6z)yOKQXx zLS~M6ntQ%2ddtFtGi023xzGE+p@0H6vr~d)1}ALNzc*C6?{j$F(7t_Y(JU{Rf#mgw zbpGwYZUHB&WIED#Hb1fo9oKvLCy&PFy+6PLSe)$?j5)cb>9x<4Jok&)NnNb5Cl7cf zIAZ7Le|_kVT=b|pGw6tTy7qDIvr&9((azStkC`qZ>$bxD529ht8oWZvBtRa=nr{LEN z6-jDS`tp3Eu1GRne;H`B&O;I;g#tEqBr4-VzoFI2H!0@taHlfHpJA1h3H$Q?Piaxi zamEv7k|eXhnkLNiJT#vld3u%UwOlM zWM6!x?K$SRQZ|@Ye|E$87$edb0TEf;0;`W@giZtu^7?bD<_a)8rwln!Yo+?}9>rJI zklH5-=Q!iro6LY_pdvG8xG5+Q5_k@;K%$WRa4>~5#=k%%aL-9Y3eq$Y7Q#?3BdplM z>^L7k87}SVY27_c0#AqbvgFE0?!(<0CDD>CJY2c_NEyRickw3a_J-(W6>t?wH1`Lh z7D59Odjw+%1tamQYy`(>MuR9$FeW53%y->5!FI=)w>(^$_ei|7VS#(5ugKB`l+qk?aGoZ_H6_rRKC@PUDIHj1JbZ49=^ zQWP}sbWW#PoH{p8_BMsEf0MUi{xi8Koh<(-Ij$u!rH@ggT~?MeMCe*hXnAG)svZ5R z;(+Ado$&BW7@SbAw2ftK$;Zl*lV$RYyhNB|w+95>#%?gYm=@VLD%!j6B*L}T|7O`3 zU|=Sd#CkHGbb9z;|F&+lDL2bu)kl$eOd%r!a>8W6gm?*@70vN2tBs+n{G=rTl-NCu z74X-0oFCX<5PZWxkKIFY`~JQq#;`D3LS80nrTt9#Q~G^@x88eZ%8J2D4mj4yLScuY zKYBk&Rqw>r1K0(4jz=qof(_OlRW=TZ8XlCC>6*W@Q+zTyl)VH5aAmyHdv-WNlCawM z;{}0#C<)ZW5=I2=YmYfRfKPZP;m9erc5d%>g5m!|-J8Z!xwr5C8A2tj%rlEf86!i< zxD16v!=^GtAydmxlrhROW+)M*v}CBvnNo&~5gE#yVj*LhGyl$y+Pn7t?)(1!U$6h~ z$?tw}KiEmEb*=0AoX2?_@8i)#A)T*7Jk#bRIa}*#rRd8aC=;IRLy#aclVDi?@a??l z^DQ|CL%W1(b`RgQyEWE4A1L_(oR6zxdHrmKruOd+Vy_i7TH-@Xa3%Ox>UiBX)99oG zfdI!{;d&QcJQWN1ylD993C=?$0uy>9Y?U6@%+Ub9PG=LRS{>pmRgzb1%ni>jT=tFj z?H;LQ><$XPkv?a}YD?jFkQki@zQOUkRA+Rw^%{dzU*w94&_v|qabCB~lC2XCfBmWf ze>$C7aJL&p#vxpe#uk?bxQvPNS@}sfja?PKv6orXx@EhH2!-2T&Y<8ScBqGHh=m7T z3op1?IA|1how){=d74RfSxz-1OE@{$BGKj2+IP?4gp1)7c4R^I>a301+NFnkjEwLN z{@Pbi;;lf%4IKM~7! zfkDczGTY~>76H4D;V!{OkUq(utDu%ukX)@&kS;1d)nM%)7Y06<6w{P`T2~|5*^FOj zV`U?l+pZ5tu?ObmUQrbdj}&dYN257fUo7-G(vU`qv}ZcdrwUdm1lc6-PGL7KLxEC; zf;_PaZ5bDFJgxTWuBHIaG3=8=Ps-+P7^C0UO6S)s_IGqj3{!vCaTT2tEZ9}!%W&?+ z8BT4b57{!j@LH=>?whkT$+z{$i#*eYVGli;(m3xlj#Fu=&J%W%nkt0d_n?9}r9ig^ z@rM6E)d;N`zCj^xjEqdrL=7V4LN!%W*CPjU1Ko?mpUE6*+^Ri3#rloC4Ue9X?xUJj z|I#{ae^HU=RO}~el5jiy*$*xJ2@uot=4pv=(M`-QDTAlN1IG^wHg?@q?<7Xlb%%Sh zcm*XK-r`-vMM`Ar65$SSal~h^>+#o9A8oKLBZ0E=JpSp~5+7%Zh5F^~!MJJ1k=FS9 zY??Ad{)fqj@>J0^ij)sbNt4fQ%(vQdhIR#8jxs)8+{9y!hv}#YejTMIk35HWyo(8x zOLXnx?UY{E22xClK7L<$e9v|p&;5&zUxf2^CU(w8L}@hl|}G(DX=hAN{Jxtx3f%Ta=@b_tzi}JvhM5mKz&zieRXYlCXx+1SG9ENXZ{ z)NzJvwp-e^1z|Lg43QUMhSb7UV@S)2J98v>&O8(1JFWZaM#t23hhTY4Z|8=FZO7!< z+64ZH7oZ&Q*_kOqNRf1wq%>gBOyE@Pp?OrXjqGgm^yFi|;u;b9hYr>(-i_QmW4xUETZ+&*H1ISfmD~cqhe|e?_Nw4 zIMs(!`1VdR^bM<;D36T1>6BsX)%1(r-}=w=olyUunf{Yp?UdLBg3S3WnWR?dx@u=% z&*zII>k|w+b$4$g(imLXcYF4|ADh5vgiWhwl2~;mUZI@tx;CtUT4HBKua{Ng-57#& zk2`~B-qTggc0UW14zod{)x-;$Yj@XeXzC%Y&|k%(Kx7f2GHBeVOO#GLjSH%wFZrS$ zqw{gCo1(C(`R41Z`fkdft~0FD!qHLZ!(^KA^}q$sK$3;wT04{A*6!^yJtwI#>G-w_3M$719GJE8+NrR)cEtw;S(jO#3)uB( zFg%?4bd9u)Wl{BFD{Bj3(x)F8!sH=ZeCKKJv0U19w`#sx;wS7*mb7>pS{mN7=d%y~ zU5MuQ5m{f$K$-c`>pfqlWNx=e?EkuZcc<9O@TQ&jGon_Lnm)KD1AO1RVNxvw>q z#(71S5VgjB{{eK_{z`tcD_prIF*WO%oIU{tN)#L)+=_7mgAO71xBtDt?xH=eb9XOe z4NUSGtTnWnJt_LU%10eOr2Z*V!!30~b|olA@Stws3;;taX(Rnx-M)Nsnq2oB=5_Ti*aB4yL%D z-wSsGIcEE-rYeJNUsX!a+*T8ZTw6kwV4K=4jtvGx7cEB{RPYsmUiUp`L$)*lVYX-M zo~NH;OfDHCY-ec?3fNa&Xh4Y|FpRu}M37n=&2NMW^cnt{4j-DyE;=mkv*#D(Kvp?5 zvJl4wDs1&O!T==I-#$Zhl1E{(&Y(O#N<0Z}LFgwxVXN@?B^*RH(o!h+k20Ujpe>s$ zO!lrWgSh!#VgvAs8cCp|@cWlRcIop7>$zN74ffo7F7Y66Uq&<{PGCulL9`I`q8ex% zn2DMTg-W?%WPEYDGiP+e?S4jg=+ay-T)-c%( za^kHdtrp~(L%FTuOB16Tfdwz13QU67JKN0*QP4zLz%Th^bO0spCF+BWp4h^F;l^)v z{$Giye@7lL^;5fv<5xh^|4uW2wE26^Z$u`+?sufphQ?MLdABxPp5Q~RMv;F<zHmS)JF=iNZQS2v{+@{XEopfDyXrkKnjFK_)QjeZ9smszC8jR_-t#{vOAhK+OL z^M*lvfpoBC9*O|;Mz8I1gnU~3ez)`|KpnYBOVrKF6Mpd-!VhLq$MYT`iI6muGrS7=U+E*+UuRRH7+AVW9QM0WlB6 z^hVH#lj-%_<$691W44z7Y+$+PE%DYSY|^1X3rH)oNwU<~%Q4j;Yw{330bz7W2j6Kz zkowti`rw~86w$4}B39=bfigZWU>=!5urxibWN9iLmU`M9T+=1;;EZ=E+#;c&UbhTN z=yKckB6+|U_rgXfzN8PLN2uiVlbH{wc8-8}n$5)2K!Jx<&KVB8C+q@1-hK_Ko3eG5 zS~8E&$YRIYo~ga^qC{s&v|LKzkGXT&Q9=3WNARv+6BFGP&OSSl^_tEFb!hTdhL?ZR&53GX zfak+}t2a3J5!^-M=2rx|fZM`S`S%A$`(sxsN$SYC*bBM6$(w;Pj5y{3`_2~Bu{a5< zqp8ShRLcuXB{!79=ViNDdyXhsGlP=YXiZ{sE-A%WDN)`VRC>>=0Eu<*K0v#JhGZwy zC;b&U04|qrW)&R!^JdKd+iycgV~}*g1rcGdOmW7@>-W}13apx6LOm(G$Hl406wf!r zg6H%ZY4Q1-{W`=AX1XnvQIgUuJfVVE#rO3a;OADI?PuWDr43i!9Dz!o$n&mb~XDQ+&XLha6KHjFZL(JW^scV za`QjN>?;6#Q~L^BmP%gaBTU*~5t^O16Xvd}aCF;*$LN1fngsB+v6%Eg#4$0j5VTcC zh#|S6ogETX^(P*nQ_uMI64J)02M#K=509|kyUcuhoP}!7 z%WpVZFiT*AW8uH{>cvWyqpq#{&pQxaW*Hu_D`QY~xQyL6X5o~6y>dGNz}_=}x?a)I zs4zL^RCp2<8rItvY6h&*1m3=GXT8+E^Vtw$VzRga%yjr`&@< z|J5t1q}V}}gh%M3q@%R;xCTl%HZ2~ z*SU82&ZlJXqfRd9QM{#GK;r}5Y+L~|i;-X{c zrJf9_r#hT9%bQ25WS?&D{ECvx0E?R;mE7p>=!%~M#MB-8Kuf;E_m*D54P&iX3Sl1Z z(O2%ds3!x@@;9o_#cis0_b1EEoQ zmKuKWs856C+u%^4>aK8%1I<%^y8X3qPqEE_B~B3WATo}i)8P7!XC9CvTO84T4c-E! zPE&{t^+Ns1=fPfJiVt4uu%KS&F%FS`1xXWvAB1VPus+Ax8*rH}Z@O^E;?V z^us5$z)62JltEaHBQ7Wpw?8Kl3fha|RVryI_VvXn4(+b3Wow*3JfDR_-DMiACM1Li zD5WO~=deKv=O_xmZCFu|+dyCa9YXE#3OG1J?SY3!y=!#J;)FwYTb#o&iKGnM+%v9V%S;^3ZQNg@u85p`Fd46{aVjiE`lme9pAAgn-LJJKM0$ z^Yv$Ub4;Cij zxnQ%Wk&U>M=RjMbWY1eEthSGG>J@x@VMDHRr}iDT$8{$unoA%+73jl#pPEpWZNf@p zlCkZqZ5Q5|0Z3YQ$X12;Lt zAb8`|2SEL<-yKWzrbw{t8ijn`!q<@liUhJX7e4zvd~ev}=((i3cT+ph9Wq#IBO!%G z@jp&5`xaGAU?(1fDLQ=1I^)dCYT{qV@kYRTp|fivwZ(#uFl zFB~EFcyrZi0t^U@PoH1B=vY@|=Q`2#K4m=1Qt(tfc>;CPo4_UDK}==Z?GE3bG-(^nB6dn}$H6=DblpKNjzy6oxLu5gXo+SI#dWQHxDH%g z4Rq*+0v1v+e{t_L0IDJ`yn()I3Yh%28j#5F0tPnt2MnXf9`1IxVc2@XSqNUT z!(nG8wgK27?bbH97so8Kfl~e;n5w^>hcaTwkE=rE_`Gwy*l?9px5d>(717MX{@}HJ z{cL`AeBlF^`tJtu`$*9r6s_4i{JP~zCH)Zy*+>d9)etXaD_KUI^v~P<nKYWlL|^jD zH)86KRh`n_SuTZ2L0)-3-^o&g+uHu*kEWM!`!c1?)t|4Dil8Dtbg0o1YZiACrx_ZR z<1eeZE8camgBp+z!}udJL6(ifpdSo(hdR*lD0b+hJBa6`4ON=u{)w}gG5e;j+hZZr zn{Xb{O&u9Kr{@jzsoX8Sy24#H;aaBO`Pm*lhA#ow(efp8C0oH%fis>{RZ8?y^iuMf za3w!wtsz`$QIE&~CZW=le>;Br^j*7eU}*VPL!-#VUyx(+BwJoH%EeKh_l(b((zdvg zw|NKJl^is95A6xJO*Fd5SY^L>uU@3)N2orP{V(+m;u4EDh}(78_SN}1>Z%tHQEWMn zNg>EpCi%?@aO_~HA)F+#U1Qi{&DTw*Kw>DBuo5C?BkVqRRrV8G5`(eU5fhwHa~)Mn z*eojxUZ-nwbTk#FXgrXK4Cf1En^snAID2KNmw{G2UiS(aCoO|TV5l_1!H2MM=q^K=duxI zy}>IHHCLBrPcg&)`So!&VgrrKB}NmyKFY^6XE&VAYPaP6dhuxtBq7!F$yIIB-rEW9 z6^Sb8`F8E%rbIrzaLlXZtx2UIEwAls?p8FHis)eaF%WIQO#i-#iMp-oPuRU(;MDa^ zEfx$8I)bv1uW`N-TM3lHqsF&3_-Iyx=$_8-ABnI3-ERELsjik`=KMmuQ4MFC@bv@@ z|JJboeZVbmW&7lSk3o1Mzo7tzvj#~BzW}$ve_GFf0S^9!TUJG_%+{6fz^T7sWB}18 zo9!<(9;f;jHH$EQ^e^@pKs0{)?}!-o6AZgq^)I`04kFt80|{7H@jw+x-#+9>6VAB^ zPy>>t{{Zv9H)keo8)-XMYD3Bg66Y z3-8}(u`-^s?{`Pa1t3I|QFRcD#|Tuw0yLWN1jQ%OUq37L>mAs|KLo=S{LG}ga!!-S zPDFHrhp^hQBgCYMe@GoQ!E=Y~Z88Fek5bf^&VCLgh_ZOl>;C~@w5YtutMYk@( zu?r~*xaJM)_zB>{RrYKA_LeH~#y=rVBh=mhy72gkv_Pm6V@1`Udx!sp1M>gn`=XuK z|IbCrPsIeVd1y`Yk37?Rm{%Nom)rzg-j~;xp#JRU(AphKC<~VOKivuQNFhZRUk0JtNUC*Dq zfcdZo^aEyzoOf0xTmp4yI9y)>KZ^^1Ai|a9fZlZjaj8mF8p+?r%Pl0yTfrc6L~tGC z)bVhT>JHCT^=tix>HK?yswjRzRYvfkjbn>c9;u8$&8^mtI`l#B|4OFw32oxp(=n+QuOmiAnT=8VVlg~=iqM-L7rsKAK(G?T$yW}GCx@TxX5am+ zkLs|27(&1)W&KRkm!7>{uA?_bX2p_HJ_FJ8o5Mv|t}HHm?|TsDRQQ?dGCbm!_QRR^ zl-;x24egJ#=uwl*&P-6VFZcBsnX+N@d-07HrA88NuVs?rsOfnU8nnEwPqTK~O-Or8 zldP6d?|Xasd^0aW&=J5~u&0~xQXAvy~H8w@1%WW-^q6n|?!aE&+s zSM)|>xNSPBW(m;i9}s%IHlR9=VUeiB+VoIbF3SwB6s?muB2S8GTqx_;VA0z8VQXL4vsF2Lt0*+n&<>oGHA+K-Ki ztduk_+r+!CQ5GM{1Ixt3SD;B1TZSuC7c_x8b8xdgt-}#7zE<0I#Jbl1`r%nTYOh?i zhS~F?SwY#_Q913eaNFwDx^#LKW1?3~-{l1gi`ee)5q05&i`UlIA@Xzy?M|GOKWMyA zg1@`=5?u8)6R?8x_5c;AG#>OoFCT8SM{P1MMGaKz%4#0>+&)aTLM&;2pHdIo1BrLS zku@|nT~c~nt$%z!Qw!XYy}e;f&4r+KI2b0*Brqi5Bjlm#@`#v($5ZIUI1e6%n~CzB zcfP+pd#)oD4@}pP+lKWLDI0rt9KNMeiqCWSuAR6l~T(MuQ+M+h_d~xBX*HRDx@(eLH?A22E8DLod<#IOl9zvMeJ<} z>5(i$Vg3Z}UDPzW8a}jkC%Hnvl2#=r+Gm{hUL5pSSfj=ij-QTYyt}IxK6XOO6=sxD zmeXY=hM4sx=_8nZcLzP9{>s`zmf*UxLqXt&>zz<8w-EsFO94u^np|=VQVLF!kfd)*{)P~gcK`SY8hKSk{p(GhfOd$j+c4kICpwYoSW5)Vgz{+bjl1Mf7Oos~y zT1%#1Zq%!5W5o^`G(+L8#A7ix^}F(PaD+)B`%IfHFBJUzmi&1y(I%Q>Cm5zed}#|9 z4FF!u8$8t@-Bh@l#i&e+UO+lB_48gMYA9=QqY8y~R)@T1%{8BZJtCaIJ%)wwP1baN z+y`v%Yd41N-2Rf9!G`qO_z*VimHjKC*LS&8;@OIH*X#q7r`;eYrTsX%i=V0H$+P^s zcj}4L<|ag5GQ-n+g|Fur$aI&2<%ea`l;QgDYOa);zNdhuef-MhsE z9yP&B9htrkB+VN?4r;eG)mI%(ByY4!Y+PE+w+a2ww7Jy2!W7$%57-$c*RUGA39*Nc zxqQcxi;>4rH}UIYYHC#~Q#F#KM$0Ot_Qu@WmaL+JTN)jC}qh8+n3m(KfQ& zg1==)gg?!>$NU3O1Ej<&rAxHa77h24*^b*H;2U`v=SrT&Q%m6qcLxut&;Xn$c|Y?O z9o!6vSqTJ7+|1!U&q%x%Zj=dflXqVP6YQ0`3kIcuT+W0<-Xg72fb5s$|B%L* zbPuv9!lho?E<6odbi+uM{>mElG3iWerBqIg43?ND2_HU{lZij@@9bhBrp24OalD^FRkK@GhKv*?}!35?;A2Ljlb<4ja%V*-1!9Vyd+m!TN= z&I=}wm6ez)WDJG4N8Ipnz7m?vNi?a2j}}F=^U{&Xk-aE}<&& z<-@KS$#?lCu4`Cv)_Tnc5gh!f$N5vjfWG&ARXuNp;h_H&D{kF_FD8}GH3D)a^0*Vd zfMOQiL{@R9DTn?keeF3tj;$0HjuVY*{MkkLGFc-gnQr~l*RyD^^6@zwW|A9AG7DIY zIaE}hpPi}g_C)wY5QA1>u5!#V&Zl1PiGh2_y+Y3AijH2 z?;UdpD}XnAzqy9+?YmK^;jjWfZXhXe;Dq@8l$bk;9{_uuyJ1EVl4%U674~@bfUYW= zLHh8naBCqNS4YC8=I$wfYK?(M=zGwwk=l7z-UyWNvqTTWQ|WnlC4Ml;<$_0@r5Ds@ z*r6k=mD+$V!82-j+XX`$aj>2lyNyV_MW9h*0;c@;n(!SFq-_m0);@Qgu)kMV{djgiAzjT8+6Wqr!tqY(X0n^!ZPbH97|9Ba#hEA-?+ z@VEu-BGJG(tV#bU0?>-J5PZ2ElJWJ=Z=`eL$4&htKdUJH-D4X8^0dkd^#d^K3{_<_ zCqO{-EGN2h zQA+#Al`OMWdjMIEKr1fXF56Mo3o7U&FnEKk8Q}{mY5)2CFG8XA?qan3jy(j9Y&!sU zuT<8(s&r!6Gni1C&@X=z02HaR8}H2(#4|q$Gkg~g-o@zvbi?J1E zeGS>PCSf*nkJ07wAVoU%R|ZXy;u?`_Xu$LEX^oW3?2>>-_~GvdtS^GhQuSXfN?_DS!f#LNI)NfCfT752tUR z${hhFj7LM&#+n?wi>t0y2>AU~7A2V1Lg&Nax2hI{=H*wWVH>_e^rO2>bUoK0?jygN z`Eq``6?xT`pl8cdXd6u@3VU%o*Yelp!7yHkj`IlS=m`B{u5kJ?{Ty5fbp8D2oy zcYoapKQC5Wpa!B1d(vf%=PeO%6FEDyaXTu0CGqFk7O5jYE@KX7RWEQM6oKO1P+wuh zg{)qtY)|*g-Jzc%4Ojsb_9FgnD=aFe%zdaM80Bt^>D1u zct>`ft4dz3=z}A=JvZjg&DDAf@O83|_Ek>77JRT^i^bh)817dTRhApUPvCvEV;+1Q zi-TpGS((aV3v8g(s6hkuB>24m{6uqRlm@yB2Iyc+S@(fx zT`#y8N8n3q`_4wbOE&ii%~CS69cB;4put|Kc?E@sufl?OTT%r?JFoT)cODtjFO$Vv3ik*NdBLr2 zYc?yl-|siKX0aRG5|AUpE!{l#%~ZOLsAqDh%M6Q`a}F*uZ1^wRXta0h>5%* zSXS;$09?ZwYU(kS>;tqa37JR>z+QVb`$mHT@K#H*aE+LekV4de<4F`x$^vo9)SaJG z?+c#^=7x%(EwB(h1&2_Riz#%+meS!kqww%d)?f8dj!Z{oh^+ny>welc`S#-wuFa%J zL6%q}R5Uyfb}5OP&hsiN+0V4k=U;~E?b+6lAj=kwJcsTN?XOj-xk*S*DD|_+^^vDhYLw%N8H>(V(7 zApEv{;0feQ-?u$roP3+;r=6dX;U_uSkINn$25hlE(w6OQl+!f^>GvYxVe2&@F^5={cR*C5DsZZH53 z(gs;pwTKWN?NL;)}0W!Nz;4B$b?@K-9n1bBV3W(f6It$be{I{%6)LCHjde z`p!M(@n4)~@zvsz8En=dd*mXKtwiNK=K;4-Quk+K^pWWq6L181bL$SwT%!04&x83) zW4ToifWpq#aCF(j=ln9vXV3bm=A${;^F(TB^P}A^k9`p9R5tLi;vQBb7zd(<+1T>G zh$LLBZMOa2A+t4`#O5~(_GI_E*JGd$y)svVJXxYpS;Rx+Xc6oIcf{w-yjipX=>{;Y z*z5K1Cq`7on38vDSK86$up3Z`(o3<1SYq#=&bN=loyCS@T*_nx?srh*`apVQ@@|Wc zNhfb6RH~mhzO}P8wG}qHm8C*kfx2bpKHIG2wVO(X3x)C5PpeYam=amtR97QMeT#*r zTLg21EO9(J4Jup`#b1&R2})MP7w_3TVHdUC*gA=WAbZY4<=}OLEP|Sqy%+zRyYbt` zb#{dZ?V=ZO#iYSEOj+l(wVBxFDfEn&iK1ECN@@-REW)Qnbqs2K4{!4EcOF^#7)r1t zibZy!GK4$CJbtf$!KfqwB0M{luH7jv0Tm!>Bu_eR&)JBjOWBR{&8(Qm;QYFGbHA*S zvfPf*ZF7g?*$|55&PRaqMQgCQxLmg+H+Wg*EhQ_;{T3-(t-+h7V|Lk4c<{c#n-|SW zRPMVt{z-llaf0tn+>}b`eXsdWudB{8WaSQg_nY>QiTX4IKdROkGz{N(IDqH4=*!Zp z5)!P2VJ3r}kaxe8S-)J!ZHq_Mi#zlsP97G6gVKh7Q2NMSd8(w)Af>^BmD{6g1)uoI z({0}45eM^P;crHy1*o3=%Q^Guu(tZNoC^3&Pqit1EnA~wYHn$LY&7WHwd%Rj0iu>|FalJT{i6{*nW2FI5=K0Y8wwF46s#G zva?6D(34f=WGZ$fFsId@qpzUX~sJj$(fh;ZTOCG_Vu?CTZquvpC z!xzBn&dgk+ZGhv%yt)x|_B4Ib<)Ed_I4Ofps>tM+lMj>_92_v+G(MDqZaOb9?=Q$P zX-`=!2ka6`YC{cG(Iu60ellC~83i55rd@)*{uxT6D)O#$n#7}=$yHuPgMa9Gi+;DO*nJR2MT-xn;vHh754tp zPzT?}5m`s3t?%TCjUG`oE9xzQG-?&!TAkMD1%iXV;fDBcVCsot1m362a03#FhxHk> zcv-Bwcm{}Ia#VQCh%EFuY7zXYgrk~C*cy)A;fo^&7Eji>HF)za8p%$x1?kcm<=;80 z5Cx553fMI5CRJ~fdB2i|aq2NmC4N1>CBEIAG3u&I?{|i$$tCf6lyT-rdcHel(jP0c zIS*q?A7MStdaYX$-*9zYxaj=rzybEEN~yW9N};>$^L3Pay?hDgl}cwjTMYPC36osX z=R;qBZEodNl@~>nyc377E(zrg-$L3_3Wms4nXa0a)89O>ft@@p z-VB&A4@xJ(4w#S(pqfXm~ys=ux$>jO_fikhz!&g5M(?{i`jzOd~Oo+0tcjc(>6hC9~2go@7TrU15IUO1evft zIfgsLIF*x_pu@NLi(G^DhA*@VvP4az#EyqyWa~q1^(=n3PM@pVd_}SY=dwMm%)gr- z7~LwxcCAMpN;F`RfZuVVekmwll&Tw}S+qlsL(>i$Bepx1`U8KUtptw>%TVM_t#Jsv zM3%_fl)5LT%6*ck7f_RNv^Eq!DmVWiPsXOZ7&PD=Ly{Wv+4^maIm%2eJrOFw*v7~k zwpj;}maJH`iHN<{qEaj%^voe5NlO3V+^st;wplMNu56(QIuwkFslU`ofgu}t%Qo;5 zo)a(ly3kxO?-nSpAN@jQUG@CTJB6WK#cExxt*zmg)XCEt)CX&Gb-C4~@U+BPbJNkg z@sircju}}>{+EJWBwB~RNQ^U1_s$35*|nBihkEQSdW<=?3Ch>dD&6GE&l4d&;9r-O z+uOlf9U~T>>dF&Z6DHOY$yYMMS?eC7ez0xss5X*=2^R!Fg8=DZKvC!+OH$W6c}8sF ztt&PRm}}Z>l~!UZx3J_a4`s~De6M*vsZ!xOEt9=VnMLEk9{$7o#FjkHU#pd=6FJZG z+m_}aSEh5b!t*~LVMtcoyD@eLNu(~??r-)|ycM2!aVBDqp-3A!QI^lSBZ+p=!^?!> zrA}oM19ly40d{~+!my~?U-<9eDSO)mXd(7j$uQ<2X=Ip^@XxfN%4`l)o9D<`u?SGV zfB%d8)Z?IMPV$mIz$2gdElb)tFe+G8Lqj8(<1ieNAICp7?7bfN1WL&w&5%lC(s$>l zY7_%~KWJ(fd#(P&z3?QIaG5}su|$5T{36lrJG_&gOItSzL2e)3GtXdvURo>k$|7|K z4gMGX4noZUTEr&ioJCB3_>{T<*e%2wRfcTb`I3K$1La{RMx@EyqRv zl3xfMzPqeiz=DGQCXg+*BL(-j(}HuO*%5p~4I~4Pb0TSp@gM^F?Ln_?5*#!ZHRx-e z{JCH`t0eo3xQI3axF51uF|>NLZAEYC7e!&5cjT{K%#Sba282Uo4b_gHSId`ckdFNA zTmPpQ|G&#FX}e*c_fuEe__hA7t{|r0hIIGa*QDn=3m3$oQ`C9+9oAZBh8yo~I}CWS{!Wet zWS%c}9qEU9)h4ShI2cW~FTl)4<_r2GY^N3D0`zEA=Q4kTrTme0S*R&eQ#?%u8LsJ_dp4+3YZUZ~Mi5`MtCQQ0OxFH6(zzo~g$8n^usNDfov7*3$L^|;LKe@+TnSw3+ zcL#Ot94{u=q(-=)LK{d$nD2E!d#%=pg0L#s6po>g18E<*53Fy#uS?(BLTOWYX0%Ye zQvtS@1#koxnovVc&;y-Y3&B?E)mOW96~WkDK^VKVcLD?uXXGSD=7Vbr)?zTCrH#FR z0{buENl%~UWq2tDgULI8Kjd{^fPkfNCcy6e_m34a!9FteYZIf8?nNZ8Tdu$hA8!4G z71biJE?6wDL-1kBe*1Y?COIQRuc8Mk{+1PSyU(a5BYs8R^xxHA!_jLT3ib(*@-%In zpX}`}eFb@l)iL-PdrHyve{~FY(ZY#R?(}g5py(`Xe*)o8`F_>5WTZf>-=sVM47iuB z7y4ek>;_G#7hdzpK7mt_?X@i^z2wtaIR*Lxr+ABcn=3ezJYC1p9Dlt7M!E5GoRPWj z3Y^SJ6}Lt;eL)6R*h2R}RCEB=1UwSqk>jmYY{nSF2f8|be-i?En3xicM{Gd+1c_A zfFilDtY(5`)(YnufyTIQr~Gcd^w@d zF1H*KUK}AUI>(KWfhgs>3?R0b+R0h>VO!bKz6vtnQ2xg&c4iA`v@Q=L z`%tcM!@=0%IleE#!P+q2Y9q~9L(>KAfvhz8 z0jS+^*S2BB?rFB>UT(JN+2_T4({cF)veI$ftpHhE zA0vMdZX;byH!%-S8=)EGzH5+)KMOwIwbnyqOG?=L6XzFY!v<*i9vdIw`0XM4->eXD4Hd8&yEI4zLWOTY;s>ae zk+2gJD?^E1vv^4vf)Q?w? zuEVd3WJVn*FRq_=ao5@GORLU=J&w7!;(DLvIFT>&P;wS@1THyP@>HfLlT@OxqJADw zI$V#URtrGu7bM@qR8y-DWdx6I&iAtu-e^u@p9|^VAJ;hBb-e}UaITAHt>dJRT^^^e zXj3sY9A*H7+^*L$%-XH+4|lIOEfw+m8YPQG^o`4PpI7q%vy*NGH>~G1!z17GCANOVr)I^n~~0L}>NSp}>8 zUO`MkzS)%NIRM8GnO*W=@wu^jguds?KoL2l9RZ$iTLD`JOwI0KaJpr>r&|QXd4Du z(T3|kZkUS2QzL_EIEhj%@A>w!7>BS?Rbn#rEq8bXmr+(qIk)lU%%NwYDUh+gkq2Ml%aH;3K^8pfm`v?zHtb&X zPmtyyggn&E#86W!XTA=BrG4u@KvI-(-DMmUMOTGSNUw+qlb7K?L}wndNuRNXMQ^LN zKCI-UWc#28YsD2x7U%<}05;h_c+eTG{X^HRu`Z}iH4(t*QT%79y~L;vacVZ?dR9s$ z-EtLcdvaTY#klgT-CcQ2Pv>L4OOhQoKv&H)XP;GdbUxZduvJOA57N?_59MXg+#5Fb zN!~hsx>{d2+I%2gvjV1)-GoUf2Enx8*aQ-TSi)U&)DzOlG22&4!F(h*gHPah{%Fok zu=LN?@7?6J4v#;bj}%MF*jM7{(ULZX-Y&&|yj`p{1HwnSPdz%2P^@(e)Y?>j9xJ@dx{cL}%taN9fuliaQ-`UsHG}v}}aKx`G_jefDGYs0P zIciqnvyU@}h!@*TD@V0BvJitTA7AHdwyj5Qlp+s{el}Q<*q8DW4Wa2qZY#rb8|yW8 zk;ZlH?sGB5)hu7TxwE;C>2gJr`*Za@Dn_t^^D_!v4>GU=Qs_m1?gPyt4n z!@Eq;E{@b1Smo4c#R`Ov{K2oN+gw?^aW1lv|5M(<-S&T=RvQ)Hf#vgvW#wDa-lt+E zjNPuF04_Ba?VaxMpj>VG8fixJ$B=Z;jqd^)YOyas2GTyTTy&s+yDD`pzs^oF(E@%* zqvzr1W?J2G{V3)3$mwCMJXIxga}{6|+o(x)8jjhbl}5Y5jb&RSb8r{1-+f&f=8vjY zW)B)`24JNZ+1Edn`+I@yztiX@Xz^}ht{#VW$*rgFXNZ_~E1*Q+n_lVYj2IAR8k ztE46lr*EYcT|V+A;s(Jo2{z~E@ap5l&NdtFRM{0?Cug6yrAo@+{Ykl6H^Zb@`N#Ce z@qCXaRDHFNDmS|bTucnjvOEn!r=|~6spGnJFXpW4|Kk{Sf@!=SCdh!z#^O)Krz?D{K*SCm$wcG)mMW(G- zy3dg4ngPI3*m2xmvT2z!`N_MXF!t6VRh3y$`aS9`WY;3wwe_(M=)5NJq<;c;l7hU2 z$CpYw&qxiMR2v=6|#FM|{EU0GDFkdyfNK23af$IVS&RNyl_=*Vdpt-|*CjWfedNs9`~ zKGyNi?x;`1E0!;fPK{ruGGp!`11Z1mB=%Bd;_OXOhifGKIho*1tV)yXB4}IHuj?dc z&kE6;T_T&Sdp$*Pm-W*piMeU56ZM{LXXvUmxhl`ILxyRf$Jg9DKbm^9`LYpRtNYQ1 z08EC0*_1JV`)$|yKB*G&6tZ-qN2F#M;p^$Oth~xHxAe;jsIjLW7_C@J3G-N@>yL@2 zot4&Es8AOlIM+;tyLOs`RYgYngF4$WF8|oZOQ#&|v4aC-R!09Jsl~b_C9^>J4hF>PyfW$I-F8M429T+jr0HkL?k5P?~12&d_l~j)ZXe+=Cly81*n{tokuUtwq z%`X)LXZ$OGvhtf32Gy;Pz~^ zgg*E5?P1dV(}#F?I0Qj3h-F5SMpPhZ8d&b%l&6rzj=ZQLCsaThw;^e@!IlXIt75bQ zxqs#)k|%Q^;F6{{y5TLR|8<}i9kOgae7E#j`f+9td%i#az3gqHutD!Kxmdp0@%@QE z+|X%n<&rV-ax`q+m=Z6cDNzYoavIYHcixj_n^l?SDx(v z%6@E!3@{CTVjR>0y+dlxhTjD;E5CbKeva%2-SNEP`_w=$iiV+S;nedBgLh?Y`%#k` z^hb{)Qa_wB_aCc=$zSX*IYoj0mkzZ#6y_Z*R)LV$Es!5500>PH{Drr(6<5#_)V=jj zbw!5!m(;ZTEK(jG_|r}AC<4R{5&#@wyL|~{40XjPKP4N&ZeVWysdimo0pTPAZ21|` zkY>u436$@sKaDU3>gWOiwdXf=D%y~KJ9g_LqPt)rCAOOzy)lRHfd&}%yYJ`EAL++C z`hQ1u`eow!?`|KUZha4m_6q6~J$WOHV^~M=rIE<<6{u1Cslfes88XV(PX1lDN)NrU zg!Hn%eOZ!j+HQ{AABM|~TNnHv&I6DvAHY=fy9sK}!{!~Jp(fy6<0Y4C;ZZE*NUK4FiK#C4z z+_k1i#ud;~&=o4KoW!6PJ*^XuZ40#Tr?fguj}h&lRc-|B5`-<-*otXoq;GIQcttvGzi!cVc~* zd=RGTBOlf0xdCQWH+EJOBq~$dtV||TiL5g6x-p2Z>{`=)IPMx}2-i z+!z#w&$>lHSrF+e21j@`ybh?NgWLQ2=e2d1$*LBbAW5_Ht3X=ZrJ*=$q1GH{$a57U zPxk%sa4=^@EvqU&`?{C~i$}+{RD&tfnRk$UkWddp?s^0|gMX&ODq8PH@$I}Q*pa4) zBk-&2=>dDYp`-&?R4&3x?OvX@^udc(4*O-YAZ`eSwt4`+jM z0tyz`^tJ_}8CVPkqdI)BGsJ6_7LerH#vv_Rp~{WvUt0JOFj(MR@+#GSvWZVh~mZ(~m2Ng8|i(0>_7bI}U`X8YqZI7gS3 zy1>1+YSt(PeC%;po789?)hn}NR1Wec8IJiJIQ}6Z*)O2+8uz(m0z_z z1K$MQAa=)Ua*Khey1AQ68aP*N4sR=vnc!xx?=G-;h4sQbJ7DHAp3EydOZ9-kLk*r^ zam_C2+QR?iZYhE<0S{F%I6&IBL!9`zV{exZxnKYuWWd7v2d@?v4c&c0l-HzTxXv6b z^c{2~FQzPwe!N9v9z&9x4~*8NeS0aUST)yx>EL!Tauu#&R&B$8g~}kt)pZ0FxWkKc zMs2Q>a94MRhl0aiiSA|4I)r98wV7V{QVVX~D@%e`e`fi$btl!Q{=`*W0-L?^@*6Yp zSc2%b&->h1yl*VAgKBkiGE^Y3Q(RDBj$-@|Q1n;xBwl^5&|ij&b!(;a7|j@OsuM}x z$GCLM&O*@hdf`V&EYYmU;wIe6TH8ccfpW%rob-4uG!$2m?{FK|% z0mQR--pgfu5bLk}%Gqt<#bJ{PT#=v%`Agww?H_Dewi zpPu3OjXC3dr~vM8?vVr%>bzOKijr`5Bs|ZWK)pZ%L%yB zz56mDp<~vBV>c`{DR!NjF|ZcmN@E=zMhg@-3s#3(xzWIhlp{u^*&uBc!ZUr3M=v@= zATSIB#X1T;14eYzfJ3#x6?{vhPbmWcPi~bzR@j=W{>q`~EHSpFCpbbe`|?e7}z4cpmjf z+KJ!_?lHP>I`QZ##&|9WZ2HTdYTrxKt1r7ot(_0%{5=v++YGGuzKjFkRrP!B76U-G z@xmBJ2&@r*mw&IP^8s>?|*SgUYmpz8d+B!?bz}2Lj$ql*ZM%^3#7)wz8jc~{4*Y7 zEZxH-E=W3f0@}+|Yq&RX!+i{Y%~{NJ$X$8n2{&z^GpP}Dz~anf?2+__JV~7Zdc?tY zImzSv-Dn-8M%T^0)dL(esA|=8=+7zg+xL@gfv;7PW}^EppGmvmoSxxRE5Cq@W2h9; zr@^sDXHK9)kF>y>5@hVSh|;*rB2=VRw7z@r!cq2&$;cl*93vEAxlUsCyfVqD;K+Fu zpSXc>Jif4;8lYp}0FMdwFn+qqN7PpHxv^~|sK61z^Eu}H@cyFDy9rWIadRPn}sLAFP}f@NEs+q>sQPm%3%PNDZF zOD#oTF_}X3H~06MW}@ee@5|r-=%RLNFFdGZ?fLrwzCDGY2wM&^|DIj?DJbjs?lh#U zblC1wm5BiACCPN1pFPonP5C>X_kZotty2#7fZ(>pDu5G<1nG`NLpGmQ{)ZjG8G2`> zs1wcUP~tzUOXhPYRP53t;#*6DHU#_2K%njT#2Bi)J!nQl+Wx ztFQ}Mv4|b^8r57WV+e5brM1K~h*RgbEoPd1OtiBB@*l(FpdH>&_pkh z3~~W%Ed|xH_S<&0Vv4j!kDUn9(LoxitljM=+U#+wD7o)Sc%-KFmC_d0b_I~OsCc>v zM^)S$-SqPpB)@W#xsfXloqxWJ!$oe8`8COEU&U6HbHxg2hn**36@5f}MiH)(fCi`O6e+J+ugZxFciWV1Lt zPy%2Ym-Kp!S%8ZN^9xl+;bC{gP^H=&e;yc3g86q=pVF#FF%*47R^FvHp9jur6!d-N z$ZZ`esi0KMAnZ1W1>z`0N#(#tinuR@K#Y&5v~MyT?Ku;3SjCC6wuT?lw) zPyWMx>u%rd37%wheNVgi%KmmXnCtDpSh$nZUN?D$^5bN`!REgcIa!EU>XEb!)stGF z3B<;tM!ipjk7me%Gv&7Xj3lT9ec@UU$*o>fAaj@Ux2rFn zRJaQsz79^79?6W$j(c0{b~g8P({kHxpCgVHOMTbN_7lr{?;{n%O)H@7?;X%UC2VU10}1hLml ziR2=2F{0(7QV|?1H@l2fJN_C;1Jr(s%K&WYnyn93%-)IxR!qz3q}=!ZJ+iXRo_{~B z4PnJQO?3(+ux<~ij58aYo@LMw=AU7X*JkVhb!GTL&g3t-MPQX3AFOR|ZP@^}jm%pQjseD%1~u=-CYt+Ge_XFt{5%`<9JeJw(ls4`>wnnb>%+LZnp-Ko(`6) z@<>#CQ9C{XXV%eEj8%&~%aF`HU}-X_pj^|GAGs$aXfSWOz~0>%h1*XQ9BIWNi$vFsM{=NzFf26KTG+9uI_wm5H+x^F2#sCM}{9 zqxNIbr1vr!UiHDc)?Wkaw_3XRknj#neOsp?ecW+qlswT6HwBFbRok-f%%Usf>9$LL z3_SPT-9Cj-LW*;`P2({^`<{SP^}c5L^n%l?9Cje0z~0Ki889IK$+8ssGC#+dp-k~gf(5Ba znJT-53yszaepA@|lG?1&VU=k?I_6VpL#*(`G!aS%A8715hv{L|)d(;ea#7}{5&SGB zSzHHb-=blQ@VE{QlyT1NXA~yAIMNtOsg@8VctoN6RK!>6FhJ6@y#+pcZlRP~kO$1F zlWFW7`9Cmcg$8~C6YrEi!y<9Pn!o$+(M%&#e-ECU=Cgc^zpCA5zsU)2tzmwT@oonH zM`=~B%gX~phgu9fzvSu$Vz6RX2DkU3ccfl>wBcXz7B=VR+q(>~7bI;ww95i~n z-swkqr-W9jK)1*~?}t7|8FK}O+GnTH6j+Wxv2QEc?tJvJhL0@&LJ9i4!F7Xrma>EiBTzJqf#N7DM8zF=>+ zf9&FoEunL;!zL56J?yhuh(pY5M3tjbaUiUEd+J7RYle`_6Nez+7j{%ecL17aL0JtW zuigdKvsbp0?xi$M?ix8tAEotjfm6h~^Tpy-2p_u!!-j+<^Czr*c#X4Vg(A_8(!^bM zl~Y-yNZqH)M}9R0Y_agRN(Z`%5euy0nGdHJ=_m0Job56kiUuo6OBcW^{oet_oEX?K zD!qsIcLq;GPpby^>s1Uw{ko>|r1$t_&o)KEOq|+i1r3rO_%_i0cYWj=c#=OxsFvb)GFgb+eMqJ}UHO zwpj8xx1LI4twHV_ppFIh@M76x(ccVE7uL(cA4}6vsA-r8j2tp7*MM7}_JwO?og+Bw z^>3Nb{`MJ(I|)b#ZmZkMSi8^!Gq^4IEhZ}8fF-mPS`r7^{y(rT_{xFC-cW3ow44v=mUt~Zx%M<*x>X$?NNMb zS1EB>k)5c}h86W%jyakM(z~I0iP}+kO`UIQmryB?RPE+GjX&1@;rV zSt2xV?+JO!G(M9u|5b^t>z8@x)3 zBWSs?=zyTPT|?NW28Bj>-^Gb~$w!eNK%Pmw4Fb13j4x@Z4nVXUY!?n#>n)2@{f>QO z|Eon0$(!V_{k9~+=)M+n%R1W>?FDA> zx}<0^dI2YY$&TBiI2hRSv-hQ2y>4>~$X z|7{ETn>IH$`O9rSB{lRms+X$rm1RAr*>>*htT;E?wNNciPy|B#k)2_3{)+PqV4jmp~Q+=$j=ALN{5pF0>jWqA`;G z+mUbaM1&~zA?l@D$>Gay1cBf3voc%#G#ELse6Hq8lz9AR^fWF}u;bHLg3qrK$d00v z)RY~#f>;WLJXycoa2xoQ)vdI_8Xgs%-XsPq`LT?8vtiokj~i<*}q3>XB=ho`B|H>XJe}6QBap(pZUom)~lh*T^yK zSzCbqEkDXACDd85P843~m+W!rk5&2sPFnUz>@e=&6491lBPQPKIbTF_^8O2^GX3TF zI(zRdole_cpC*(mc;;ZLl!)*-JO9Pmdh;^lH)1Kl^9&bMugNxoY{}Za-j&|dJaS15 zV7esq3$4;Th@Q(aaMcicuMEyH>k@88nxojA1@f5Rcl7Qz`rtgI7tQYd1pRyiWE%z{ z(|7}_t3h8?PLIN&i-@VJg<;o=?nqB>%BK9!%ug_X1v>wZI{de3eJU6lw(wW#8-nmc z^xRik6m8#YQg7y=`A?XSf|RQ8@}Y$?rsYHQPPgdmm68199=|b_sVHi1wtin-O5!-I zUJ8LoJW-oH=UL#h)#@qsO5pz`6Izr{e>+K{?apnMs_n2s=iGJei_FJo<=-<`dn&hb zd<`S%CqOCaL-I%H)T>?M&E4Pg*Up--`rjm&|7v#RV#7U2yf4rjw=#JEj zHe3k6StX@?wfLBLs7qyAaE(=zsE;&qiH}@0ySxN zfA<+%t`DhoLBUP#e-*<}9D{0@o2o$1VgsZ8zthbBlXMazUI-Md$N&5V{x;_RPw{EC zTgLSN{{4dWq-@>44>G$1j%bv%s(0vID(OCmw4GjR)h(KPYW7zT%HJOl91(BE9a66N zpJPK(Lrk3hBarZ~e|OTo-iQBpbowvZq(kq&>yH<=zdQ#3qfel1;HwAN277|Nz7NPC zYBwH`dMW@v;m6?tC=E7ick0&1DehPdfsweaIPj_cX=YMxDVIJCCPI}5UT@?q7sD7lt>gGNGA$lXmywzb^_ehf~U|Fkv;F||vJC1*V6OKclgd_{J zEimE(d7L_>Nm61I2m@}7wF0C$6u@4ZDS(d9<*%K$0HI`KmTGw#OtWGx48;Kh(iji0 zQP%a4yp;FBfQe8pjR&Gp_Ycq>avbvQBdE0AZ=!qxR4CSNMMV<-h~%pU+?yJZQ(Lnp zd2&f;^zX~96>bA#q_7~#8Fn9d+z#%&I?`*Dks5D4CisF8`-2IYcxqzD1BOEo?yd6# z^G&aleUP!;CX$V$$tW!s=8XZlf?jFBn3VXiO_2F%14cv-(r6m1u`@pEyI__}>M`Ip z5^o0|@TPZh2-vtK|ABaI>`el}SAQIMC=XKPzJnd?(cU)rLT{YO-ksQ)n*~&YpCEg8 zX?Kj|^nF*ee2FBXob7r9=CYr_$ae_2oe$53DD>aE*FFaIJx z;{a?20mvqwfO+l5XTZ{IKLAG9-++l!3NW6ZfT#EEVk@BwjPcnwIA*PpK1rl)3K5)Q zBoNZdM1B3q32+|&Rnjd%;)!v<4^Ke1hepnAYuC;aBP?)9l19})>6k1Bf{ML@EXQB? z7J$rdzmwlhDw&~Xb3iYCd7*Q_U%yVOB^if^KO^O&ANYEbhV-8muFxEiLUx3d6>3Qm zx2vv4X@4;gU(psM_wgOyqTWj({zxKd1 z{K9*;KQ*)MpfrJm99Ymu4|De%sVG_jcyZW5apa71m4P=fbFz<#$&vakNG47oa8dpN zgXT{_=&1%7TIpR>^Ybe}Tnh?GiJFs|?+8*~11PTF2tKYRuxKuLi>dI00;anw14Kk_3z-g3=d}_p;)?dHLd?5Bfq#aGMZx2(aw4 zX0IL~@2w8o;oSK*fetbKpbUU+zj(IJcRv1KlcA}zrzA{4O4Csys(BG9ZiodSs+Rf? zu$2=Tm;|%g4HGlcgw~3Lv*}7veL#-7R@U6N2#@t4d?V|Ix`VY1r zlmPg)K@sls4h-For+Ev@!O&U!JI#C;z^o0%jgqJoTJc7Bn1JOva6?Q}lW(#4M{0X~ zD^BMK6XSD(gJ6qre^%uf*cFWdqS;(ti`X)AX{a%&Gv-K!CCQb=b1>;En{lu9#pTet zrr^@|=C6P@Vk9ZSQcxV{A>fcmameUp$1krRTgKGYTUNDXfAn~<%s}RQi^JWzL?Zt*AKpWdrjR2BdBwr6#oivMGgRApRUKTsf*vllhESIjxuSeGCB zXh|1+rS5bdgNZM3{MOn3{8ia_Vk}=k%^RE;`sZGuTm{F{6MP7Df>D#;yx*2g2(qhR zRFC=~9~1#6|Gfp4fsP5h+BhJoT{@;=IeNB_z@+9G?uxB3{Z0VJ(B_kzom&z29J5TA zVKg(5_nb$;<{M)v^SEoDV$G8%DlD^ZR$sx{e}D6;*^7QFSLpNi$PmO_sMj2h~dO_o&B|)HsjI%9MGb;{3!Np~XO=4dx`J0R*!j z&R^}E^kCz00OJFF$L8%$1;_Wf+dhpXwdpqUVvDl&LXOd#yL zJBOR~q>o8+tEhLF+D%#&n+j0G(y2iuV#+y1jAV8okohFt85WajW|xF--^HB1$Z z>(F5Gp1Px4t-)|Jm7V_!gZMi!xtJU?wkMH?YLx zc6#;wS0)FB4TrUsV}Z+s;Uuu)72pm~+K-hZMJiI9Mr8Q$`uqH|?tf(qY_&78?34v? zByyQ|-1W z^SSca;aFfg9U9~_jdF@Cy2Q*$CxVhcdU70Y?8x{1%z><2Zsgiq`h6*m?Fyk4HJW~0 zt#i(j5r*1Rr6h2l`iQ2q!+-@))mr=;A>c$xvZJ$4Vff8P$UREU4z@RWMS^Q_Txhe5 z;5|5{?VD!5q?BmRzU`KT55?#4Y9}=;EK%MY=-77xoyOc}L>_$S)w{0x$aGyqU=8?C z^qN^hIPAyxvn>!FIyse@taR6BdA-fTW|+KdRlY#dUO$%8G8BvnQ+oV`99XOcmT_ph z1Dy;yHsWCE2YRC9NX$;buRBbF1z{eihP;e>+pf+s5otg5k&T6-A3@&#?j$u#yuXY? z7L0$V4X-0SFbF@6zE(NIE{VNIO~t?6QCV^X6pjrFEc)CwDnzr+7n;9*HhjZv^8h&g z^;+hEnPY9pKw5URvCcQpC8y9&e*U`;42Q(6WCWxAy90U%ax;V;HlG$aLIjDU*!JjG zXxWzyVzKYu7@5!W65wQ+G?C~`(6)b64*~Bf2lllQUf(bm?2+*cy*&wP$F7k z;?XJxcNaPg%&X=D?{v;v_msA^z+vO7cs;(?UNyRd zt5wmRe|^R+D^2~0gsR7AzmRXj-ds?r-~n8LTeqmX_*!t>Iu*uIX%Jr=IGcM zw0Kkum6Q)%aYBtI)XMR^camFK^wC3bFq8j)G`x~d1k;}MTf^$%S3QZ+_W9zYH=iOk zabJeqE!)nFP&0$plFF~*ZD>$#aV7uk%5w=z(hPKG%L~)$vupM!_jHljNE;Oyv!#u# zk2}IFR1QTm94siEWISkvA4;a*mGWvqHr(cSlxT?M@73}#O&wzW;8$ommE5b+RC(?IVb&wzQ+*tmcSC7ZCm9Bb;y1idblzjjW~i< zrMJe@gwN#0*e}a;oo7Tf3_bA`0QgVkx9`aiihIx0avbDi?$O}fruMMtiwm%k(3*uu zROm(mazV6b?whik**EnZ`>_@<7lM9l8XL=^=RAPUG?(_g4-%tLI!ko7-PLaCM3G@Q5!~ccfLOoHx#{T%6J;Wx0)4EWgG?6PU55_2dvx;uoAteqWo(Aw&mCRw` z*y(AyD(`7Uals@V_JBC+sfE{58kHKO5k;3U9p#I*sB;_-r(8sfDI3`|tN6PgFg3;5 z3O-t$K5c1m*Q`oRJeNE@Y|K&q%Cu}8^2{6~N0W824_xya)FIAE724OV@p0~(Mj%W= z`Mn6Ah()TvV25R92|&r96wPPA~}Acj9JJdLm37FB1}3^p^>t#OA;?4RE+uk zo8+8Ms@?1;n3%<1AkXkUM+Nm8VIFIqj_52ayqgc*d=lq2OQ9xi0FC}NfZhR%oDU=t zs7QM8WPxeiqSl3Mj)8ERr8JaIbv5!J6t7BCz@xV;giH46^660@I(}`7a>WWxd7!Gr{ z0X{)9zH4pL!k%=9$BQd~r12a+F1DGnMZeDWBtbf_o;Zz1U$Yp>${fDGJhDUXYW60_ z0!mpOvUESoWjdNc4cq|z`+l+;*os}J-Yx^}8tnP*6#F{rEpVdT!8T({(IqwAm#?22 z_6z2M-t=EGKBwCVC0>!ao-Fmk{Q< zNg!1-esxj4)`+|B{oaSDAdaa(*NbYQwwB*zu|CZbuaO0k&xT#?sR$uy4w2M(0N?-gUTBFkW~r}lg7x4{7eQ=wr#CX8CfOT4&u7a zbX~?21LN$an!>ysyVhb)K~U#7QdS9oJP*=_STNpNWc)N${7#=1g{)UIkj{JTmaz<4 z;(az_5q2TXfkofO?UjWS;76&w5iBVpWehDfpZN-`ONT65OMjd#y+*SNFxhS?u-s8# zYdJ6s%G92fLKT)CRmdZ_Er!5G3x5gMTx0h=|J;;_o~R-?*DdNy^^O{3BKm5@GNChL zsrF+61zoU(75unV`>4XzTV$A6B?o+?gdIa3L!OYyF6e@YkXn#qlbid?aVxRUungMIV)!cyePJ{^eCfY zayp1Fn~QhDAji3cl&hu3INAXZ`-lWA`sA-RxQ^$xseF=cBP zByM+`e2o#s;3xJNNraEO5kEOhLlX0T2gtuCBkn6;(97Mp*57%*3P3kuZmkYWn8nql zHUl2{UccbVt#dP}+djPZAu+|3+^JH4{QRhkxM`t00303`&BqMCt>g*5<-?}GBfP`O zU+@Lff)BnB?D@q)xxfSbe(ZI5!_r={1fSM`{yL#A&e)Kh$Qil`BQcKBptKo_2LrIw z1{A`|&}aFvzqi7h_ehZs(O-F*@TEYM4syzQn=`^F06-4^&I%OzCi3n`nO~j??_oCB zI8ay=n+0PueO~XV&<}AY;)N`%ZH&3r&7xeJze>f#qZq{dtH{(&Z*AF$OS8Od@NGIw zwkG>deQ8Bqsf4$3$n+Bb5GF&-kj*UyRz|NS%x9V!cd3P5)+>x&Q~3GOZ#>2|f`ubp zCO+tDQ6yHK9jks*SZ;^Det!PMn`!xZ00jyg?EBQt8bn z6;~tM#AjJeAN!Zvcs}G;`F>`W+rQ2BKu2oS-J@L^Wdcd>rv4mru_bnHojT2f2_j69 z;d*~LD=r|eL7j}>XIj+MuD&_PTnJVb)3j3x-T$a4cG|va*zN>CebXp8j=xEyPb@#q zlo3lfR%7nPm?}>>-FOaEu@Ww?g;jklw0*?PuY6-caipO|K{-f7gK^E^ zbD0eR*{M<7S3itckw*>OjNoO3`p^Z6V1pgAN`flbau9_fIG*BOUaHng*f*p(EI!k6 zO8wg+<&Lu8m*9JR2{%T5c+z+)*zpu+K6I=O)&2-=7wGaL3KO^mv7$lyqUf*v7x;UJ z4RgD{E3Ys?Z!|>*uwK|V<#c3u*8U*|795= zRAMt~V^=ExyYf1F|5g3>0P6NmyxkA&rK)!<{)1PKWX~=E39NEH3OY)ZZ0qbCcF3_5 zKky=6*^k+#CD@6YyU~u2Z4fQudsUy-{U9TAveZGTUd!2`KNlgkW{_=hiIxxu*-NMC z{E!(AA48S-LHV!5b$52BJ{Z+E_Z6$GMCUZ$rG>6Q64I=mY2U2;L8wx^n*?X$m2wS9 zNyyT}=Va>t=KiGP;2qxDb2J$5@$`on`ACu<*uvE>AbTsx7Lc9}5IWoC#&5mxW(Tc? z3<&Sin+md7??_F3MBR=|VH4<4_!Yv1#S*vB5azWOAk2m!!91h4?R~uU_+q!~a zZoVc1z0l+%5m6XO(vbciup}w_3efk^7UfTou#8Le?0u<`4mqW3v3YV=C`uu2oqD6h zDC65lt=gkB;z)zgmr0cpFWcmlIWk19>n2sM$;8J2ineCan)aiFhb6A1rZA(C%GB@@ z1D39|&9J)YwqD)ZoT<&E=Q|~N`2BAh42Y$6{?53c5?iupVIlMSVYZ46ndK=FbUaAs*oywoiK?r&j1g496be##IjH8zg%&rC zmrv+8#?qepIjble>IL*M56>4jVYGERw=+1LTkj{O_tlA1FH?HFM_USj_ZRnaqxdZG z3C)s)SZlbeLLxDrPmor*$VFS7^>2Z-d^+o<-ajC{f4jB?)_G;uL5c9S*Pnm=dIb>6 z+Z>yUL{hD=^R1~N7zkbhxi#d%keo@|tou{sJN>&swj}8Af2OcbGf9!!H?EU@%wMk# zn+JA+Oekp|{nsL)tGaYl`(&t#_b87$6 zbN|sD;XFY1s7`?W1R;&wxX414|E|PBz~GVeEj!Y;Kp5{oqj6UiJ#6R%bV1k8Kipzo z>??r47Woe?^#2R%=b!!4KiQrC9kKlXCzbUdOTmBV8U9Oky;D;Ecf9Z~S(Rjmxs>zj#M~9ZpfR*Rce#ztq~6E+-(TDNTGMF;M{h#u?$(pa1v|sX;~g2-LItlEhXL zUW9%;=lU2(mHZ@4Y@C4nwlNw{Ai>o^srbPqS{Gg5rA+6#oDYb6X2 zX;}*}n0hKezs>9R}$;jU4{l zFLc%eVXi>F5|K*k*a-qbfsI*x@MNOSj&%86x{;*CoBqylP4P0R34QYzh{itvvvqc5 z7La=G15}&Ac$|G#YiI5k1mNs?D%UX*m%QA5saBI)eZL84>SOo#oNmF2jBWVKBX?OFmhrsXJQpa-kG#c)mBbpmf8%FYC|e^`P&< z%?-oOnI8Ste%((%?d=!BK7iiHkc6{;``H6`K|A97&9Lu2fU@&7?h}otoi)&$wx?-GjJlgZ zDqe17YXt&(jyZtR+yyn(vw@|+hCY;u%>bgkT;@dR_wCYzAhD-G3|d{2_XzM6rLFYd ze5T-E;opZf0q}+(oT(0UMTd9jLMs>&-mUd8iM3!9G-PKc2<(D)mAz7azy9DyYnmB5 zR;B0VEGe=F(np>%TOcu%(HUqd-m#q*Sqn-{x8?#{j^QFj2qLSe^;>||-k!!yii~P0 zZ6-bRu>!2;Ulm=FowGHh)*VBykb_abx|i9XKmcQ4wY>M)P^37B#T|YIUWW_aK%}p( zJ8u9Q8WU^zJjbri08Y8v>WqiiBdh&sj(Ysb!Xqp9QjIzm_EnIZtX7I{Ww#RJ;}|8) zG^=@OJqg7hzTH&Etu@l}b*;$!xI19Yn_1egdpX{MJ+x-=*80O6TnJC=cWBbPCUiqb zd>HD;spOAHYRyAqv>54Th0uOTbE4vAn-KSe!0lbDiGA}CzzmB*nfNL}6fmxru-!~Si|TG5 zh1f1aMzrA~LN1f2i83d!%P>I->`Z@&oh^i)g(up_TGLt_sLJ2Zk}r<{o=|BiP8-o5 zfyO+z!ED8OBvftU3gF5MW^x>| zBj|Kz@aP$cta5)D)_G=s4Xlx}1`zzWy*HeIwR5jdDKKGzQh%LPSp149=@EHf9)4_0 zm|xl3(3p#e&!&ZXtAOT@GjgH}eqF*0sGfv7Ej|tplM?C<=OkVM+1^h(m2VF}(}+4B z0#xG{rQ-ZeJmuMiH#e^i6-hsU3)gQxc|VK3l9#=ga|aioVHpOC6Mclg54i@tJoieB z&j`riIEUFW7)I8xe-d2cUdQ#bIVXoM$9l2{^N9NN_d1qQyGl!4auIvwy^kXKpT3AOzsyU%n4HT9Ng* zsZsC8lW{AApDEF{uw3xrlg;A-#OI4S^nC*=BPNw6jOj*xmNON)khMpc87oAl;(LaIFhwLn$2H($K*koVXReoM;`Voyn>nCgJ zAUs`tp3OSpQhvW07f}2OcpQX3)jv~|k2sJFw4e8?ZL&FQm%LmO(tvLIoMtJVg|~Jn2LJyak;| zxbg%4>;uziku**tUr5YF@Q054Ox0|8&XK!JRA(cvU7B4T3`zI}V#c&l#l+C5PY?l; zb))&v(L2s3wpz2r3{O1dqr8LV*=e85p0NKFQ>02IJa5Dxt~Z09u>vdZ)=9}%b1Nqy zav}Ecg@K&oyEcZug;;E%l+FWI_jVFW37X1I1euT1b*Ei zmIAAy??5wjD3hS`lmK^@Hi^vVS%`>%INNzZDK+_{F4_g#xt1}8d*Ci&PxZXVuC_2+ z0tA#_hEFk`8CByT>5~=6v6UcAh-i>*fLge|h~(zAashS|!-=Dx_qR%a zjpAuk9N`NDf1;zH;)B^^#q|KAarW@Upu}$w=p1?qB7ypDFH&-aKv~}i`gq%g02%M* zIBz+Lx7Mi%rQuxSiXwpfe~y6|)@4T)ko%>BU!AiS!m-VS2NJzB#?|qbcwvar>?2%G zZF$q_%lS+pyUhW7vtA66lU&}PWj(PHUC4fX#loRB?WRPOAPp0ZMCUv(O-1c>tb3L; z((HxwXQ5}aIz$ygPUfcmwQp}h@|RKQ$%(&DTT+&spCo5Pk7%Zl38t zi0kP?=G~k?gX8%F{6y*=X85I8@(q~yUYCJTkL+@>`B3)V^9Fl{`l_m&llKejGYk&Q zz|hb^T^B7^cNYTz_l(i-uhjNCd6meacem!8eEQ~`o_!j%rC zq|MnmxemVOXENA3y*v5x2g9y;@tZCk?kG7k-aU^^71<+Nk2A#H?HPF}{anO;rd)KU zuL%d)+Qq)c3iw$5^LKN|qKWDPDE&~>-34UmiotiW7fP`^)UJT58|tfLt4>23KQrFC zaWvc?zhrq(lC3*qfs$+T73t5`$NK0yj^44^%20UDq<+WydovyER5H1Eg_#>E8Q)1)u87Y$wfwbs& za%2<}4O4+(2>@CqxI)n?4r;A%W+$ydaS_o`=9gAyevTSk53@g4z~mil-y$uA)6o)K zBb$MQvzn1L&!yxXI-*~n#->TevJI3;AH>MC2*>#~4895`ASX{k+s2ZxXnhzqZkU55 z15X>ep*tQs^Z4QG(+v?#um(trb)5Dx_zH^_R1Y6P9`6#B$1S3LV#C778@F+Hbn;hP zz|Gc}d`aIXZcCUiQ3)cP@1nwWRSE}BKt7Ya{o&bb3Nx00k6Cw8SGY`wvo|{Ey=_pR z(LG(TW`?~7p@#;R7}0B853mMyR-2Eq)K{6L5;kv`WoXQPh1m*p&>|=@G_WZPu(%A> zfCebjE{~umIVF64o-pZnn(R4LtL`xjcai87yp_pq#A3$|sE>p3C)GRn$79;lBj_qW zp3b66urqM>qUhZtJZoqTz@|tV9GUhSbae|o6Qyg5wugdF$f)XZ>~BKXte@g_>Y>|! zXK*KQo`wtYFhg~G@}ZVelku$7fpEyiHSJKJI3JGsUIS*q-J)K>_zYE>D~y)Q_xq`4 zAVPAJ)>WmH`4nKJ8_oZf=xsCnMgK z9%clWX+{@YiTx7m@~hBxefQN|VdH(`;ZOc%OU*Um=lj z#nXYz&9r9-nre0(bxcQ@71-$Vj(+ZmTnsmVK9j%8nETsTuN#o_1%}4l#Er8w-fM;T zT|afm;VZ?l=sLaMzNdyB-xKCPLWaPmAN>0AU+*US9Y}T-mZW*RV29IEbe`K3Jc8M< zoZ&N4A0kvQC*5H<%_)s$2KN?6`(ikZ$Q<7$N<%~mmlSUFoi- zh1B}D!0qlOxxme4ipcqEp8giped^oQorjmCnPEw}+F-Qq$CzO&HsQQ4$}> zY+D*Z5(dTM(VJIA{H3bwjTdaYv^L2IaKr#bK_S0l=fuK8)AtU^l@FJo3%6`2LCC-e zj?>Fa@Jrwh9Hd^&(RS-nW9S`0I&bYw`*bQ*Z43l*}FLDDyaULbf=0uOT!}Hoh z(K0Q0Gk8^dC5KS0)LWNf)tF6(9Q+xEnsA+~xbzDtel6I@5v0hPgDpy&IF=mh1MLfJ zu;WcBqRFd}c7vU5M$TtAydxjtDnC`LrfzS(31lUEG4lFdW>lIk5>6@2km)Te8!IZD4S3)4@;8l6lT_D9u~J-qRO4rc!7tjM*cuCx$fBwG4~Ii>9P5m3ay( zdza^N4mN&x!FeE@Ex7I{mM_0;BtL_kd}utu{M~RQae=~fKjKv9J%u7zvXB0;hyNKU zy4-&oN*rrzS&c9!z?t9P%FKk+r!*sRMVE9(IEfV>w&~A>_s_}#g$`?x|!0jXnGrttfJMFU9?Tm?bX^rwU@F=LJt{arCIvRlZLRlaLILj>vFNAGX zf~GH!DGx)ZPtyz!A}6qkFfUltL%oD&@Ii?phjYV%HC!Dvm zuwifW>z1da_dDq2dYxnb9BM0W-v#X?D69_8n7`jo!xJIa7lY%jsJd{)NUHN9Sz zUGNM11FROQTp;m$6+R?2pauH~KcM%+Ejs9{^he!!oStCWcD3dnIbZ`J_(o?2GfmTB7jUF=!|pc2j@BN3@2?69P0?Q!lmTKkIAUGr z#c-IwMf%w*@8AgTrrA6tp(9bNe7Dq?5#)T)ndLK^wDrASsI$mELaO}cse^@^B}S7` z=N5WCEQ}D=DAOR@ha|pF}W&O~`Utu|WF+60RqIK~iU$he}#zz|^krX&5r++p04epoFW^3FMNe*i@ex-b-LyWO^C|=6Tcw}H|?6l`PuJn2U&;(j-=1qKE8(5 zw(s;TaqY@u#Zz1@dZ%?(+8+7aTJ-&|#}<&8DZEjpISG}`!N+AuzqhR;t{Z##y3bWp?nTbMv}}*3?l5KCmK;rH zpSz}xe~?AHQq_34Zl%Kd!sw1o%GrxMs=P03 zt|XKe(^q*q@eq_cIqq(b4qh3X6n$mb=#4vU@t@VfxL(*(@-Uwz-*i_*RQwhn>~+e~ zMm>xhiC(+*9r^56+l=LQhdEd08nGYwlb}STG@RzU%|6v}BjUW~tnnHL^OqEjv~cVjm|7TJx%&uIqCjI)H$8v()U+T(47Ha7ab@~^eck`Et#$ZlT6y@J*7JbTU0 zxAoL@t3Q>fAk4)Di|VXQ(>~nP_%IhwwiL9e_3Ij)$;KSx4_4BIQ{O%F5}OvHz~!=X zWqQWmM=qjZKg(Yu^Bc5-XvXmG=HVHt5rxft8NENqOdg?z9U#P+A7~xa*IfLod&PD+ z@Xw7cDyjUM%lwUOj2naMoGhcUF>7(mPrk37ip^*bIC5Hr7|2Jn+%yyj!^dhyskO+@ z#TCwPwkAss8z)E_oF3@$7P?CsSt34miaLTVrIW+xwR9vHyISp)b`N&duYo7CnhlDW zzeo8I#5Zt-RMXcJ8jil7^|$NsaxZKOQZpo{X}GtNx31A_{gSFz_RG{dTSo9%pWY32 zUn2Z24jMLm=_4k{V=v3LWca}HjD)?ZZc#9YM9G_{MaNHvZWZ0QQH#6_sA_Elcm|kD z`JI(qd5{xD@bUVOpYZhtp%nAa3(HfRco!tF+M_l=Y7f6K?eg>Irbe7#fA*k~`>TG^ z2I8+*lD!=lo23GnH@{>=jKS)S)KCwjNoNf>&i}k5vDx=V+7}&?cnfyy<)8ffcVM)0 zNca8M@3AwUSrIq$CrYwn?p}D6<*WPo|J3&7@ldY+`;}6il%%o^LXm7U$i7tenvzr` z`(!IiW8W!d$ruS4d)Z0$h^%2o)+~{|8iN@myRpshezctP`R?D}>-U>K&N+@5b3gZU zKllB9U+?R>-kPduVufPjxX0%&FFm^2uURfjfyM=RXHdcF3mtTXAXBJm zp7BsjC4c~A#grC5sw|!PHP1%7gDjyK_DR#XZS;{K`#fwa!ArTV+rfg&&_~@7dEW3P3peM45=mTEq*bN%!?f$&a z#Qp1I8({Q!83+=64M6vR?ygH>iCY^J%F@dsKW~566trL{Ld3Tm0xyq~rTO0^41+ol z`I5=nwF+R_(*;@zL|Mk!WY`{JKE|N{+NzU>XMNo~TW*hZb;0TZ^D@m>Cqd~vJ^2+Z3qtdGY+QJ2;V2tG; zr1P8!<=Wbu^~)UH7?V+n>jdSkh!yTBlyWU_ytjiz^24-?H=PcFqSY*%h?~O8l~P+w zwyt>#kv0oz#634abH_F^X!j|dro8c$(W9TXIZ7k>C;+Wr*vuEuHnF`g7im(xURJXy zqDUQ!^cXc{Iq_O*=zhI3XplKR^}&^u0M%89?^N|rC!t=1VHUMTP)EkO*n9E-#vGfZ zd;yZsi=o0rnx4Q!j=jb6pb7g)-hlUM%W7$5g^A2$V{cjGf4AOQUpX$nWb`>5UysYF zJ0w5-w9S||0z`Pqt96WBK&cqh3nr!ntVRo`l~nFc>jBe%gI#(xYoAI0Hl7Lm2gNoi zdNmgvyaos7=Yh+baC{UP4N~et08~_@is?!m_NlHrji&p1Yl}zFlnzXRWvox+YlEKXWo4hUy71LFiUQHK;$f@yoT!5HVy3 zF%TrGZ&h;wWLQgwXe-KyYUf_WiTc z)pKDbYm`@+;Dg}IdSO8Y)z(DFUN1r23`U#OP;Q=b1q(R=4_o)6t&diIUr45T1D)NQ zF5tisb=>fNUEuzA;33Ea;!26plP7lJM)`7LP^7o;s0UN+E1L4{HUM*OZb5u059Fpc zY0SF?Un4A%ywd18>kK=fdx%3bK(!46AV&-o2Drwo&#V_&BAJRTY!qq*g+bXw&trRg zc*Rd+ZkVWg9vGC3_^w8L>sEM~^Is}-7&Tcw-bI&uFQX*$9RbYnY0yljV(9B#V{b|M{S|fK+`9sVKjj=%w+~(fSti#_Fzyc-Vd4#7-k}BF^#s5 z!6R5Qw|T@jNOK5u*}JP*vcw?K`f<{hiBt)SizFH%DP-57k(KI|bRgpr>u70l*g<~q z!3RhMt<*fD@N-8l2}eMX;W&6F;!#Ltqv}CsHn?TI41V%d1xv8{c$_JWxv>hs(~{Lr zK&@%n%>l;}!nu=a1lWX}GN|`a7ar{8!sUxp(l%$gpDCpg!19X@Lt%_l@*g;T234g6 zTJrkPM4&3Ov;?+a0Pmz_2lJZ?HL;y8e&h_QcFa?aiUHfZJU&H?w<_pAI}Oxtw_gEG zBorouGZ5WZcC*HF(^d%!i@TH8dNPZ&%E5CiopEL#NY|MIj9mLqX`~b>mq6tJkCFBe zz!yT*>S7Q)CuGDzY2*vQ&ONWt)t<0;a-e+*cn^1E2>(Tt!l1Bxn6<;B7L)_LK`kuH zb3PbUMqu#be%^1abgN*awnCZUYE_TX;St?-JJxYP&C5P&RSPxgnV^k=vcIL1!t#Ut zT)Msm;r>qhi#^N%j@veM?-mDW9!OBSB!f-fTFhMfmQ=U%O)$e>sWYLP4(9R#T~*ew z716(s)tqW^P-lAg^|1x(d)9gc>3nTV%eO|4bPzV+cm~blzJtd&tOKe{O6kTw2x+Y> z+o>+jkAO=f#!nU4HXhTnwcX1~u%&yALA4IRkq^jhaO?w*xY*e;`h_J>DRnUbdv#Fd z+x&#}x02Y9F%b^#4EvduaoIwqNR zGx9D>Vpep4-O0WNRB4N~*g;Wj0#u4)W-Ec1e(Ba>dIUi=N<{ZuR14Ubw77i(Ccgli zOd)O-et`{eXli#OB`(W>BXGfLJS+(qb+WGuhM^d4W|*6LVt}$)7Bm17)liq{gFCGs z*?GC-J4EV$KSP*$@Ng3Rbv7V&MdHF6N`SHSaY!U)Ib>9Vj?x0`qd@@SfR9T<`~vTT9_P71<*AV8 z*J&>HEBuQP++`_%Hxxnkv12rQqq~v2kpWk0YBBq68)Gy|1jSIHvk9F8>! z_Y#;FTO=Kbv9>sIL^{Qt&Nz@|VS2tx7Sn497Z9y!aWFMC_ue&rva>695D-n+#lng1 zyO4vyS)wsCuoc@PrFY`Gz!Z2$Rktjf%lk?jH@VgAVMzn6(y9u%0^TGX31Fe16@oWG zS&J1fybc%%iD4^nU&Qm+;#k?|h=i7GA^3DG|D#z1qf={ND=ICQt}EO|WEaCdUm;W9 zyX~PqFmwYGRV}EJp&&ty4i&qx1IhW`ageSbb^%gAhP! zL3Dr57jE@Q=(de;8TX~%K@!E$74Q>C!MYhnIBXOQlZ-qOMfi?#s7CwvbEyyTr zdKL6DAYNenw*hhKJSsjr)^AWx_l7fZ&k?e;yH3CNLR$<7)bb>OeZ<*@0~GiEe(+Ov zRNVV-=>b1u<__hbk@~;J%-i*{hmNj*6Oaz^9Ka@OZUe|<9r!uWHyb{-$AMSe0)0+| zH{hYqR$YVZl{7D??rp0}cy5pEfTq*O0v6g7MFJwNOAu+o0N6TkIKl350aSVWRk`+i zIU;yco`xt?PZzaNKK;r&c*XZ*|9ZAjf9W(;pV0nwtHwU1|4$N;YDvw1093Y8zF zTz)5b(P6~>;R;DN@@>=aIaDjZG9I}&v#k95ANf>pn$9|u;Qo2};H@_YtK)LaB{AH} zO0R)6vklOOUji1h`UAkxr3(b&efqQj?u`?#LF%C544gNQdre}Lx8}mhK)#U6Qj_Do z^vdM2K*IeCRqjPUfbo6CO67#&(zjBDjUV+kAVJJ#aoYnNwl#q$|1NO>+P0enUgZ{D z8~|0J?o%;_&-wsG$fth(+G(y?aE_q1qswt%bWsB6T$#ZCzpBvTcMcU1@pCiYQ0~=7 zg)R1X_)5)yd%vAwKzz0VK#1g+s`>F)EUmBVy9>$^2D+)tk?)zT8&s{{P$Rl`Q9lG7tl{*cZg*;~YoE1no5&<{i9=98kD+*V`AQ0o{P>mpnq-TFHcV zZ4VQ$?^?!zBeqEPR|><+D~|Y;#JEdFUIm@3y2EJNzzoM0<^YO_VHT_eiB`DEb~ZB$ zqDP+2rk(5hp#7VpGc=0{d`QS+qy%{FW&lrZ> zRLCrud$o42@8Y=up#u=Gxo> zCB&J4aH;SbkT}Uuh_g+AgE(8>Z8G`5#eo8ueWougy-zIqOAmS`|kSfTpM;OlcKGyn+H3kqF zZ*9>a>ZT<>DAOP>`^$28lY~rc5*PzP?qffwzXB!N9K~SxevXgMjNAAK{{TJaq_&g+ z@vRH2i@{ZHJ}HJ)fh2cc+Cd_#nLpkU(*`&>BJpw1%DvBastK*jNXg11;!7!e9mlAR9f5QJWrTm#Rf zS#EP|Fh=CRLRJ8J=zkjU*+C-S$1^1g+z^5x+()yb=OE)ab~z1=d+T2a zy1r>hDWO`AxU?-Eu;noIG|%0|FE(0tr_rB~#m~-G&v}u!3gHph!&pi4D<-@F)UaSf zdftqBnGxIKwm@et#A{grtQWu{+I6uFTfx)gpW)bY!NWh418vWxiQS(GITAkWx7MB9 zt}|LojmSIa8#1L>h)48&0X-^hz4O4l*jEU)AqcRrjym8jl9+EM4Igmi=Z)O^Uv2p5 z=nduIw9Owu$_Wz)2n?F~r1BOR&=jK$o>4o%~70|N)xI_ zmmyZIU(FDAcd#jpFiYoT0AVm;9S~h(VRW0}s6yQY0pO{8;s*TjPl#Z)ft4@>$Jtkq z*&~Jki9uBsu-g}9eJ;YkwPY>mMRrC>FvWVyyf_|lzOK5-^gl^gH0lXT%Z0Y|5=J{a zbAvtn>Bj%s`tDW}-*&MTS=wkSoE?TTm3xGH#mZ|Coqe=fHVSIq2V-k3+21OJyz|{U z3^hI%#*)kQfCG=vy&(+XE{`XBvodo4EV1>pjkwwdrU~MBKpS4zEaCbwM4KvW4NOeT zJ=6)5b264q{|d#)11)qIOndj~ebP?h`N@>!7KmQ6UCR zqoH&jo#|rGt)XTa{%bf%AAn)aPjtyxvhmCG6()sONMRpz03NY%b?nZxB`Cbd6{-9N z6z`mbLPi@HjWGokd!X#7?$O=y(*-I?mj1za7|t`)I?C-mk@E(7Vf>U;D`V>ro;9>} z^Z+gm`Jr|eo=(@z^&>W>go%o5KvNwn#dqOih|%hzOz%jGdv}TvP*;qQw~!9wOo!Z= zxTlT-R1Lp)5FEE|Yh-P|IGVuw=dv!dVCz&E-Yr&gKxw7C!y@%)JLunPD~@`Ls+QnX zEBL@4KQ_8&nNTh+Eq>UiC3JjON+sfPKTF_!WL4dPnGrzco0zmQZY~43kYsiJrAZlI z-yoLHh~d2oU~PN+6{hJlegW$<16EVmEXp}1P~H+<3v0nO91tnFs>C&JMvtWea=(o& zixUjB>eME46Jt^u;@uFsC=+O@a|AbxuyQ;H)X%cMUC|0^s>Q!z#NeieYXD`wYl=Z( zszB4f)0{3nI!kBBI&jJZGAI0L2<6!C_=72o$iy=rscDd+5i9qA<&Bo#xs!0Q1(ZR! z++Dptci|}%RsJIwD;)9_4kO}}rBgy<^LEtZ$lN%~3*Gv67M+QAW_iC|3Q)as{|~BM z4a@%x>HJ3jg@`zo6wsf>w_g~4dc5&&u!m)AOiawMU^>4Mi?O?z-pxn6+c@{${{ZLy z73nX3x_%5aqjmJnW*6hFS4aZgUO@6Gmh=vEM2^0e`Vsv3k?3PDgQ4fC?!S1RS`I9N z10w_Kb0Jbb_RI#PSgqr803f_-E5aLjLHfTM3Pe-|)FOL4q$>%&kOb`Nx`BGd(BV%w z%G;&49Y=Wqvc%~z&_^c2SoqH`fBugBG$7gie%(3zJ30UTt}d%zf!HrJ8REo-h#frg zk4<9LvGSi$^WS9=*wxUc7p5k6;M=IEB-ZQyS2+K7Qb!($pY6zrDZBP=GaMSopO;O~ z*6V)+?wZL616pZ*M$X|sOECfwm-FpY6uz)YXvl4J(`G}5km0i;doJw4-f3EZ^TwP& z24shK#hlConxN>B3((3iSD$EhAx`HnZ=IV3m9;=u55vSwRr+1?N*4Y_S7R8@E>qI{ z908X)oiChK*_QiabP?y&LE($gy`%P~gY!v#}0J^E9n z%9k|-lI}Yu?orzw!3$BAyYn{YYXU2yv~zOQ28lo)y=+S`0DCY1!92mfQqG>G9_o_Y zr^yD^h(dFD`YOL}VLzxs1BwUwX?o=#bm^i_Y+35R)QimH=0Y1Lws!Hs+@?e7^D?hR zUeX9xu5N$GOSjXbMwIdH{JpfT58A$tah3rT%~I5GD5+ez@hoYeG@zn-D~X*0anqLY zV#Ohjw4G;yoXMyb!pNNG^7FZFPJT+8%Ho?*T{*)}xO^Bru3a^#!3*ABh4xQaGiqFASGg{VhTuK&D5zZ)D*XW4;0Jv)K#ReLh@L{@+z z3h?mn209-+Pm3kcSuKJ(PdboIAhw@yAksl@=Nhe6N|8XmQCissnce|8O!XoN{Uq!_ zg~|rN2X}D<@K!?uz~AmZe%m(U5E!^)A8)=KI{}p+L2n6xiLV3&_YR3?$rO0HNM56< zrfRvI87%zXI6qXA6IRY1M*JWUMvS=eb8Zx^U?A4?(=Go>g@p?rUfo9GU}1G`*iGXMYO<$Jcv1VU*B6ns%<_plf~je(pPZ9u_EODMn6Q3Xn% z*1#=E?nA*qAVn&akI1_~be}%kRg0`(2}7MdMe@H*e{Ao~e5^Vp*;+FDiiLb{m44E? z-W!BoDq4Y>hE^z|Y(V_r9HB5GNFMnwgVZprL#%4?nrA)7$VWK#>(?OeD^OD14eDEw zzRShQ>EI}puOeS|xCklR4kPjrrZ5vwKMR`;b!MIcaXgLETrwx1^yy=%$;L&H2a3W< zhc8PHMV1a)29<0$&R%K}t}&-kZHh>bBv+RV_jUc%Qll(V)#KHjDMdA` z7<+#Yw8CNDq?T%Q>1bfu$3SF;zR0uEwpQz>Z&SdtupF!ThYk>DVpI zBEys;m)DlJ$l*1#g@Tzl@+9|`yXLL6x}8CpHACy+W`iyPSXoMx^TcaLDJ2uNMrwVC zrR&-y(fFH&U+Ku*@xDq%ZlCPxOF3R=5q&)e@&iSZugiXUem;G;_K6%ng`;^X-`ArN z8g253bFp)6br8dCV@gqI{-uenZ$)M;@|`o%v{ODVK}KEcH#bc4B4xr4w6D(;%*cJ( zT8AZoifMm$f>X7`ut$puc_}~H^RpD6P1=^0d@6Yrb%mqrxVJ+M+UC8Y_MyQ%4(I7r z-EPjZjW=_#N>(1zx9zLjT4uVr1AX#it!DvzF@m?+g=v;5>e;JYa8Yh)tT;#{nYB}I zWMknrqYWXiiqdN~7nLpS$JMjI4B&tQe1KbGy@;%ZFZ9^=fp7`#JX(%&5OA?x=)^hfk4Gb?jO{l}KRO z{GRT;X=KsnJ#z0gHA3M^cG=V(pY_}8X~n*4Q#>trPK7Pff~+8o7IbeGsZ5;{3(TEJ z_>jL5E-<&b$;eSU>dGF5Rw!SS9?p>4-ea5bbB{E*I&$<%yY%fxTV(=oStXu@T;!;J z4BwzSGY5gS4&Xf2wq?MeH61EmQE5QLssONvyuw3HvDqT$i24<=Ksy$WRpx7xpWP@A z2wC3z{(x*W8z-RWTzD{J4*2gym-er<^b1$7xk+aRRQuie9#y>68_(4=+HYQLH}5|k zC*+_N1arT>wqaGqzU-Aax%SFI8;I&`!3^<+p|Zgm&95F9u$g*Y+F|i^jJX48^4am- zVsBY{&!XkU*ZJOLj&c;+or>KUlf2smntT( zH3!%2U%zwka}M4wQI0{$Z8~rHx;2K8%VKB<&W!->j7wq__Mcr-k4bmKA5%z?XP~{U zghRhgZpya*OL{f6H_waY8BncKWt91TN9EUwcYz{eZzjI4leR8ad7D3e`L1@?fwT5c zorW~HT)n@edInw3P{!X1hJQtmv#~G?TAlW+89KJ1hEzSqN|2HiGW#> zk5!&zEM5i2qLXnMNP;tgv+v7|{SkR)CyA?yp`wU8Y=G7SV7Rr$TPzbDL$bGLQ#lT; z+pFr@*Uad3$KCUOOY&T-+C0=2y>><@A93=@YKd<92YyL9<`gmOeJPPOjg}@zP)_iv zMrovXh^NSei1S3}=?2YT?%87z+OCA31CW~3nEPajhQ?mqG|a~rpm*!)S1`+dXk}6In8je*Q`M; z5xJ>WMNopPbgX_TFZU~G_;v}jZQ4qGZFQSB`c{W4!(vu=3x{rx6S_D-?%~=CrOcuZ z6~#;3d(@0V;19E>T>Hs`)VJ=QQ+ccu+;D>45$wDNrQtCbH$p0=8|*4OlEVhMK_S3WCCuV?Fi?qGkFzu;Id@6hD7ij=if zT)Mv-;z!r*Uwnu1)~w!alAD^C_4ZiL&-9x0B*{mi9wuaYP2TJ7$ndpxTL`TPUVGwG zD6-k6ukmBwMD3i8QuVuumB8K4e!NR#B7E@pzPL2ZKJ{69+AZ---({90zZ=F^Vl%kD z#$9!Ya=G?;0QbrU=p$_8i5Lei*M(p?#MXp?gzLnr>lXg|L6umk$MN=KEO|WilIuL$ zSN*QCI4(rlm2)L;E*_`C00WZ0;S9eCNX=wZC*z@r zw4$_cm_y2gaG!(8HqPc(_3oxUh;V& zdlPRpcZ0Jyc5k+`X}=h!BgQi6gz3jtx5*#RnF!9SllvTVJ3nlEW2u(B^W_|9!1sFi z`Y~3Rcg#)+f((FX&SFsXTQKiskMsX6H0VTNQF98PL--=xs;0@a!))TuUUXd zzKxVL`y4pr>OOJQ-qeV-pm`^ATw6T#Svupy;5Ci=5@GSWw&CUY7s^-`70INO6k{O= zslF6t6`$r=Gxz(IbHVVyW(J8$!9Kr5G1j+Tt_9egQrezBI5AF?abUw&fm1q=AvJN? zBxtP!c0UlL0VNV~01sWX+;J%m?@1qp+nXk^#7j8Jf5vXNi=@vP`pv~rxR*X5OtIDf zGvjEo!^oa_S^`J-F^LzXxTiNujC$6`4pIz!L#Hl`hA;0)r6$fM$oFk_VxyS|J)aE4 zuxDHK24<{T4tt!Y%(w#laUbeL#CPvx=Wr*bjgKfN?l`GLSK)!vy%8GLgnif(e&$8@ zt88@R-k?eZN-pD+Y`LWo`WkUPGpu&TuUvBza^Ab<*4XGJskC8GYk|dW%UIiS9jj`$ z{$uRpUjw-ZWn#2j)#eXwHm}(fTH+C#W^4Yq@-efuww`R9(->kcQ9+Ac`?2Z!uH&kH zQ3xAM`FoiR##Ux7G~Xjf_3E&qa6wj=vldU5{kH@IIX2z!&OK^TMwFWa{xT!$vs+&F zx3dm>W?7Q318rc_7>U=KrKbd2hO9$oyC<+aA{=x6qMTmq$GLfjJF-h-CI=<(xja8>BZcSXd>)4xgY=ez)Qvp>ir*jMt=JdJs_c5z z`LyX&QDNuXCP1v_Hl32{56|Vz3ACf^i#OQL+sHjK8GK=Aq<{?4e?HGAxK2562d6E& zjoeexMku=e!kDF5 z<3DNhEol$1U1p$Um!)!|UKl9#UXlCA-{|u4N=8kS+}dr{bA=)^gJ5n9_15f2OL+fN z+&^f-#g&R2oi6gdwmDCvYp5bB0|+b+CPfQ@Mf{-FY}nCv0U=sbmlcL6nlH9_d*0Ne zwFFgiqZ8XH)o8f6_?XBK=S}=@FSlMMZ=EvK8tf|5TGhmzj>haCwZ&c@EZllwoGNyAttq9aOK2^M$c=uL0AUInnQuhp;y{*m1DQ`G$Bb8Gf1~7)v>1nOcy&v zf0PVAyCf_zX2iFsfjG!FDvnPb0=k44jT(st=8PyY`&JzXb7WCMp^!yEu2+^GuN=-< zkPj15l8*VP^dq*2%MOUioP49|=2;IfNfCGBv*x^1HDk1Aq5)PD2UeQRQEiG8bH;tD z{8Dd5p#=fnAX^RVCuE zr)zY-M;5W+5&ZiS)92r^?IUarwnboLRe}^54#)*3Se1*($>;KgeY3nCjOEBU9B_d5 zLoYRT2VbrN+;rgR5I8z;tho=K#CJxyv}9W3mUDdn7U7>J&L)_<^R zQURv=`aZz86n><2OgkVDT#}1o<0@QPWX`c4XsSg?VpB4r$YICz!+{*qHWVe<&B>e8 z<3ya?s}ViH0S=5MQXCsLZ!{}39-C=bryx|L{qEdw-9fF^cIz^rqT|c&mwAS6fRy}| zc^g%l;RXQJ>u#3G@JUI`i3yslL2(hRlD?cc7bt1AaTU84-d=wGcrxOd2PKdOHi7df zPMZsfN0Sj6*?iRGLI#xoyiAkO+-`9jHl4CygeJy9CT76uM6*ItFS$Wxe7~iVgnH_0 z(afmRXAOvNZ_xNNn6eiPk!|PSU5b(>Yf^(Z2nTzbVuBr+9=HLc(cMD z6&`iU?{#|$D5l@jI`y9~tVd5}x3)}ju)ngtI)!xs5cT}$ahikw;bZ#cVo0IyTY)_4 zkM4NvEsjRF?#M{ceRFhBRUj=|9m;IGvVrfx-J!`Z5%TkT#W9z9oL~~k$0E==qLVwy z;x2`X>yKzTqXo9tfO4w$`3e(tXZ%CTL!x|JC}@eG53xlN1FmHN2^{ zB`n6A&E*h{x$$Pqs4a|^-*Lp;pHTZ;fc;|kV;?3aqrP`@KK9oUPeVzp!mT%&Xn@S= zR2*7o(mMBHM>r}e0grfJkmegsu7P{qR(yazSLMCN5O4|<6=&f$4%=N}B`nI8XShU} ziqMZt^YL!rzGo+_RD;KH;7DT&P2u&;_E)XCE}%NVdd6bp1SdQa#88RC6VHX!Xr;pk zuQsGkm#A%MzcbNr34D8K>EP8zov;~wg)R(;GpDTNduvLBI$na<3?;`PyXLyMza`!% z6@m4*_|j1Qz7F-|E=wa;ou0Gl4{km22R`p9jy-0dl z9YTW1qxNql8{*LlJTRibfHZy&Ao6j3|s*p0U#?9)(@r zf|N|3it%$0rRb27wPD40^UFQ?b6YumAz8rodj`bLdev;@jr^uUNo`^^LNjw8a`v;rp5^o_@=`$@RLhUAn zQU2n3rj;*V0Aa3T@IHBp-$FpKHhqRSe@ABl!RqsK|zw;k#eH7(ye59$Oyn7IF^T_G%rXa?221sx#3Y9?pR$LD71>b0W{8Lb8TgeMgGkEinR z9)@dAK0Q*`Ry}!CP0|f)p9KDX4bJnxB@0n@W{Ha~IXIWED|C%c<)K}9Mw-he$99I5 z$dJqWrBnLFchesi%F~PpX=%RZ{zoG+dQ5ui7j@xElf|k=H-NB!v*%Wn4m}o8kdg!( zVbU5}Uc|8xf_F-;hg8UzZ#is)1+tqT9OQ;8x50stWbM) zLK-D%M!DbXRGG%J&JHGzcCdH!bXg&cj-Gx-A7@( z6rnyUG3T5xC`C;NktOx}nq|g4KVmu}xba2LyP50%9tD&b5-;5;E7>5%Go>71&<>)uuwtj5d- z+G0OKv0gGY`N~n@O<#%~g*8b%gu?;GbBKB_H6u=1Ccyj)Vax5yX8six{-*HfM zZ6UY*YOV<@>#z9C{32{^NhkToJ2dlCsX%8dHsT@5DqKn+293$0v6?t3jD@_l2^?S} z;M#Moyc^uglX~7E!j8-+*IZJ15+4J$-0Bvo>7XfNjlm#1;0vuwduJe@2Bs;JwccIg zP~{HNG#%JG&qrp~_PWMplWJWf!_us?dk5lBsu6E8qLjz)jRaQq(EKiGGtIr#%swHV zW7{=P#d|Prr^Pw)^Z4)F&(FQNqDXCZ1}d~qCM@JLU@*XiR6m$7W$hoY*rk5wVD#~5 zu>dZ)3et&C^=F5K=?<~)k$Xql$Ya1xe(4UuK7pB?h;g7Nxi_Cyl{+TsJ2%N4=Bs%1 zi7au|NKT4`MkgS`nMOq>R=+Z-@@GzXO(#EzA=xsEG1{bWM|4o7WK6^3#avovZt6(z z6Z0=>)pf1GvgNE#JITUj>Gf&>uw)x=gep~(so3oBiBgoT4udK+2z38V=<56u$WX(Q zaXQ6ToOR;ebiCt5k2@dRs`cByyv3}Va`FpLXbB{=yCz7R)0Q?&(B97GlRF5+mL3*T zXv+LxER7{3sQ*I$H?Jr``T~pVM+3zYoqHwr1!tXS_%0_>ejLUz*u^Hsova>Z!Zl@~ zIM_I3tt#;`_Dq?Y>i0q;SwGGv9x+4-`kgK;F1J-}`MM4k2al%m85}xs@mEnSCy#bG z;62U?xASPSpozlL`I-|c@3R8R>stH+*B5L=n2pl;2J=$* zpf0ce4!;7gA~psViu9G(65eO|ru)g4aPpa`%HutHr$UaN^x-e%B!0Ff(8oi_Xi}qh zXvdh{yfbxGZs{75}xm?&gs$+QqB-fxG)-4 zEjhK{^ob{~LX5$X;ewJzd>wQ4+%w&^<_L%VB`-up{gP`L8Eod2hkzuz?Tlt4Up$8nVH&1-F}ABT}n!kEY|mAxw`l<*Hz3aL%`rRiSb~r|y62k?Ll=ObSUXXV4>VFA)6-nS}-VtQ2q+c96>gJTgf3hzG$LsA6%v(RWyUXLaq^B8ZFd2B!sX${j>vVoQ96>n_+eL$&%OjofA8#uH5J;=)b z)vQ!(o*Pfs4ah^7Jeexlg1<&QBwKXFlFS6i-&W*aWkvXO+?lhzDJYj7#_;Uo7ft8R zjr4uQ&9i6wIRd3Z#zIGx`RA3WyZnG;f}bBs{ubwGH+bI2#7{fS7{kOSREqkzF5Nc& zdtrSAP1Cd|Ha7a|k7OT>C3)?IMO~JSu%9Su$|a z67VOM+{!{N41Zf2ICJ)<+}7vFZZjel3E5}88B316aDki%SAJ=7&&YC?*8(?i;LTm z-NftDYqI|)BN99b`kQC}W`tEk`uQ8$9E2Z$FO--G@s8^NN`Tb1nSkz( zQk^NNKUDwx%@NCzKf~8l?)p`L(Pkwuhw9$=^P6By1bXA{KgV{*_P5&S_eK6B3;yj@ z09>ggello(>xY0j6&o}`z+dzCC91Ar{?)#Km)A%A|KU{qgJ|(adNlclm_xejdfy*E z1tuo8+v-!0a_i5fF0!RAIV9#>_%p(vcLBIH%qgu;N9F+mh36mNhTx`i+nx#zK;j$5 z4U9pZL7hDv5P8dt|EO7b#o^m5$^*nz;O(FZbz~+ktj-a;fpNHw?JuPtm_%`z?YDUV zJ*A*15Ml7N7z4e(GicJB0kN~19RI$z=eO@ISei(!x}KwL?!X$e3oL^!(5F{F|Hz;B zk?%qCzmy9kn@5paomdSnkCTv;d&&IITOxj~a&`jpd>!;I#1ZfY4Zt)mrZoP(v*4aw z+CDBMu(6&G1yAFin46bFj?17aua4MnZwhd~FKvHc0=l(_8F{`_5b_$X0CLTL`Ag7) zkYfweb|0I|^|TNQBP}Pg5|FZWJHM`OJHXq{)3u|Bu?}2?=YeF8+N_#h@qa6El7eP<4&P z*pC5SvNFF-(w)C2#`xzPv;IThB|Zn;q5nrKJSPv1jlOnKFg@zmKf)5B*{h=93;+3l zv%f$4H~)j@->>OYE2GBWYpRC)yvk|Ijk^Ux literal 0 HcmV?d00001 diff --git a/docs/src/images/dp-scheduler-seq-diagram.png b/docs/src/images/dp-scheduler-seq-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..476c4a4d6deb2cf0a85506b62d06deb1ae329cf4 GIT binary patch literal 138909 zcmY&mFzHabMvf2|D4^0IAl*5-22;9wlnMeO(jhfqARwSfw{(xL z-~4_*pXd2Kf9>_Uci-1_uIuzU?*rG>RwE^%C&IzOAytPc>*3(sgW}-eQW4-`pCH{| zZQ>pXROQ?QJj2+2YTloMt*gBpn|-z-0|HSD>N1 zcZc%+12Dxsb#`zc2(8F z3T4$0bD3Kz)ydw8BB9@iB4d7Vnf+%pD&DKy;FDW4Im-!)@c;eNYvmE#eX;X?z1 z@xXOJz3be|daI+v21n!i-nUFozCQ)X_b^Ggjs%bYyW4*@Wiw1H1EOOe@Xl1T70srx zkXy6rhKV>_=!pmZhOim`|I`Tq4vG9!kqWVx^|@ZB8#l8UiG%y`DXRm9XVCvcjL-`G zAwqew!L5iv+}}S&2u>A~cIO)Z@_*hsBxpX}_IU2_#PwA6#fW^q352s5n3W9AP=V0h4`rVq}yqZ@w zgS6qjE^u3Td$>+)v(zr`Jjpe@@IephXydcj9@}{8@XI5+-Z?(H(RC5&FjLd&sIqAC z^G)z78KVqMdp-fTcAn?gc1ND>PLHaV9Xv7?CU$5{^5fL1i_GS=1ItRSNb=c~Ob^`8 zKvYetgK@t*OW(u7C6{OJN~(yr^IgPi*#`2EE8qDRlajRQIxEAgpmsKU_p0GJ`j|8VZN^v**i(=F}`aM(MV-r8a1m)0Y4WC9tE4`wboiS%0DveiP?(X)Q zxl0F6F~vMW;6{^a2F;%r)X7ZziOxHmsbi8}m5UgiXZD}-+Waw3${;)U0QL#Fvavns zDN=kIvbbAyD#yRaAZ?#TA;2K!lz6#Pe z^4#lZ4S#!)F}}r$di|y>rsXVWBl;zIF+e9Ey9&_w6w>HuFkHS~;XJ0|JJLCEUKs4BG z)AJ&VdM2^QD%<~TD+g?q|GLRoY)S(08dSxZO$RR&71yuQ31^r1#Q^`P$6iNDTUd&pWz#vH zxujlb8+7l07+xb5wDbFb+TCfUdxMaWu%~i1pxc8J-6o@&WIG9hMDNgq= z62YDJBa%I?k@nh5kCdsSxHCF^6Za)MNGw8&jJghs{-vb?hKWqIOvQ?2d%jlz4o z1VgeUQY>E&>W{2B|7#7Hldxy2c=;9m$>ee%Hu4`|$mSC&JT&2nN5Zfg6`#ermU~Z~ zh6K|o*v^UHGnCt|T*=|64h6yQvUPOSFm|Fv8M{B?57cdqziJD?r3>-V$UOXMGF$bk zXBl$ux|0-tzVvP8iYcE7Tat3<%-PAMv-8Z#hPy7`uxph1>xC@^U)ZzvH;oMPHc>f~ ziZ9kXYI1}-EgEJoNFI_ilBL>Q##8ZQ&!ZqgHnlV9@;*U;@#ex2?jBT4Au*HQg2Q6?NRrVX4fb02(^^I`Gn^@e}j8)33ol~gBmU$sqTh$RuK^1ne0lp!;nxa z1O^_`iiCz+ve{jEJfjK?cRA-1wI(dqNyxF-BF(q?GfSaB!FI#_j_nlk zgb@Vz&95J5AkWM`^z)zP(f`L9856GX!0&nlFLp+1+4RIda6aDrS-90kk^T=C7t!He z5MJ0b!@A$q$M;;~^;`_Ur`^~9WB>lIN7<-~+|RUZ(NcX&pZcNJWq$5@zl*$Z3!Etj zYnrZi$-gD~*C9N>dF4EJS^dsYb1^U>%Zt9b^-`eqI~PHlH!i#;X!-T6LFKpk-T!#f zu2hA}M9_;!r?vkRAtfOGVMrA4+a#yM>>PPklao)|vNlP6(&Aj2+6 za_H0iCcYOf8DpB~#iGx{%3b^iIaxKVG!G?bkm)SXT;HqL>l&?K204RtJBE7Y2_k2M zT<-fU{w9=vFWu~3H0{5fUATYwQS2z3=$)4iaiq#Sa=;$`gKhqKAsPd;+p=JEEswyq zEa1hv=Dey6?Ba~_;@HJUo2vdU4let^G}+cd|H@@+RJ^Nf-77+JzN>_VQO=@GPMTEA20UbrvM3F=KQD#h-vHcfsQPDB=X0|RZRZJGMmKKz8G z$6H4C5lv8U_=9WnFi~Pdb>kr4IlB9!>(MHG`ZOS1c|IF2=Lu8nUA6C&^|{Ynzci75 zAL7?d9>=)DxRdKu;GL~&G&M3=vUOFkyYxZS`SSXK(&D3OP)=SmdkrJ`ugeZ{9*NPq zU=W|X`wSoU+t1(0HRom9rLWHSJ+i%3hx`zvPzhmcJ?sm$Tdwl&&4a)1DOx;ILy55r zav~ywd{J6T?p^=Hdxro;=BsPkq6u*v@+Vt#dNm0VK#fHqc+K^$&umXuFP4azCjK9) z4m~LB?HsgODOw~h!+No|)4d%16oO=wp|Zc)3#^P{d8Tl4T@A6MDha@D?5}zPNr~&# zxd6{j{>(-W2vfpG9iD;slANe)lM zjoYiw@mMmHC5hlv7FXOrXp?F(s5e>*%Q>nfQ1)oMFZDe|4U&zwo16o=IAQ)b4amk;ml#@@CejkbvY#MI_OE5hRq@`onv5JCKZY=x5hxBp?2 z?S!*-!$+HcvMUHP;NM{TU(0;||2|PlvRD&+GLuLaemxK+6ZOv?|Cm7_r36Xd(Wr#c zOoH+M8uz~|MFGPSLH)!W(EmSMg0KVNNiF{x9T`;H$L}+3=%-f$|Bd5_KnP`Z(|@*C zVz$D{{_Kh3ga7?oAWw<|NJPvZwW6q(2%;4<^&Cj!r)K|;Q;7--V zo2p?8knrBlm~L>hde8h+D@i5j>Ojrsa7Dd+!&F$=4_RI9idlzZWAi9oe=VZY?fG`I zsh$#r=7anv{kLJx$*C9e0Vj^3KTeaN-n%o8yj&j%l^MxqzIU8&o6VK>8FJxb8*#=a zvW)(7D`?`T`A}SZ_TJfIHYj9ar9at5C{xm-^i{oc?%$}LWiUa>ai*dFcu)HGTRO?f zxAelot2UlfK=JBZ-ON6c{y1V4Gx6 zCkI!YH>z`tWRP){R!bgCxX?~=GV|J$TsE3X%rkE9KKF+0`gSA+s_sGM36sg78YS0{6^~w34 zZmx+{%Rb=q_TQ*D4xUFx(}Obx5LDoF!ur>#VwvxHx542{N|wcw3*9ie9n&tvB4|w(shV(X)qWGs^gmkg`ZgJ#)bx2E;J2)A$C6 zj#N@prbz(70?3I0Yj(Bi=;DGs`o|enKlWWCq z*vTe~&J*r8$rKMrebwOVW_L`l5H^(8s1W0KE14X|ulcajN55_ugs3(pv^3+TKR0#LZwf#;#x8C8@v)q3KW9`u+RZ04Ir36`8TZ-IxW8CFg)@ zQNa^IT&hT5J611~sNw9;p`EEMfjk)}pa!!BRtvXJO~1T;>p0y{GF|87VQ+8mzF*9? zhSl-^L>EB$zn}NBVwc3K{*@&#R$aTB8;YUsy_A1r^LL5S1d!w6V(qlB70P;Jkhu0JB}SO42S|r4El_hnlgtpx!u{|FNJ>|4g!1I8w!%b^A4pvZx&KJN ztvfl_Nh&8(#AQzA65~deNC&TR{;6GWzDtj-{Y+`{n3kuxc#Uxhl9&v?)5@b22srYq8!ALTJd`k-6{9zg<|kzp8VR0_c!OFFH^Af@rwp2{uaOdj`CB z=5#YaiwXK95;taTzRB700bcJ(&SX8Nk~B{aK$9&&?XMmO=Vr^>dcDij0lpGvcJ;8W zY;lZyLI+7PZ?#!xc~Q|t;`V9icdr1qfZ4CAIZoAk8^?P#&+DAeO4=#o=YQ3(58Bth zxS<*jlXMVa(#-I%uIm}IY z)cu5TMUyeX%w7z=jY80u?BShka%{lCkkAk`XqtT(d4b$c=br*Jr9Wpg3!19Q7MKf` zyncceBdB47wXCpvR;efec25?Qe58bp+o>SD(r!cGYndDH#u^gu1YS?A5K#P(EP7=Va6 zWl@Q%>6F)i7|Z8pRTppu={KtD{<^F$d0Gwmi?6H=v5v;sS{3=&`S<8^zgs24%j70@ z*(OyT#>Pil>o)VCy*IF{$jj=N{1e4L>XF;$i%BMFd}lv?R!tZkDoh&qSG`F?(;`#% zujZS3bx*%f%txkB(8Odtk@HyY9VC*?&)k3V3GcYgjRki0q5RrS_7uDtKhy2zp|t4E z4tnbJ(hFW<@4as7z49grMEj1>gO==dP&FS~WqMCalSvmSk4%IOO0{1uNO`UEJAEgQ9!cw$z77+ysC+YDH*J$1i5>wj8^@LI zDE?Qbgshvqm|49vB@IAklBN?~(OLvO`I#g5Adb9G(AKno%TK44O-la8Cep{`k;Cv7 z%+i2!Pty0otrribNH^G@*5<~44CXrTZCJQjxMMYwZuXe$tqJI$Xsi@25TUCov)7xi zU&xdi+^o0muMOk|YMct-mdHQNtrZGl$=#jxnez$1h$oC3)&dQa}k6Q=BD0k z$tP#7L?|zsTwfNevy|*W>vDtFVUu{~rhN@RL~TeoSw4!Iydf*y#y@6ZV$i(MxUDcO z`^+FBWW38)LnRpTK3Gtc?I|L(7XQL`R*LislNx)6v;DQx+Q2)(GN1;QUp<&odc9vO zpG~#|46VVy`iLO2c4z8y%hcdK_j|d8LuF=->dR}9yEbarqRHeJ+xTqRfV#k@3{hwe zv5euLVI4!}8l`Ulq&iKCW=yP(+y%RsB*Lc)Plux9q(cl}7SUlWWeH}Rl!+Yn% z4hZ*%=rB~X`{YA-bVE8nS=CFdHJ<51DEqZRpu)TOCk&jO#UGsRtlm=*k^q~xjNRzA zy--PcUh~`KIIo~}tkgbRX0Pg@Ypz7NQx;hEI@zPVN9iu-c9Vlk0!9ejYFv?`Wen~? zRCiMwr=~t0e#sfd2~(@=nUc0wgd?vnG^k*a`yGUtb@A1DqA)a)q&&KMXC-c{_*GR| zKonnUTeuqD#QK8$CBw8UcvOCg0tdyjyRE(#6z~+nIac)htZb^6I4d6MY2iNu9Ee0wJ!a1(Ney| z{U$wdDi@NmmepQg@7-PpuLP&SP*zS=^l3FnIVGo^wUayQPpaqb*t&1lf^S&es91I) zZ!QyJ6hTSSnkI6POnvl0tWErA!Z6DPa zaNl*)WkHa3Ja->en?_`fC2S{wY6+K$oHO_G1OD{*njG!^Jmr8isoLg?x89J6)X9UT z5MkEe%y!8XN)(MTWm~Yeb4B3H*%UO z5gX&RFL=A9+0Kc#k3^3ZICN?S(W#jAj4kTImBfQ=F;(f}b_&}gmnk$19WGa513uTd zCGh>|`c|ZLc3&HXl)qZkal5`_Va&!f2<=vB)W1pEGctKxF+T`1E_%0;-vFRx)rnyU zW1$LZN$@#XC#3L{K;Mk`J3sj(a-t3SI;Qz zk5(ejLxMo(h_}SxDqM7iL^1zh0Y6{Dd_oY|{h>SJG@f3#C)I!TNxikjMNZ{1uh++& zh=zVzdoDr5Sczuq;+vlp3=H$!nk;)0()?X5%7H6N101GN!m~F0SEQrdAHI;RGHY^g zlsO5_+naGQr6kumkXFIqJ}Sq1IZ2>Zv_Zll@>BhrA7GRp1l+0v`{C>Emd@xW%@SM85y>#(n4R+zQyIV(A0xusV}DK80Brk(4~Yf$03 zHBr^hr$Z&@*W!C%Sr5DPzD;AJR!#&x{%vR~z)?;p1wcuwR&c=POPhI3Yf~z6%m_Im6nm@%51SO)y1he8{HmJ zm7I$^i5OdT+K(4RYCWuYECLq&CQ2Zxesr1Zo~le(_=)PB*wM+W2eV&>TNc_J&cn5q zCR#jT$)i{?x4Q zd#EzcZ-9A_X^dg&R=QO>Q0(Z=V7o4kd@gyxkc(f;VZNjo-tuj-{C&Gb5yQhM!y6h*rrzZVWRgJwY0eTRc3qh2imqJ#4aOHa~yOak} zv^FY@LW}KYpPK;HTIMh3BZ`<+%bupEY9HbdmPcVy2#II(Z~eu*ubp1lpH-ZskG}S- zc}>#k%4&AJ8OX0JY{lgD!RGmPiQ&>c0UH8ggQoH98ltzIG>*ZB$;y)l<|_5Ax{+sl zASzA}i0t(O8DSr8iN>|6Vzsmy88|KgaTDwVp3>@IEzQFzX2yYco|q`jKxV^qixJVnrnul0LcsvpE~FfZUlB{SMi z=X+Ld;$fDM_q&-Q9no8h3a>p82#s%L(F|o?o$AF0!4v}m;mug#=m0Q>DQ7cKLl?w= z&N=DY!(6OC6s3@-w0aO8`SS&W?Z$S9i)sA%2SZcZB*kT7rbbR z2*bNOA(HgrxxOIHxwx=+W^nr-)2Zr5?k3nK18EC{^_hJyHf)XkI&WAq1gP?phtVpo z_pHSYNWT)hG$kp@c4>N&r#;u}HK&x50E)~PU_Z{6L#0uKJ>Xb+`tyN% zY?^i_$rm2&&Q6A=@5H6LZ`mt|i>6A-KZy}?7S`MEm*JWFg~-g6!noxJ9hLwh zb!1Lzo7+c}M0SPCplNUHeu$a*wi)&DV$&nE_}li>n_(9c|Azuz(6G-hr|~?5o#GEs zFF}UcbvMCKi#)B?XoWr_!#$~Od2BRnN z_yaKH=Lbzr#)*urjj~!V6g}q7AC8MV_8nrZ0)0<64GEt-^xUtpc3hL(JqHpUW6p|n z)NjjLHsZ!fZTbQ~SrK|jNZ~l;92U0f8`QqoOz^b3T@X}kEQ;2xtes>ZZly7ET$WlU zTaB25G+P*tNsN@Q`)XSXa|azAsPB`Y%||11^IM-PuSK+!l3A94NvR+I zA!TOt2XRlSsXH45gRHqA+`O{E4E#qrQ*^{A8s3enUlP&0HE~#X{aCXe+H*?l0z&xK`YK znkOWP3-9#0N&nfO0qs)Qhh|RQ|2%`f9oB8IYAw% z@~Gr{wZ9fa(U7o#?rXqIra6oMzA)4vvzBiA)_X#}C&Wt6s=BzmF_!9SlP*Hn9m3$ZIIFd^k_k`tZ@WH`R*^I?bVeut^hmKj z6t-S463zg{Lpe$tZ_o4PV-Ga*=rncv)VYV{PS%t41Q9wQQ-qmTu}k}NHo3YVVtwM) zl*JYPVBJ0WzxpqD-X_Y{5^1{6%^UMvmlx&?4;+^x|=DpN5rQpk}v5O@lEqZ%3&_?ln+{?6A&JU;}-9xUv zy&@jP?RwcumEt@_Vc&TEr(NT=fTXtl(6n+9-YKIjZBEO^z7|r9g3@m=Ug!6zRRIY{27`TiX0! z&ShL1RD&v$jP5!m9sW;UR6!UYgRrM%j$=XokJmA@Ryi^WVxw9wl6K+Ce;RD_f=p3# zBm^j48NJ^IQA@){Dw7bFIFsIn_vedihh$Uo{&W%fX7Sj%kI)tzN2j|0SqxaH;O zBv8}+2)ReAw4&pH-hEb9#=V}9FdQ~=Ko}x)yubLsW48Tzx*dKKBBYw=EtBDVy~QL# zUV8eu@bkfvedZpWJbh#mqqm2H z+q7?N7nSQzl31qHzSHMxwA)rZGs(jU<%^;Yf>2sh_(#H4wZ90xz`-^Zjn{SU>XSk& zvtDcROGji<0Q<4e;ofTJqO^tFu7VKc0>96+lTTk@L_Nz6-gT&V2a;e||FoEx15jfE z7mhKS0xlC4-CrUN8m}Lfq&sAL(Nf@s6`l zvNbpEvu}~oqb{t(cJec2CRT&Cb`yLj;~xb#R&psy!SSzvvC2^6SmjWmu#7?()*M!) zhTf|_cIuswpqQsBCW&|`?NKfIewJm46UONh2sd+PLMvhe@e08q#r>I|u{FuRD88Wl zqamLMi<2QHs$|0hr-|8$!YX!%_x7q6cHk$3#j_Z-H)6y$a}NMiG)y@+s+o#L2YgNB zJF#xa-LW6r_rg0j zy}?(}B%J`?3L3oGV+t-}6Lkxm(9mKfAvXDoK57x8(Mbwygb!2IuzGD)gbE!k$pgJ$ zg95amVNXoCpfrFva`+N&23YvNVS6MP>-a@F3$(JqcY;)wJZ8T_{@V`g^?qMkdD>`@41s%t&0I0U>s_|x~=Q%_s;ufdft>{n2IbM zGHhs$b2Dw?qsiEk4rZ{4IDsn?LLZ?QhF@vuBJzE&KKT0gWFQ{B12}*fHkV>7zkSor zf=&r&peMqn*M{l9I-QG# zX&Y|{O^P*IkvzvnM5P~PxAqOy&?_@B1dY$(hPf zuv_cNRC|oty=B`R)>t}s`ltu2Xb9;iWgt4;E7;ya5#2*0OhAD?u6wnYQd6w6@E`;U z(86XGJLFWwKJgZABC&NR+4II6rZ_30PE;Hza@wz?5ybvH;GqPaPgR$@ zFa?6HyxFLj3FI$9wEV`gI?a^aGCtZezA{`i!3K1)LOS}VpF59om7zycg8YZti`3W5 zijOAb)H0LQViZ!I)A%D8(O8L@5{PAB7VHq|>a$1;J5ahLR+J`>Y90j=*+(&&%k@x< zIRz(xFbum&`$DRPpe-yCiO8?_{iskaKO(Am_aeZNBv5zm&LWyoXJ-6!KFXo;JhxU9 z1!%>yP+?kzt4=DQbXgoXR1(#6Myh>l&wxZ1_jeEnslwD@s zk8~yZ$Qx^`)65Y|;&gz|pJvyBw2`{;gB&gT2IIkbMK6UL9bTsYDBve|+ZONF3Ul+0YQI0rt z!sL`Tb6z<*<15eX`r8d$1b!1~#MMHsg0ziFj!|hT)F6gEp%FR4DqxHoSZj^OOMc(Dqrn z+Iw6kvmdXj@2n5Eq+b<^j`)mlXUzMV4s6WdUf)s&ggyp4CaK2gq3GxE6Jrhr0>j=a zv=0ct6`6`o?fo--)>+0mV5G2w8x4ApD8iX@;>s;e=0^}+TqZ6c9IyuuZ=uF#u3X2d z0JUr6cO+N$_07D+XCi5$PFx-CO~5FwNN7XGE3fHz0eXcYY%if$knvY+8UA%< zfw zA1TzEDGDdRtlmIqBqc^4qvGj%NHKmMmSjbBe=o5sn!FhcUyCz8v&KUh)a@9P)j3YG z@AYqQQ`Zq3e6XfZW%IyRJ3IxR*Bp!EicsSd;fAZbRLtP zg-Rkgh04OLM{-iUxR2c|89$B=qS;hab&wPY)9OM1%Io=4K(m2 zm$gugxofvVnQS4VNJ|vD_$zKLCMr@n6rh~AAjg~cQA-H&MMEKwq!Z*xQakEwTlH`` zx=t-{Qvh+V{zxIOI_vu{wm#JqK?4E1e0uWayE`;J1mWSSd;|fEHo@!GF;C` z`-@SsDRK346$uH-;2*D7&FXJ;=dMcM0R(H?9+QHYTgW3%yFPwhwkcmNXjd0MNMPo& z+JuX6lA-Q$8tZOi=py`Iw{$>Cv>G1*q5DRbcYl46_H^u$P(DBl4OM8d*goF1=7vI8 zRE3BnUX|N%7v~=e9y?bSu9G zHAYW_Vwv%4eXRl_%3XN1!C0~yR!?016+<_rhV&z#;)JmOc%3;QbWcbNBZJEc^{w)M zhMJy8>#C_4JPSGecpd3(b2ZYC$omvo%mDwy$%0|{q=}8*X+}}L5yEQCBWqXyvO(4d zZqhCu;bnFdo!?V4PG?RH-evA(PZ`*&(Sf@*8FCta`gk(YV8vVO!10>JR?yF4;&6rK z19cj&{x!cAYdTT&BB~>RJR-OjOaS~ueb{9*_=eLKq!6+7f|G!^Kn54CD0d92{Yn2* zF7-3LtVU~DigHJcl~-!rd)2hI0r+!mzsM{=r) zKG8QRQGR!fTj-a73Q{O~bm;rxqlVrH;8=_rx%HDv$^m41Az% zc|LL;=EZu=@mebpUnZos3OF^?fgv4Iu{&8PyrT0ZDTf&Xx0 zCgg7xr$*?Y5N-981#jjl>1h$Wq7>35BAD(`{JMlrK&im*lNjze{}$ol&EHWS5EdH; zw)4_!xA=<^d69=>uMf_E+844`LUD9guXDC0oeB_JeEy?yLC580nrFW4h_8ZZzJ}cB z7NJUEv*pBhrpMS;CE*Wn3qn0J5kxVVP%R~=&rBhn0PXcwJxfB%b@JJaJF`%lqmyf8;!YCNY0db!*UvC!EpHDD7X46sKYiHhGOg$WY8m?kkChoZ|LD$6r!yvk7;y&5BC%5% zo7kC(Kd}$_H+c*zbJ!5^kMv6$s5mvw(!WH$`T4Q2P$~MgMnc7_`k_>w=Nd6gN`J5k zdxloVppo0_P7Bs=-zfztxgO!sWT@}5J+8nKG{_Q570w#>EU6B_s)z1g&DF;@sHG0`iW{SGj?_rTrH>%#)D1Zf- zqeYbF&6BK#Wr^6FSc>q{jb^u=vS=zJM`$xyeerF=EfvfxRALW1s#nBUqr;SLGK7Tr zbhR_=_L`3JM{OJ9f5ahGo5qG6{bbqToec^vM$_T>P7>mCu5k!%EmJ_8R%Ht1DO)5~ zU(pZip@&+N777P^c!awb70-k}P?N9r{iaeuCcNIv{S4NzB2NOfu&9J$Jo433n@XQn z^H4I{hT+q)|E-VSLV4?}Q1u7%Sj2$t-QPj?{1o%{AM3fK@Pd(jnMyjeiHBp)wXoaV z^KqL~X)fE=b@Y-ZFDBfHcrHxy5Fys5)K`I#6IMD5v&^EyLf%u{r}z&FSd}iQ^G@Gs zXY**#!9vA!rn&eN_8r-R_5NFg4k`xITd)Uv7lH17@)BcUQAA&gn15Cc3 zsM8!ma(u-WBMUYRsUpvWMub@HeOibuV{5waXSabTAs1YH1Tc$5jbu;?aW{1^G~t5} z`amNAL~+-bv)W%1CUUd!&{)RLXtu09*^pleO&w04V@TtMq70%IEH=C_5>rD_Bak4f z$eUqbu)Om`)0YaFQ7%9LK$K0*h<~khowfMBN!VvVA*`URfLfSaNZ>H&C%i$GPH#vK z9yTHl$abxo5S>|?_7f`!;1&LJlzzra0AWDU-@~3bt`V8yDmVBR+($GV$QWk1pKs{Uw18!)!c z4cNetqwGjTJE6BSU1twekk!X&?$3TGD}C zOwr5{jB@~4R7I}y7ijH>DVujlBbQ#-4{Y*a+WtHbFRSaYTT=J*0KJ6_m zCNV872UF6c+@8#~S4iWL)3Ghd)>{RN(cUT|cqh0rKZsuc{nk%v7n&fQY%%^WSX+ju z^Nhs#fD0!yW2N^U(^eYa3n~_6?R>c%gG%E^i|}ZPJ97fQ`C2&=&?Udio%&@UI_rsp z^|w2{@hr5Qce;LaL(i@bA_%Eht^+k*v>K%enD7i!oKAgpSbYgrqJbkds4gLN_n_u< ze7{vgLcoQ*Gz=MTuiHmSRg>s;l;S(8-LUQK$ z_UT$H1h_)s(nNVtHc0c)*RE@FK*GwH86~)R(pc+zz!0ceON5}DN~=Ljrn}rtF;>WK z%LNZMC1yb|Y)!KKlrcgnv+!*Z#facKZmnCM3N*Z-K*u$ANA==4FAZf3PF_dM2&`Gl zSW8Q)&uV0U7o*hmQC;40$*L!wFuP4D0~3Cix0k#HCf9PZ*E0fjjdVMG<*jgHvzb{3#O`ouEsfT?`)) zN1@$vb>tZ~xb19+mHv36x>4q$`wr0zfUJ;nz?;6wnsp(c6?m?r;#W8*B=ME0phYCF`VEB1yp{srDe()pkPsU;>x^A6-XwNOR`53K8oYEe<=Bn_>POyR`*`TcQ8x9>h z(p!?0XZ^PvVe9PM(sm*SdO|Y(KFl3gocC$Hw zjxaq9E+vc^TYZ8%xfj;b5;jqbTIVCob{>PcQ}QvXH+1~tsju(Ypjr=m@ z&vf=kD2*b`(g@&SsfGR|2H4v9Ao|x$Ztl#lbESyFl&ggE4zf>0_K?A)@rKWrjs%-% z#c1qTWD!bIK_6FAl)?4gnvmczL)KD2oSp6Z$8m-x(v2m#ZUXy48NirbK#H^1_cuR=?=Uu^P<`(xHIhzCO0gq!Da zkW@zdROCoTh9lJ@>$Z{pm;oR86465I3)>3TCLjIb+?bS%n&ECid$?=17VsZyEJ8h{ z`7EH75v=}Nmy9PvlJbJ&;NG;Cni(vcgKjA@gbvgN9GxIZ#QlpEykFCz* zKb+L?BwEGGLT4n(ZMSe6wBi1!uvPCSeR>{3e6a3~^B`C7Y9Cz4pZL0cFEP5WExpSR zl-NC8J28C5Le;^o^GqOGxu2LVzp(A(%lkvEd9+4*o8I!{yOR)O|CX z9rlITT}enz{-U~$!t+Kp6{}(|C=ba$tGvLwq}h3Xff{zwmt|p9Az*ppBQk%!gN5R? zRL!$WiJvtEzfPJAOoBXaz43~W;Ti>pDKgm3#9sg+r<5ku^rxKY-`+`!{*a2K=X!#}XffPjrs4B+vrm;H@x{ef)v8BtGPjPm_?&MMcFLeg zQ&ZCeE3013-aAyB1+I&om1gZhqvoaoJ5R)|`zX}Ohr!Pu`lYNkJ#n*;w#SjZqG<=Ml(jYe!|MGtKBm7ocg4Q!|J#Gd9>$>dgq${ir z(8lL`X5W5EnKo?fw@C(ZN&d~~a5}9oPj(bbF`GXJW<7>woi8vO&Objs-~HUnOzqB2 z5Q9iJ^;f^Ky(F9C@hz?}ZJkOLFnL+G8F;>$&Mw%pALiJ#G1K@Z;*M`q3-$=kBL!eK zs{1{Qae{3Gi69cl>Oi{Be9+}53Nhb&%+9o7UZ}Equ`3M?7K9E_rLm^E|I4g?FJp!L zUtoCh3=kR<_uQOJf4EZ3>SJuskkl9VAqOnlf@^a}kJ*o_pOcMhbzhhbdXnaXO#zuT z-a%*x-Y(F(V`sA0^8EhL9jH!LTZH=}*6Zk0_RU{FX!=NGUPv4j8#lUF>X&INhAeyt za}^*m<;LcwRGoM(X-S-#O;|v(X1L#4u8(oO^DI!VFPZ20M=UZOI~>v|VuNnL&U>IU zd3*=4FncxZ3D_LL*!h_#Yh^GORO>MRFQA^}FCyMK345!_Eq@=DVJmJqNgPx*w>Q@Z zU!JmZ(m#2#g6$d0!wm^vc}es}I7#G~jed17#LlghMgWQKsbf+1k!K+^bVBAKj(co_ zH8umr2TMJ(;`4zVw&^qeTc6yh?(3xCGy8t?+M6@!qiit_q`L2tHto`G(g%=vPV|7s z8mNKuzfgND?jLCyt*y++_;R45#u0Z3)USY2lwBf0KfiSYU#LJ*z=qLS98lk-Gj?ofx&5uOusSgC z0d5VDh>z$05%%WsP=5dauzekjA=%B0!N@k2WM3zS>@rbA$`;wmzL$Nu&%BrW@%eu5&+q>IL4=v>I@h_*dA^>@>$)Q#yOPA0;+mc_@V1Yv zd2t2<6H<&dE~~Jr(pT9HbGY|0SJA%7eMa5Itg=(YRFYRUEnHZ?^Zn}bh;O;Q{TIef ztrEkmrElf70`r}h;iizXsD&>swWfl?UB>f9{!`drnnz#0*ZVKU#mb^Y*0r+Fc{TCK z`rLTR3<5-YoXVw!c9ZY#@~xel5u>2XScyB+5m6Lm%&9(Nn59tBk}@XzkV8#-ydbO; zGWH^7azBzh=KG=IbFIEdvk_BE?BfmFiBd2f$8Rr2JZIPG`V8D?3;!4xkJ6vb`U)sGB*0xqOZ@#B|)-88f_QFF+Uv1hu z{!2s4460r}{!Z@)J3XgkMSTu&P(9tk$xCfn=y)-f-n@k&ugIl|ce>?zbG^1U+48^` zUhxLPEr-p3sN_LDFxl8Xl(J{NnSG@F$2DEnZS2kCrNJ`a`Fg$HCxL5f^lwdU=)r=O z??e`)=e-nP#;{ek?z(eE#-u}&f_@b^}|6@UDM zo;ded6x^gX4yk7q9GG?Q``EJEOR*72PANEzv(tM^I~HFq(vW|kX&|5;{=Fh;R?u{J zx(}3JAkP-cupI^}x$$Oq5Nbz)VLWy6y8#gaPr^zUUjxX%hdH|y8Yd8OxbX~@6aSss z+7=|4=I!N+F$(dmgwYoM9C?pN5sLO81=)Tn4rJrBSKDzJhY5VCmp*t4CrzCO_c##( zv;mG3tY{FJb$=XD`jRcUj6sl&`{$Qwn?AA~Dy9A(em7FvnqmK;pBX(b@1y%i4Ky)R z-BA?}M@G!z&B>LbBJ#6a^A$pVKMfTfAaGC0ma8BAz85hE?ELz5iM36C-*EUH?1N*Vf?0h^Xnos6XWuZ)mcA zG`1BGlQMO!*|^VkiuN?`0;lQSsvXV`Bz{#rZvv?pg3%ID%GE73IvC9t5DUved1O> zX;1NboP)E!&6tT==Q?}ZILGJU*;$r2^}(73kSPxMO7r$@XuHYo#cM8mn$#lk1`Ht9 zd(J3}FSv5KRf+ZI?yZ~ZnUjZiRXy!H-xsWRh1>;6QzK*R+3m*rRlGY*TlbY;q4&vV z1wHoTrY9_)lr?yxLun@Cu8AOfHoUiTK{7VWc}cKm1Eg6=DOvIvZO0vpl-FGn${?&v znfHb%PTi#8j(~x8_`xdCM`a=~1sv9m#WoWVVYTl2v@keE%Je?-La&Bh%C2M7y}PIR zlY8fIJ;7S1D=gr{sJG(#fq)y+p&&3D)O@R;R3d10MCe=m{5{iQ`{({>30k$22^L|6=3Bacip+^>wY#&mAVi%pGjfUtq^Pw&g~Yaf z36$bjUm#_~`P0qN^GHQ?_jsFf>#_XL2NpCL8 zN6|hZM#H$q${h!Ejy}6d%aJ!-k}Z4fYQlM#=w`UWeMa?_of!S_LvLa)^6XK*R-yZg zuQKc$dQMR4`+fP?G-}T9a0RC&`-$}!1`OR%C0(~KJBIGCl9*UOtx|5mNX#H3G_zoO2oW?F**u$QxJaqw?*qL^3|8J#(Wy$&VW< zY-*IplyGplkPZ$}QRm(KB9;eo&d~eeR;V=1nwf$fkPMgYiTE8;@2l>~7^nT__>TnW zPpX~0<%#C4Zt&v+m(5|E8RVSPjSIVqR|1=P zz>(PU$z%G;$9WkN+#6oz2$<)K9XFw5J_j~ikkasq;pGD6DL2$}7@U_Wk^L+Q%i%@2 zVA4#!3Mi<=v5PX1lH@*~VY`u*L?dZ9W%QhI$m2IRA2?2jjDPaT?XZh*BkuHN_T89|`Uh&TVgKj+&TQCs&(`#ogO1@zcHT=jER9ZamP7 zUCt2X=MJ~(mhK0wA-WFNU#yEzJpTmVoQ2kVV=&8GQ3rZ~LFL0?t*k`X>ul|*jR}3K zMzl<9>Jy3Xr&Oo!^0RdEo&SXff7hNj23zqU$M>-GDHlO6-A%YIslS_etv6wPrsM?- zQO$ep7D~$r+=HBP%MS#deUs-?ToKD zfsekrbC4pwGx_wvLx^bnwXVBQa#S<@Q2KUP7Y3e?Dr?XA&jm7~?T>zn$)CC3KUn$l#u4&fIYE=zY|1`_^>mO>Sy?E^9s;hAa+hbm~ zFR#3|>C06eU!Q6p@4ml4yE6HHq$%vS^zn$-*Lwe(mhVFRcNR+X>c2jh&CLj3{jR83 zG!w(3291&q8y0SrRND=<2Ip0mWXTW@D6X_<*zL4?U}o!ajE zVhsTLCXKP7bRJ6Dq72X87=*6Dy2+PEU-vL^%i*R?Y-+t*`9KKx4xh5cK)p9;%HuvQ zWE=F|{DoCNsnds~goI)DDVQ5L!)bj*w*eHw%=_K6D)?h>P!c`d{3o{m*FObtGHE)jeIHZzx2(YR2|-i0!uObEzlXs4_rHcuXleQ5Jc=Fj$R)eAFPe1 z2{!Xq(l}OmjL8Snj$MMY2peR==V;l_nB$n=IG_066E?Kj`t>QU7%thUtdq*WBJVLe ztN&0^0!2Bb_XS`pl%Y#s?ww}ZR061j7+5LFOiPXU-a37NA++Hkl6H+~C&&=O@|k%V zat~p6>AA)I7zD5eW_xi^xPaHyDwlH~Hkn=1g+jzv?M) z3Qh;G4q}2pINEvltEGLNRP&)(H-(uiC^9@32g^uh@WT_t%hl$SV`88%MR9L5H6y82 zz|@&&X?W?_9wgq0a_dh#Qz8qu9J{;wNOk8+>xaARy4uGS$zh;~-Nd?LbC?Yl~rsJ;c*{weH(<}@vZ|;}ohIBtxxTU2c4sDxyIRMhr z5ZbRkd{>JAYFq_}?l)u~@h<3%_t8{>0EY~faZ13-Xx&{t;*{PF>a2<7rZgZ1wGtcw4*wXX(PONN$kUKJ2fUc zfG#Qv&{CNIcjRG+ZIkU&5k%=H zF!R{}o<<0g%k3Ua&&?KeXHqEoAwzt8?rn<1&E1*Q3qrnc^w%cd*}e<;GpoYhE7p1& z`2VLVHmTAnwPrt^`;oD(Hkcfn$Y(~kc3PNbqWv-oif*K=eD_oylr{`^dSo1 zZ#$D=P!BB9v9pc`X3+0lwhVn-o5cJMuRl)YAh@HvXx3RL?JkJ?n4{?1F+&3k!!yAH zEpLRI_9@q&e&{VFqP=i@pflcWhv)po&szx$8Y1}7UpFyieoxI0{H|-#j0O7@^qF8< zsu=!XfRCck(Ik{PIWw5Wy~3_@PDNS(?m}d6E6PJ{=KUQuEv?o%Azmb-V(T^+9Vg9A zTwy1ux02 zMpw+uk04dl16OTEuEcaPcF-`8ug!mZX^pv}XisI#F}qbQGj3g z8NsJFDZDd5LfJj8jV(cD+P0Wn3&$kGynd;I-TQF&7NRVA?LQYZ#HlndTt+mriq$$z zDe7ponotmQZRkvt>JCIKGmJt ztDQ+7hGK@do$P7;qv4VEGXz*KnNw%-FRCHj)74{Rf)%~DK@89*V*g8CtjUxCN3WS< zHq1I20?HZkwzIo#fiVd67+E5GnJFG!dFj#C5~Pg0_h9CurgkfN7cHk5Pmcd=go1Fh zAb8!!@wse8bMPtMx8=-6#xMCNoXsQ(f(Q8L9xvB zzT6f5@Lk89$rzS;uq-86OV}^=%UaLN0qO@*M{3kkMg=})wfeKRqghRaf8S)b4 zIL*jQPIJmoy14Qxc(;HyEu|y~5H`>l>G-9@@K{JY32|zrwOG&;lmUa$eBw7Tvueu@ zm$lEdGCw%$M5Ama|Js4JheHKapJgoIyZ-?!lfc-bk`S_B7_MMoPbNuc1qz5dvcJa% zoH6Ka@!!w*1NC~D&9*kT8RcAGUZ99k&bpKfPlxaC`F@DU%yeh6C+O@|Zrc!04!X2> zf{h|dq&l8oHN@$HVxIIholvjis%u@dN8pElw0=SW<8rD^52SkvBTkM1$Rk+;>H4%BeWU(}z9uts}ggf7B1xV`dZPPR;sHGzJ&Q6nnb#<}8f9@mK=< zs^E?r<>1#h9%Iu}!{^>Pv{KZ)y)~nA&V9JD_Tyt^%gx0YpAPgG2$&xVhmjC~%;h)k zm*W+l-aDVn@gdyylk6?fUiAk+9~e##UXYgzfkD^cU_fi9P(6{p5K}}e9g7KX#@wJi{Xv;SDiN=waHgz(`IV_9ON(DJx zDO@w#tK(w9j3sPS{6CAEMGP_tj$UGJBB{lLCJN^(bF!4G_Qd*?xbVdH2X zpv1&BoJu{o7-&8-LSqmNDPiB#%xFK7Tk8Lio@KGQYjlGsP;0jBAVa!w!OuBef%pTX zWctjdesbt*S>tLtRG>{e^z4$DC0KdfD|9ZEnROcGs*E(}Ku`9Enqw7O$vnTk$a-$~ zZvT|xXXdy9&=ZCPW3Re{tJix9pq)*;=J5xSJQ9z&oBERuN@E|o5f`D@lMji;;;w@* z0bq19I2TF4>GsT>bJGlKq`t6*=f-CjzrEy%__aLpYW3aDgfBoR)qn%LN22?m+tgLZ z9&PqWb&^>+e10flZ2<}GUI6dn_h)J7^ukbuea=%l3a!{`rzgn=Dt*duKDC2oS<4qz zRYANNr(!h$-tOEj^@N`d5>8r~p6n9033_qXFpomWn4GBbn%L zpa9fc+64>6oSq2V8K0YoI0>_Sb=}ai7{`iWAfFuvRIh|v{-pe zOsm^)?Ap2HlpNQd0aR4Y-TO>Vhg0m3)0V`guTevsROI-<`B*lE2220m-(C@mzqU>l zm?GC!y?Hmy=JkzB^v~@@6+?F`Ip0D>fDQKHjze+vBbLiiRZl0V4}bD?^aDXg!EGG? z=!UY3r3_pq1zOaJY?Y6U6sBV!QK%Mt2SLP^^T#YX1q}<`KQiIoq^xM-)rW~Em)VLS&1V?4X9o1fR?ZoS^-6CNM$L@uC;$C?!}ln!XGSe7E*`cfS>5AjUp29x{IIFXVdJQ%~ zo<%7-*F}D79e`lHanuG=J@5gSE27l~mrPRp}#@0W7%4RP{uBx@?`T}`IC=pp*|LeHlO)Gxkimpf2x@zUh3 zxkZ}lSgFtvTyleT=%~xDebj#7P^^h{fW9j1ZIE`lT-C+dAx1R=g(WQ6#Gv5<1eO<{ zug$&+VRo(H{PAM;74;J^dpCJvxCUs~C?Ix3x<&!GL(pk!eY(^Fy5OjOy<>zs>AoZy zAEtQ@K>EoNquw$&w=kS|yd4+rAnTxtmyK6*qILdoG8-4@__=BCSGE~5LEWY+|NYE# zS!r&%YI5-%Opqwyx{AuW65Ke5OQVud0R#^m@aNv*^=o|+y8b@DyfD&~`Ethr+>gle zUY3gdCm)KN?5A8C}8!3scv+zZLBC~DBCGPWuWND8WW4dUtAMjm`pndA|xfw)cPFL z9^0G8rIW31wu;qcUeRuerQgL?#W-JN%;FP2us``@KTv>yNS~4JR=5}Kp~(4w^9H9m zr!(i8z8M+*{#w(4q+I{ij{a~A@$x&DWUBX~*mi;A^#np;Tv{T6SbV27C5<+?fS;k5 zyq!aeVv{0~LOSLgts6y<@1#)6v*wa4Ofgdh89w&b%zOH*sD69`Jum^t!D3z-l3mN(jIuI?QL@mg=EdwRaTi%<+5)F{G4k!19QI0KG-F?mI1P}+&xgFv3^BWhZRRbi%6IJ+*| zZtYnolp!fl6WD0_lx34*K_fd76AkIF4Hj@KU>Y!@qpW&rJpeb&jW`Y-vBRn^S;!Ny zDsSa%!E25y#U;?Mov~{S0)33V;et-|SfsNq|0z>ccQZ)hC({lC#Z_d%f<-V7TkvjY zdF0|K|3W0RAWQ?RS@<~sJ`KMT^uUL#(O9)^UR~dun?at0o32-qE?hvRdut4zc=kT{ z&X=5|On|zN^QAje%X)ie2f8D(E&qKsSvzAptX0DjcK%LCN_a|peuFv>Tov^YvM_V}s!uIjGci8}P6b!88tKXx3I~754l!@M zM&^b*33>??h?jQjT#DReN=J9J`w}>y=A84KyPOh)v%E44LHvC*DL;U`2@gk$bu&9t znrgjsoS#1f3wFN1KARaLK(?9|kaBCY?^SG+Mt&o_3tpB{B`h9G?oX%4`u$hO9$)K< zN=^2Ks1RY^vD)=vM;_SE{IOv8dSS~O5tRrT%3Eks(!%3WbsXGL*@VC;DDK?FaeDLo zL43zWW5{@K`(^POx5n#e<;6Uii#{=%QGWSGwYrM)L&1Y$q6~u(kECE=!+-Uiuk0ea%1>L`!?SuP9fzgo~nFmBia&&gvH6s z+IB=)GP^u_N=vrpoJ@_PT0`U&Z;`S{m?T8ax+j8vB4#7SlCgu6iWSa3a3Lp11WN1H zW&I1_mIj00{0WaEe@v&U>9Z{T?5Ym(k#d5?k%@QpOQjd^WXx(c4QpW|x z0elnjHr<8nj*E#ua=x~|N+BQ24tbWsud0;GuiXlizxa=T^V%8Fw4lTg&?%gyz&J%e z{}zA91Sxy!Axc)k3Q>a?q)0ciU}}c>V>aSw=_59b9L{rV!=za7WGdpAmf&1(&d;1< zer80<6y4mP?reD46d9soxi1qXQxUX?pBhp*-4|vts>d@S*^%s+ES)@sXCw9q&rUxf(B` zjU|w|AtWQod;L>xbSM43==r@XJ4S+&vnuC#)J77kVd!j}-v(r%Rb!}+{+?t?;3kvk zdk)cDLBm61)peDPi{m)Pi}{h`wThxFL1xSf8o!*~gp0Yx|Hj7n{iZ=wTBm z%ig4_3d=^jFWpM(jta$~?8d@WCE;|2s&lJsP?#$(=RP+rin&AUI=rHWYrycGrniYO z)MN1UkeufzpMTrIDd^RVQy$b5+I^h}reZF$3vV4vB?o!@FVvZ+fuJ-Loguh_sXZ@3 zw=a~G{6>0!*MJdQl#Ynpjh-9(%!1GMiDDnq~}9a-*x^KiNjTzsIMy^2<7 zne3qJ$ed1LNb#no;faSQz4BDlXw~VD$C)_utU=o}vGW!T7B#`lok{BXU_Fi5kY_DE zC(^;B=)OY6Z@P5yMge4`LJeoFoUiP(R1{r!2|O7dxJsIbLH!hy>FW$FBtZw!Wth2 zK{xe+rs;DV=n(o!9+=;g%=J?3X~9tcFl{CRei7CLX=(jfPv zI8TLU=37vW?af1IHuMZPN3_uHYZ2{53JN3j6AymszrkcWsmb;xgmdDB!^}~Od>pzW z>JS3C@J7%eOGXIGiPcPu!#=NyU#2vsVJ_;_&|WWYx%SgXbIiHt;rrxD=MU=h3YQ~G z^)WBqi@vN_m!LRBTfQc($abN!pWAc%v-*85U{*iv?c z+7<#`yH@oi>LsT3TWb~HMlFR8 zJ3;p|?U}zx8m8;H77=nnLkGiu_EacrYHNk?=NlC~W{sj}^#I~Wc4ZwDfYZUa2fU*iP6G-Cum zilzmO++urE?>$C9p}jUS(aT6aEu|CgjFeqBSU;Iu>(Mk*O#cU_Nm$!= z%#aUsd|iJQUY}yjBP__}J=qfFZpgsd$RqDgI02HGpbn*E_d~|%N9qyb@i%L`fpDVW zc&MWNj%!oOJIkp}29RX)R7z^x5H)~8rUBhLXD>f{F~8_zuIgB`JI*nMPx)K36HeN} za(5dSt_x0$hEgGLHHXwaSQ+?244#tB3jW7ZvA!Dkn*d{x~44}<{If#=Y7Dle0mjey^38(-2yuQd>o81JPL`EM4QpW*jJ2I z*S!9OCj7jP{eC-hAz1a&^X-Y7-xj})-`t*fo)^5+;Ps=);~@NaZ_bJ<)VPD1XU+#Q zcTute*mjJ!6KU05sDEyS97<=j{t11ok`cHvdRF;C$hwAQwUb`p(l?1Q0eh*%b^*4# ze*i`P<+bPd=xD>$P6o@DSI)k@y|X&Jk1YSAHLiCB~=R==$j3V?;L?_BTebt$9~hosGX ziE-X4@UXdjNZ9+m0A!_e3oXB?VQ%GDUY`->A_-r;xvDqkdLNzb>9wzRN)*bUnIp}+ zjhU{oIy{m7FKz*kpug|0k5wJ!myw1 zEb28g;9|HJl;L>fyMJ1Z0>2&y+vyC;x1P2)y?`=in7s}OkXOl%2*i~SGzY{apk$4NJLv){ z&EPs*0?HJ}a7yMfCUn<(s92Bf9ut4>58#m0V~S-$Ql&*YX!v-)18QeIcv8XT4%)7o z8(+@|yV0qNRP^;oHM>^>VOW|(lUk@#F{D#P$wnN9CM!&h;vjb`0MRrCBwGV3LO~PA z{doevqUZ8(Rk>~|KZmFm7jVzL!Ld>?hG|E9cHzCSN0W1w+QS-{CoDpmBJafG*Naed7wj zQv2i^Jys%7fWoQ-7%_6^N5l`L7(5O80lcetzt|?3W}9^5(2c>Tw~YDukw#FC7#gN} zT{Vnh{89u6_mo>!*sTJSsy8hQf6ZE&&_<#Yu%&Fxy{(6`4pi8ylNi~d5g7bE(I63ssp1SjXHwPWqqKS>0f+X; zRkIjcmj2Z*Hz={~7F||T*a5H>e#^>m>_z(j_GY3Q$TQ3kBgI`GqF|#j02&JQc^Yu#-+4t{?n%F4Y72IRyx0SZn_a^-=U?>b!PTPkK% znSZ{DBwm!aPI8KBp%^YkhFbV;3z|ShaT)Y%!d89^7qf=@Ub~%^d4B;g1?PyP?TwUBud{(1P1W=EoK0k(+Sh2aET(4(d663fEwCvO zGG!jp`VBWfWYESj`rL#2?H09fxMNdH_~G|5yJt#|2`9zId1@Atbw*ZgnM!PXZ5e{{ zg*OSv3_W$YIiN0u|yiYq$WFb3N(+7@!Obl z=tTHr5gg8mXij4~@u=7T-q8!-uZvq8a$I304+%AA?VCbZ<4)UEy2;a#Z_L3%;&H5n zI{z$tS}0mQ_Ov35YTSL>P_wTQM@#mrBQAOVsp`kIVSV8xE3QCY0*@}b?13U$7zvYB zjA8~+5mVw${u9Oa);SKxMZH{=;O{{AU&{rsc+4uG7FZq1fqpYu@&LYbI;|WD$y|LY^huVH!wc%DZP)qvIzL#6Os|eqt|!LgYV@bg zEne?UCrf{>&gYRCR`TNDW6R{~$U68t6g&~e(f>8r ziYeigJ19+Bupd-Vrfs?~A$nY4?0NITAmV+W z?d8kXS9ido+5a#O8bPkd&I%r9fn)`D4k9r<82nx;N*@Lf&Vdo*+JStrnIJ}lo5w;& zXq$aMik(JM{kDO_S~DC9ZO(I6J}UANyv_ksmc&(9RBI67@Dd`;2X_AN1s9AV@>Cj@ z^`EPShz%k$Gb)5emt%f=?}GjGRJ;T>9ntuh;EK@F=%Yrm#wPx#^A)MLE*1^({HPMX zzfd|a$;yd1wk!R6xN7aMUPfSh3p%Qis0=F$6d0R!@FB}0^ngh~=z-y1cfT$yuDCi= z58^B{84_9;+-$tS_HNsyz_#p>+(YA4{@?e;5B?;sUFe3D#r>a03YqBhXYtZG=Wh15 z?jte>$Egl2;gc=ozzEbh43xGv>Ou}yubqI3zB=Du)c-IAEcFdN52bViPu!r4K+21l zgkGa*thqzbneE6`z5N~RCjzR=1(T`Y07AH`e(@Mct?oSxvwewdB3GaDtsm6-&KYGR zVJseC2W$YDkTunV&LUmd`Pf*nTZ3nnSm6qt_|wr-f{E!=oGmxXo_jlJgaf1WyystA zH;_-ZFZ|K%fm5DX0eG{>E$2E3tUC^4VBa{-z&bjNfR_|M(G+9`y47zD|2;w19bloE z_xxVV8YX&y1R{a81ekKwJUqaHd4Hvc`I*Jb8)olgup_T;KJ>V($)}UTIc1cq;-*fr zOE3Daey_WIw zF?6RK+i73z@UWDKfjbL(y+tG$s3`tGvo7N~=b6K$UugD7-ea-eZ{cb`3o#AopY>pT z>Y4eJhL?b#Q5mI7iIo@Ns{)W>2za_k8|pcLu`=lp4Dm;}Nbr=jN7w}*wyoUkFFbEx z(~_?l^Bs_4%ePTPuv6NANW@AlP=rQb6BL$<8c&H?bd(4JJp>gXK3BE20^0vwd!JGz z&w77aV3UC2)#ak2E9r*&E95n~PBhlNPN1dl7I~Hr3^m{6-Bp)f^kqu7gBA3nor`wJ zA&{5?Job~eP1TQG!3%#x2B`2F^VJHcC)D3!_P_cA2*kEv`LF>3?Dxg5&tK-%$vbx$ z8z((PFVBV4ByOQ8q4og8`2%yg@5o<}p^|cJIRj zLEXExF|;i8Krn1j3)>%#6kxqrVW-OToo%sa3CCWn10-DurI?#O9wiK?ByEPvDI334 zI%?nJTAsPcS~6dm=R;t^(JuDB1~z$@tC34^SpwRtX|E(3Je3E--#X2e_dh zU14MeHVWDw$^I0?wV3A9Adu~-nardZDoxKy1haw*7(aQuZY5k$7$EZ)-{P25vMGgP zG9*oZOCJ#UW$86)&Xe@z$kZS!;7~jtv0GfGMiHYTZm=hMxw=@){3(I{PHPNp_Tc(- zyKd?1r|hBbncJgDCYn0%^Z3XqmG0@YD2C}MQ4Q>m-Qo&d;@gRkp1TMJ+7Hnw`Qu-o zTfU{^$Fj3Z)!cb{7kCJ*ol^2R9^S`J1yRwlYSzTj!PL((NXITua)aOMM{Vdi%?L59 z6o%uaMvWiGL%6OU)VXBzgaMCK#?PXKAI;56piW#47mTQWbXDk|U9k+?t;Y^l&tz<( za}g|Vflov7uFucz&c6_pyLy+jiA#tvdr2?!YfDQmj$`u)=4HZs;x4ape-7G?0`pQR zaP_-gOykP4-E8{(>!KShCe7s{0>!{uqE2=PX6Fw3?(&sUv}9!z$9ba1{99^fjtLkGR|n^A_*k*YDivLF=2utSd5*yz_r z-Zr^5`aH1^I#ASQS=p^uUR z(#9%|qv4JEiTlrJaw>7cv(3y!_V#>O(`IC zLD1CkC+lNiL49PEXIhfitDP>gsF98Db>wD&^%$#b^}=n@jA^jEj)zO8G}R2Pv9*q8 zSo{xxN!4A6GY6j~7|uwJ`6oS@xkEUMgqUJD6VCBTWSRAh59{&Wbf4(#h2Uze3g zRWj}Ek_{7M3+{bj2{o`Xp-IR4q+xNVsVG1H0{c_8AAFj!PCLvs+dhiIEgDnEyO{8~ zz>^5E4;j~G6hTgIgeO>u=>KndujNDw(3~!4pz%7Am+Qks=A(9- zkbjsCC{h`xn5Z>KaiJ**1Su{dy?{fjD&X|DdcIq_dT!0`wdehtRrT>bge z=w9}+afH&9d<~Xi1YfFwGK~a30o;Oq_&-1z@Q~-i%8!u_b(6ZU{jyL=M02{hCeX3n z?=94gKE^4RDVE4k5+ZtXS)r2snZ;U(a8(>`mS!D7E(Fw`oZ&-x|PyB6>cO1 zFp)0Vs}eE^!1JvWnAqZ=g0ssYfEOtQJH-@R4+A0{>=j7_u!E%0fi03z=)oryP=tko zkN?|r1~nI0M)9+P|9w3rrSUvKRX7#`=%4x3X2>3YrfiRf^w$R;r^$iod`mHXETP)? zvC>b^iAJul1!LdkVQFv~>S$M+pIcUp=IA&i-tb{R90{KPF12`)Kg7;LyVLnY3^Bl` zdK!;iKOr=tk&xAicQ^ZZkiK*hmhro)if8#lGf1}yQVeGVXyDL5;Iusm1|>|n-@?FO z+*R<_FMklD2yu$hL3(_vuwU~W-#-Zt1h{fhZEu#1WWxexH0hzLYr1S4J29=~dG&zr z5!xSEvuX^g%9eF8TD}qy2SU!5gF)GN;rA19x8cfy*QcVYL2>g}e&T57hc3$nA^`R; zc!g9?{e5c{3zF_ppWSsw9=v^>Pp7;<0YKC0nda+h!Uh{R0KT=Z^LavlAHeEN1w=YU z0Rik;Z?AviR(?2>7~r5!853YHoaebb-Y~=mSW9TDPLt)IH6yp})FI)=QS@rm7c6w1U{4nTM`HS=c@C?AQxiKv(C4>i<^lvutPk|vX zk4MSx>G{gtQ+M8Yj^C(m8@e;3@C*2#lfTAoirxfSY~gcIBpuJ9?i=6jPJy0qtr^i* zfaV6?!pojNn!-E*g51F-crSw)Xn19BDFUS20Vwbf2n9FXO8qhKYC9+$T%RKuT{|hJ<+lC1Rq$OX1+j8;_n1a6&ZvWsOu)xj(4vOJ-Kk&5`cuE z#$exjFg6h*KQKam;N^XTH)pG&l+#22QR_o0C_pO$e?|^*Z_u^^GZMXv2K=M->l^o( zCIEr4(gZqJ488HN-T`dL$m9geM9+P(xhroyJ<^b1`Ikqu+zJnV4T^=@GM`DB&tzng zWO`Ul^NXcdToG4`_3xi4H!adB2V}1>9bN&kNiE%`V>|lR%ZpW-w@=!}1@OsL0sMdq zi)Hc9pzvS)K5{qgo`Mmmp2Q!~3vc{*BY8#D_5OIsiE$~>u{8g2q*id9|BPEo3w zk7~$oZ_wRCW+8UHK%hRu9`gHkLzrC=3@?4G)9H>ZZQ-d-NL=D|L~ zc*HC9ITq)#%d?%&>Wkd^d>|-(NGpx>HSzP=MREUKw33(@n9zf;)w(%x?wEu;z`qa6 zv8<|o!E$B_GIDwgsu9@e$dJMgfQA;9gi7FY)E;gu2}qOtMtGoT6hvwXW6 z%FE_uW+n(1sewKHwETY zC!iAa{V2leJA7%Tj%#BIOGH@ro7_SlNZ!<@t z82Yubd+l$WoQ*<`*bRnu%-c$;d@E3qE;g0D(8-&+w@`m_NNAWMjh-WDWmY?@D zkwhfo*h|K}nJ-?$6Z6ArT6ITE1ZF$t@*+GpsrzGGC$G>w0RuSdPf)QdG|?T^>7Y%g z(9<$v+fS7DQIWac5=kB)QJ%Oy6;%x8Ajji&e^%tOcA?1*5G00J1$Bn~(d`eDmp2tn zYt@c)f78R_uGL9KC(a!C2{x7T(LDfUPv`1ETO8xdHL@!0G3++-zweKJKEK&~)0!}7 z7ijLO*>$)81En`o75~kl(nyYaI%=M|qz~HKn!!%7 z=K>cUYcXZN2xND}xCSI?Yy=GUG}}yA3e7UpwdxF zT37AmOom$%>5^S$bHv9IB;RPd)(S$}#(P1R^M75$^D8P<> zpy{)_i~WUsH`R{LHO2nQXX$Gf=?}u#0(gj&p^c^davev|&-efq0Yf?XDg1Q}7+iyx z-5Rd;SVI^S7Z;YZcJBj%Vz)FIqd^CCY}=ojS@--zMxM%<*Rm#G9RDBW!l%?%Y`$ zF+Nhvt1eUaz*a+5+x+L*MiYq|jVAIuW(o4h2Y8+J#7BwT1rm)$!8pWzyicPF`CXBu zSfv4bB2Qjvr}5=!7lB}{gWx-A1ztg!PYm{3lTr4xQVmbl=Web2p^t@TF-6?YMB>da zMQf6_#9}29T!_NJWnxhkSPW|E9<5IT^ivc8caEqX&8hbXG6adm-_^{A8>tZ4iUIq> z6xFGQ!2XKvQFUda35RW#v zo_6%oiSrlh}04S;dP1+Q^@%B#SxvWbE@TI)o3+%U;0&rcF8NCv#68 z+W7!~e$8#Ln|hqbl-Q+j=>cKv06_jWt>;@#{M~m>|L@AHm<{rXB>eV#W=m_6%iWzL zlei@g16Z6R9i5ec(%;1uqR1bpBR7iaD37_$e6+yNwncua&=i-cR#?Ky{#L z{aaT3Eec>NF8SuQM=5&i?+Rmm948j;i&+*n#NjRk2`dC|I)O6qJuujh18Kde;rt^l zqYO}0fyy+ppMrhL7l7!=_c4G`{=3rQN2G%?=l`ep0YwaU^WSRbf1N#5H(-$q!F%}Z zZ92aNO#l3<`wXdou+{Vi z8&PkKuZeYu29&05AfqEMV2ITs*&JJ58M($tq-6s7`21hh29Y-OFRcVm`x7j{0btp# zt|*Z9n>V>lAVV_fXP~D@L@o(74;AUM9B60Y;D~Y(3~l>YUcr+18nbkO{H)Aj$Vkj8 z(5mY}l4WEN-h|}b_1k>;cA`3rSDWtXNewJmqp%Ka?N$Sdb^w_+JiZMg&~wLu$&e9gdo5D4UsWU8rJuzkMYUYU3|1}kK()(Mg}@#-%0#6C1( z8SPNCdFTT&JlFm>6!r4vDF=FzK)Kk%;v}$JRvV87c>rJq_)#BEkctjC1~SOiIbZQ1 zAdKcOeJ+R@1Ev8XU!}19ns=)QUv%p~pN2{NGbuJ4K&hBPMGv|^Q4YPk9e9t16iXt- zl|d{i0Fc*#Bt#2B;yx#bbLzIu;YZ&ALLgPMHUI;xggR;Gsf8J+0B2a|B@mzn+j|cP z2z7^pgQ;E`c~$z{>`9U}=<=tEjJjk9awRuS1#(^mZ(TnJlIuW!R~~w4ea^BuJnSA1 zJ{|ZxY5_>U;ztD0@ar&_pWXM%LCo8NL&lXzKnPnwIYAqmTL7Z^kkU9{TFSR-$}#=) zehCn51F#v?TiS@!(||)!3^g_Z|XJCQ6gnIJ5DYWM2PW#J?tzr0-2+m z6wyQmEJKALdh^#Oo*q`MV(Kn(R=%9nWes+7VB|pR;tc>;O*!v|;kypx5n*ePT3~OG zXGLbD9!~CK^F#l9we$pvlCT2zIZtfEjikLe$X)hug5OM^#73d=J8PX?b|pJW1q1rF~%OUm8CG2 zu@r?=_E0G$Yl!S-WF}>aM97k)5|V7$Dbj){l6{ZtYu4xdbY0i)yFB-OKY#psz3N5G z@Y&w)<2=seI8F(s?;#;nI76Vnjbbf9bhKr}C*nPNKaI>q_k>U@_cjOoLB-KdF#xiD z5H+Tu;>*#!A(sdk_F3TQ;j4)jd9^x%W>>)R=Qh+}$R~he0-mcg1OQz9?z$`}=f&(| z6d@qsrDs#@!svaw2LvGn$F;dZpMAcQPoE%(axZ4lYDV`ngZQ5t#LvM({)ZF#U#ln_ zn6Cw;?$iUMW+ynDFK4dH1eQVU7>G$S(ehy`SV0s~FZ&%kw~~xuE0W^5_U!OA9W~8l0k+I?kIe1AjT}bP$G{1 zz{nY`ytyL{9&c}Pz1kEv;FzpGCE)p?RWQYHgwdM=n&JPF3lD0Qt_wlMLOWIO`d)%I zc0W0ntbJj3zQ3<=!6b96$W<~+^NQaI-#ckZhLNO!OZZO_=Ox%SRf8-qOpcj9n_oLW zl;5)2glQhTvH8&>nQile{JcNtf%&i1=uRC9ZT>*}ieNhoTB^_kehaBt4u3f^WP98L?=E&Zor zC6um(s<&H`2kdu0fNa6~?2fD>MUes0w=&uPT5ieiuotS{iY~(DN3@tBwdoru^tvm- z`;eG%Dt-Y2Gab!`NypfyMl;K5t=Gz)}9vQn9Bp|ie&yH^n_CZuhC-}(mNYQ=^5@X-T%|9V?#-Ta1( zp&+O{E#U7Ml%#|8z9tmMYrJq14%~m2|5-=Dj6J__^MKSBqyu;6p2Ov{LWC8B8vTo{ zhVT2IWTtKffiey(!HThG9f^VaF^<%v@vr$yO;yvnu!%UrwSmpM4cg>i#D3MF61X1K z4R6qvS|{ScjVX`NyUif1fN&Izazk3W;o3h!fhULHkpEuV;W{8l^e@5(flKt*QITd~ zv#%thh!A)9skhQ!x!f8ROPo8>IQtMSls>*I7FCI_({8zwL~m9~=pwE8r-JSR^bkWq z6vuG1>i-|=5*JHQ^_@$vJi$9tc5S=|mdEKnj&BHo{-HGON<%*X+trS^h!S9Fx^77X zbDlK%fv7qGC=pFm73?>j@&_Pc7d?QQdLan$JYVb6dcm*t_m}luj&XN{7ytTE(z1Ob zrm{bRzHk-@Zy&)fLAy(Rmv|1?6wIn7jJ_ULBrPhA&jOr6+Ux7#!-y;)|1r>nI1#uG zxIgTw=pxk6f=A;;YSP1_xE(@JsvbiGuM(h1ZT#h`e5M~HYXp{0eSLnP<%01;wV$xd zC*LL3^i}#dR$apOgK$yVsTB)}txn4JHB`nY*k7k8SJd@;q2uMUQ*zZhR_td;yIOYt+<#E_Gf~C(?fC9__c{E1wu6AP>F?sMvfrU^ zzXvS}+%@}x7I6c>f<9oQRbZ%<<)zsI;Jo9CHpq&&k7mA`f3|XxRZ!#VE;trd$TEE8 z!;isrooN1zB*4HKa-ljNzpHI`2(LX=5GZfs1o-g)UrIo2yFq_KWLH`cB6)4jct|+7 zIlHr+zmJ%LEq??|peukc?%nbbma_`MCy6$LH~1AnNPj2%c9n9iz}4 zaRqY82LWt_`~{04P4Xbze659V)>8-j}b#HrP|rALKmk2RE$DEy`8}4_G|+a_Dx92E(9k8^}QY733kkRa==o3t;)` zXq$2mn}|Ro^);WyYt(uNAj7FY+D{U}pL1A6=Pj{|(vHD%yiz79V1A`~x9360)b zUsik>QlZISwUs1`4v+V-Vze5yKb z*XFv84bnjgIioe(I=eC#!Va6b3F>|U5_yWI!2nS9IkUu=qfOi+Y=z?xa`7hB47}Qr z7w6f-0|CU}p$J6>_j?5UM)$cLe(*+)b8nDEMf9uZbm73c`HO)BKV! zU=j(BF1JVb14y*Ea15T8<2iDvL<45JRx)P;5icOQ3uN;JIp|EDD(E_JP!j~?zCO|n z-NjuQ7Jy|8uRS;}eDq<^`ehD<0Kt0_nn_SY6jQ4^UenN~|E-fuzN`5ey;WBgbiptT>ACBJ_N)f#%TUWMik(3hS~Tet>!)LEsU}M<1Er z8>_J-j5z@3FJPOS4Te!{BdOD6o})IkVY{a_bVV7l-DF%waI8cHj|vaK^OsQPLc!Y54p;>4`pRn^0d#SN!Bbenj*(^)Lb!jd z8tj_X+vdt5z!G-+?Ai@s0kjrS`Q+o)yC#oUD|~%x>M0{J7123PNOI?1|_$NaUtu;+6wZQjfpwCEFr^R?-%Zoor*HAvf4slnc?mpN}=ei!yp=#=V0-xW|cvk!)caEtI{(U&sxcz0pg zg`$yBd$|)N6wQ@PnHZkUkM_S|xV%!VW)VG&(~9CDIuGr5+E@vj=<~jJ3QLQ39y+l# z(6(WB)*A8W{7&Vtwe63uE!*_KULny3WeZ^1wD`y@7nsbv(fc}JF;FGEM_bmn7#YvU z;+TJ3=E)R=y=&!2twys}P|V&jFdG*JR!#F8?n`_?3Q1=HZG%$9iR!j9xa(+)0mq8s zV{-YI15x^ig-p>VO>AeMT%~1f+mljXiSwkR0POq?iuk-ycS^+eT$$sLa;C-RF(Wvk z71E2y0hW%-RxaIL7T?QeR}_(7krpFSBj}zTJPM$xkB3lxD#xT&gfc5kioP$=b2vAO>@H6A@)qsOpgKqO5i7Evq+WtI(!nAWZs!*C@brhz5 z&K(~;VbemUKJlJl5fe3|J2#-!cYJ@_I2nh#;v~Mc1WolW4+GKd5JeVp6Hb$^O?F8+ zu&Jy`HGQZ3(97I^twcp?9VQxt`1+;}4+)=I zdno6YDN62{Lz{*k6jGqcsPpDESv34O-@(*27?-}F&fci+?k)hm$E-%ewTwnDBzrRUl~hCka?a#> z#E>37hkl(HRcL*IrG*obPJnilqkF(=Q{Z9#R>WuqtaiGn0Unt>lm$7tUXlUDiyuW3 z7IM#hSl*FH{%__-dQuI);uwjIl__3W2ZR zI(7gfq`=}iAG4uLMq?d{Nu{NNMjod>r?_Kft)0p^2ybK~mhocQh!1RJ*Ce9BYpgEA&9M$}z!y*}gR^nem-+J~Bk3q?! zIsjNz@47h5{EB^gwZCfXx*>@vHgB$$v4swny61C9xDQf?zsE1aLdm~CvGBJlhTsE@838_!Qk3L8N;!rSr~IQX!X1HH@#yGG@0;Q;bpVMG zaEokOJpeZzML073FS*Req;hEfd6gKL0S>wRg(Nx!l;AMpt3q_XeK)3LR=*g^mVuL{ zd$Th|lU?`1erb+iXn{|-3VmUJ$kat@x&_#%6tUTX4rvrh3RUh5>608X66_kc=|LgE z)tmYaR1Jt0sh0Su)MMBN8ooBL>(~Me=`k}_A4L*m?gKdqM}t=e0}uaqz+{-72P4{Q zC|J2jaMCdpxC9PR5h}3#H@y;QT9BqW*aBWXJ%Z%;_(SkrFNd+(AZ+mCppS5)sY>Z0 z2MEpHLMPG921BQh@&6XmLzTj`W2nR13DgPF*tdl`xOcBx#UN7Gc7nJzc~bb_)|UhK z?Sft&C)GkJI0^EPYb`A!R}dQ?f@YDud8)rU_>btnxdp#-wc1&m6!MUXfHSbjU)5LH zgjmc~HRW4#W!_V5QR=#%Irg#L)P%}C)^Z~4@n5*>|6=Bmc195M!X>hty_MDp83$=n z)1IL5wO$T{mNS`=_P{4Hro)@dsZW87xvdvx-*jK7POM?x{x2aZ)3)69M*mLK47c_O z_CNX;;8qcVw(Y-?)*Z^sgtOJ7CuoUB0*v(!p| zGs_D|{W~*3Sl)stKE-#FZ8@gG%D!{<*FniLeIWT}eSUR0Oq1XTmBGDd zz2o((3rTlwY|Pkq+wq&z647KJb`c@jUux zSs;7quGmoz@K+5ei7ax3OO0^iYQ1v zyUFqy$tr{7RCNw<9@(>AAY^Mgc{KneY_1>u`gC#zUk9 zDwba7{K_$?_qf!hC3CG_H?#1{)r`<#N8tD`t-8Xsn}OI0c~y6OY*_9CDEE`q{5+Q) z`=@ULbNC|=aRR(nmx|$np8U%Nl@-%h-cx7dM5}_KoWH0r0iil2{$Sfvj|;BoObmKI zYX>!HFXVUTvc(eltwB`>qP)XXMCeIj8e6?_Rg4Ii+eKWp zx_De=7m~#m`8mDHE^zrCg(lr4-gt-OUVTli7rAnaq%fu0+O zf4+1_lGC>Y1D9gIzJj9K$C$5`2V51EFcak4lZqDJ8VGt@oQX5mc$p(Mvv25w4r%vl z!uI2tiU{Ad`LkL*LY1ImA4NiZBP%oy7ddx+&a2&qxFAc_A{xH>jZcQ0Rrx@PZ$Rpy zO;x~eWN^@nC`7lR`^-HEd?GK{o=Q37fmfus5U2^4uw7LfGe@Qro3*YWlTG^42)aUC z4x4N9u5i3Qvt@2YN!x>4+GU$jrvW9;{IS-6LiMtv`r=2$8N|amNa!sy*H9P*ZHiBI zfEQPF@RIx%-DXg$ATR>2(n42HgWGj_tFaP}K|FMo2k%Td34CC>!P}48tJYb%A833VG~uzDwxNqMinc%B zjn)pWRi+t)33Q-f$M?kF$+3?)MnDm5pprS;Py+!{FMa_FPzLrO3tP|_y5@VZ(AAn! zv@b9X-PDqyIYyT`vOhz8+bk%HipDK_q4g}?7W36I4ebHg2DpW4HB&;x+HopThlLH- zywavxHR!$=h-C7W(glVGoU{KhUliRbnS9weMW+%i6J*7NJ%Z5ko!L>mnZ^S4TMcxUtiSs{}>X(1RQd`hee?1D6>5q;luWh7av&Ld4C&Ig<1+k%zLVGE1ac=8#g+GpM)OyXXB zcv+8s>ROJ8U;}&gB@K4b{ieRMCU$}pj(ZaEp^20e4#RH-x5Ju80!^}$(8)3Hps5Rq=G zHT{q2akZjE_e_bkhU8HGw%zk`t${$vPq+KpiPNIm1er+@;0z&lf`i{*!0e z!z~Nn4q@d|x`hl#OBFLD(`qKjk;fkd=oZR_*xOG9E2H-;agA<- zYMiVw-xw`#x{ zG*-TJXjIN#UuC{;xZFs8Fi<1qY-77lU6E!0q`!bQ;#U#)5&BL?_mAlh7oQ3@7;@swoD_+E-CC(RJHRoY zH#@Oe^MLwL<16`6!pXU>#%@D52wrm=HB@1e0?Y&I%&wsCbBYyWt^sq9SyBwEhKKCd zhx9!RHB>2ud?W38I7!}scAh2CWqlEkU^Hh@c&+l5t{v!o@ zMmV>+5#rWX0)>#Ky`c>ljr-EBX{jK^%_k#3nu$I1fh46@B z-(VB&1=5BViLtOzKWJ`yarS(}ty#hDkgJ)pzXL}fu5E*ga#O%Vde)^IWlE{rprCj^ zFFxlmb<1LN8M)M#4-0AW@MYmy;;ANj;gevp``-rIxq`G?6#2LHwOE;;0YNqzr3R|B zkXLulIy5G!aDK^x!R0<&) zIyUIz+Wl>M4b-b)&S0%On%2s*BuEUTtG0=_v2eI^_^jL=O0nsIyOe;!y5{Vjo1SyTRMR5w0D)pl)UvqrIfQ8o7>p>^p0!f>+_B9gJdfPy_ZajQ;#vPc+y1F?!Ol8$2O-N9W?4aAmGs- z%VHBrX3e78(mCQ3cV8-K&pqRnTWje?ndWFIBc{xq#B(}3CTzj8u^I+KdAe`VdTLE` z{`IRB(SG>W{w20i6Z-Xtpv>Tqe)_y&`m&DXep6c3>4o;C2_4ysrB(MIp+uwRV`a4o zgGpGfGkNS!8Fnu196RS1Yc*#ywD^$nF=Sj6qeOW}{CNy1qEzPw>Ie2x>I6;bx;2bx zaipg_%+Sw1c!aXpKYEu3*`;Dn;RFZO5?2&u79#1&G;RkN}+uE z_=7&uVq}!eXuBs}pwSNplh5`k#{Fdi-pg#u%y*y4(rD3jGv5sCB;EMw-^BbSONrDBaId7{Pz)|$98Wl9CS zb#9uP=jo=B6Xv8A-FKW6w`#X^5~*g7_xS@Mi&h9_-wmVe>6Rc^-D%D15bwy(U?)(hX*3d?T|2u!u z@PuIZiGH+8-A3|MTS-UiY#F!r*!bGLNZ)>J^5(2)t5}|4T3ZPr;`dX>j#S$B@yGk! zThK~po1u`2*uigp8V;U22%+S<>B+j6j?X+Pa$ zn0Y+8c;xDchtRmOSGAJM&(c_Yqxi(P1i!uQTTSgt0J%S~C*zkp%w|%r_sM5ypODWk zTECo&y#A?ikE{YsHI{_p(O?!GclgJiRXcfbBnx{z97IfaU&&8kTaNwp&c9RC+P1nC zE}cJgko`R#@;+xk&sIX)2X*4>fKw<=U_n@cr&_g2hYVjt$D^Chmd+w}XXsQG-s|SI*B4TKogErMBlRWL$@T(kE>^PcW;h2VNbmiN z=lM%E7)HH}G^Rla_DB}7NklXMkLEe>NPiPqaGelakmuM(zUzS0ky5|lRBD0%C$5ZN zBcN*m!+_bP#xZvlOE?4V`94r_?MG-bqu#?%zI&CR5?d3hxT+)O?VT8g%uZwiEmr`i z1b7S3VQK^AO(y*PUr7-_-NFCH(f&WIKYvmd{+;{~6?+cJ-Pr;OMq96By<7Fx*vf4G zEOa|ZLAE)A^zo3|3jb=yVX&v|k^UQy#p&Cv>om#8`TVkY&bDlS#B;=8sel(4hL@`2 z0Eb_vLCo|RQi?1lSl(CwUB|5yIAMPXO$WGxPqMS4U&cza`oE{z{_@yx1_r0j024BU z@J^lmzQZinp_2Wv3ZWN2LIlxL0S{`cAi=y03WNtJP3)Ya%LD96@)St(pSMUYJ+PrN1AwbIRn6~9R(*j({hv{U@Ux~b<$ap!NV;Ka z*S!WrN0(}eTYuEo26^2k@H-E&!??fR-v_5Rex;r0WQ1pLeY z&sYQQtds288_+x+g`j1pShidx1u)VfVKoSS6WL&o-f0QmZoeal?~}1D@5rJ(M}?LK z)2%1M)F2N19Hz5yuqhkRIJX62CU+Bvf;Wepzcr9{%gyPZ1f|p31h5dvAS=^#bMFHc zfDyO*7Q!eX;XQBu+S#5GM2Zg^7bJ5-|AKo0!!T4^zx z8^Oc~BFu4mzAiw(BXSxsxzaIUNsfQS+E3hvn?>oHF(DCadF5N~PoP*`y`S{JyxKAl z$2B%EyXZ|IdcJ1u2S(6d&tjV1fU&i?>ZIfOwr{?D9(4wp!3#Gy`!hFL_g!&VMRAhQ z%bK6(x)Wrq&ut+RUgU}Tv+?b`uK)p}ionJ9SLnvz&6OE69I^1b`#w(?{(U92?lekluI;1g<(580c31lNYm+u6hxfig}W-?qn6kVJT(&f z;@j0wXb=yQDof7YY{*2@5;7X6fZoqMg4u<*Tei+=<0y#7`bTuUMO8oc&;3H?d8zI$_XTqE&U}WkPkbu^u?it5}e*>v;m5OQ8YzE5umxHH5$TLqpdo*I3S@|G>{=67ri}y*Wr+SOM5=j% zX4Vt9Gx?0Vn@$;QFhk~iBlZe;6B)Urn}>Z-%BIUy?`Og6{Usju7{+DDL-7|v5TZw! zIfM+8C&dEu8#WwC$uAF#}9?5mua$=Rk@{CiXfj zzluE`7@p?tDNMI=1|s?B-4=}v=|&Mowq-)3J9Df= ze2Wghsgx1dB*>$mpMIVo1-a4E9@D^*duE!jVnmEy`~7;^n0u!Q6LtOuOXv9;%_3 zWU&%DjKIi_X2IHpXKELOtu3BLd_9Ne;EyKRYgDGW9W5e7cT>^QpA^=3DLv}xVka9V zzR*xD8_Sr|xWsk0ywJn;>=XVf{Zm-(#J-{X_7_j@n;hIAmSdTR%{R1go&p;f%_YIT z7*U%DQXz8`g;7YK!6o}ZivAJF$cUrs6jEMC>Xcm0iV0iP_m}4*Y>9VPPF|Pw%3)Hy zG-22M_O+ApXqzi7IdJ`4~)W^&(vg3LO-C`axHEhj2 zuc62dBuSQE$wckl;g_x(jRvr9fPj5I^vjwKj=M$dhvw6O@Y);Y}d@ROCl z@d@ED>_MLVblfElUA)8%#WnEtd`;N2nfn`DYW7OVv#4HXJ z(y~M>ej^|4zn{HZdj|N;%H?m?f3mZT)tBR?Kw&I;RrF^L{91)>Q(qoO?$rob`{R3w zvPL$xr#=YgL%#O^~j@dwD9su-$WD(AQ5xT9N1QAvL z18w~;t>zg+evaZs_DaM$DH`}&B@6VYNYo$36dsF9C9WQ; z;#2}5s1|7Y6WB9q6si9-OMffIn+fq-6k1`Lliotl4r!GBBc=Vfa?Sq>aUve_*wcT~ zs^G(apH}sstSF#MZDOD-fV`x;Sr{ziW>??%EoM6g1(^hGOe`-+3Fxk0f{}EBxDVDVTfx zBw{C|6ihDa1DmJOj0neQr=3uR%;QUj@h`}>?Ratd+v zZo(~|jGF!c8NdBA^oHeWs@KF23?HCERN+dh2dtI(r`4?n$oTJ8_XX};mKMAr-S$-t zMiTuPwA`#u(QcsSkx?peB9*m52wEy5pHqLl22?OY5835wy1Or+*u$J6LD~5ofAO=r z|4Qyr&MX>7B(OISq#j~2W1mx}^3GOX!9~-*0!L2Q%O-UZ*6|wwOZ07KC>8>p!V_ps zt{iUTD5^!Kx_=gnxni;8=DU-R^?_PW8 zKco6az&qm?G(O58j>5`%O}q<~ug~-UZvK+UBDZ`mATxUavwma|)adb^Xst!yE>1+r zwPylM^3#9^ClL{28@G|M1mr9or8&FWhHGNO358sP2-6X<;}0&-1XQuJH>dvz?d&RW za0Q(usld%gP?C0%>%xa zFwG>Sjfa37T5Rx*m0y<~eSUsj-qy8Gs6CPtg%Q zMzleGDZPs*LBGNw6qW@ZimLIHHO8eU%vxVDp6dM;hZ?mBytq6 z@k!(yFfh50xoMl|1q1Bupn2&sJ>-9Zj-Bznw#Hpptlq#Xnm97J2U=#YCougGVh^Dp zwd?aiu@Z~pEhXg&$0Is1288$6z=_qJ_57W}4eblej!2j;&RPxNUZKxF8xtA8szXh- zabbPvWwE9R2~>3T*0h@#!`eeNe&%kziu?OdH!Ef8H{X|?J(eJS*)zQ97KpWb%uZAg zs0q|)RoMMIG^nn*jTO=ksm81AMa6O%F^v&fF?0b~c=(GaTrqNL#m5_pA%aCo|EF2Q9Mi_Cp&(A}WZ34WOxLgP72KsMvRhh85U1Gqvr1 zm@9G&ta0Z&iBI)Vd=2%WqUi-m-<4SYrkmhyY;2|$VO$ouMKa*jyoH6?$7)V4TD~Sc zBF!)>i0cRs1)^UCaF*mC>V;%?TUpZz=3^qJ>5xCkJy^daIPw`_B8kCUWLLPPNfS@D z=vzb1Zr!B$=fCBqQI|yoWd5VpMHfd=xY8l9w?pyuO#>FeudUC`Wh2)$edZ$W{gjBW z55m7~(5GOfHu2UXDE5#FNdulJ$8|P^0nm9Ll&sV1V9I<;^u?M3#~zRg!?qqL5;B4e z&^Qak!iWUQbU7QnkTlOr`O3!7@WercsQVCWfM^T+EYL@cW`&wbOe`qIu|2FBD_HH1dZL8{~#pnAdy@l7WBx4aAR$Pwc9`?MGz}| z5{)ei)w&t2F}O-DWT5Cyv}OZa(?~1IPOhR}B|QninZ)RcyaFexgVF6)ZUWbI zrd_OFQRBF^))%Zx!=vyN)p?Azc9!BM+W@rfEo*l^w60#Q2|jxICbsCInlH$$%8$mwt=3mL|8B3+bmDI6p~Y{MIfg$;IwOY} zT_o54OUVcFEk021p%?(BvV(~13sd$Ki12!mv_o)vdfR-96C(=_x?<`&vNtKvv& zDMXedLOzh84MXnv-d>qqW5D43m*pFwHXthrR9pW!HR;%unkS{zTPxPUuX6tDf)frf zG9_Mu6tEBt)bE?N`(JJXE_(wNMkxeOz5(P=jx**UU@ky$z5j{|b$gB^6OEnK5#0+HW6z1FllXw}pa!ZlN1}MB1 zg!)3JhO+Rhw0_11ZI{~ zc6b#z;xIOqY@t;e0LljLw_$3(;Yysx1M(kxxcJXqzPW>V$g_aQ$^&!P%H_jj?7u^q zwWMzYx@Fm`^pTd~Zg?Q{8Wj+5JI=A*``Xz&S>W3fW5LK3>@J!gV9Rksyx?)7gdnUv zN^lvgL|*EG)b=-CKkse;L*ENIqtaJIfamsM_SSv}0go8y9~90iqhrfuz9U}_+Qy{~VkqjF%tYV^-4vF5(b#3^A zhln(;j;1U`S~JI&qD@POHH{H4+Py1NH<6J;)|Bl26woq-h3*j<9aZf<7>3O0yeinZ zI{l+lgfSnZ8dhV^;`RB_XXkI5_>~#Ocg2&(R=1yQyzt)WlX~6p&b~Xtr9hsypmxOG z^PuXu&gY*oF#TdYh48tR*4D}MYO70V(@QO0C%G=o`hex+t9ngoafm-Nvg;7h`&u zu@I(>`AqgBuEY&Wa{%qwNRM6lFQ9)_^!3x$suWBh^tM zPe?NtvckMAV~5X?=TP(R!2_Z6-vSzr&hy2-W#79?S9T#4PEf>{iYb}wS;rtA&vc+P zol!$g$LcD3-Mc%=eHK0t+m)dPg3+&&jyjudteNO}GIn@dsHZ7H4u|n+xH3qo(|L=R z>U^(5WmUas)W+ktx0MB#NjiYRh~6d;;$1Nq$2l_jOCT=u_AZQR`C9`xk#uU}(r!P; zDdG=vY8{fVlh&{ew1&36_bi&*^TgeIfGjyZQa}j5j-u8x;us=tT5Fnz-Hz~Jv-KUl zXWk6fro}_%_Kt^7KoKyqlMlSNE)26U7~yV8@u&84w6RxiO}6(QuW-am^AnOzE9c*xPOmQhqQHy2|I{ordIw zLJlo!NLL=q57hXEJw^6du>Ce-eU8x6buLn7^5mnVE`I!V9U&pwr{M{=Q#|ZJQ$E82KEPBVzIe+|@r((O2;@4Dvr3=pC2VA7|$?&+JPtvNy-Zb!g zwQ7BB7nc0{&*g%+f!_)85$HM#F?i9K`ztQD^Dp0WOW`-Zw={dTA2+|4;_NCPYR9v*Wv}CA9F~r z={n^QyY`wjyM>Y$g*Jgj7`f4Lp^o&?vh&LL|3p1$%ArfR|}mGGkq3;1R2m`w0O9c()O zs}_##pN$D6bXymG+CN}S$cPSO`lq}0=N~a5d~3zh7sFAIQO_W6DHt%P;mloNc$8H8hd{mONH#`)CXtE=y4)E;TVQzrkKz-cX% z%#&G6eBcKL$NR{Nvz>BsR+MeLQ7|~*d+^Sd^I2#n-`J6C=XWU|jylo|w}?5b+FZ?g z%Y`DJGpaC-*7V6 zo}34n;*)K)iF9H!4DGS`F);lV46vrf-B+G_emd0I(VH>XER`P*DWz-9jywdN|y%5;9rA7j6#?jHw>=4l|5$ ze!E@okg@=6TVv0uLd4rq0m|a9UBY|heEFWj*L4qF<(tX7{tMP)$z+#e%TFKt##64H zNRo5VE`vG8&rP=??KaOf4LDM5^cE+|7`tPEP zz|ZbylyMQ~Z+q?Z>?}RO--Es2$>wr?@QY=$&K2eBug@H?(Wkm=euQ4*KL7b#L7y%M zN1#Szp$G40>G789L#)1lstE7z?|C~P?6x^O?A^Xr~b!@ssZ zyZ&&%z1O?*>-p*KZx=hKtha`)J6?Hnt*G4x<~v<-zQ6c@YUI^7!F37>$JS_N88n&p zP0a7v2N zlUK3Oygw!vh6#^CPG^3gUQ>I1CDN|-{vKKUtXjj><&~|N!}MLl15bmWZ+x?Mvt8WB zhUWXRg|72$RMbjvCLJ9Nb~;<-P2Zw-mX=gI2;VY|=REw*U}eD9@gqgT!QFn%_0=aO z$4=YpGQf1Xt4`&+@J*sSaimlE5u>Ea*Fm!G7bVYxteKsX2h zVSXQDUNr#QW}rVUTSgtVrR^maEx!BwaQOftZtJ(yqa3C_-It&2|AJ=QT)f!9!R_uJ^p$v&y_}C|g;S;$Fw#_(7&5clz=< z*3gR)`+Ran(Ny4QS8S1nrON0#7#1dH)wIdGN%UJ-yRfipeukxI1M@0Zy1V<}cgica z6Fi=x@^TN~w{Q+Se&MJbL67i=NQI7G{Qj*cZc8){TK(+$zTOi0{k`yvz3zYgNd2RP z&R!eCCDqBCM^`qA-lQ%<-cCnA5pke4x#4$(?$S%8%ReNaq@0XiWL1Us@lArz@Z=pu z%;Qd}rY`oR2e)3h1;e!Nuk#V|eSYJqA9nND$a%5MWjyb{r+C9=wJyQ^gkb~%l$Mnq-2M{U?|61ML^Y2 zXN2%#F>=0{Xbm!o**18Q6U#4M(&PEH)v=Ug*Jse`U-4bk!`h(FBv@T}dc{QXW~b$) z)Eg<}IynsxA59$!8&GKF+ayuHTh`m_atfaNm!1F>?!nVnRyBI5XfYs0+>P$t2J|lM zY)$-HKLQMUz1?i#R=L~^6Y4DTs<+o4@MMJ6R*yY!{I{lyd4&Y*aUu z*_i8yEk91m_U%jXoh$6R5O@5UII8}^%d1D}O&gXg4*K4mTes&V#v1WE;TY$&nOU`W z$%nNWHK_c|ihI^bJ8WG^=6Rl1K54SW*Gm$dTQ}i8FEFWR;;{wtkh4=b<1j4_(+8cj zJj%WPL{?wup z@+sPPm%rb=p2az-wZz=k`?QO)G4oc#UJ{vbbx13-%ECT$_p_$mFC)o1Tew>q9d>Hh zeLZuk%NM`3rI8-Pt$N*wfz2U|C#q^-AEBvmGuw_JRD}-f;M&45wh=WQZ|_-mtKH3$ zTlYhmBGLUKYV6LiTgANI7Q|$mj6o82|LVx!do1t4cPZpALnb8G8s{v&!Ai7HgS^A3 z#nOTAHIZR*H7b{U7x%~T{#qDdn1@2LbBZG2qRN(!uA}&heIAdkk$Pm?(kh;XX7lB2 zYAJjumU&S2&dov;&*VZ44cn<`%u6}mi3QhXa{&kXgqw2}_5t;W2&kbDQK5>%R)HuK zAzloHKG^Lk@KU+sUO~H>2Ja4G55UBt2>Y6AI{xWt3{(lyPm|a86DJ6S%Y7nhH&Ch- z>Ptu51UxKIJnJ>y>-SdK=0i($OsG8~%p*4azU3A+?>`+NbYdRu(W4+Sj`8;0K#NEJ$~Dp1aH&9Z^7(a#C3(?HL6f^hjP%g{Xw?Lyd2jbq8$DneF8yrLD(R<{$5QX~f6X7Ib* z6>QczYB%z9;h!)lHT0Rs$m1uJZP3G)($YB6KWH7Xb7R$b?(uDfY9m=69P80@tU z45OufB-CFV5K*^3ZESZfO~da6&pWcLNB5-;mltR0?JY8Wv&Y5Xc5Cp)uBi3Wv(=I< zI*&B29O1_B7DZ2F^ml*O6Oc<@tGT9I{k}zYz2C1vDsaq7KqcY3%KcW+i?<-^zc8gf zl6cABhviYbJ(sq=RfO9-+UfW)PRr?X{lsE=Xpr!Yxf;`cA0z7`^L@)&`Ex^RX(jU! z70Yje&SXz@7YYrJACK4+xu(@C+WFIPQMYnmdbj&RQKi$Ini2;KfZco6BVbsdn||sYiU1 zFM_rL%mPtd31{Y6A6V(_#4o`tA$J$5j3%AltZw=4x!Xg!7bxte1#h{p{OjM0!GX~s zi}k)Q?Lp&P+b!8XC+S*HeT=sUw_D=G@ULAG{5+O|S6uGO4|*TmGu;2$3)+00qQ~%C zKaD@AEq*k+FFbE@`Pz}cc2&7$3Yk4yvhSDmEupBRCr^IqIDT$=b3`03xw96%V^L!F z5RmcI0mHtYXs-OEzm~LesDU!D8o7*miG z-5r$!e;0F4KR^YL8-6uZvwkf?sB^E}A<|qg=Fz(uLlTFfAA+*WlVj4W)J$^KU1~`> zyt5-^*xRJhbeAW^a@&{~ynH2HWbsj*i37Gp^o_6vK=E}htENzhB&l;%Ff`D`&1K5z zjpQ=?e}*B{alBBXO>;w4<_n*!9<~2&iv0t_+3PwQefnJtX9oeA|2Yb%hRbyc@wLLZ zW2+Rh!k;P*pgS*=`2V`>M@p=33)fL@&2QbHmJ&EeYhf;+%`<0%4VUA8$cG257Xt3j zf$C9nkM#O|k%*Z#U4M7Lm4BZKIECk9$^Y%MxLPrjxw@&j=jzn8LjKKK6vOce-dh>OJ>6F>)**9MVZmxgW%03)1tM{Mj8>?Fq zeH!X(1H}Gh`R!VI!FcEvmh)fl@fd~5MV80LT}u`Gg&$z#odqD42c}H7i$VWrK6p_D zoR5z^+jM#Efdl^a_CfCTPaZ!;SR0{{csj3i=vFmGBb#>UPDBQ1F1MT;qczqij#gb> z(9Cr;`#*%ecRbbo|36-clX;{#_6%i{l^F*WC6&EGTC$QHJNqajS3+4KMY77C*=5Tr z<5(FTvJa8*d%SvI*XQ>6eSh!k`@8;f>ne`dcs?KZ`P6J=z4~vL9${Q!9|=b|^EH7Z z*xCvh?~}1rYn@7@GvNUpAN^OFSlYPvmcv%zoS6Z)GV)cIkAVG*x@J!!)3jk1^{n?- zFUXZTaIFdoDwMMD1+7j`bIh)F;wC~^?wD3xOqxpF0SIM+@NC;n1t93e+eNFCBmrWw z4?iA<*?yx=+s#0{sORFSvS3WE4+%;zjgLD|qiwYCLFSrkC?`@yu+`Y(g zXZNKAWyFTOjV59H;=XX32`j%^af|Q5nK|gl7Xu`F(-XL_y-K%z|BK2ijVIHEHB+-b z+$;8kp*1%(r|ng6oTT)>;Lm^?%b`x!%+G6YlupB$f8qdJtra04mvdnJ;16(aiUTmd zr#Ok{Zw2Pg3~J_6R&%U(-zK>am}<@@-D^ET^0Hs0o)WX#i9m-o_@tI02dp>{ye?*4{k?1cI?VRjvj}vyhv091IvFyYdU5rfsg6>d zNx1jroX|4_=PR#%;y08m|M5DpRrUA6m5WM0i|zrzV3e_mfLmY}a3m*Qzh7TsN}#{t zq?bbUX5`LJ?nO9i*7w+HF0Z+?dk8P+r5JtS{g?A1{oArezD#x-1Jcds^+F2 zaHb_YY|xbW7jo>dGSoa9(wYE(&v8j#Y!k1%dyF1em?dI>RS_e(Mp3D{3u%9P`c>0~ zXTt(sq<(f?bBlT1n`t4;Z#53Bgu2|DOA8y2F5X(&TU;V42GgfsS|iwCna&|=^?N|u z#)Uz@ur9naw@NdWu+IQb6`yQWHpMF|-(3%X;1IoyezKl8g+-@_sm9iBFq*6f>`C4r zo<6}C@+*@8Z$FgnY@vzmr?meO?4jLaR2i7|y6 zLkAxznzuSb@sw@{FDZWP6@Y;0ud-ajNC zoB)^j-OQV)?NZOFEORD1aTE?Mjm-MICB;35>Sa&Hm!Q-rr2pBWmR7B!~yuJsBsCIieWEY~`_iB$M16j|#= zd8ROo;?v3li#ZH70WYUy91U*dC@meGW>oXC^ZV3wgv-_V<5Vc-43^g_O9}UW`PM{c z-EgoF^-93p_$)*uEL61d=c`Xih2ErN4CyB0YrWSdCND6z{z#aZx69LL!u4t|_=uxB z-9)g^qtWobm;~E*C^>!roL!`m^(Jhg#ix6(HR%;!Q7ius>u47Vlp~5O9=7xV1GVD; z_TE}Yn@{D_5{?9x9brMR^_>EVs^$8Ak!je_^4D&_GM{fHRHcR@zkt16c=zCT}SZ^ouQaG$ZvI6|JdZ=w4hDuI`zqNTgB*UDcj(77=-#o zG6B>DE;siGu4wLU?(dC}ICSlbE+|$h;ZOJ-vBZ%+d1PnCz2(FI5_QNeHaez+7aY^Z z@)CqKFO4C*OuOu$h5K!EPiVqDV9eryX31%mjJ4iX8ZoJ*iccEj5UbhQfU$1PV2tj7ZUbIN3 zoi)I*SjTfn`|O=_oygJol7cN`7A0A7=3#aMARLc)DwmHn$$}nT6 z;)Vb4zC9A@l1Z%74<1hR6n^4+eAPxEf*Zxz$Zs22IBXNf`F8F|GyT78YX_Y>-(1%n zk(8TQQOB(5$Hb$S$lBx@8-F!P_!-2%SWZVxQE`dbR-&rfXz=k2OEHoUc(5&xh3+5f zqOcrhlhRYc$=}c&S>zco#}?MrHy=tHKIL7Y8=(jUK239RrVQEhM=dKbUR6|Sc-l1>Q#0;m+yMdKXafSISf4pX9|qY7$G8Eh2~>UOI+sH(c?-5f0JJt7zX$> zO-`Jq!$X`4QT)v3gKkpyG!8U8_*U4Fmuw2OI>V-Mx)2|`L8n7rFG<|=yn31xaj-*qzz0DT z1D@ALs0N#CmNc>Z$ z`~TK1;IAT7&HK1oO&_53r{P!vqS{V%hSh^Z@26M$G*Xso_x-kDRNGd2al;3!0?CBd zfnQZHfIS_3(^;Cb4RgcK{aY^)Esi*X2}fLsZ^udleJ?oeU%U|eBjx$fnz7$%ve8$= z_Wi8SKV;UreUe^`$pqo|O&F$v)wR8PhR zqAX7)VvlOCx@a6C4=Mag*pSxPfbopiF8JC#VAkoX9phVorQRv7La@+VpN$%+xO_KE zfewc&);FlX^C#)yd)=d&*=m!u8@LZ6G!xBgyX;P7X>6bO2tXLNfs|{~L}SfXy_H2y zURr8}2r{|x8|$?bPVS2;`NfY{DnYI>=FcxagaCkVaJB9Pd(XB@wQyL=oK6&q+F?Va zr`-4|Z-X~`r9X1H8a#dGG6++h$It(;3w6`v^s45G_zX*$k(K{0Da|cuANXH}XSw3d z*~KBQqg)_Y!5xcZ0$kvr?YzX{;QoVUxJZ9jkY%gwL+SodS*v*>{tm^<<{OEQMi;@G ze_9B|g2)ky0M_Qq6yvjyfhk@juPlAfanIVN?vQ=&1^L{mvsZaPA~XD$i80?J#gXPS zW9u7m)_tqwa%3HZ%32L{I1Ss}t$)26e5z&s`%ufdj&B20yTTtLAMY9Ed57Z2w|>w# zKILX4GWh?}EQj&^0o?j+1{_m5q~$kf6T--fVJnOfX`Y9e^Za|54*OOSp8$dx z;^^eQz8t6|9-lN|EOTj87wv-#HxH`KEqugQJ8{`CFA z`!=JxkLC{Urf_>5s{2z{rY&}PoKs~+N=6?HZ*RWM$uk>nSkv-dPxbL+7JmHD4bV@2 z7^C*s{x9zRjFM-J-u;Hg{qx4A_RmfhNwTGS5wnMkSI4~ObLYnZPTF@ESCSG{dzgLS zS>;)S4Dr_ZNlo|b0iDO9@6UELlq=gNy-CF}W2RtGterV`7hZ~FXqf9}AGGQ^;w~>3 z@fAMy>!_HmbVBQHf4-mL>=Q2}hHa?s%7s5Nlx|J0F#hn)Lni5+T=CM;2j-5^{)C~t zJ@dPTS{;d{b*wj&42BQ5T^KFyHSz&D%EA<;qqkW$P6=&8xm+T-MVCEcrFF1E)b~5j z7Z0v2#+f^1Pw`7p$AkWowp0m`px1XnVIwIONR9cd86MOscdT&)tAYE1P{>x*M>3i| zDM7v4LHGK)l!amu>}fyq{AS)y!OIEW%J#8!?4QaoN^3W}R5ssDz3HGhIG37RGW0gi z#%En6%@$Vjyys{I>8te^y|m6@h5(IV-<(Kb4iXpyX};yI}<(Cdc~h~ zkDJK}w};;FIwU};5)+O(ceK~aEv#UQ&Fi;oJVEza=@O@=o&PVQC^yHdESJF5)U|7_q>Vd>9B3)`-0| z)$nOUaROV}AClINmaW+iF%htKj~jFL|2>?B!zhb%!o+Q(b#zL^T7%8?qvEO*)%PaU zM%FeYqGFZlV z`Mcdk#ses|ndd96QJz0)_^HuSMaC-OsG&J4={yRmwRlNe`cEG^v?dSm_KkH)&8GP8 zZJlOVuiQvznK{)(=V?Z~vB>y(jx0SkI{zz%Nene8OL)(Ukw*f#b^piryI%c19x`3b2?l!WWKVx zDT`ukB6KKa&xG-|N!}0+I56etvDP=PrE&i&>|&u7nJ)es)tfpWsbnU8!PSr*cLUG_JMJlZPJ9=bY+`k~!5s ze2*WVg$R$yn8HgnUbjBn(>`K=<6n*)SF}k%w_w^Sc z?$|D@ZX0x!1Y@lboEg$1#_(CW;Qpi8pt0$uv&Mo9N9<2+YaQhaET1r2e=JPY!_P-D zW;mm$bFCU|=(9vDe%I~|?!R5r_aznoKC6TZo!tAFB+!Y`-MyaoLZVYjf)oyl+^SKi zo0ls?v57zam@tWD6m{0k=@0} zw2b6*msayVk_zzdS*?ZXyL4uW%Xz;&ynOLYeRlawirdm>e*s1<&W_t_R?o3|o~!D2 z?5KSg)^H}F2%fkfglOJVZ`5{g`^wBms@jDd{9M_)+O zt$ev{N(xMGimi6BQahNzIK3<({>Z{tqS~yQFYuZ?wq+G5Qf(-^yTv7p6qrI~L5wZ- zkzl$cwr+fe@fvIBc21aAaf0x&k!5ls{!82%`jM#r_>uhTRUx_CVXOTH-s|!9LKKu+ za3jTp%~j*BNlvo%ZdaW?wjlGOR>MERxj(P z7B?0+l6vzSkJ{oD>y`yuvmU>X)Ny0Ej$=}IQ5x*`C2xjg=M$mCo8zforkz4;n+naE zRd#%k3*4&}dOQE{sqL6?&5>oro`+Oqc9k8hyJv8>QnccFBv%s)1|`zf)eb)#X_XRc z*CM&yE#o}V?mPHVF)lvKqcU|pV;&xvT_Oi*@|mqFs{}#sLP59oTENHeDL<1RH&VHc zEHz*9?jWCiD<2W#dEH`KtD7f1E8b6vt95{~<;9zX(`#I=3|Xn=ueh2cvh3c$!i0Xv z<7_2|J^FboZ;%12wRWC$=!CyxPuS2Ww_r?G=jw{3oyBghzwb=VDgFL_l^mbid6_`! zgMCrww(pcl{fRc9row7|~EMt#KCaY!$Ea!I0^PTJg+T4fC+yw0IIn916;DtI9Lr+REb zd&7HLN#<#-?*?&h;rwwzxGVHlT=0JJ9yuq*BN(8gf^pf_(b+v4~k@T6-iM z#jfdYaDPi1gh)t=dN``VA=>wY;W zIBXk!BiiIugsxm1ms$F4Ag_Dn1y#(JP1M_`iaK*Wsf%xzb=0{}NeFGe@H!HR=UFtE z#P6*=ec0I0YI3`F?^IY(s>idg^<-BmzZom@_t@{4qfx|pvnY~rLBS@l?wwVHROlO@-HUpYq@K#V4MBYT zS2&BzD{*#`?H{|B<1APgc?k!IC`!C~s-q`G&}Gi`YPsc02G)*t?L-z|o%@sS>0LO2 zdJ3)HEOE0WhcT#@^|TmQWEUo{(>_?bU{t2HO)EwQg8$dT;P_qqgwJjd;;!~Iv3 z`Wt3fjTzR$SM>0!T8@M{(5kf)+neITY8+DzZa?L{=);r#Fu>`e!VSHly^W!P?XI0m z&LpWNyvfi@y(z5`8yvgPZdz?|TheXaUN)lh9%-@4PJD&gz}oHV?(jXSW>52y0h*YP z%FgB-4jHR`k#PD*rEz=z21De_zM-2w>xqhKK5CKvOhzweBg?aVrsfWQ8j+TW&G2v6 zxBIU8(=vV9BK_H(^y^g*{{*U;aEIYevl^<1H&XnQ3a|b0dgHe(tB2n!rIvHI3%NDO zcGElZeaxy=HB2ezSB2AwYjfh|p6j$qYcCGUaYr6M({93+FNi5#Ix(HNw&>}b=Cpl5 z#pi~_!uGKmP7p)O&_%w+p6A9@xuX8JJi4-w^znUf0>ElpfLsbP^sL9KxH0x)$0(0v z#M`jcut!|gwrApPCZAy^_dHLqm7Pj*ImI=8T>6Azs_++V?6Nza8+)~$b|Ma?l*^MP zd0;rMjpCaiG4wN7i^nzYPqQ{Rbg9g95m~PM7aJ_>@fhYRd7LWFn-ItC!%{^zE7e1I z8+M}Q3kSm+u>-R_*J?@}BU@w`4Fu`(rBg&|JaGCK&f}@=!oW~VQx`n=(mwgY#{0(D z?heg&;ZOe$J=Z18X_08NV<%c(XIohMn&cCCDjz_li#d&XV>$61&5e z6#Cmr>^m%A^<~L#vr>BWe(y76Iz%J}*{?-i+wq$Sh1dH6e85)=1O)!}EdLl5AiM+K zZLIi`QHGk1Rm`pIg4Lh|Ak5E*R% zkcYsZEJ^n{E;S8~&|JQYu6OxLzX>UXABd$m&ZT;1wGH9@cw@ty)_`%k-}y(={yY5} z3_flF_(1=#e)ZSUj8K90w@cYZ8soc$nxKwZ;~IpZSj7} zV+&^*+JmyBLmWepz9QIq=7%0AgQM8LSDrz6Ssk&1hDJtSqbq*dhgiP$b;v(!GN{C2 z4yMWA1kZlll01RXS;&E_8{G6$M+5AaKx#zTR|}wZ5cV}8Zex72=^twF%cv(PogeOC z`~fw~D*S{K8x8%pXHP0HlJ9`zA__yJhggG@ZL9U(>e$-gY~JP?;QIWQLqoAUViKOZ z4dIfVTXPxFcP_I`D|L~G-9&+XqkKY@8Efa}hkfNiXrN!2POyt&ecQrf?PHKX3dB<@ zZy3jAZoUV=lDvljeQ?dZrqfWOR+A7_>{VQ{S<+MZAg|_LwQ7v>yt)5oX@}J8#!Q;p zP@z#6^di(Q!0|M)={5eL2>3r{7(o}b2U?j3@J+UemfQw>uQ6f)V9WGQ@tipUgrv78 z9N+iWM(XLW5mV7*TP3@qDV|ppA+mtX=iu3x~XH$~};=jS(sP9*EL|W1i~oiU-ULSslqJ z@wD-l4@^PWbZ;GU$hSlc)prWUfP=Nq$t^i4@~%MR8?e`Hh^N8hjDcFpQN(z?8_4r& z1hfG|EDtT>lqjwt77*X&#EF%BO;;5_8^{ga%J?Hh-|K5D#>OIIB(-FI)#~}JZ(V*y zAqUfAyns#eDs}=Xl>-jhzV;1qFcOrFaXbjKWe?>Q_K+jWn_Zpc5k-21ihX8}a6*@V z$Z73Fr;6K(pq{s#7s<&WJCp(oLnmEy_iEXj21LQ8~iaiD51E5 zV>d#w^VdtgPJs;SEcsWTv3Dqw0hiye31V)#6Fe=hoSUF=eb`XzdWL8hhAv;mTG_Av zn?a*Q&Guc0(|GCxq*{9Yf3e%d<|8(3egEw%#~47ot37Ff)>{~=^+$9?4OCFbVLjZzf?J=48&CmtH=|NLXPy^Vx2{zx-*|ER^~nb-3zy_#0hG+!)rZMIl!ce_6k`aAd_SoR^n!{A%D8s4g4dKzS&wIZS?J)Q zy1;7%5cL?Wve0j|DPlU9)R5D7IaZ6=^cA&86&6?h@DpFY-=P87Dz%r3jl5fog19Us zcw-MQSgr;Y!!aq1I9MyqYW#Sle`)Z2M2oI}1~L0;n@}9$D9~(k-|O1nQX}y`ouzY@ zK%b9Nn8Xk1P^6Y{oF@n+xo9A%k~=<3j)sCUH$XbbCUAH5I5}=~JwWSdnXm zh$_YI3&aZ?J%b3ow>0mMY>6yWP`3UbA?UR%aBK;|22SUw7(=q^+#28070L+MlIKSO zSL`4h+JZP)R9JDIzmi#WKobATla9WLr{K8@_hi+!+ni71>vH+D;$yKAlXBqo!-yY) zf=cma$7#Kr)s~(Jrr#p>(e^2@(D63&Vd8s4;i`!%ImM_o+_cLhZNl6DATTG7%}n~0 z56MBw93_j5gFUE3d>VcT8=kK*ntOLnhvdkq&s-nl^(#r9HvbSwFdZ+?$4&YYP1sw# z4yIyZ5O>KKP5RF4;sQWPA8Ijge*fn6X$GP6yU=yyZc%i_x9_oWq)(W6XD`V7Ld}H4 zlYAY>ut7Ybi9YZ0{y$q|eGzP$n5Xa^A{PG0853B#6k*ysfhbq4cSohLc(%#=3&n$0 z{WQT7YRCDg4IY;9)Sv8UQ&|FMi#~vpT1L6%jj?`*z@oRh@OX?(%9#EwwrFGf1ADPNPnt}9u)^J}A7`qKJ? zmeZrKT(;pQ^3lg?nK&$_IPA~G9mp=-J3e!E`-_wYP1+|Zjl%|K{D?MNkzRj1IPE>o zL&Fcg8+ec=N6%dUiz-HE^kx3^i4|;2SP7B+GtNZ1@4QvGkX4b;>V zpAE0}l@ed=cJfA*U);t0$f{iF5n5dk^G5d!hKC=i2weKTVackYw)duVf5m2J%CupB zM^ZuWGv!emA5c-v4lKhI2$GqT)X1VTmePtNu;5Ws-S2Dnt_d^AXb;ttB==I_PuO3! zSG?Fh*WrJ!ZN0q2zt?jt`2BX9Lkk=BsIIwSf9T%4P>4gA(%=e#GqQ$8EPqeB=J4Qp zso0MEw`8IFpb-CyK9_EcouK1A`!@Vy}D1 zwOts_bAuWG`6=gVNrkWW()ZanUb6UD+5&HyW^O_~DaM%5xlsjlzxi{UP}Wwp-Ci|t z;h1K!c^#btDIWeicf9iH@#Ca7h(uH~K?{?o_EVVG`<7oeh%|}ILhK9o9G3g}i{fMn ze|R}$9JNbE{z~DM$Cg*`>MPmQ2eTcJ{bx+Y%!67#4MR<&!;P>4!_l+mSB3=3JVeIP z^&Ty81to_VkkI5Ow=)Sx{kMBliOid6?6{sdu^g($6!c4~3w4NVaDTb%2K)}+lHZuq zmNvPF_Th8z9=Kc)X?st=aj{3EzGbiFr=e; z(VI!$62ao*&p#sG{FtpCvDFa;oDW11|5ynoeN`ae`OkRH8(er6Tub6B13y4p&+SRs z<&bb5)k6@7^U?d{!c#EB^wBKM7`CHk$$&Jp{z^?ip{2z{$Yn_vXZsu!Y`k8YEZm5%-N_tqKfl^-?6_FGwdqI*^n;n*zt`ibhUxb2;RwgS7la{XK?tR5 z5V%iHDVecSSr0XMTX6VAp8qiO7D(YDND8DJguqt#@oF=47eY5+HB>4(#!vpuY#(fu zv-3iJy5HTp&rtO{Ito!yRwb|^CfA>}h!GI7h2$iHH~1ynz+_-14vwUI$hZRb)BL?% z;8~6B2&bpP$CZO*4W}WYFM)(-FV!Fd8lsiHKAVtmboKp!C1;;jvUtT@`u3*BbK?Q~ zJEd~I<`!-s(Zs`JRyHsNZUu4B^o;gzYtCJ<>kW>BY3f6#_0%#9wb66UcL<3xaMZEM zz7GE?oO64zq91LDgmF2_3{%$e2ZmdeGZtIGvcUFA# zgHOL9ew{V_S@YY_OEE5LOmUk;tq-#O6Wu%Qf|o!6BRP{EZ3$7uNlw@9uij8VLaf7k2s&eX<)ln`YAR)rLemeCNVjC}vq0JjNrM{kPC^J# z1j!02PRCwpMJx!BW|TjX^sEZ&pQIeE_uOen=KnE4^dgBxIvdJOB;gnZ&fGf%Om}y# z)J}x5w}KzsVt)PuMDE|PRVz!P7^AG+I%JFKW|k3XIsZ~@`(rsa7M`bawn?Hp=-Y;j zTGga5AB!ooP`QRZNOHa-4G7jU9mG=+$4B5OopRBlZjEBSW@UZGpDX33Wok(AHCJ2U zo@Q=59%HB7LUP!}$=8Y`N8rgNY|F3QfY(ce0W2upvfD23UgndWLRAY75;J{%@)u>* zdVz()iZt;l;r3%WMm{H)q=v-#p3nDnxaT=5=&)`dYQBR+Dc6FH>G*R<&%nAe9L_x? zo-em?0UN`@7+gwqP|G|vNB_7d+Z%EQ<$}FjOKb|DiGoM6%?5j|QWECe>^fU(8j9l& z$^Rh4c`Z@b5tBa{0`S#4PXZuP!%N-(>gN-boZ;%{?KY}^1>a04w*K?KIjGQJi;ECQ z7lq};{TyoQNg84nH_o_^uoI!e4Jl_j$OE3dhbP(4;Gyf;uj9w+%95d~!4$52&ctWo z8=sCaasj156syC{ewH4za5I!ZrwFa`3@qO;pmMv&<>wE+UgA=xeHr32L)<3 zddV!-0WM*bQWVp{oxHBT;rK7$WHtMJw)WGQl%pvaC~{gJRrQbVX6}OR^q$|Z25$OM zZ}`%|Z!K}mr-MBZr;dA{be@~(#L#L29P-=O6?-!xB6+1qK*#&=1#nm2Id%zWfsyxB z7v%{)HSzHmBIQ|egPFyWlcoiCPZ^%n;(Qb@;!Qdx6%i@jA!DzgjWtlS)A`h+su8EC zb(I&7_CeV<2eo|qSvqDlPaLt{B^nzZI2E-WgUci!lm+lZc77NpyOR=bc%3NMC$*kM zK#9a75&BNc*&hs(PgdONd~WQaYX#%~#l3idoX^3$lHAU-tT;DfqjvvyZGLFZSU5ke zW%KcaK`t8C#~n2kc=8tDed<>a!7uX3^9)s7zUn9zJ9najRGKL;L0EZ1R{e zOyk4Z@Or4ruFsnJF{!LA?;Jw#GJpMQAAde?R#x-Yq}YU;6?>fbvQf7m8QGbKEm6wr zk89SOw6MH054*vBiT2x@;ND9cV1$t4!Lx~8@rh{qqzskqs5e^XPOqH%7?dOMbi}tE z055DhbgUV(A`a`Lebp5teZmwxsN9HdQ9gKlTHOT^b)U>Tp~zPRE$kygfC2|G3Cm9 zxFobXCh4b_ycqvCHG4|8#|CFGu1?+!=dSa{E-PHClIaYmA~s#yU}ZRnMx)pW4;h_f z?XqbaM(({tfHPZvE`3}%t~>Fn-qD|Y%(smzLvMy`uR4vB>N0eE50f=g{v>4zm@@M* zXoT+Ibc=jY0&qv^0qOQwSDBhU>Gfl5((C}gxM{qIEb1VIV9L?JcSLTxgM8EsjLWJM zbhfk+`8CJW&l1nw?b#$?Z&}l%pT}+sGTI~2^IC@tMNJE7A>2d))9U8aCp|QbAwq}q zIVZ2?M4JdTW({~WU-pe~fBZbdXKJJ|I&@`bl+2w!WhL~B{Lq((kAmrFoyPZ4osv;l z%$oX6Dl>k7LtukMbLMX%wnvEX{4WAJwVvI#`E{mBgnuB#QUnlUrQ(b$N)v&o39(%y zKdnVSsrfjZEL6N9Zrs+Cf`QgHJX4^`0)?i%N~z+^;JgtoO!)OX@l6ZjwE~yXUEGH( zmY9$3*oX<>;K>+;aGsLQ@uoMP6CbsXvuFmh&>lQ<$;dr97O!zFruKn(a|S_K3NuCP zy(JPIf7tGYtJCc#JK+~Qd40eW{R6JB(9t5CWZdhA`1;|%gZT`*efx2AvaoQVZ?7Ay zZTy^I>DsrD1MdH1J=&?r;WFPN z<)WbF%&L;4Fw~UCTJ-#TIdmXy@Ygpo8g?2hDbp0>6b^V%H@kqv!O(Z$P*dD?irHe$ zoGfnw(>`B$c!psRt##K%@9Zt-DSQ6K) zcKXqJyG^byyV%@vc$Hx~D>ak1G4V$GJXK3^qI{&B#@O=WvgzZVdYrR%SuH8KIQ%cE zxiK!Y1^U@jU4MmEteNp*)|uR^N<5rAnPkt-kjpESaX?ebo)6Dqzve^ zvben>aZ4PZ61OHfxXbL#r0kF~*Ve=RWb{c-Yo{~wIavS$q3Jy7ec8W1Tk7dP zwrYXawtY1lA-VSn_kf|xyvk8XHm_q%?tZ*DVeo&=zHRC_auedUEsZq`j9A~}FddoY z29KZ?iU4Az&$-q26W{EFI0#Zf>z3+O6ET`Nhe?LIX5Su>xW%1EdOXAsUBGgCA@ljHGS^8tHq@vfH2EO&gNfW3&l2V1fSB+9 zRVEqdM&i~#&lVl-@d9JI+w$};0oT&TLV(VvRG%Inn4EzB%T{|%Pf6-fQ~&Qe2B>$2 z5`caaciUg15bkil?J*~Z^p+GdVhCyM_`?&Cfyb%OojaHD^sb0lp6>n%eBguyf*{%x zz7#mImSO(+v3$TX2yBsKSOLaH|2WedCy;d3XmamgKa(E}Ws6T2p3s<+6C1FN2PNl! zWS$jq5MG?#O&jbpIe zGJoaN5LHoX52>pYPfi;ypyCEQ>t4gui@zBZUN*Nq)=OYStTTtM?Z@M>ay_bnp-~_gXI{o8V{=W)r;}8Zs0g(I79|;ZM zN*i3T(LWLzkPbIn!HGTgzY2d^;QU?#JMmzr#R-;VhH#YzcY2tc&~BUcYeF@TMBNu{e(5bl6B_&ei}mln`KPof!W>bgDx&V) zp<%Miu(1vm0RbL)iO@8rl2g1eU-<{^q#L zNI8udbctL_mtuRCIvzf>h`0la#YP&^F`SyM* z)GOZ*L8j>ulqEasMsLO(Wpk;U!snuvL8Fn&{R$1@X?5CMV z++d_nJkrY*3Sj>tlDM)i!ijk&?~7oIyoFHL~EU0Ta8(lM3rzAJ7IUW(pKv z6F4@N*BN#Z*FVy-@(n50g1DoF7HjIt_E#idfLN1})aS{UlGlgJ5djn_m3hniG6j&e z6EOdlcL<0sl{*Wft2MIWd2tzBhkuc0-}?BBPoII#Gk`rFpFVv56D9?ke>eHzeWb|; z2_;fX@i3n-x1I#=e|E~rwBP?fVUrWb3{ccc3k8~%d-dkm5sjeD1b-OI*k-PL6*Rvc z!}ak1jVhjqhi~u>c!{_Q#2nrq6ue0-RWFV4hott8X}Hb#x;VB~{wKl>imSXSNVkL>4Wz!Cre`C_)GupL0JlgQ(QG?2VS^>spK znKVz%Qx}S!Ec1RDF3j`lmrz6RtDhBLMADbv8N>_n$#$Nee|Pd#8G>>blBhXCVQ~7d zd71GuC-}~L@C2f%Ys9ce)5A06utAV#DcwQ(h5P|7wZfG$Rgpsp(1;aXB9*xi&hnjS zV&8Eq83`$S2!dZWhifCF^8IMIUo}0IF88 zIE`q5`g3qo=iEd&Gg%3hfgtXnSaop*#NqejfiwfkaFTA#cCTytCx|^8!P!Gwv$cZh z4^i*IC{24O<~Gu(Qn`_aZ9@TcrTT^&I(6 zNI0>Hfh2%K3C?q@oTGSXn3@@9_EvTMbAu;wQ9=fzM<0U44HC7(lTa29gXWg6_MymhLp}67gp+57`NwG=~btp;=U}yht z4ZWvGKnD_k$|h`@K8o;eI?x_-8oxks@F0{Nwz=_K5^;_!!BSduibmdk^aF0JF~VG( z{D?j-y2e{D0o-CwX~6$78eWe=J8ToSZa&$l6Y)SyiBlPq7?)t>}9%)UO|AijMz> zm%XA5PMzvA-Q%88Nr@%)Vh^6(Zo7mMY@}S#6K!D}4iXBpSE4wgaY%y3mfE#$O!>;f zCp+&6W}0AQ{3@Oq{V)z)Kb29pHU&*|+(XA(#ZQlXe%6vW6-XnV#YgAbaPhRV4bn4Z zmS=y|(eaHB$E=vfr5+=~NuoOHNQ99dGwF(cDwE4gj!8NOw!ml%OWsc{&fkLpl_}$m zmCg5rSFW$8VXqxQ$^T8D*T*7ku5$-#Y#KZIh*#zIw% z%Q5qa_-^03Gz5u?5J{^Qz3$5I(!o|$pKeL;01rx2c-K_L1jox(aiLb_E8^S%2E9US zI63T1*l~rpPLoM#SlHvA-+1xE`9bqxULOxI9Lya7bt3}$>3%SS<6s%=2ctrjYkwLa zL#74dUg28`kvNI<&LoRH(6$l?9Wna+1`@%l9QaQi26L2pBQk3>+YQ00>Y$gkH+AmZ zU$Zoo-N?9=`dK{4ssz3wI=$CZy`T7Z{&C`Up@mMt;4>q$Y_HUvz+Dpjs8dckjmWQ{sH2Lz$kbP+#mPBcb}387xMrRxZknzYEe*%QnM7Pvp^9} zggEgq>kR+(m0khMz|lg(sZvso{~KBZL-W9zNq6j=kQ3Sh~xYdOoSw51QhK3*U~*CPV|2r zz&^&mA?>8Ge|^v$$pjznodmNG4~$dKj4Wl?~uDu{JMcfe?Xz?(7M572BPAdr0S?LoIO@Cb+_i z%l>2dnSG?A5Q@WV_Bwvwh7TqoQN4b7>^c7w-B)lZT?LLfYA?DKN;Ityu6M5?qZ6DC zmIz^3p@Iu)iTBRv*n&32yeilkRE#4pAQ`B89}iOXoo3HR994)}iu3{4P?szfF;?=n zScLSKcAacoZBg4R^ov%8;~`0GV@ycj!eFzd+7^96taq8i(B_jB`3=|}Y{pFgGtKYF zgJ!?k4BOwwc{G5vOMkHEbfofIm|j zre*9wqV;fA3Xs>+Fd!25vpC%Q4D8Z#;Rm~y!yFB}p36<-tjiqq3SuQf>^qNWFFe8I zpx;4VVDp)J;3s33a3O`I1%n2EPSGj_Hdge z5Teo)9-ua)n>6HzbI&lxa&z)ZE2;tpnw+{`k#zJqjAaB*^EB9+6a~_kjaHO`+z>0{ z*6vo9zc}LMQ5iG8J1cyJv)>NVWx14zrK5yf&S1m(V~|1`B9KWpCt(fA^+d`IWr>1V z)^CCoZ09gm?(h9JCP!)+McPC^=#e72{YdeptKV%b$C1cXmuq_>t~u|)Pk6!%mnWXp zUlVvMDIG3M^aO`l|LKzrOOsZ@V`D?Fe5PAd4`evw#6Plc7)2uqC9i{$8lJ{wRyD+W z_J=ovwlNvVzRdwm9>r0O;qVN+p*~7$l-`ddPpT;~)rids$vOV9IXMCIdnclG_)jYC z*a=J-!UU@rb1&Bzq{tDCNyE6QNQYq`?xI5J8Va(>gF)X&QTb3|VW__L8zQ0>zlUd6 z4TYLN_#uhFJE~;bcI+>oJM67B(nbTL(Gl29qWJGO$ z#Yiu9lp?hcdyez~Y;PBC(FZkF)MkH{ToqL+D?rXTPt-G!$4H*H6&~Yp0aF5 zQ|O)}j>zugZqI@4#qhkewa3WrozK=0^CW-ZLI=t-{rj6N%7FQw-|^s9G-i*8yjN_^ zMdZh4Y-Q_hsylpGO>*X9^>D8OZ`qw>WT+U5Mgoz5U3`hY>dN0>TL_~tSktv!XHC*9 z=-mi)l~vtlo;fVlRo!5hV~y>}-?rp-2wOAr4L?;AkeMGcB{krj^9xBh?UI* zAZ<@E7TRkXkE>XlR^9xW-oVmba$4S}`PSx!wYUE1AFs?pyCPU7nv;0`y7knr{~~4e zadQf?sp%>!{iB|r;(PS&+Dhg`m(P_+eT6kECjtAicXQ@qK3=1sFt~AN`BEuV9z+PI zWB3E{TVKd*w!_ktbsdWWFzH<#z_@Q2RYH^pG| z+PAWKCHr!2yb*$8JFb+&%63y$y4{W}qNdl+lSy^G^tbkKhZ`SjPlw|6d4HzCb$O{^ zbvvu9CE)B$=k2-J$bTYCx_6Ls?Dj>I+fwV z5sgR}cu8h|wI@Yc6iPrMg(AYXS=2SursMY^)hV$C-G}3vF5MEud`lNq#t_%eryrME z`6#kk3~WJT%$cPZN=Mw>np|X!>{bjEhixhHF}ttyLS=9=SOe_|wN% zhyiRN3HVplaUQ3(*DQ#+;(W3%@xh48{e=Zh7!Gf*mfGJQ_o-t^0q zP)zz^e#e?0+`ebA6ZTUUE$gMO{3c}{FZJ<`6gr!xOjAXVakuZb50(f-mxlfRQuf1~ za8CIeUVBE~Sw`cz7y4erXK-mPy@cENZci2NakQoGdXS%_QRC&!v9o;CHtoJPR!qtr zPeGH|!XDjaZHB7o|4()Jcv^|twOg24$@Z!kF8XwGyO17@Td*wL!wWik26YJ?PnTR} zws5Jt(Voe#(%11q!@hq>OkOZ zWTMQKClEGd2s;9OpTbx)m=1VGqV4Ve5qB?A)BLr)qF!5RxNtD_WQ<#Xc!0C=j)0*0evWrO-u5C< z$Z=4=ao1Fi_VUt(Eq&O-+_u@7 zH09A1rh2@Q#8mNkcD=CZTyx(wFe^cO@^|T%n6*ag^pM1M>EMzEd}(KdCBr#d6J6S> zkl8y+X)kO#6ztX&qQ^g8vMrN(n;6qEH_7?&p)G#R?@4VYDvFVPM=Z5uN1G6b%SzqG zDRpMqebi+*9zmkxQbCQvLjzVN6tD-nYWhNyfD@n+%C{)%F zk+L!p$tZi4nO&5!S9Y?65XU&*$J2H7{(L{5&-eZL{PDZ}{Zx@z8o&f-hQ&!-iCKR&W+RO>nE zlaZJIyS#K#qiY=XJ%wxj<)e|-f>B^zp>kW0PQX+t`~IdJ)w}e>ij5a9adTOuCS0~Q z=U^W3G{h>|^YW&#CK8^JBE(-hyq7)qT>jU49n;a1X1{do=sq#IE$Auu?sSU?811cj ze3h;E755fy+QIGGj)46ey_VnD)+nD8?Fn6ZeQak;l{TG@jN7e8@)C0*v%JQKd?q@A zoR^q>a*VzRId1xiV?a!yO~F%3RzkG7#rsF4{Av#+0YmXwt?QZf52~>DcO4m8{CUSK zBq~4ePJ6i$`{qZ=om$yxF>c)-dlv{5q|o>E`9b4E-pr{fFXxKsSjU#RT@*gQO!{sX z=~monH<++Z!0udr<{j7A#kd$AddE*@597h#2eNrw+O0JUhU_gwi^onl4#sBJt8AL^ zVF4o|oi*A0Gg46P+ZJ;e9P!zQbKTciJV$xqq0$~?&~c& zkA~ZK3!I!xevRl_+v{7;b}=^U%PhBQS$*ugJ%8gdk2dYd0|nQNmRr z3+RhSq^x+$fl1aewuZ|Qrj>hH3Tm9jCWS2%O_#N&9V|z=`93VDp8L{y@rTkLLf?C9>0oD63L@DEnBO=z;EMJ#w*6$66HI>FCLnP-Iyd}7 znSPT1nUJvq78&z7Ybj%{blU!|OlPU+s~K@G>%LFW+Ea6J<9yCAmB*wq>-u+h7UnX* zH&We6KI{H%arL{()S8V>0*`Vr&@m5XVoi*W9Q|M*Jv_U3ySDF^LtQC@!(R@Q?!C9y z2g?59_xsYTRD2=A3M=JkSmn^ zRrwc<_}NSDTWd7=TZ0IneOfbE?sD?w^Dn&JT_?Cld${BF0sRNX0{ssz`WWoAFamhy zk=EWIf^jIqI7TG`&+;L9?8#xVW&hG`w8P>~;78Dy+T0ary0TjkF;{Dd6}!b#(Dzc- zc_i2GUz|=gySjvQ+NW(YjMAEUFqwS&T8#s8rBGi&n1p|GfUyyHwTV^6qU4kN2cm->w6}-iMj7jz@jPJ!shb zDtEcij56==Hku#8kg${|n#xOjtX@(02F&p?G@``_>1o1IKEzzewful0DdWF3zuNbv zux>*&{}5=HdKr(HB>_}LLY)k zvg|!Mwpdn%kSgXk4|f-Qzk?{}tR90a#^4_P5yzWiT2C|O% zlofe)a4VDS$d?!9Hb7UjJbg?v=qVTW8}Arojjt(DAk=lA~P(@rUO$fIkn%{^bA z>+lv_>tW`_6&MC%c!#k7ae!d#4%N>SbOTfa%I`5d?icSc<&CaVK54d(f|BOx&$U+r zGr#7)_6M7#OlDPVVVRGZXzRxu%YVICG1Z)bkR=pXhw2>sAtx5qIc$NL79wJU|N7f* zh*ABMYgY45u9^4WeGf`(R)}@lEUjmrA_MNyISYqH7&*-Pe?E1Fl%Tcu4&@E2nVVaH zt1`~~?em7C4nMCtS~a}sHp{ugMYj>F0x(-KYTdDdHhDK}Jqn&eMk6=!Zh<%*6az}~ zzFt{;oQ1m_DO@3nNDo0gq!byYKMX4!c=^PIa1>>*)poq|hF{Cpw+>7>CdxN6tI5AB z$>m#HeS=#?Uf!tw>1a}tXHQ_xBJ?wKP6TNytR>IgdcK4{{zcHXMj?5!KXJX3)k5jm zRp@5dcg!&?s~lSfMJfa5I637-IRtAjvk3+qvH|I%2D4napQHI85|B1^G)Gi=PW8>} zGuu?4rV$YTqzfA=p9rsWXpX2ynFJ`b!WnX0A`!lhfH5ls0Kr;LpbseYMgfW(b~&du z$zUVUeGV*79*A(G_(Eipwmg9Zi2xCY{m?X(-8$4m(uE8BL6jBQ-0+P+1d9(P`{y!Z zN7wJ>ksb6kbji6aI=GTbU`68pK z;;X_WihO3yFfVppqf0mk0fc5lO2EED7;JwnnpL8lGH>DQHO?{Nmln3mk{{8Bk z$h5YQ7%0D}^Bmr3-Z$V6kH(`I(a2wrTAO7POMM%F+4j72MkgN~CkU&JLS&$-k05K6 z-#uptoZoI#igW-4uplxcGM`Hx?q)fmbQ_m+IOjdZPAh9$d?W0aGML1dAxXNtgYavY z^}f7F*9%a|^%EKk64qh%HQg$MGqY{DV5@I$g6Wmm=)Qa&V8xo-S#plN(q~h=ZNpYV z@2!)3Hwuo}85T%V9=v7VgJK))0D3>_OsJ*Ntx{+{VVxkzd**xP)^z3deKZRv!y6Ht z_eExcKOD24NP+Hajfh9`dk6qQ=3xLS^oh8yGk_2czRgiF$5^3?9d=En2~?yob$h7zm_%#)zV-D)!|nDsBwW6WWgsOUOQMfIEcgOFd-W z6G|c;ep;=t+cJPxifV9hTfY>^ic|JvHqP-FM--MHhQei3An8|9ed&IP+r#?62 zyY8V&Rjpy+@5K7bqg!A#T0`^9~4sQ z6YdfB+Ta^eR&pqAFL6seepf5}xgt9yL)bxICgMps;H9bJ&V}(O865sz0^g>Ch_z~U zQ1Sj?>KeSPN_a=a>rz*m0`spANk^L~GTb&!Ho?r&uob$DOc{Xa7U~w9MhUPg`&tB0 zrc|bO{{8!^WLNGGDkHg6k$ru_NzwkUV213pq*2!y(7$L>NULCfB3fhD{=PpBhmgMm z-BUJn@z<5R z4RXS(r$1~Q4 ze4+QLxeX!#gceFNaP1@=n8Mjp1O^06p21&g*FQk(RL^;Cc^!b+%Uuq-jA0UZO^+@{ z_>Eq0+g;1O)aKC%_l=m#OxocQo;BTm;$U*(sx}Z9eIU+xIW5`Sm3p+I?8AQ3CGR1e zYv|8u#*nM3@erNMikG>2KZS?l)X!K~)`UYc3Vmn8=dHLO5Pzw77}V4Zb{@~YusaO; z>k|E5oSeUAaIZO5(@|PQartt0EAHy;(p}?5-mmCep95s>QFDvaY8h_+S+)@cVhhB1 z9W9GT&g?^Sg9^HGv$n?uin@P8!lOPm)ssu@86h*L3GQSUStBE&esKyvA*{W6BLoO$UVYH|?y)^uf8DQOaW@Un+jh4Szl zJMqC{6oD9QOCCHTLaz~aBofv4{WFye_XW#i>o^uy^uxqga52OXj2rG;bqsIkAQ0Jd zjYxvrMSE>`pVin|GCNK>&adxIA1;=sKdTd+{kfc~-GEV^u;tB}=`%i|j0&^2Mc7 zU!#}OqeRe2-?jBF%6%6PcRDw7GQ5wHQWLOSZ_X)Cuz^+!wz2*s&k+N*s4oobt-;xy z+w8-|N|OGj{3EemUrY+uTCLN>UiiWmau>+7?@SG&q3~iI0$F!_3i?$S_LV-vq3!1< z8%xt?$T2fDemC>!UZv=pt9g1|3I|#1#p)^dv-&3Ha6ddMr6e;yRt2WzH{Q1<#LG<| zL+P8iw`0Z;ID+C{nD`Nw_fTl-LDv2=hE~Jv_s>iM3dr;S$=omW=aFM?-h-+B5aws4 z9QC0(&s;VS=n!}@8Va9^XI{<>4N|(nb#DG0#?|d8f6l*Wv4ww1*0%4~FyV?X{=2)u zYDN!?KmC=*f_1m&8puOm`I&WX91T0(zhA6A`2xZ#)6l4tF9*D#Ty;>kems9E0k!jP z4O%m9PXEk(puL_R>!GFju4gRZF8dsenZl?a;Pct%@D$K5?9tJ9e%$?ahPax2~rrPQpkK|AdB zZCAd&q+G;KU$A&BbU>lwPUz%duhX1%?+db_7g!ur$Bs(HEM$BT(5}&0OIJ9N$eO1y$t! z448)}Tog(+{f1&;73n|1P<3HD93GjYkg&xVZq;KbDFEql>~DGAf(D~E4RH|Sj>h7` z%QF40dgT}5T;Nbg9nwp2t=?^2gFN<~@s2bJ#}$!Ss!)O5(bHy+Js{-c;oGxSC-&?d zFg>K$^-m~_KSXhBl~gRP&$!%rb=L4^`KkCLX2=kz#8wI?k|I0epWGs4{ZKWZ8=ZaJ zo0a-_m!A#}{BS(qkyM|YtbR6}0p1sXNEvv&9qe6&Bvg&aOT+aQOb8Y7WcrXp4G}N< z2W~fAlq=iB47^z; z*`!y(d3fX_njY+x4tPMI!juzdK60U@ywtG6@#x|ffuu8{8VD9!-r8@>^^%R!vu*yoJu{*`Mu#6%~#J8=D0*YhPnb~){i^sAvdWmm` z$jvTZ`T3r$1vb>WM=~!j>LskKgl&oforwbCFEr?zPHcPKkU+&=X10f8bI5qMP58%Z zzthwUV|pt~M!h|29!FW|9-x++sJ!tNcQy8BLxt-*Y3tIYeopslV~ zDDoD~DQ}K6w&=+?+PZj&P>f`9`kEL>M_KhDXc;^Esc5CAKqgiJLNT`GF6&ErYZ#ASDX!%% zQO%Q&18#Z&w@uwRdv{#Qyk0JgLz<1Y`FQDs>CBYjNWruWN%)w{(z04ZTIp!FBl3zh z5ZxO|zzv*&1<_6Zp53pGfQwwL3(gWgZYy7GWW!t*`DYJrRpGX!0Db^<&Gb9$t)Np6uzkM3&Ph4Te`; zNU{XZME<1i;`VcAJ0?%WIj^G;bSl{Cirh43*}*8|JX#@6!Eu*WS(UiMNz~M;@^OOI z!LETKanfVIR1;n%8*_pxbyC}nJ>apY7Ken-Q}y>aXW0b4Yb622U*cTG@)JgHoS{mr z8RQhTUK`RRLFdH2cw7~Gm2eEs45bGwYo zTiRDfz6lAWc6+%OEf&!1Ump9*MQEA6IkQNBWi3j%FX>f0p{JY2sdyVe`YTh$zAV4t{wWD@q4YZ!;`>DrURq#vMvll^+D zKp5*cuOzD{<91**hocX9Lpu^3R_2&>y9o&bQfA^Erzt zZ?k_nQpGZTIWuwf0d;+ApsHz?Q?I3L+ljL8rwr~K+d}0s^6f}c z0&AAaltY{X!yENIA^RFa`5GquzIrp{k~YBXz~yKrH*QbM5b##ch>}K&pW3kCzVpL0 zTr0*2X;$w`qQKvdL`jiTQunsRKBKVTA2n6qKRp;4rT4hw^7)W0{-M01(lEWkozqP} zGs~vV4B6=ZHPFcDPYOD}T?P2H>RC7y)=#G@gj3U?OSK>3EF0fn9at6KDR2u6Xs?UJ zt^8`JzrA1s9I`Hz0hS5%qG%oKAGLk*NS^_O<*LV z1lvjAM7iLFo^k_}6giP;de({%EQIHJ;!&GcWN!8)w3t|yJ%>l@1Qna|V8PAN)ZfJNx$tom8+wt>YWEQ5(R zNk+n_J5h8>?9u6`{2i_U&y5LnzD*5s#w$o4L-8*=@w6Ogz7(c0s)OH|~NFiRUM~f5qOR!Zy48^#F_0=RnD5%eUR%t#k*> zQZwm(?;zP!5=RU#{Ju=onwe9e`Yq+&)a9ml-pJqkarQ6Y;SXKzC4P52Dw>Z}pqJY4 zf&w)p4<1Y+HVzSPKmDA}8djA)`u+Cw4Uc*qJgs%;kb_OUPd~;N|HIUA5}dE=96tadVLYdCngq zPHzpQobCEKYuey+ZZn~OTijBqY42FJS<9rAGPwuVYz^Dtnd(Om%ZYoCP{M6h_Gd~N z=HZ4Zm(1v@Oa*zcwM&oqlVrIG`^GQz`Q^Xy>}40LWx~_u z2JybQf#)WCaol?K=G)vZPQfr{$GELTMk%9ZwRoxaQzr_=uqw}?`x1Abx25yBjXiHA zOXBytvsuBNr8*1^7_OMV96K*rcgWl3NBV14GL3!xDnK+RJ(kR89Zsa*f+O8qAkrZG za$5OHU|#ygSW?HH9ZGi!D%5j^R*Ne-`+gQ2)b-_}GY<4v{LRs#O=dlsoK2j`c~?ZE zn02s=SP<7o*ym;~yGabCLDQ>BvSL|g4?tTBO|Vn4Rdt6@5uBxtnPhUi!B{xLSKwG+f-Ie~X$Wjn=^ zCOCZMJTJ;GI%U4)?x7}yrxgy|(Pl$wcd~!S{f++9GEVb&65SvskK-j}U&Yz#Q4IYYKAM4-a4y0@1j&KOI}DWD~kTDLZocKgUoVuttbz63zL+>*b& zl{*t>WJCNk-x;Hx$jNz(;aF;WQWPh*yK@;0K2BMSS0LhPd-bwhlozYHvIh2}+2$@X z`;2?(>@BM&6*AxBBpBW-^%`Ou=WZ?I+tU;S%H{UeVkxkx9kKQdCy1wJtULmxIRIJOsNOJgBl z_S{vijFP=lOH3_zd!af9U}fZGf3Ptzu-DSHNKC!?oRKQQ)=Su$5-1@)-Z^#vljdV1 zYQM?0d0#MPj?-mQC}>jO7eDD(%LS3$q8x`37IRMAnT(whWmiAJLGo1?lC^*7aIEN5 zblVpXK5f}$QdXYen`=~7tqpS1trG!5{DUmZ@3ru!+QT+`f@xlwPU`j6El_3Q)s3~A zbp@0jD33(FbY;Zb{JP6AAJ$;-?18#NMk7YZpEnn4D z;awdGeFc<`9%Xf`@n+%!oNghaEUg}8_|MAkI9U$JF4K=5>uqGl2)+?D%q=Le@7rsu=7g67S%b*b!i^}__p9fCUo zcYc;*Wv+U6_3Uhq!ous2B&zQBPF`(Pwfv0w>$0i6=~dVi%+)Wa@lb=XjTZK-$Fz*x-iT;>u z3i-&V#zPcmOs{{?>42Gf zmPLmm{txP@npID-osVVjmS4QGzH(L1l>f)udID#zaJa5f(^*ohUDtD0JG+7PLjD{d zk0n)t&DqU!1Fg;)LB_ZejBynMCA+&HpnWDa`Co!0zr2%9p-J3>up5l*T-;%bI)eb- z7ou@iSZd7$2U8)NJy*7lVDPd&N!Devzw147h><)@jP^-JE|*JHnWj>hefPDPYl=?` zQL zDlJ%50s>Mlx_IMtcGQ0S+0?pdDPJBRn0js>>H4u4kQ+*5&@=b_VBHjHe?@xFcnp6l zz==hy_TQr)Xmv)`E z@&?YsD@&JrugIf?07i0@#xDOL9>nY3b$3S938oC+KpJ}ee7;RqVjm1@+xVlzH-Qj{ z_o3FyILytV#QH%Xq{b1Tz>T$-2^yk4!Afg|I5J8BoduUuYR3>=+f!dm@1 zbnQ$r?-LYWfi%TwmP~E(cRywWc?5_gc%c48Dl@MdDASnyFEC&uFg6B?K^!Gj@T*4_ z?pIw`>NhUmc-^*+WsL3^+{t`U%JQU z2tJWVwh}|Iqza*0`Ag=0%#CR87JWPPz=IxwA1tkac-;)Rh!!7b7z-k{Z3d}dvBoZ0 zoOTf}zEB(e^m625KV3f_PL zS_UPNYylMZsYvBN24VrRBcH^Tna`~NbG8UxkVsVD_?!$NU=$c8Dzxa2!D6-TnMC=E zc~J2bi6S{u#(tuRM=cpB5zMAq^a75vsYQ*)q!T34D2p@-qy~k?Ad0sFw)1c$dGqJC z;@>Y8&Vr&0&~A@~dT&^Yw88IZ+6-#=JOE`PR9H;_c0_D!0kZw&K{sT_q8Jzo+zvXw zoPZL27r>nG8A~md^!9#5aFU|S^ONz z6~|YJ7|>q)KTLEzA1vj=bU*0bZ($p;+ zk*L!}NbFwFK8)gE#&B1VA+pe`aY81E3hS?9>G>B~;J^ultH53>ikz^_^6eJ~ro13U zm64FNF=MUxX3Q^L0dElQfP7I!N2z;ac5Ww~jXN=drBg`y&?xBY^$=g~L3e5r&KjN^ zeYW_CBctvyMiVT+E)37dogq&!)VtIxm~T9uH0mE;C;7a52KQVbWp6BavPK@1H-;mz zY<^}4Q;vJ`K<9nBB3wkau4f>-Fz((UL zVeS!{F`^kxBR^3pSf~D$bePzpz>~B#fTc}#VOtJoB~}r_rN;|Q3Ga}<@)GAd+HqAE zx_`=Eqh8@p^36TPg7TgAH2Dt$q0tX7LKK1B_!lj-piI~o##D=CY)3Gn)~OanI#7+) zGPh|8c2nMi$fq>b;M;t4fd>du*fA99I=Poq<0me82*5J7^8_q2N_t}8wCw$iydu<# ztp(SFRhA)E_O;R{I9D$NsaL_?k%!dVb;~FY7cyFHnazoU0N%OvJDm9h4w6fLLBPXS zrcmP>fp6ApQlA$;ahi*$Mj4IbWIJ{D6B=@YHu_@3vpA+-3WnBQHS*$3Dj0>3jWL^` z$9r|djeO~`^?kg0Ug4%*N#{4XRl)G{Zxu9kznOJq=Q=5XLgpZB`<(PQ#K-ESSQskQ zG1WeY#F34O*+}Cz_Nc||-p%wtCf>KvDX3t$qw+)=vTMj|rVf+wXL^<69C>>QzJVL4 zF;d=)PnF22%iVIQ6OT)ZPC5ccC86eMA?1m;2(1Nh`IvZw+Lb_$LzR+B?Sa1tDa0+_ z70i+E=GQ~XK{QTrgAq?3wO_nKZ%*K*3isvEb8hcD&o`pZq^q&>NeGe2B1kY22=0bD zr+=OtMBwrdXm+hYN|(?cB7dcL{z0dYieHp;z0`43-DG3Mkj6NRhtPUX1`o z^Nc_AFTu)yhfw>XA$|V zRE^UxT6BXBM))GmiN-&`PwIK#1?nf?#!!?!N4j6E0{gyQMdsQ7(;uwakiNByC4m7;0?LqkY|)7S-n^_isA8F)%wB;5*-cqsN04GWv4 zFlL6K=Qq-x!N0LREdxiR8|1>988Ft3@llfV0r=C{r)F>y255U(!pn z2Pduscmw6^8usGmr!d$~D;`g>n+s~}_|jB7HxHvx2kQ>=4U};A8&SE2 zYd$Xn;UVa|;B$?_rJw#W%nY+rOM#6rJBc*{73X0|~3YPZqLUw*&EhMP@m_&VJXw z2s^W6^I(Z|3tVhni9HPrJ zxmH=imPYFPSx(_U`d!Z8Kv0Q%OA;JlDAMrd8C3ZMJ2bZ9JvMkKS)XknSRKB_92qCLo zca!e`8-0EUGS`bCLXG}x)D{(ooerNOq;r%B;lV$%YdH=n@Q%ZeJ^K0w(k-I2{LDSM z6DVpF*@6q{OR?cZi7hc63yQ>1V4j5#`=@zU{cxo;=yr5?ZtNJ9;v5Cb$OC>;GBs$+h+pWQY?m&h2AG74JH=+h0QWrJi>pYm}Ra?5qO;gZoFtFMlU zKlK5{=)L~R+xx(v`W@V$>)zbPO%awpK~|*Ejkm-8LM(w6xbqnPcD1EF!~TxAa=4?v z>Qy?ysePVzfJnd*avr6zHrT<{;>P{f&6fqZ19=tVV}h^F05V7;lJ}^H8)htVPUQM4 zR(vB;M|P6(_a6%s$34g7bwANL^}&LvA-YCrB*5UM390KQ?wZ_HWQeSelr}<4?Y$%o z-@~(&@QGf;sdHe{5i4xSI*HXKs!+06;S8&OqA(@aWE{Bw+6{~%($3r{V*5;G+Yh)cbpzC2wLTJ#b9Unq)+=au(F9`1qO@lt%XsGGhAy>RSS4w%6;Y59Cm3*z$zlhhaoL}>q~vbr ze9ld&8i!TTuvdqFz^-6sCHFNhh>;h?^wsz77(r zAj$a`2OJ$+<-TsQoH*tBmGy4esc<3Xt~j0~aS zgQ*D(2KWfNUF#d)1r*b7-r7PNaUCng-cpj02PKbS!YHaPFifdpS+)-o4AJ%SG)KN~ z8QrAa9GIZ?vjt)4a+Em(`fGuLy{8N*S@oX1P|N`4a_96{x9VDa5G#1^5%=Y;1Wx!PL;Xp@ zeFZ)WU*3^h(!OMyO(xsS z0;$YQ9Ag75by28b6^?Zom|sI*8Z9peV!BkkuN84MKcsDBRbvJV=Y>k|K^C{z zn3?r-l%VPQ>##-Q3v7C8|dXM8r(n~qyg$XObRaLEine;XLh|_$KvcoM;BZc zhSuvak%4ef&)cJT<yYGrcT?3>{U<*~ z3b{K`TK53nm9SFaIY4P>RwSbt^z9)#-f<~4>_0|*KxZo#Cd&*qp{UZhs|+>yPUTS~ zt(9+Z3Mp9~2EbV3B8^~*?c1E~&EQ`vf>E*b5xNXd*=-XK>UolC)7VbmwB?Ga9xSx^ z4I`@rt7O8D8HP@*gyuLr!9KxaL1WKy;JK{R2y6sMDZ1x6^Z;8oUCq~72Vi+KbF-)Z z2LkWfRer4hww|852}TA_ThpT4;&-g;L;j&k2qP9m;L_%8U&esdufEHSrL}2Hx=0tv zb_0nemv8_nh=O9W#t{J|@S=eSRO>UAqb19@-DHLpeelEZ-4pZfR+2$u>C`S}B{sPX;f}h9uQjU&kd7JYBu%`b1wN5n zae%F!5m2Jb{}*KY4^*Z^V=lG#|1N(*`p}1tJVp~#EX*WGU58Z#lfswvZeUlr5?j+1 zV~oG@;D@EUxuKqAIIb|sbMdzwE!Aetp8f|nLW)v0cQ|SjdoLMC(;;m?3ml~l`14Y2 zmPoE0f0LH~v*o95X`ikyxgYYv+z$T6RFeV8-p==OL=-nxx1I;2sBlEF@2#^2yvdte z{pP_+6Sn~yTfgNkz;k#0)TV~;saZ%5$#R+8#`D34fUkJn$BCF zN}u|>x02c+gx#2Wq?H8efG{!Z6>Ys+*+Mwxw1im2s)a~^$8ne{-ClHt6wlRu|M;{C zU^cE;dQBaD1R+h{4Rn8y&x ziC_Dm%~cj=PVLPD!D?eDzwKMK#y{(>_ytr|elUR3S)Cb2@F-hs6t)8NIy;G2%tUyM zqW>Q<09byTnSPRxJ1V%Ik*vBeFuQ;o1w(R10<%)!~@725??{!m76VSYecCc{F*W6L*%E@ zgyPJ=4V6B|o3rslrt%Ps_r@Z9$S1OEpL;jbG&{py(Jr+iS{CtmWc82U9$oBU1WUrm zX!K!+a{+q@M@ZvC+}1&9;=BRUJYDfmm#hgCL|GvH+7rAlz5%$k{1XO@$~X|UyESV0 z5c5DpO8==!pdbSVKgb2VnZCxP3uy53?V$OVE0AlQ7AD@94?3@ouNGkG2 z_UT1LLOGbnVJzGtl3qojq?r`lAQHx+!3af?Z`4ctK^Rp1;ktD+&d;&CB_6s7rizKd z&3KG12-Jvd3gy)UQ~ z1DeGdenGFZ1au#~?z@N6p)n-=sEBuUz~_>Ki}(_C7jS;O`SqL2x}^`npQ@%;lgyURRl9GcRw?g;lK)M=^&L09@45VFm{cZ$~$-mmeJn-ubL%m^= zXU%HW`Sq{%t3GYh%6y6}f1r%tn=oLq;Mg%jG(yOF^hgkSdE$GOZbLGjEJ9~Xc!hNz zi06SCI}-VMXmKeuME3hZ-5v*L9g_Uqv^C;wq8};;OGMC6{UmU;_U4RkXifNK$(sAQl^wcc@>(1`4n9hS4Ekl#+V$b32v(Qp#U$o1^09y-$}ukD+#jM6<)<2=;X7}WlhpN z%x_D-cSL2~g0V`O@?rXs3$EU6MVX_7iL~oZQ^mVLAR+9_lGb@by0+!Acg`L}thq9=hJK)HJEw8!8SAY)|A86*874Y2 zKaW(E^DIn2uS2z~AJGEW4yRd?Xfp}~H1jt~`DYf%G>UZ- z$rR{02<{(?DxE!^K!Pq9xEP7}zcvwBg#@L5NVu`4qU;hYxHW(3rN?Pl9uA(->w|UZ zOGt)0*1tLi+&muUGnOr$K@$NBKA{jRTf=h~U=Gr)P#gHZPtfeYmHi9ZSdYe?b$!_J z_TnY02vmKs6o*w>O$0VM@hp5Xmj!U{25bny2&J2Q3NN-%&PrlykT#iAFb^nP4_S|9 znEKzLZvYR7wNqP6P^Ii!71E|L|Beg;jc`5mRUY$|2$Z;=dy2+K0yt8l9hXwUqK??0 zk_^(ilWzHiRPoI8re+{$`9G}7?ElHg%>18mOL%LG0IpmS^$u7Cm&BCy8)X{4fG<@j zvvwPyWMkUKM$ug24%i!<7ZQ!B30{V%*l?>qu?8V0o25z(LyU%%ug{|M1YI#Tp7GXY zi*O6RSh@i{?4d6?QfN(cBy*OI3U4-4qD{(I6n)ti$fhlnJzOW=`gMBZ)mz+7A#Pl!Tr>T&RFds>Im0l0Ie2bATF$iRR~S)4(1 zxWu#9|NZ;MsOP}lf+heVgAB4m_bOgiC?a=?I{=nc6%gf zxgqFu1WlRCowF{Gw|Rl%8$5Z&;8A}l!E@yj*?i}}gPhR}1n&c}N^)?FsJGzM&3-c~ zr1X+?J(b_0hBvn6tl&b*a|0&;4=<+P23WNtH=IFY3_kuD#d360-@jTZd3$s)RNIwa zHE8c&)_2nuQHv#tenxny7{Veeut}nihB-&QsTeAjB6dwxiV?|DO>wdEtm-1Kg;z++M?6w27NCg%a{ z(p42VDY0l33v9!K`#FrplE=P>bIl7Nz|<)=XB|{WgW~NX6gP$$k2?EKRJq4lMP6z| zxE~PKxp{EdqR`;=4f$NbHz$Hhfi$hb^CODD=^o@V`~vF6?M!5tJKiEx3Yjr@9Ir$u zyEY=X2a0xOzl4+r`or5g4tK}nU%;k|46;#(H0J~%uD#jn{h659Zp(PYCB41 zt@4eyQWSHE9{NaKd(<;bx}0qvav58}RB;%9Nf+iQgp@9V#U~ujzM{kMggyENvX;zr zc35A33s`>>!tpME##!hu%wv=m9Fw&a5gPq|4nY~00DfyiZ87mTLw=(=JHzc^Kz_RQ z7$UwM?`_BK?crn}w-sJPDh34f2!r0<@kD~ofzPNjhL&ql0X>Rw3@7g(IMOQ7k3|SQ zs(TzNH?MO{Go3x0OISl2-t22mL|;9#FY0Lo6N3@5nD+i41+6gOPZn)$y-AS72P_eA zYma7CT&{gV9DEA(GFZG#8WF7Gwbo?XxzdB&3gy3{4z!oGg@oFdE_*?FS0to}m;87c zI^lx|e>qV0zR3)a7eP+o>lb~hfrZWQw1&YWYpemJ1$y>114#UGRU7bMd>ot z7we@Z84!~;e7uFH6?YDzhUJ#o@H+##q_C`S1qB%nON@cHAfru1BLQPRWZayizANNi z-HdE8LcAXt59cr)rs3tZVenE6L6tyL3%>V(0LfD|MIi_J(Zf>tr?P8!zX0Z5`cSc) zL-&O@_6J;F*$=g*b}s)|3VO&(g?UZXA#fxyoKo56%h89No2hJaD=2t5WDEX3BV@R@ zr*5wBw9`C}#|!}$^G26bBnCuoHHf87?aw(FhPI4NwNqrVsj1_!r0Y!8ky|g~D8;2& zcjUn)(OD$18Q6dF1K5?3^a0JFhBx(0!c9DPv)a+wYnn;MpM9S4{T>(b(u`S5#N9;i z9z_77I}|?N=zWxi%Vu$_u2{6OKSj4!E@=ia&EdkA_WCys!@Tj%x20LXsApwBPb5Rf5-@3{Xm; zLgQ=AT5m5K=Z069 zr8u@uVoFGRIiKx@gKr=Wm0mE7Y&pk5Y9Jf^Sd`ka!IC4KHy$vV`@L+wIx|9U)L>g6 zL%hK|)vq~6Tkg@k2=?98b|bvzd^}vXTYZH>v}#u<&3(&ftp%!e@?UAkuJt=49q%S~ zCo&2?J-(Y7uTG%GI>x2WG+HywD!GQKfxw06qvFHWMc7B~>TEq&kEtb$OlbF>de?JK z9=~mwim8yav78;1)w|oRtp zl7cH#CoGK8LM8V`5GC!a`c`6996FkoJG>tqG9t%_;DYrfMQLh=d)gKCrGib4yJRL{ z>KR4yj=M$;Z&c+r{s$Cw$zq2<*gahJi}L%dRu&H_tbYk8uATNAZLZ=McT|;2YAzh0 z3g{|eu|2CE@Y43gzD?qU6G{oSOg`lNfY)Mb=XeYoFRgO&$)M}FGZ=j$3#VlmHK)0f zFOe?D^rMj!ty%={DBEhKI*SY~L&mZkh)%Eqi^YTNtc_KwoE9A8&-DhyLNr8Dx<2^) z8r=GE&u9;U)OaS?%v4X4hK(S+hBYP9Otqv)q?eKml?erYZVE&Urw4_mQE6Q`M^aFs zPV(6gytEjG7?D7=sq4LGj1K4b+98QJq6LY;`WuI;)-653%u7}7#ip*54G}rKjY>-t)?M#7&o#fCWaBgn5u}bE zy?)Ya)XeZ}xaHa7_wEvCF}n!@L<<{>do+~zN57J_=N}iPQA|3=sW9Gt&va&~BQlH3 z@##=P8r9@)G5@q1wcHJk&T><=JI1heN)tN2w|=FtjD623PUmlx@T-^Nd+-;>>rfgd zZzk37OYRZMTpfKsTOBJ%d6hPzy-M5!){&D;3~^#SZaIO*x!U_w@?X*H_2I*xE)CmD z+5Kc5ALBxbBU8%WDA_~>!oKP~=9#;1iXZh)tD&NN zQRGk8*A0Zr&5_`A?N;8Bk7wfx`HVj5DYjAfwzF4G%QWl#F6yK=SN5vKxZSiD6h0<@ zk>@~$)DF#FQYsDbYqs4@%LMi7Vd6>GJW@~p>1X?|h#oKG|DaxC(Ze3sJ)e9vq2b4i z5^RVx5{^^k+GWO5;hL;0CI^WahTS5Rar}4hLq&Z4e@DwFA*ovRFRcf~nTZ^Za}__^ z!&#j6mIPg6)m6u9%ARf`^%mdIV*O$E@E$JaEkB{;Zr6jnBu5Wc0|xWMmd5j*itF4Z z+D5j*t~D)81C<=rzHwSj)qixxKOOVSThLe0SH?WEnmE-d^56Xi7EXLBP+m z3bMZ&q*^A+*xD7iN&ahJ;z8H2Z$|OEE%TW+mXQq!M5Y7C>-4c*V{Ny2#$%^bY#~L_ zcP6M5f4r60ki9~3`aL<~cH*C;1TPS?DFR7lUIxJO|9_@^ap#J(R<64q^AI~cJ9y$f zQY3(=$!Dv-4YauCWs5MKjXljtJi^A>4aqhMHJ=5}mHIdh0}cv`6b7cSvh{gtQxK?Z z^;^bl%P6XVq|!%)oM`xuLIfRo+(rp#jDLcFehHItRI-6w!fU8+YU`IGIUnk5H`Fd&Pk%L4cG3K9nSzDA_0`97WECk2 z5sZscdz!!=X#im5(xX9T8pYs$cOfLyEW0;U&& zEG>~-Z`1HS`WpfmuKNV)>u)IL;4bYl2s!4EXb5oKuOJ$t+cN8_okJh&pJR#(R{hT+ zB|we-d|H^42k=*;e{aCr^c%^3hC4ysq``Bz`q@z^wh-t4U}$45n8$yn_(P1~XlO=Y ztLHzOa0KE9$*#xSy+o1ZKxT-rq4X>4D z%GcHh$(K=4EXi}0OyVQ(J;6wG$E ziR2S8<^jU$=2LGMSk#5MG7O}m~c))5cS z6YmjH>Y2o<7!Y|%tr#ou6me_Jrfw7@!0b?(;zOsPyjqrY(;TI-2902wPrCGF9ze>+ zx92=13flN%kF`CZ4MNRSewV7TPsj_n<-9zGOmjq0IE=Xte4jZe8@RrVi^OqH zcMy)02odl!nWh00!E9!!dI^*lMUHKSQj zu{zteIKw>H1rI#17CKbONQJl!Dvf3DS ztnB0~JP>qt9msWXWid>$chL)7j6PcsVAv=d5xw`6{mf^LIBa`=L+^Y$+AF^rl<5vD zSvAEqfE5lXY%QDw`R}U}S4I5YUy+aDsKqNy(9Vy0wU$u)lg}~(5Ljp4UddS%lw8n# zp<;;%nEU5&kzLI|RY_+ILO-~0SrA%)+5VFc#XY${`N_XBE_Go8r$5UCIpD@XjCL3I z%;HE*qHRIWPu8wIh4+URqy`wW_SF_H7kNb&x`vH_K&};nR^B%Kq<}tkdB(p$ZiD2X zVEPWh2{)Yb`9%I^yCeemNe{=ReE7xjw1=(m)-|;^WV+pppI8^V+$lJ-;=%JFGhXlP z-fA1QATm#Q({}6kFEi(p5^Qm&N)#Mr*&BW)66&B+P?J?u;>K|{fxn6i z>?6|LFA=dd!42#65)&rF5oivyy4<)J^5vr9deS?8W++h>Q;bG_YIsn@8NEO4YYp=I z9g;+D+1rRdb_GEQ*#bDv#_RFRkY6^IuX-LSO;r~0zhJU(cycg)66C>Xs)id9g015n zVFb+2Ty6s;z0fyZqwEpfsHZPVK2uJ`pAK-No$i4M8n3e~vAcT4IGI#CE(Kyk5AfPr zcQ3%`FJ?VX3Z|7M-V#wq-I@o%}j+Q&(NM6J(@I)wN>vZqFQ*ap63NC)W$1Hb=WTxy?2m=&tL+xkaUv|(*B>vZGbJX zXrtt0UEkPxty|@MVm+^FucW(5mx=#gc=3rae%{yQoMMT|LzCulJG|kY3~|%Z300tS zJZ3OI$}_ldN)rznvA<&ZGp9neJyln=YHd7desFVY`z58w^d? zehxBsToT~o$9-y!tTgl$>w@Ps-3Tm0VV3*!aUo9cT*P1-g7fHS9n?bdemmKKHh0be z`I#STun}bhguwT3CH@e&x)t7agQlWwD<`Q*;RWF_sHpiJqiO%V&;_;;+`z1&uBPVY zu~1RKX|Kd&Yjm*pb57V!dl1ij1nI)^4FvsP#+1hzYGrJJNouc?FTiW2Ctd@3!$y&! zUQB;KgUHlw>|)ZHDTGOb_0P}^XJwjn8I~@CHgC+J`}sn$!7zL$JQ7bz>sgSqBQ$_{k295|+rW$_Q{yqj zOPt?~`WwsI(u237zX?PkMCm+f2Xg~Xi>lm&qL!C5=j`Ik(V@H*CP{$a9llJb0K8E%T^Hxg&_HjetL#TH$6PgWv&x91muWh)ESUe-{>Vl0EwyPi!r zrw)bRKhEbL482P+896jz#WTThgYv=2qkyWavm!lbUC}NBzJ;SToFk^#Iy7lwFhKm1 zhAu_@kh<=!>;BO_xG^A^vrguudh}C0o+It7iD-;B5|7^zzF^N8Pw%Y0z z3g#&ze+hn4t>M+Q#s;6yxYs@kkIw&bXCYdgB9|g~&w0I4I)<(<&mw-`Nm2L~XwmyzZtkPMgmIFe4 zGtZ3|g{pd{8;%qTs%L|(W!tY-=ADG)wdNKQXV25aDzb$Lv$~JQ<}X5j*$5L@PB4x2 ze|SfwJ{l0IF07+*oN5uCAiQJwg~9TU>H(*UNJU=G%D3OGdA*jbq!m8{YQ`)^3+A#y zk3-ya__o1LqCWep?Jdq+gmb?YQw%CGf$Mlv7Bwbw%6>)a^BP@mwv2#d!pggGkr7qq z6C5dh&`!1db!~R+L%N03=jHY4F%YxgSoNf0a04-t_;XlM*lOKkJ!d#u+$MlZ9eCQZ zhPy{Mbh>e%KbQ5r<76q0%H11JMleXAG=zuwzL1#uBr7F4uesNz4=bLHtyuPM32Ij) z4t1R=4e2~>TX$a3>^5+)QehPZb|eRl62~w*4_nE@5-`ECe4|XHA)`=+O|`TQ^#B&yW<>h}|1A ziMZz6sMcr7pRO5^Nf)hNO6gB+#pd{ynm zq*WDWX3E166xQjH@L5xO+xM{B(2;VJJsAhJ2^@P|J@))UfZblzO4_jD=-8nmeKh(U zL?;g{M6I+HAVtmS4;0M#4ZckY9SMIM(mZ;o{{-uvCY50u0=Iqf`P`A|*LrpYz>F2= z>7&T&XNAeCqSh~hIA2~94z-moZPaIdzoIQlzp~UkEL*ctqDnjyg4=L>NNqM}41P(g z3g1R>A<_`9AP*3?al}39&dk(nHH*oWvTO=XG_=y+Ab5J@)L;%XRqMG-t&&z!vEPdI>2)`Lc5QC2=RwBi zCPzvOmRTx+{8&7gyi%-%{2{pr&;+f#aP+Ziw*5lBF2?_RY-9$fQx~B{ULr89u}6{% zQ8ZDI3*Bh5sZFMY;E$gZ>-`lfz_|%&+F(GQ3^luZxawPj1R?^J@_f2dGbXF_m$|-As21U>yZ0xKeNd9#U?4-i@dxDMDu^n&8jv=~ z&M}?qVvf5Yac|;qdu^Ymj(a?l&9si`u%}VOuaD9DH}vy-iu)ypq+j`-)+i!>Ssz!O zZzzk)pCrN6$p`uy95N`1cuz9Ep|ZKx%xbNfTc&vM*5tMM;j$B^GKQa}3f~SYrUjo| zYDP!26t6Z79A^ut5n-R?pi?Az^Ny57%~z5c9-$h~=5ezJ&!#~xGHR_GM1l!C=Wtx$ zalSJczw;6}3!RI(2h22T%CKgxdmqyU21>^0tA5Y&0}1b5lfe^7Y>>pNi({3-(+Pc! zGkM9$$$}({XLN-Ryu1>yAiwB1Dqpzad|+Wuc2I#JlDS7owyeGtuqkeqda4A)_&Gem0HB1Q)Brv zSm0yUakMKny%`azx@y$E<~a?v_#~=0D(R#T07J>e2XikzJ#Y769Brt{LD!!jm)Ma{ zUG@p&v}G)Rrx0y5_~Rl5)dscUlZq4SAMA>hx)aoXlK%T+nuALduYj);)-N z2>Y>5fY7(P&IzBMFB!t}2rSu{GtW35wf;NV;gCdss$yfUd7Y582YK9DEk}-kJ~9 z0pIJ0M(76%zIpjbQSdhKE?edr;u+?qjCS%GG7$(k<VGmto=ayk;a44x*bKEiJe>RbAO~P_@>}5|QYXRfrJ8=I#rHaY(e~gtpE8bEdib zPQB|8h3&sQup#*IBKaTr6R_hg!@EWTe47!)Qg=>k-YwITyXeYTp?=`zxD5;q03?yB zfjsIg+(4j+4CdC4R)8d6O)dT>UH-DV?$t{(^o5aO;k;L06iY6T=3{{xPimG4iYA`zLAwGa`pJmu ziiYD|Rm9+R&TY$ynZc_Y?!uZ?*N5YH>Ya^Eu^B5W!a}YX3T$F^i?G9nT~aS)+Xf;d z9+M>S`};4@*V|}e3uAz}7u_7OyEp3d0)wYBvF<8>%9DM#sO{6a22!mIsI1oAJMYD7 zp+2Bfs`}NPhn~BZa)_iyyTfJm_LbBSj!$gBl;9xRo;Sf@d-(RDNPlbsH@Ek<{3%Eh2Tp*Xr`lyJ01f zorkj3`t;th=Nsj{rpz7($ZmUwK8Zw`L^_58_rb37Yv$jTk&aI5R4FfR__;EW0_dX<@CzL-^bn(^Ar0z@4B~o>mJOWubxjjt<4Az zv+B6m1uHa05lyw7ISawRDjWOdeJr+OeZK`F6Ng65EIHwgrAYUtqQt=SHJVs=VxKH3 z+H%>T_p3N_<{yKkt=@t;OEKgff*-PkKtg;G`4B=x4CLN;my212IX#9}pEib`dSpSW zU45sPCu1kQmY~c4X2d+-#GUh;9jX)hY$Hz3=LG>JaI|<_T+h8l)jY1>PB5b1kJr-> z%(f&o&(e70Ek)X#=E2)g%uZVA%%98SbU%ifd1!%UZ7SP(oSOC(9a2bjjIoct*wWJs zmKKlbMbxg!x=mMh0(z}zbn?b6pMVsv$0Ig8jdZ++o6M#(mtDy3u65H+VW%1O&%M9chHq?Kg<_%;|k-lHswaAM-a-wP*G%H&l(w5+-oECkwP!OkBUgh)T-?Xvm z9{e^mT-)WDO~d-$XqZ4XVfGt$;Nok7XVZ`oI_7*{qQS|hh+}u{Nypc+)DCd?yo)}S zkAV*o^|;1*XOo_R_N|ym8FlwPQGQ>xUGCdX}An83}!VJf)AOKC&)_?d+cBDde z7dlLztx+#a)@nl;vl6E3+W0c%HV24sXrw$o(q|~Q1@H>`JlsS&J${BoH zRT{SD^$sBUO~{aBq|j9@=*`ru8Cy4ZXx91S3kNsuGk~SNQV9&HEF>86_C>ym~jcO7cHiAcT&52&9jXT;WWuZe)YUm#$e@PCRWhm4$k<$BG z&%Ervc;bu;JW|pC{w4BI@*m=9;=5v8Bh*)+4+P&4$cJ{TvA!ol<7e-RD^MWOq=?6m z-tlQ#T3i0gz@6It&DTMr$$^}Rc*OoaTc{z_ion>Q86oCNI>$%5Q@i(Yzb7JnhTH`N zBqEj(7>4`iMSl$pHTYyzF#=pkzAes`U}1}$Z=~W8Bd;>&)8GB_;P5`@M*-9PEXL{1 z>jC2pAK30#3G6h*7-gMT8I*uW=Kl5E*91^Yn`c2B)=MD5z=n<<4Y*Opq^ zzdRMaU1EPaadekql$Sw};Qrh@hOKXYgZ6F|U-|>q3V(wN&gFXPEh-}a+_cbmD)0CE zee1^|UN;uxTt|eAAe_emUN-_BQ~RGMd#Gfg4fStRNAsnSL)3a%JM5kal~Sx_Dy19c zmdq4IyR>UefLlk7ptwhIIu!HNwDztQb~WlYOtAUz04`D zqFrH4Q7TNfPQf7PU}t0YbhAG!HAVRZY#Y2W#@Y^VDQ?qO{E!4i#+l-ENCLw;dy1Kn z&%}Py(~D;sbH6-Jh)7n`x!+{&0(HZFNs($hE94oQNe$$#CC;qKa<6#s;w{J>{zGsnHwohr*2H3dwft%@VA1X*rixV!7J>FPJJ z?L1Y+(TK5b@~$zXk;!U<4>>P;e{+)$jx7j%A0K?JeIR;J#qP4wyneD~RW8yduRA;S z;y~5D^ki(lRlkJnx}(fTHuL}1G-M`|hKf2v? zTNZQhG%^n4ER;HiEf)uwo5TIYUa^zTJrZXR6^m#gnr_+Ky&2!Um|Wev@RS`PF_)x_ zOy)ub-ZoDidy+2Z+Uj95^MP93DX}7X2UCC@jUg9xE-h(!UUu>85EB}z`=lGHU6IW)@6tHr!!?rbUhpiz~&!iqa!2AZd2wj zB7?#lrX@|&shlj7bgO(#q>`NW5o+Y~I^`HkBfNm!Lf_%jTI_SqKBDzGAMw)(D39#2 zX!~%=9JVCda%Wg@E4{jR9aRkJesz4kchQJt`1+Z!@#jpAw2*SPyR z-))nre|C@FyhV1*xg92e980rYdgpfQsWUVDru&VQS^PjwJM}1Zy=SZxJ5(Q$|8`X@ z)0vj%OTE4{g(<9l(la!Hh~mH?bGv8OMdS@6n*5rR(6(B7#DWAu;jn=qu#OUX74mv? z2FfVNOlx*O#sF527caEwIW<)$6Sqm{#Pid;L|q7BMlMtug^#ewbOPqu$*I}6&W|dP zSe=F<3>7UikLP4v>lZ(wsD_7$_Lw+)+9iEvC9$mlA_gD$smTZAbtf}E{pOarc$;E* zA+bWb8_vTmWE0JJPCZ7%Y9`escm(k6_imG71h2Q__<4C)l!B=t;hqx`u3oluLI(O* zY#?HXvM2L`KAOGqItrpb_~e4U4a|N#bZUHf`!3Ss;Y?z-bZKf#rT%X=^NpDm<2mh@ ziMMVrRx^>#wd$h_sko=5TolN#d}b^cw1rc&`iI(2dRy_lPvv7aD~)Uyos}8Bj~qUF zss57qK6zpEpb;STzO+S-pgO%F>)d`RlQb;>q2`%~45T_v*W zTY|_#F;030A7_XYMvwvq!yqq^C=vJR3Ww`b~>g`|-uO{*^tC_vJcUhjP-VUQANXs~_ zz@$)4G9n1ytM)66c)omf&&x$YJl(C;2r`Nm+WmMaQ*JJ)BVTPldIA|H-1BpJOdJ1g z{Rxu-)B)}Ckb!3q#ZKpAG%XkFETLXJ8$r(!iI2KsEc3#Qqo22}W#tMM#x$ICpjdcy z8d;ilwWh^p`PG%>V34QFX1z;5>q`6)Jv2*V)k|SDd_9HmEbc_g`HAG>NuUomUD{et zO*m5`<#valGW*7Nj&TpQrd^GGgKghRHbg(+GKsr$Rq8~lD$Dq}T=Z#jWofiixPT2` zzvyYYB}v?FkKQCvjdPS;0=FLcZq~4HGj@GsN|dX2#E(2wkLoAej+;2I{94^BN8BFm z1k;0*L?&QumV>#&Cf#cv&I`}?$oH7qL8UE@wNzgyqy{zj<1bg12oFm-xAFN{z#MuD zUbxM(ntx4jO8m91i#H|c@YzvXH?{lA#$LIiONQ#BO)8c)(;#M2X3GyRZ4`;49!frR zh9Mk6@;4>o&i!^PoF0vBX-W1QFY{o3na&8Js2oCEez7CblV=DY3NK31lOszrlaZJCdt(%aZ6kQ#bwJxgLNk2oS@ zncg^K--{Pfi6{24wM+96YhX%Zp%lAVN)6{Lg%svI-HtY+uA<07utZ7W1-FMoC)t*4|xu^l>MA5IwmQ+{OYLf6dF(%n&`~vmo(u9 z&ZA;?;}u`dm>*Pb@7G-|&E~PNSEyabd916}_Q(o(O;w^(jL`Ig!+VkVOzzO= zJnWqscDi?AHrp;XPd_Nhi=2%^Z=CqQ5({7ZWGkk8x^Elq&b}j})eSz8_@gFKR7f+C z7+1Y4&nCWGI9Md(ZoTOk%b&Eiv@fbx?*^6-hH*I29ovMZVpnGE!Z;Sf9X?My0_#gD zHo>gBQ7SNGExr-|_N8;&-bvq4$Hpry&u_nSX2cG*J<6F>ny&V4DeuRsh=5-RUI?8? z!kpZ@SUXyFURY{EVq6 zO7xC)xNn)wqSCrj>Tjg8F!#yYDDHX5Xq6Yl3R) z>AuLvOb(S|0m7H6;Z~kI?m5^ys@fuyi6x18?i9{Z9WvbnIdO<yH!Oc%z!0*MCVeo`sb82c0I&RkOVt`(a%rrTG3h0!ll@%-79?T5Q8Oi~2#F*x zy3_=be&thzH9X9Z8zj=Zj+wmDNRX6CV@H)Lu4&8q{vW{{l7$s6`;SC!nwe7w?pI_l z1Sg6#ezU{Lf^3wDdn$R`P-^DVN3!2xhdrfvrE1R2s=@pEb)fg4DR8e4EYguYef#^E zo~C?Ont#1yxs@DZJOf^x$ALN)xGegV4K9oE1!;$5e_r@{2v!2D%C;vmvNS^h?w62= z&C%0i>2n%d^Ef{}GU$lmoj2)ng>!AP;yQbS63zU#qyuH)M9 z?v%_l5M&nBpl^amN|YSGl#>}yYtNs0O-dkPL^b&&%oyjzp5$aHA4u|FKc6RCjT9%# z23-?SkkN4lG9MX`9@hYPe_#nZn#(om{Cu{q>OTbw4e%p=`t|YE9eQk$1vdBf?;jmS zOHMau5+oMV`qdUtV=Fh07sKtKX6=px3Swa-1{(GN~DmRU_C)?FIQY4MyD-Nr_#|2&NF) z$m3E}dF-ZS^>_lXS& zJ7W2o-T4&Lz<)Re9Kr?0vJ4(T$XE~J&J5kLKQ+3Mu%t#WeeV`K5PT7*KdtBwy8}~q zyGD-fOcWnEon~-thw#LKq@-LfG?VY>th=8*=y@2umoU zyI-3b(G)^I5=>LCPe%!3Pw57>wAXy9wjDeMO{A=kD;(Fj#BG5@%*14Xr(zalD?DH! zstd^VC#SpO4`s5R4mqhY?M4I@TyXy_olQB#eepv?imu&9IIa%D=erW(B-|dt6k(jO z2afBb<^}g-9O^QmQJCB`e+qOTD`~+qPc06vpLTG+SO$IcT z%P1wI$ea#n?oy)^a7+Nr?mbG7C$AhQP*kM|qa9rQbOC>U*l9^_ z;)XMTPl2bqxTd2EEaEg|nut#%rfeEq+4EY#RO2wXLJ;S}cpy_2rjk0WCs_bZv72$a z^(F}&@688ulaHzl8X%(nXlb3zci3#*DIkC5$@4W906Dz@6x%Q#q2@Pc zPpSb(WexZ=;z1-@E0V1it-l0Qbmn1)8Nj~5qBQT#GE3i&fK16#b03R@p-E5)^0U%< z3xQ;)gl~sESm$YZy@ghSE9%p%(dhbBvdd^oJ$SN=Y#t!>U@C)>zI3cbuYcdS^VVYS zXnH7Q@SS3sq(CXAsJ;lzHrfoyLX*`hQDW#IbDop6TvwCSr}aHqKLg9?gDIfPIHb z&zvi`r#)k7EFC}lp2>5Xl8Z-;GW$Mr9IWu=CSh#7Nj-aqk=ivIJ?&4Yq?AC-3ZbRi z?0~V-TXuA1s=$dVFTW_YH8{>i)0$WZmA0BdpF5JOAmMiPTqGv%c`DLuCaQ~tSHT?A z*L-&j2MGd7(w?R(Vk?WE)Ks0#K8oMkD}Qb?^Tm!h@5aPboUR4PI&3=aM3Q^2iQfRctl5tb*e zEv^6jT#YsoE{A;z0f-`|sP_0doQTV2DguKG#EIcG^0$g|0XaavAl3TrDUf{^p^&FP zAG~? z)Kd)EK6CeW4EPSqg2yIk7VA7nQUgfxTs|BXecnZT1M&-xgl z|K$e!CgPA)RRBN;bvv-F^Ma#(RvNsC0`iu`4IHAO{>cz*X>)b`>Ve=e)_fW;r!==Rj%)jb77yZG+n+;NnYf?9FWO9Gq% z1&?Kr4r`kyK0#*X(dd>pA(?c6NoPHX)M*&6a&4&fBH*(@2us?}cWeTQyleBMey5gXJ3svU zr$zw45`NZW<$h)M3opv20Q`86S_<&U{sGYIcQG)5LvOM7YtS6Y2gLU4wc#SV8rdzd znEwqjwcJbzW)vOpB?zu6wi@|tTW@pO3naMQWlI64C_8SeWapX0?O1$QV$Yj}F)_GP z9pp?g8tlw>eph=_VA%NH7KkW6OQLDSU3NK3>O@$(Kd{J1pX{&oeS2K@1s{I_#7L7? zxgJo0NsAGIEMQb(>enKGo}wgio(&QX299hn{LC_j-#q-Q*Gep8Bu7uOVnCKw9YC-D z@E}Br)s8sOd2E5?9m!WFDrrj-H6EIUW*-CbJx*tsWB|BtSRE>miC|Z*&rnazyvO4t z0TftPdRLJo4AZ0*D?;53QAV`E#3W))v5)Q>V$eO6&O2(~R2X-HVi6cWIs{aFSFIF0 zxJquCrMAgua-}CA0o&4_Y!504`wR9EW(JdK3Z|n<1gz{PkiGlrRyg{B*dXyya6l(p zV6z8=VH~~Ej%2d3#8l#nIjBV3+D~0{JAXF)}e|I2Z()-zl;*n3qbze29CF0un^u4Lfih zERB{GbRJJqw1Tk$#)|Cvvn?O5K?5#2b_n0;xFsmw*2>MMi$Qqw%?gV3y@THBR8}OwQlM5;{p0)I3T{ zz5LZq`p)C3|_=@ ztYhjJ`JTq`5{x#p=dB)1yH-pR9bulZPK&=?R8xeymhp@<5zwg)JpLu!uV2 zeh(cEN-qi0)Ytj4G@ioc#J&=-zExWq@O3!FsfS~Ah9g>BQ|lHuDd^jqWV1tz3j#Sa zV`VfagM`Ht=os=0xTY`Be`6)*;qybuYu4KXU)yl?4K%`1p*@q@eF7LY*i|;^?YzF; zC5GyRP3W{&b8vNk2Z%M0N=K2F_;wkRnxWnKx}deCxsf9|E9JpWS!$8sn~G7dkkk-D zOMgYNaLQi|2cibg$xo7C5^-~2MAbdJI;Q$pf$##I77mlH-2MR6Q&^zqg?7>Fua3_! zW=2|YDzJ;sN`p`AClGa40+iOLW=2DQisd(foIXU*VlQ4&{u7T}XRcs`_^^P@o*;a6 z%Zszt{|B0`Ah$XhW*fH3f3NZvj7+)DpghQn?LV%pI# zPH!gk1L8Zrt@DNF0!iWuLZP`nxnBCxZfKfzH`psDD*u>i8>KQDDT3t1hhMt#g}Llz z$iy*?jnQi(CEBMkjov)71t!E1MrnRKTr|?9bxaZg@$pq`?*Rb%k6;L#lCt;UU%6sh zf0fuwbOclC`;)TbuRqjdO#aAfo2#mD>~a~l&p5?`V)mfC%t~U{(VS0$f%k?}23^3D zTbP)w$R%Qav%RPJFYmu7hQsulj`dgKWc->OOOwh?97)>WxcOiWFRA-!-L>?*^PXln z42P+|*m<=O7Tj}gWuEr53x0Wtng$0{jz6G-8;Xd?VgZOj>hQ>Tt6tu@HRUF}FKVzs z9aWKLYW)v=5DHz%?|O+8Hi zcVYqyvP>P25&}^nK@CHi#g%~fcz@N3>y9C~Q5!kxI3eh~K8+q|s-5ubVmrOU%Xxt`SZ^9W={|fB&OERqYLGeci&}g)T&Uf6V zfvZZ$g`+lj@Z3RCP(3&==)n|aC#oknUuv%N&ip1gfBuR&0kKGZAOqE9*8u^i0!2-W zA4nRb0XTCD08UI;byo!LHp(7THH$Hv9BeQFvw?_qmpu*+gK;|ib^);I1;9-GC;Wq# zG_O-)t$UoCk%k+zt#xqIz`td*%)SBSAYLtZT>C6>*Y}W)3l!&3y(DmgU=mt-;*S(B zF=k~NVw<=62h-1jMiuyjm{z5tJ~t!5mY#Ch6HfncoQgyF|1K1KVMJd4CxZ3)GKq`? zpr@mHkagNq^|yJdKFfCdkH3D_XX{q<2KZ$}^04b$G9v{Bk=peA%?$uC)&mj)2T?nc zBqdP_$R==Vz!UyZaJcnY%Gn`ixs3qz9>%x2i|q|=ZNNf3bpv30D>Y~5PfzrJqtApi z;d3DIDQ}JlnC*1IRK8gps|1n7aa+gz_*3o5Zr4JN)#;8vh|Q{f$gGsaf9A@hBiUs9 z$w}^LWEW1jiJk(}b?2$xQ0<@|SZpUyEjIu!80|T5|M&qH^#dhvG+K@Ul@1`DaGB*( zfB+>`{>;zYP|j6)4)8MhNQq6!McF<`JSG7IB?Um|9fu@jH225>#(O-T_zBd)w{T?- zg=xH53EN=6~JKsgxu!HZEj6m|JIZPv2=f${c`i1Mr0|R=+EK$%Z{P4|9?0viF*b5XMj% z9nw08A!cC}Ch(@CSf`oNAFQxzw>iZaRNCJ?n4vkp0?jsd#P@#m#P^{AE;U*j5CaVW zVOTAcx&tSZK*S|U=Zbm^gylLOl(m5V@w_xHFCRwo>>2i$VhI205hxNSd=w8Bx>PA zChS~#)nb((G1OonQ_JA=a7$nWe}>Kn_&seVY)tUI@wc|QFPGZZPTb(q`th==JW!69 z5EM-IPQX{aHVy`O^&oGU*ut(EbR~+5DkX^g4tDF6w4HnyBzYwDVx|8(6>ye4rosb! zHQkFsGXp+BP-Kv_Ga(tK1JS*pdff@jB_T*X2XmbBDyPeV*|XgjFZdi}z140{0~|>X zN{E73=~>|;*`rEum)PA3twsjwS=>v;9OFJgE0%FIWv)@j6)<|hdydVyV>NZS$YP`n z?ENpUw?6ev24?HQ?Zj<1YvL=!Dv?k_lJ zW==tc#xIjp?yu&l;O!MsT1O7WM1$ctg28lrO(vfD#08g$8t6TQL%rfJvk=P+MGrBv z=lXsHhiI$?Pi`O+M0A>Ts~luPb!2dKg6xH2zh+A!WEd#ld;yjHnME&Ejv|OH$qU5g zVJ+u<0)m(?&t9z|iPoaV#JR$)H=zcXFG`c#_NtQ?4b1L*t^m%*j3`qj+(g0xFsJlr*zhom;XP@OTmeq_TxkTuW z07TI>qV1r!>gTgpj}+rk>{qbi?Ask!ohvlkk%!FIEJ}H&fo~@z?P_h#pRg0#%|b)@ z!;{c%0N-{Io75aOa&ze>zQfSx`B}75Aw}94el@KyVz02fZ^z}LzCV}ss+FaOBb{I6gI3D8VNkR?DbykMj8ZKDph%elLLLj<%fTu{TAXTb*KuT69( zVI<*`lI^;h(-G9_Fe1$;X{W;wTQA-agD@c(oguOr5Jm7nz1cKgsN=2v5Yf55rxDi- zH!JBygo=rqem@n&u8DR*EQJEglS+V(#y2PySBI(d^u`)A=;`lMEMYR5^@1HP)A-)( zz5}^2%fcti31x7fhDmM4ow6fF<;6J3o*7Nc>cmdfkzXB$%rG6bZ8MmZGkY?d@$UG! zvFgs|b>Dvj_j{Z73LQ&}i7qX;5pciGri~(I&j;=8RBuulne{Eeo^yz6Bzg4_fj8+C z+@r12?<$gd+xze&^X4%Pl4JVuk*oNZ@eeeKlKuGG5h$1NJsP-Qej`hZT6*;cse?}` zYYHseNAGavs7@v=#dPG+f995OZBtMQqM18ZYis3fYZaP^3HOU0a_V0rgyc8qUx$00 z!0rwPUG|}CpkwhwN%E+bCEdKWB>rfViS2qS6I((oJ(r@vZ61Fyf5CcEzLzi(;v}ux zfC=W8s>^xQ&B;tje2>e`j?MabnN& zDg1O+&{+vcnvhqbB;b?=%!9U)iWV@g7HCv9Gdm2b_rr%+`o+*sKRTX{mvtE&O6v;@ zlvBQ^29EG4yh|ilxXm);WPFlii&bnp(9I-}YED51@E@!}-0p0e>P%=T_6cPq5!BpQ zC<@Li6R;WkTgLtA_~F8lOmz=5&#lF@A5YPn0mE>a!{iqHngcX1z}Bspn(Jld;N|PV z4}0q}6W=HP;QB9kTrRX)2M(pJH(u-XM0={$ol_7KiV2}MNHCX7V23nM7|!JfAClL> z_R77{b#^A#^fQiw0}BVeF3zuB-L&%LyOYDr+TG@8$e1<&+v$ z5M7!@m~fRg9D=?CS|}}mM|i$ngP5K;`t8k`Nj~G!sM8sbs8(hOGwTOPu-inmkC>9b zDq@nu*_UZjZ+Yvj6M~P;&$gAQx5;igK*sVRbDe7M%9I`SAe_A|Dt8mx&ftM&YHl%| zo-%%PT-n8QLgeLrpSAmtDZr^NlfItiBV)}(j@m(MD!y`v>=uv zT0-i{DKmN3OiA>uBcyPdll-x!w=c|B62oZMzVkhW`68U2iA+8ASLwr9?{oek)u1+t z?&nPuDNYLRc+!|h_gr)HpWvGDjh6@{$zLa-cI>4N^#y!A2-8!fJPk=G`1Eg+>ZSYQ zH)6wExJ?prnMiHE^|5MQ*_SX7c$#f-`#Q}FHv*k$RwK24VX8J<{XMlQ$FRpc5i0mp zzSr~G-=z$k2Jnsghp%6lh~p|wB@=?~X* zk5f0|^Et|n_GD}ABdEY{fv$Tr0QJ%3pE4US1INr#aSpiS+t?d zf2qd*7l8m?()HiNrB3>cCm-+60|amAr^;VYmJj2tjq%L$f(_L!&RYN(*5RAv{enC9 z0OcMnaf8(d^%H>N@V{$T^frzT_}5DOskVhuHjCW=5x&$g=lwtR-CQ7&+778%V4`dW z8jiTpR{vtd+X?@tB5aHJ`}{nVlLpa|Az2nl{N_(G0De7Ov+XHA2T*qyIhmRG$sU*~c=04+(=UK1$)L?432zpzPc;Wjz>jkd;@E4k1+@;WQl|~j^ z?0+Or+y=OcRhh5y;eviN%ib6tf`kyOFMWWT*aT@rejt*SCm!V5PJu{$GC`nPIA|HBe3f-95{#&w{gtj&+2{(axtJpboTfj~rDCH}x-z!xYj04?*lZ6V16 zaqaGlNC__AjE6u`aHZI0B2j`1`PG*f--v)EKMz!oI7!B?f%+qZ4PVfO!Ld29AUx?0 zn^QNqU;S@-!_jD-uSptx3W{-b4CuZh@9jDymV!ULhk}N(JNV0gQ|bKE8PfzU!l)hO z<-U?@T$&&Le5PyMFD;>5EQX$6#eyk7Q=r}1@Z_xtZ^N5J&{ImoeHQQ_;kY(HYRj2I zuf?NTFGG89%oCHxDKF}u&@mxHus(#%04T31izGe#_ZOUn6g;KV==wKI1LZcLEVs|Z z@$>+^O#y0v1W*B0%5x?^)&Wm(ADe3u<**Gp1HXc={+so1P6q1S2%A7gJF74_Hl z`%_X5FarpK)X=C%D%}hq(gqm7&>KIeYc z?>uLn|6D8=YxvA(UwiNO`*po9ApiR0h?sb~MX3g$z~F|#de8Gg@MZRLY{oD@24DE5 z$8vva3$SQ5#OJ$!s||Zq`Ia6;KgnC`+(Qx{MM|6h-?#(|7pUl#{Ao!4-%2qd_GUPcctQLuV22PO>N?08}Ve*B{%}9y5C1?0Kptk<4XF~r4ht=^NSoFKhT8e_Z zmMSBa>@$SNUhHK+02;gfhA`7NFyjtoZc~mv0e9;|rF~{2Af;er#1aA{kUIpJ3}C)e z6LNxwOze+O(*1*0%FAP{+TKj`u`DtYxU_u0p_Kde&z}Ws}?4}DBnU6U*?asHYwt(OLKa{(|h0#}6 zE+^m~+zdkNlO$7s#)>vv0vDBUj!UwwKJ7P{sSCf)l$ohx^v0bV3P;tjHvgjs@6%Z< z$h0WIzDTI42Ug^NJu=NS@b97jjnpAs!3N(AE{>lpR$xu#y*fWjlKgu9rT=njncJ63 zfQs<13YB>VqXyY){_$qFXl|JhpquBUkMvD2v?Jz`jSI$`=wb%DQ-O*C1uExl;P-u> zImK*koLT_FLlc3k$2$ym2Sjr!#{VP`m0*D+ff&jvb%`ZXoZM?ac_sie6}dTp6o{WM z{?BUQKP-UNWW{|54L@rboR7I2@L$ggwC<;X%y|s&a1%(Gb}Qp$d3}#Q6ONV`$bsL* z`9J(FeIR5RfV%Nt6y95RI3~w&ZnWXbQ5cMIZo$O#*n%+wn0PxD2%T_Av(EL^Wed=q z{}GY%kN@L2l>#lP)sHGedNmFfi9$AdXXXEE=d#6+2jB}}p5%xKgx(7M|G&-x!~f4v zB=A6xSO3%F1W?_OC(h?Y44!9&#TC%Wh*50^y#CpP|B<`#7BDip=Zj}gjyA?A;(1VD zGh|k9XKDX0bo1YTQ2-QVul@(5;SVZPeq6~iu4M*hF5$r)e|1i9!4~rXae=E(&zr8q zz>^WG1o*(gYORcTNO4~9fYd;W$j|w*|cReL!Y^ zx@`yI*gvRd=7ymuY_0k}K0I;;3R?xTlWAM4SkKz~&b{3EC*HmeUje(prBgL{q0;hT zlSwWp01$b`-Ep-oj6A4|oVTI?;j3=52Oua$HatI77T*c2HM0o-Cp#v4QndF=$0<-W zD|D9!)HbJscvy^L{&m<8{~fL{qMsF;G6LLr9L3Eo8Uix*$z|CO_%iS zJ?W7L<+=srlTnH~ZNM?{hJye^IAC-2b(;aaz~l=W-nF;&r>4qs7*QO^=F}6FHVx@> z>G%uf032OyIrrrgwHRi#Gy<9uDY)Kp*>6K2z+Cd0giNL1GmLyX;DX6t?YVJJH7ql zV_urun}+bsNA}>2>@Kt)xwVI%xun5V2kJxtk?F<~<`l+jJyCvOvV`yYcp@J~fa^!- zh>_^YKmcaY5{ZdYy6;Z6NW`yv&6YL9HH#h-zAnJ)GJ!;|#W3Ov##kq&6AA-;y>r}q z$*-hGuJ@LmA697M-FYk-u{&9zC!agbGB_dd{rHEuUkd-tFBKK<>nEO4BjE@E!Vcb# zqK6i72K7&IN4ade~DbwCWTQFs2?6Q@FX)Rijp;YKZ+5;Q} zt&b_xho=CMWDB&!7iYp1=0nV(V)&;3soIo`yyver@4x%FBWNfb)f(b~7=StQI{YbV z(`T2e4&rY^FsDr^>hLB?MDp^(@;(Y$zY!pR#Hb(?L}%hNi*?cmL4=_$Y(G(IhL&&+ zV9?4ne4Ca#)Luq|MvF1&ba~u6`1JY8XmgW3t|s+0ttV4p3Te*P-3^u81Ctj7ywTJGcvZj69w6|a=w%a>hPlL|;J0-Y zlMJ8~>=7Pp+t(lu_~)>+luBx2)L$M8`l8BXyz!58eCy%}@fd!c&>6u4mG6|A2GF1Wl?Ai5lQuB~g* zOmdhciC2m)$wvjqojzGEcwP`+9qFWy6jMxyrK3y-74og|uTl~jB+yvaIbfzkgrkEO zA9mDk{jf2u6+mkGCm|ct-~xYot|%s6lYUiorJWCoC4&9Ld=JQfO9qL#=1~ zNo!TJMw4{oSeyBhTPtn^80#S?7-cr4xj!#Ne$a6KUkkk^nqkO7R?JntXv zfWh3De6d>{As4&U{A6bHK-%u}ScXC)x?3z2@|WpjwIcsO?_lxhVC=;xLo6Qd}KSOPVsfc3$7WRm?U0_bLzSlgHA&RUwrSSy^ zDg6705d$1PlTFZ&RO%Zg?YcmnMSXZ1w=vv&*a>+0uf}7fM#Lbx-OB(@BLw&Gq7ZE0 zPX_j_H1Y&+UN~yToIM^h>WFx_@Ims!UI&z|Xp!kE%4v=0F#W-k_7f#g{0Z)5MM+NF z2oi4>-i-553FUG#K9BGOl%#)zYjc_y{@BVFQ9L{qG|Fy7+M}=L=*{^ zv$ghrGu3}0NT`1D(4?Ev68C_wJ}l%a;zHqI;nkV)8mo1rI5SyNlbr4+HI`YZGakp9 z4Y(~8(sfY;yt>G}i1mMkjPX9p^X~3tW#Un)9GGS%#c+uNqqvUbOu+}(r)nZGql-LQ zt4-i=|KAZ;KsHKCO6*X5pq@62KV1Fx4M|B&54W1p!JMLQ%6%+?VsyJEyG0GC}S2?cRwd?-2Z zgf3-s6?-lC#iEc1mS?kM0yw<3yp6oVFisc$a05SIAK1dv7a`tHkWHK|&}OMHu`u>{ zli6($IBwxbcp4^y*NVG9JA{i1mm|j&Oh)_3M4U9!+U><)LxR|X=oM&rL-FK9s$21P zaTY?)JKga(FFJPGg_UK~jH*tP&GBySVb2BUJkIv7k@jMyf>J3fLy390mB^TJ?KrsO zh>j6n8j5I+vRJg#+~RE?&9`W4cm-NY7=qd(GfGOjc@o~DKCecj$<9@5s6}XDGOPCD zEmVLIaeookE^)m;fX|E1;lBB1|-*$3gjw@wbyN9`?<)8!@QtRbwi{zTn#{9{V z8{J_JfYS4@AUeGUaof{Kfq(??B!~TNPaKR8sE_uXvOmhv$oS zQ=fRmvLxq<*qO_dON&)F)0Q?iAtGJ!1R1q2{vaYo_yUZ)^5iKmP7=IN6I3oAhjJc- zl&`WLj0dbLyAe{@k*^qaf4mbU{$YJ+&<@(>_*8wH-Vk{BH>jCo2DFUvd9;5k{OBYuXLpu*vbi*hq%(g=_kk`jrMo2kZM71ULD} z%qpw+U`t*Z!RHAh-96cf@kQ||74G5-co`82yl&c_3+HYx+phMB4s7y^4xACk@&~0t?d!z(1yLA!rF&@QL=*42)`p zkhEeAC#WKKmzV+~4I(tvKc38xAm-m^rci2fiUqKTz1&MnVu9x^D!JLv>;Nu8E}9n>BfdETTf4E!2HERMu+%J)oNT&dDMGf| zlwqtO`)!JEHgVjKPhFIk`?Go+jlpTMlPQovB`s(k*nzpJ2yS?X>uC7A+?f7B^Cdx_J!UBZ>I-ESULeoM33vghnVcI(l zDn--x(k*0$j>);Y=v8L~!EHgqz*gk|{@g5KM+@`U`qrRwmaG$nKKSVfDnlGbVSy^g zHbo(B^Zq~|XbN$nx&`iJL8bO?F}*)Y2ki-3?Y*@bDz&783Olj9F}OrwH;#Mu?-DuE z!UE4@H8?D2CGUm4e0qR=Am4R~*GbU&_yP<07{=_}(O-9G6UhF!Gum8VBS2ifG(1n;oj zUwR0a1{?UFwv{HVJ~4 zn-0x#c)>W<5XaSMSxmy9I(CB(+;7wkj-Y2L=%l@2ABUV*2pctH5^3=-?<43 zdDj8LDD+}Tkx%BEH@N22&e>J=0v4{?@<3>=iCns@7>A9AE7N@OcAc!)CluTD9W##m zSsKeQbtPI8nW_V!5uv>gS0BC|)w0uf9dZ0~0Zsy{M+6+)eGPjBrF|N)j2j1Ny~7x$ z2?f3~l==z{z)_H1z7jrt7mB`Q6oe4}1dc0=zXo@%J#5`+%U>Y5qkwm9q>td%Z+%Cy z{AZ;-V=kNr*Bf#tj5Z8tSQo8W?JwHNuJ&>nSfb1I2WO@5*%Tgl8O9lz+ox3V-NS1M zwYWOzvI|o}VqGymh~f^Tfp|ikF50l#ZJXdS7fi)f@#6DzK`iqSSEZ9y0t0_b^gS=$ zr#Lb`56I0R^kIe&o_U2Ai}&%V=c35yICuzu_JpkS7P`pw^9?JCzlf2Re5w_oT#LRL z1P>CnaF+46MGGvcit~+insxg-=s!A3b`E*q0iusE8zFZcForDug zi^uBcGwYpxyN%F|yS3VCo~Je^v^wwB?a$A)Ir0#N(_%NQBtp*7J<JFW;rEGIPDpYG?Wcrs zmVl1$SB?zx>*uprJzj|+(k{Id2yfU(Rq$EsWxWd790@GUoo-k`go%y`0Tr5mL_^MS zD*O5<38|wlN|^2vqeq?G$uq$`Y9CPo!%6Iu_R{2DWU?l{&SxRyCX8+cikoG=TsDPo zm;;Oq$vK0L_gJQLU2|+-ud76Lqt8^5GbLb(AbH2b6h4mwCEMDq4_RL==ERL3g?TqC zz}amq?9)0V7h0H7Pa;vRf#a`6Zk{2!_ZR%bS0(fPxT zd}q8;8sN$G1{~K$P=4BGA$pV9v=qq!o9kwx(WlfQ1RXl&yGtzShbl|Q2QbMi_bO5Y z+msrxL+Qd_X16hUo~TY$k-{2}Nq0)U zBRr^esD-&7O#)Y8V0Ts2b5Dy=D`J^mjHZ=28TcMB@O{zporUV&tA4h9nkqqya@Xov zp5>i{_0w=E^aDb9co~CGkwLqRoBzv;9dTQ9!}a!Im_rN?gIPqh;$m_~tbLrA0Zfgb zt2nZ^*{yzVX zQNZe`UCM(6vIt=fsX7T!y@8mk1u~Pk4OA_u>qVF4{HE)A6u~s9IM-HY(L0DD?S7pR zv=b`*bCVuTy_(IPVf5Be2^w(LPynWJy(wI%086Ihl>w zq=rS)Xy~a!&pcMicz<>UE?K2-=1!k_ami+9(rb9j(+ytJ^)D~TFSVIHeD5L>CGi3y z%>C0@rvn->@%^o);R%O1?`3iP@jr1`Kw9ze$t{#`{|+P!q7`6rd7ZYA@yE8`G^(9c zK*QL9iu)jRL={Qp^}{DvBbM^m5F5sNhdkyl>w!RTuE9^;qb>t{=CYv+3k~``dwb_A;ST$ot`C;=bLZ#IyHvX_6cXQi`d&#j87Fp7n70 z^idPC511STSx2sOLlj+od^^YBX^I2jS_RTU11WR&e{0bDSLA>TZNDVW1_gqHBZ0Al zJBDrn7RG1F z(f}I(X4SFapQrA8wvRV?B#yCGkeN?yK>&hjYl!fyrPwMDAE2hG>YZXPS_`2cXu@wI zxle|F{lj1b%X#37fS4*JnaAdnR@MKVQ$PwL%4N~%k&<>?hO+6xRR70zK+vQiy9i`7C~E|N{qS*xSr`YMfM(r7DyWosX@Fk_~$>;UmF z57-tV*2NIbkMPYVfE8eNz<>F%#NY(>?|%aZT?R1ffQwEQ(Y)f7e>ad;=jy0Zy0q`V zx^QbVaa!3;er_M+(7#H~>m6R=basglthBK(UbTN0yuEj6m1WI)Ly$hw&>o|SS0C&e zI(XEixY;32QjE{0U;F`+wYWc?HQDb0&+-wq^PPxRm*R`5G#381TYTU0E+1s@D!Jg7D!PxpZF7i7{CAI$fOBY^MI-f|Er z4QzUA;?v0Pdnbx^_0oeksSXBmd94%6fGZBPGvfGgSFyUqe%oRFRuF_iZ>?%-z&@1f zJ`q3y9&0ZU`&N_!lZV%4tGV`gGVt%&Ar1WOh(=@0g|Eiqr)dDNeR2wVyeiSP1{qO2im9)>zQz!gF)tIEJm~?w`cdN8|od zqv7-de7zq~VvZkMssVYp$v5UjNwhZmt^o**q`bQ{H5XUFLqA#%pxe%X{g6YH&Fd`sr== zbG_ra@*8YHT_+9?i=Zo&3X!)NC9ljaP8|A{bLN?~Q{J8qJ7$_4du0}qoVemjTCJQL zT%|}Q)Myv&{dnj-Nfks!Z`1DlQYDH1p-Pf)fqA=o;ELAOX&JB8&I$;M2r6`<5Fj%3 zdho+o=gRIy9&XRe}8{Ko} z5gbAW8;U0VV}Agj+@-(GLQ=xwj7tDfqV^2(#SIDyw{-qcqsZOx_HP)QAdBc^sJQ8D zf7(MSF4VW85zlvRoAF#aCOp(-GP<|LH5U9>=1f&m&C0N1=`@gYU-A7O0cTnc_>yvf znUMuA-q8>CKG4zI0@_|bN_spJKV6KS1G@RZ#HldXC8ohmwG>uuAnH&fOX+WPSIa7W z=gQs4=l(* zB!!tD!5|T$q}*mkvNu(1luiN0+zn>T7>|1GG!d|eAp&N#!TN3n=@3+spjm_W-D0OL z&>ScJrr)IEA_QZuo#7AP#Pvyw0A9j+9C{1ngNQoaFMLtTTnl&*OcQDjGVb%!yop^* zHwbW``9NdMOa#iV?G}3xCMTrz|LtS{Fdr@A1wtW(CT4i8El|Fb4M6MfT?$T}R((@q zT27`c+mzVWzLY@NOs&5+P}0BG*qP*#ZsLLwzIx#r^C z*jO~PHlZ5x`xIncGk94RNjsE~cT}9mn28Y)C0+NFjWMmPAZ3ay`4*T!mJqjwh;h%d zz08pDmj!;HD%NMv%Z%}btO-Nce-$K19E_JxWww3RF4l7a;x|9@d!819mt@C4dWuK~ zle+}>1IIagqYQ}15Lvd?d{5)i-wY1>c@4+12lEZe25XjR4d$zoa*C8{NucbNWhBuF+s)?=lrSVhv!B)P?bOfE`X+}MkfX&hSogF##vF#?@_5jk& znu&yxn9;)X^XnJhdrjHZIa^XuMlAJO_eBHPc^atBj%lc_t`e?auYGK>^SG1g*dwHT zR!o!APyb$Q7~DGJxP+Wjr#B3e!*dth5kd}wV}qqFVmTMIQh!_gzmQc=1^=W99G!F` zV)QOA9I5tqEEkGIHB4^@nuPw2tNAM(QFFB;7a82ZJp*S~Pqg;BpLjm{WVHHx^1UjX z0SQ?+>WwD3QzY;9f)qg2ACEt@o~i+l#xeMAR^ip;^>@KEZnLS(il-Aohb)_9TbIY| zA8M{_r|2tZ=(V_Bqt;HEYSiNk?^{sR;aoawGsv|l{-h38AH6c-+P!jr+HBb;7Pt1p z)~d57ab<}*IasjR5%QG1pWCr-PP98);c)UXJqUdhX6}v)v6n60@^5*cbKNGVWlgMfsO+QR-RHDkB%C<7K9By@qVc^rl!A=1)dbtX9E_txliB z(++N54%S*sKItI)O&@`lZBP~u*?w)~P-zGCd@`}!J^OdyRM{oZ(p&lF{^?rK@M#o3%w(L9bKe-H9jQ z1)(oO`*|v)G8G3y=$^h^$n&WFUQqF&)qTV3@~NwEJFM?x`RVxOFerTwyz2er{Fm=> zz$HLW`O6j1LN-=qXH>OWF=-V~F!S}VxPTHsxyF>2Kn~DNP{7MlKUW7EO$it@NOK#*^7G-olp&@O-k9&L)e=C%FRyZLYoVn$+WW5w(wyrv&3jN`@5&R&5CI%)X)JRJxzqg_t^?ZBy?s9EYa4;{;mZ9Ujo_2oERJtJf=Q6KU#s$5b z#fx;u;j|Jgbe;!%bAkBbEq%Xi$f$K)EJ6$(xOsbjYbBA&|I{TYr*jg%;PQ%BrJ^od zN?0mEMUFpgF{5!pv5U`AOiK59t~_w=Z2c|q@oz6q@m+KO7?1j~Gwgnj$;?0M^;eXY zZTs3ujZHQTztts%8X*rrD!>5NNyY1}2Xd_~$0zLjfRj$JukyWA3{>W>7Z=~#iDknb zb(-t{_}v=1KI+am?-_j_o}QiHvFyyRej$$Qg>MgeH*EL#@3z;a3KeV)9m>hJ)SQua zB+YPLe0|ukc`QdJ{>qS!qNhi6+F1UFq)YS{|ixNWhQ1QufLPle^Vz zl)Gw@9KhN~Aoxmhtt^)FuKzkGePbB&Hva6kuzM`Hw#!7ns2%whcXOJy;13<>lWo=5 zulTX)9hPIPyDz$U{8a6LqpRE{l^G>5peC~PVBL}{((AoHYTG2ffQKnPQsnR0&Xbm0 zs$%6YIsKs8AFZmcawV8|tDDc$3F+qKO6RslM<(9l*Byd>4 zT9@6j#}ln4HGo=s$3Lx={^T%WA)Z6(JDXIRLoiF$1D6U-%qXb959uB04**19_nX?d zSxutA7O=Trr?ngS9H6(mQ0)QNV7~gWm(;6k3ktK#)Ah@5mA|YGCt41c*_)etcpl!| zoOI|v0CH5)|2knlM@Nt`=mCin2ihD(oW@uYC#j)>Ho z*0{l?uW4E{ydXFJZ;r}Y>~86f_nU<-2T+6zn`QQ$=BDhq2?xY~@#^R-y`#14O*>Wk zdwWf*!pmRSRcznx+j8n?pTl|2Z*SAJeRW5H)PAc0EVAT_<8{~ux9~M{Po*1~Bvt=O zi^yEtarS{&NPwFkm7Sn+Rmg~45lElx9>gnFsaqIe^MG(8X^KG(N#rwT_g_@PHV+m{BNm4`$sF++!1_QAGAovOT4kB(pe3NMKUYg&yGs+gAos(ST z@#7^!Tmmu--WPU1()*17Kl+!Tgqqkr1rb(qfDanU0DMDv`++%>!DSYE5{n9EGT$1) zUQ%+(<(`M3XxQUQHq1but>BB<3DBH68T4IS&oyT=P<9N&d1=E1Ov3_|rm8CvyV$dK zs4kCQTPDq?YZ-n{td-fA_*^OR0`9UW2pW3SQhljYp*943AFG1#8(*hW6<3rAjqICU zw?!16?Z?LikDlzj+8>omPm}v<@B`f}*Ed5vOcTMlAMGd&=*6lX{Ad6sxlQLAP}-5% zNSNEE^yF$OgL4Ndm?2^F)1tv>gl0O|(R{RTjq#A^d+LI{lpcdyy+9+wl(+-mgZ#YO z%cWOg=3$%+@2vIQe_reo;5%N1AKASAlT-ARV0Rzv)XDtQt9@I4shPXCug)Pqf@N6A zPPA`-?{%o>{ImJW`CBDNfO7I&<^XQ`!U$>_`ofdP!uwCnLFJA)tsFskPdQsZ(~}35 z!xh<{M?JkR2Kpz{QAC1-Zzrlg5RnC}8?aFx(=Fd-fTB4e(ifS2 znWv$siyt)6-Y+XkSyLHJn-^8Zy`r=sCup7yp!c8ty4<{q_x5xny(#ppzyKHkMn1CB z_7UHj$oArE*Cs6={S9AOh?F!cn;N#UtBS8x&|peWbo5O>>HEGo;N!)jjQ^vKONu@G zT6n)CxAuka_|# zxV7ugisy=!H%)yCC$+t68YCJoay_VePuIPei4XmG%?5NTc(w!*USH0Q%cflPQCRzU zrBpnk?|_?Xv|VNsF$L~O=$QW4Da_R473iKBBbUUNSUP_@GdFi3rDx3CoppL|lg2+^ z`cEdQEzewZy14UJeL>Op!vmkN>!c6T=}7l1n;r4ibWiJx`vrN~EmFFVQ#ys37dJnL zh8~cJQ!5RuTZ@^QDkF9@v<_Y_UN*aWPx3HS$=%Y7i4c6$An$ew7trXk+-iF934-Ra zW8M;9PHvcJpQsPb72YFnvKXrUa3p2@;bfTF_Wog{prV(40ROqalRWL>t8O1Y`2s4N z7c?5`yB)x7i*QnIP@MYP|4MVX|NLSky}|M@=v|VaiJlT&@Z|_~t<9<1#OFM-d8Jpk z#rMAv1@%xB^TBd{r#kkMZ6^?h3d&7uILEZ(Mc8)IhP_$4uaHKoTszLdXO&eak*61% z9`}8jNLFlt?!p!pE+aZRRFSXF66~#SksT=$fN02{Ogl2xu?@bLrQ@{vVJ(3n{c57@40`tA_h&|Rn;;$_%CF{$o;>r`s`o)Fzr9e`1$Tx)N z!L6IiSABay7r6t+c8se_;zq&~^E;Uo#}t~v>XMqHodRnR&%XMjT{7<-e>=8Ik-p@%*&md#^$*0_$50c>v+&)o?C##1| zf9BqETED8(d_uN9d0l>3P*m05yx<)sxkkL3+mOV2B=;luDxfNwy&e0Aj;lI8$uun1 zMRM5YXE8^5B%cIePnCyY8z$ zLj>%YW(dKUiC*Yl*ZRZJVe06HIErS$X62gVQxRZTa-XP5d9)lpER0<6PJ1p%H6?_* z%+iA!5sNDKUL&cXIt)5P)-_2@d`);b5N=$pnkw!*W0e;3fH!x6VS;P{)gDu>yEmOa zq@(DW5qadcC=dsZ#w!W=*9Y{iu{HBN%1Xh@d{;k89!pYCM?B@QsjfCPt?jW7^1``Z zQT8ZQ*15yczYBS^ybZPyX@W*Cc0vS@LApH-@u4Cv=78xH0F6iO#EIc%QkMr$#Rm8h z0f~!d=p@fQ(8)b%Mcyyct7bq&WMC)N&U1`w&z6FFSmD^nc>UH|KS=ne= z4*Ngfg%Y>>#5O!DVXKQY0Yliwt0JwU!uU4` z-Hx=rE(+rwqtcHicZP2kf1FbxaGS2C4#-P4H$TLcd!IxtzT$c_WT@XCYP4Kq`L<-7 z4Kld>qcTlGIN#lI%e7I|5&u-&XQftMVoB(zq>;CAcCWIyOzNx^U##zAMTOerI@{&_)x|bO5G_7M zbynI9zA7`H4lWo2arsjQ{c;J@gu0>nf*fCqJ#o+!{bk0bASaer>70!kWk7~tFjtqH zMhHAi_b^7t(WYNDc_e>twxiZuQT+Jetwk0@ff09T=6XwIUEL*0>acZR&^)j95zB5y z?TS?5%f?BS39n!$-N*Pk&f~LwE)-;pjr-90^@xdh=aaTQ2*Vy)qEy?)03Fz(a%sELrqVADYiRG=gy0Go=xiK{zU*Y~u9sP_7uq-| z9-$ppxPZxd9is9;ir#R??A=o>_GEN(T{%y@siw^xfe6j^6S-;rF$15c4bv1_X0BAV zhYIQiMrh7OzV`cv8>NbAg1$Z_Z|hFg3aH~ajt#i@5m}@t*S7Ck)7jW^%V2{r_ojhR z*i497Q{djlO*4ZzE~mXZPO?+*WO{qZ;4xK{L$ApQ(}Y)PYD<2s?+hz{i>zBKUg^V+ z1x1x!J=@BmoFh%Xmot^)a=o?)Z&MXrjhOnk@p|~HwKv$WuPZJD)(2KNy1Xxrm#C&9 zOFQ@igdv9QjB&eV>J8>gk0qIj2T@PhTVopzUXL7Sqy3D0RCVYiIIawa-sD$QyBmD4tQzKlYZt z{du5wmdS?brcrQ(%2lKoUi*~&csR_`t zT)pA`m-PEG?%{C~2dbPV>mVnoDjVQcI+gwYM}jGQwDT+j$`GevC6vlJ$F@uTwmY{& z32z;pd3tgE@I-TNgBhgpaFO7&Zu{ka*arf~4-cS^ z<&?{gol2?ghK;|rU^DiUjB&cUp8T=TSodY0bk(LHg$TA;!`h zlWj-u^ZIC@U4HD0cNoq(i#-Zk;X)pl0|bIcABTCwDQKezI(~PSxwN^2#ac5j&r^sY zv2Qm*>~9b{@JL3H9pTKB_vsD26!hgrFRUhC4ZYl?A(*+hML0Ppvad?e>0|j2ZMel# zee&Md`tsEr5z21PO!D8C+K-RAG*gcmpfIkb+5Q8cjW8+ znn8(NQCg^cssrtqks3}dA9aK0;dlABV{>Gedy2|$+k!*A56MugNg`0x@V<@r`nmt< zMXaG%-z~BJh*q_xhO?fAJ2g}7dEVLw(kc3CJMY!zJzMKN=;{{S`^rvay$DJ^LXZYx zt<0r^U6M5&n%qZCIy=w*VnYpORHRNVXUbq_Y=8O8Mm@GBe5X;`8{8-!XEw_V->Z+y zZpTjt!(`&PI=pFaWFb25vFV!|y7S27VpXg;&73ycy_3G->2;8|^fHKySXrM%(-`p; z2TPDpqJx%bNKtBU9XH2ZCh8NmCTAU1&euv3@n2mCY)!4=#0v>-qO5}-bVSI~!xTCE zdL{GDsM*^k9glFt;YJZt&MEjt8?OC6rMI$qzodNg>3Ud@NuKKT;cvX@P?r6B4?8Ew z=ZruP`wES>;l0*dpk1jRVT^DB4YOD6!-c z)TAzZ=e|(sW`x#LKN$ZCbM(0IQ?G1ny!;VrWOgA!H@cc2`OfQ%wJPf5`&msL9zrVw zkFAs*RU)hlZ4;=lpJd8aZ5j`jIw)xKB_8IfU)eL{IB0^4i0-p#jHDADNpyUk6R9Wq zJ%%Uv7^@(CZPtZevBVM|J#Swaag(^0Z~EPd^lcd}k}N`YpO&q!OR9)2!D#G##Vb}U zzT^A>1Tl^|(Mf0BWcL8u{$x!XYDAW@-giC+(WtRXIQC&Hlui( z7g3VLS>HJxwXifL-FkIKOxb*nrMVK;$Q^MH5Bp?{S|-zpbu0np=BSjrc0T#U||i)v29s- z>gUpsiVWou3bh-JGMK?d@^QFKpM6=9`(u`8q|M1FJsN?RwbK`q_F|WV<-l*NguqHX z*;jqTs;(tupnfj$y08clARgh;RrPS->{NP#wV&N;yrX*b}& zfwsLJc9Ftqc;g#*rF=9Z^IJFe1&(IthiVPQgXrR0+cB9l=X;D)gB(RS`Y4fhDT*-# z5|Yy?tvV&2vEl{qTqZOt67kUdZP|RovfzPT{Y&wR^qT8UjC=h{57sRhZRxG{Wl1|S zXkfh?KF^p$MvP!9MT|kCYHw2DbH0y~yN73-u4b04!VXEKXvHy=Jtl_ejQr43$~z0l|7kZ*Wel zUgpycg6iyZ58~_K$cN~8QF^lEfRidq?`7v6!!p4!Z8tf5OPWLy^j$Txxtln|tSuegiNmjY!2gnbnOU6GM4wogRB<~)7 zlP+fdjq~wmUzaqc{UgC%F`9UeJ$XZ#Q%33M4P1WFJl|~USNxX83AVEg6?e}5Y+tqE z_$=U7vn8~19#MaK<+HIzbx4I|q69pFMa5fs%L;4Hd{^6^+3kwWzWsr1#w}zQ{||l| zG&#}^FFh~qog3c^i2p5O{OtP31xmbA6_FeKTK5Shu-V7xbgX`vo)kV2kkt%sJ*n`v zan6G3O@8$p$bVYHjm}aVK-R^);uL7XLME-TA^)qKtuidb;>KR?H4gd@>q-$~ulQ-W)R_s0>7R@AVEC9mfWk*sx# zna2%AO|raJCBhiCQo2fM-`F9>A9NYhn$CD8%bO2n*xD)M_OA9bk&dp=4LK=N!#uR1cErj_w6o4^s}2@s~S7wUlL~_vxt+g zDE5%+7^2Hj6#q@u2Wr?E$M{HzEA2}6@3fO&*&NJxM7?p)h6!;MH`e`L$-L-O#Qugi zwpH;hODrmKf{ccOH{JtFbIr)y;rR}aKAXP-X)!Kxh{MSTybV-XPs?Rcg%k3Pw-2=Z zIk!xtZj_aN+jienF08GTmTkFe6^kC^gt9qNwMuNDTm5?BvDt$ zjz4Yyn4fbWG zl0C|lu?>!8LKGyr5STt;dVUHDuN3*f;}Jvo|6I|!cY@UlCp7fJy)b$(=7T(+WtnQV2VRPZ(M)#GnrA%mpau6rK>;!szJ}+o95)XY$Z*3)5)r! z=2ZD+pbtSQuau&xj*;THkOwpmo1(YDF1Z*qbvSa`FTT*R$c&l7qHS)7sl?LUsf+)P zb+{%uEVRo?%z!4tK92R}bh_c*C3emH+p)oQiQts50wsxsU={Mf$~IDC5PSZ>Mvnn-c5EsbY*mZrxpZx0)z=`Q}S&dIu->`gvi`2EgAH%sOO*W?r2|5Q*n08Ni8U zZ60z&ER4M(=vowpt395%p^0Zg8EWu^$AGN-na@v0*@tZC;cb3hXdnfZ^hwWPw8#&2 z93-||Yn?P@kbpF$jE4_rn1c@|IRo}=E{Rz8!^6j~;8ur$ObUl$nw^DMLAV^rbVD4H zSF3Yd_s`}8g7H@N8!F(o*3zeJ=6(`2O-+0&bY0cUFK)bezKF_Vd-2KW29jpxO>y{b zq@7@j+T_chq67C$)(a88j@#w54{o4`aT1;5j~+L=uF))c_d{&(u>9anpPz$)!IYjb z&eb1RuQW?y75FXl(5LT=nPUh^MWiWxUhGi4V141lNXy(rZd;<<_&kDQhE-HE*f_(B zMlj~lHkRa%SInA(&>QM3n$Ig+2T%j|#n)4Y zsDz?Al>YJ_s-uj>K?{s z9)ezicm1@n#YG!Vg#6)9hiW{wq zON<1a?GRj#1oAixdm4*6lwb;x19!v#76b>mTgMkIFZ!0jqn%Lw%L(Mpd0_|Cg?cTY=TyZlqXzy-o-B64&(6x4s5sm)L`YfvphpRcE4`5UHDSrOlerJ~4;a?B#` z{z4E#by3)aSPCKc#AI#8y*mP%ozES)Y6Y*Q@qOMO(Jy^d{#6@GQ;||XWTSun3_)C7 zQe~`nIFyxGily0V(0G$SYrw_SYaA}fy4dzG<7QeWUrH{J{Wy=~Heha~4B{f^%DekIKzQa2BB-Wm#T1z<2y)T@o zzTe>$d&`4$TUy^Kw6680eymHhYSfQ83h0o)-zzA3FH+v@lhjE!brsM2fJCIYZiLE& z1=WwQn}QCyGu$WP>ZHz1w^Hd3B8!dh^|ck1ay&-OQ=?lQo&1w=2j0xM_=;IYnbo=v z__1NNRh=G)y|s3JwJ!9M>4yWtDcs35)Qupk7VtKGs-fP`pkLocNrT4ZKVO!7;&lHg zmZ_A5;b<0Kyi_$<RO{>U!sR=6<_?9|ty;F*TN4 zDQCuP%kR7Wf0a=eZ|q)_@^{$6{=crSJR0imjhmswU?%&TeM`vlCS;2tBuPTn5Y-G> z#@N>uTgV_u8bU(083x%YBTJTK8%vg~V-RB*jrmRQ@4W9hzu)uMJ?DP!bMJG$_qpeu z&wW16<3lhLuX|mR>01Wne&QO}?o$`watqRRvBMpDXOlGKP&sbQrD!{E68#5CcSUi39 z_h(G`8=a2_SX;K&$Rmt-z{F^n{i;To*cC1|O2v!Q?t@-|MpfJjIZFxNT=4ZIO}`qb zkcW9Q+zRH^qB^Hz9s#ss(n4~4q5%Qay0gZ*OeM&qQ&kIWQB$z*;8w{Y)jFKhn2EoX z4MHXbUxNPd(jxoM=Lhnx{#@vM6yJ;rvQ#7&iY&8 z$9>!QY08I%%M>}yV%gfrcs-Ea`73>W22zFLtFb3!Go8{57VSHHVw6y(T9zlrk^bLW z_7Ndz$Y679K8{!+`j(Y%2WuS^{BSTdZ@evbFj^-d}OaRiV4=cuv!P4ir%-axByK zI4?U^&ZB)kW58onI8hm3>86XEc1zq|5MfA;@TBcR(ZBC8hT(a!7K@!}#PNKCsG3Jn zZma?Llo;FC2X1a1&)-lP(Vl2^_4JG{L&7NCeKfl|F9$Ijr_s3OT z@*tPNjb}osyC#HK)(d|jY}!airzG4Q*KVd(-;+ebR&1NU8OM^2hL7`nHuzJ$F5_2A z7mJ=nU3b~K6&A=)VnMymysUJdHS)bn?wWqEPk1}wy<~TD3t0Cs30&Qgr-(Cj+UA_B z$Q{b6QZt|*Zd-*jmq|2C;-wazY2oyCJDbk*`Z|TNE@$T{N2-~SfCNn3ghWSaJS)DN zoOG1rTdZ6eh(@nf1omM`sfS%A5uk|9p#7I)TG3X_dXSx!GdqI&+?o-2xyl<|f_bu-pgxUy=5%OQ8aPMMI)it7I{hhZPr)sAesf~bgk$4t;vtI;G?#F)GGfo~xbeDft zzJdtsU&i?2imB2K`IV*c+DJfQvWTx#el4qgvIYANRjc^h5(LOq z=+l+_L0g4;vm|x_wlJYk9!esOl%SblXDnLd;1RObtTI9tEP$;Idv<#!uy>KWbi_?2 zL18625f{)TNo8?EcxxCL5CucW1{qRkMwTuaMzT$^oGDG3Y8b6m?tU%gkgFz;bNnDE z)-e@d@ z#4Wr>(=YYnAaTaEqJ43&(;81akh>vdJHI^A+DbF$BOF?ncs|3E=kPrPmV z#u9&{wOFErHX;j=zJELn*Uu;nI@!-;lr0bFh(f z6gD2Zs-IDZ0vfHJfts#q(2+sY(6AC)v7Yg9yAYabU3x)$`>UQi9-9895NUJK-td!F zdGqA2*@ef}v2vync}!NQcZmtJ4}Z-(#iXQHV=c|15_-k)CusN}Y>- znxRJ&AV2A37kFgNB90|j7>a{bxU}Z~J@hjTx#D$u85B}7T$%}c#pfKH-Apk64v1V34PbqH(X-nB^K}2mS_j!^*(>XlcmHRSfVSIuF?DG6^v*H$sngAo^24~pZxqwl6?QjciG#OM{> z90YNtU6GJ8-AF47p>qWqzp0(mqa8-^p;6VAHy~8cSy{#}JUOi6`x!azCIp=B$nQm2 zJ$e}0Ms4S^o00+dljSN>h)b^C_IQN?%_2HTq|@ zveo_XbJ?dQX3WuALAxCwzjoSu;l5}de6_gmIh)4<23~7)K3F!t!7$FT~G*S{$BS&|` z27`SU-uL~!&-=a4`^7(l#kK1f=Xo5*8NY?;YO9bCF%Vt5c8x?$Rq@HSYq)&ZuH9%P zxCy*+C-46HwQC=*sVT}mbvNB=!moPvyRK%Jc@g3kF+F_;>lN=kzNz(KterBqT$CDg zo8ZP1#TZg{^>Pap>g$ZpaPv-0^KVh`JRxRQqL3HmR(*U!m11RXbvYIBVQli;abRVu z|Aj3!q zTU>$MkWY6EHNQOSXp;2FKveIiN{M*3->GrTz9$Wgh5g>qnov8_0@qYC^2}$q`{}pS ztcLlo5AD}K{a!Gs9GLapsr1^Eb->^`rb3(RH znerf7=hXpcXKR5TuIKKI!rnQEidVKM!9QyxxaI=>U`sPz)Da&By}&1(FzOi7`9Zej z@ixY6Kfo-On20p@!kVFzMSz(1YikQ3 zx>}#`J(1&MmW$~Ruf8>GUd}O&-gh9W5@u_bb{A~)y0Kz)NPALD2i6JX$80{_OwdV@ z=@s@aiflMY{JB%`>GW=Tjr50=cLv2L7dsWH_v#xqMce{ZBFJ{*D5LnnP1(#w-}HD- zFB^l1vp7 z%ESQXNc5c4speVppowQ&`iYeO>j9#)g^=^)!vza3ZRFBIA;Jg)@5+SOTeL2V8dTUy z9S$?@q&8vuoTHxJq2lvqD8Zmc6zR2JU##fV%HXINHkRMLK0aOhg2Ti)k~`52tX#_N zlf>)g=5spy&0(c(+@#^Ows5NH^`N3Sds&(3;fs0Vff>Y-`FQQ}$j-JL$ISM#jmhQ& z{Aif$&wb$k`q?=Lg<2ls9$XN3N7@v+hI#GVFwXdM+{Iw-`ETT5i=P9I3K5%jqC6q3 zN)(e*TS!DNmu{fl1bHO%%FDDl#pqn}JiN}H!xqNMyFA!RvZ5ZP`<%R}vQlDyH=6@{ zIl(#pxu$NnQOjya(tMF?P<2bxa*PSo7P}*DR=aI`-Y6tgu5<6X&p}Fme%JcitQXW4 zQJyNUZVpFA?lR-FI%+y{OPn&ztIxdXHSykN4thUI!9gzO2sQQo5kg31Omzra|KZzI zXoABQV7=^tVqNWO^KpdL%s%Tn-jeK-Mz?+|o#CCaV?^SUKF@!twLRZboJipz zytNQM{F#-jwYE&w)Hj4z|2RafEv3R5z0=SRVZT)T-so^)(LngF=IK|<)}Op4=M&fj zj>jL1pR8VYvniJ32Un84ihSSTu}K$p=xEnY&`#MNdd>i-){jQn1X`OOm3KPKun!F+%Fuu2;)!!p*t3 z```;T3%`1Q_iTN)hLc1)Suu{r@5}YT<|>Q0kCm8Mn>SuaQ9wpJCwfMx%o|R<7cZ;V zQAq|dzderb6)+DQg zLXIdgM0Cv(yIbf>kKNJyn$MLmyXiCnOSu=aL}3U0(h(_-_{)9TR=(fOxy|j_nA}}d zEEq~3(Rv_%M`|wn+vgO2<#hK??1s0s&xW4;!jxB^c*{r!l5^=~`cs&rDR^t;-hyc z7I1+H%*Ws@(4Q)~-)VUW1t?luX zEmzS0)witohSYH6j~yQzuZ!E&!Q&tb`8;6epnD%7KKOTO_)(q&nNl=>d$kwi;0F#o zRA>tjtdjhU(`NL1wz|}`Qlyb*p#}2+{Z(qA@w-*1$ll#ppZa5#Wb4e^5iiX$v(V@j zu~}kkvWnHzrfMBSXp1-~V5HU2asK-|ltiSF)GI{t>L9jw_Qs$QD%OsSG!~!L&2)`G z^1j^LM{i|&uI>emCR!$7Y_9ICY7>y1ssFsJ43_7d}r zg6&eOavXf_owtb%PC599*ng)Q>3pT2ZImO4hN^NJrmb!7hjD*0qhK+ICor6h5QI;& zeOgX`*vddttolq_Yl1iu3b4_{g%4N;#BlvRJ21OI6+^)%0geEm<)FCZ%qW)H$Yfox z#3Y{}y)C9dKmw@M5NB3Ju8TeU5*kPGUiLN{?ZezX$h6qu>H*C^4hGp?w}f zc2U6dREV=8p!b79<{-FK;EM&8PAznH+7sns4pUG5T?Sgf@6EDx3z{5vSq5~NsBgB@ zD<|Npcm(z~(eWA1b%v+39NpT8Z7uoC7Rb#+$#(@h^!sZ<%q1NCM+7TY=b{ifV3KO{ zj=!$63ch=n;V~_C!m`^kZzkB6szY#45HEOgj57ZvCVUYbQLS^%c87Z9+jTLC%W%3z zlMWu9HD0#yX-JJYHL+S(Eb)#RO1thCm1 zbD}qHZc^f=b5Z!_JfFQq7)bxPZgc;{j8}*6=_ZU){>4$IM*e*>)|2ILY^KJ6{ zKxrt?KN8Yvbt_m$9eTjBaN0g$#ONWJSqpsbjW_`u!+Y5xckejbr(Fo@7YtU^M2gXh z5s4`Wy;?bUHfh3VaW+V3?q%{0&U5yKWUKFywr!!q-(8m}4Y&p$mnG^QZ@7%5pB-4$ zqqynMA?cXtd34JnQ%}*#%nEOiCUD{8od%xsxKo}xEiR2r>`&F0R{uriFHM+EqZ3*1 z#ch)35R8olYhc1zeUbGT6f9CxuIhJzwLROB;c;~{u z(EB*>gIuYAL7NF6{2F#I@FDb*Y=L4Pf9dQ80RyrlAVG<_2<7?jHhV(8L2Q$V3w%!zMTo?wAT7n=OYCXP|~?b9c=bInbRQtpn;jt+pzB+1mi!uealgOCG{!P}=Z z*rIQaI*U@~LD_f21p)Ky)-mpFAeVP1qUIx#=lEl;arhwFipR`tB`Ht#TXF|esUDhd zG%T3tsH(j;1I84aS)1s_7PzPSN2^%i9*(_uTH$>hEP_p?x!}^~tv|dGF7wi!RMY(u zRs7L>hmVgZ5asu;*Srrb+i0E_xh#1tbN*GXVXu_XB_is&Z(m|BLtQ$V>-&(zQYRHR zy2j82gX5{?hG3II$cB#X20b&5gmTx%sG$e9{{$VOgdyYa2=JG$hdi2AYjJ&K&pIpY z2fQ1zmE2D^?$&@fCfWURd-B1$u}aq(J#((5i{=gs$6uFTZ#*s-lC5yncaJCUxQX8U zCSgaF-ZxW5Lb`jU6Q~*g_FO1>>%7lTs@_@W>VrRrn6ZwGzL4nU8q0>$V9_fB?zmFY zfF0jNiO$Mf;VfT;Iw?p5nL)DO-*0sLQBNwOwFuZ03(Q8>Rt`6&90+Lv6jUUR!QrV0 zJ4gi}LX+a1<=^}Lm^82Zu9sJo>)a6w9qH8vwHf~Ud>?8>OFRa6Auv>?gWZsrRc2Z% zQ%0JJFAi!w$WrsFm-fYj$yg4RR=uJ47{qS|0Mw#1C78 zX0gQ2nuvy)c>#Z@w0A_wT7i2a%~%w(B<%o;$3(M_SF@#6hi$u)T#WL9rB!i%*Qy$b zHvX>G`#JB!$aBQp|8QX$QqXTiv7`xyppQ$5Ab$dot%`&XQZyIRR_ZEDs_B&t6( zgzW7#R~+?sT@8%yO=ijT7OlV~cbe8TJOE#7f@oKuH-cik-GhEO_!01gcOrDqw;3BN zS?l<@!!i~(oKqvQR@indFkmK4-1!|rNOyEveBVTsjZmweUBoSM@ww&-e=UC{SG{y{ zvUA5Bb;eiR+N6ZELzO*Lg)gRp=sY;38q4^64)>Xq_xV6mQ_o3G)bhr(-=3sM?j0UxXd2G)k7(ROs#Ts*_I3MhP(yDe}ic`v5Mm(ptBndGO z;93I7-ZDB`=t>lqF=-N>t43Cr=N2@4ux)nHpey@<_pl8CvQ(nrOT0KfWbZod{2)1# z5VnhG6hm{nhSN(h?J7th;yqQ{@J=hW4DY1v@2SU$S*W34grhh==Es8v>N( z?b}CgD=)=~cUd1zNrPoQLDfNW9N%YC9!Y}T^&-6Tw)vv!YsHY_nsWP^#!t#Z+BCjA zD1_a%_8d7Yn~5ksoc0o;3f;I(^uD^gao*Um=yA-AaG!VqIkWAjndvV8G3e z#rHng{obPS@7>bnug^=@S?qsz&b(-@cxJJ1K~!a8=phad*Ei=~S0Wuo}?Xy@n26RkoagH$4fw3D3A(jE1SjrhxE3>wS< z!+9uikIvNHDWfxClS4W1w1Zq^xAe&fL>AlEijp(k=gfdUTpkfi+I51?$}R< zXpVEpq?%-2zE>Z7yz3TS1sVSR)dSqTt@MDwYDb*Ol%6-tUunWG;s&5Mhif4-cXu)v zK9md;I5W?3#gEl4;1K#TuGT33@S;N0HxKc?IyW+GIQzP}CE!sD&0%Kmc$H*ozU>mP z+;5UJ?zN0449g|k$CX`>qN)*0^+yE)mz{YS9l#ELPIq`Ia?FJ!s( zA@gRWb-w))`T?ZS6EYl*A`GD$BM&UC#k|#E-0{%G>Y-0~KIJ32;Ggap{@TH=smMz3 z#O8@bHrstYA-1kGT9rY!K1fUL#d`m+-Jj1*l=U|C`C)m=ewq}2OP<@#UaYZbtYP)G z!A7SMUzx4}+vDI%T%Lr)RWff?ydaP3a zEWB&x$N2P{R}{Q`z2))3r9&r^q+wX!(}v3LGD{ijt7IFpcjq3Y2x9#44xcZB$RsB+ zto-qu&cA^#I(ITJy+kk(bQzm(#?fu8_ciloJ~sDk)(mkJykrPz1KBzp)6cW`po-hE z(XwX|UX@0&=gdb2-Ql5ejksCT1Q{GmnRrCBKxXa39OT?i3>COXL(zPf{fQ)()lJCU z+!C9HAtV%uwBbfH=1^6#<~gm(j$T`}lYEc~Q! zxUk&&q&TSrM>&5t2#?_dky~`B4-A%hemKplP2ix->a}EJP`Z^~>eDdJmnK@iov~|m z?hKKYN5Zh@E)5~24oB6iIm1c`^ZkU8van)ZM^`bOhwvNOcq*Y6j<^JEb#lh@EUiw4 zPvNaFip5FCU$t}z%Sop0ztcP=?m}q5_ul%JUD96;c)FF(aC9=7w!U#(7t_1uVV@Cp zbePT~LEg?Pk8^_tyJ!`w?Q7(P*%o{&l0#;*mf1AYYr-!>5hGE(`_8-=Hyn=BSr5h_ zu`j=nV*#Tb=nzK9P7?qmaG&cdRb#e@e(BCv_nOa~t>h!!j0725I_T_(7;G?E%9`o= zul|F%t3Uo(z<_7SM(3^} zRNw;$9d3&n;9OlY6~j;0=k=n)49&PJ*w_*T_~SX?^&md)lzdr3?Tu|58_ZT6ia3l- zZQPHNTO#SC3f_}f3EtGZTgz3dg87BcX;ZbPbVK2cVIJVP9M;2-} z0DcR3>L0{9qkQHS8!i%BAuq$`ORn7f2K~!2QGkb0Mrw`wOiJyN3l?9%Z0gz8`o=O< z*v@>MZFleaMGfpCec5X+_2W9HllZPl>bGJHMna+RgW`Er{R9bxM@1DNNr*r}3H zKSBdbJc>VCb98?0b>LLtaUok0-E#QkIl+x>5Ydk2k7Dz1 zENJ(WHFfv=@z6=Ox*6T8?b3)a+qqefpFaY%N?A<8Q31i zBcnBJyr`<>hA?tEGeClA4!KMRu8~q^IKO=45qK$SBM-&{cd@FZdyKtUomk^OVEG_E zo(rDeoS&5rF+?w3uI=LY3iw|1zMRu`WI0n$;LS*f$esr4%BKfhBlo_925hTw8lqgU zacE&2rpR~F>*1%8-3@uNAILd42y&U?+z7038D+F;qc+B^1Em=Ux1)2Tavc`SRBm-~ zv9Wm95fafFBTjXAY^NE09)d;VYl>C4-5^K4_u%YXWofEVRF@ZMriQf?>dvPNRaGh3 zOY19lV*l(c&&IBQxgIBLbeAKoJ?9`7r_+ewAqDe;hafxxngmixSXo1L0&{@+PwZ3q zJ7R>IN{eMmmU3))PJ3w1e6!zU;qMtxVJ z+9j@5hv7~;Ajieoxb_ogbs4e_b&6){YAP2X5m*l0bleRb>e+XA z!$URG&}g@cnjE~8wapX9chaoeJJmK($knnAlL$SCRM4DJN^_Pq4uAMl*F@qI(U6`1{QP+PAVtR5S9V@60XY4!vNHbf~L~_%J_l`jxj#D{Zhwy2>Wh6H}0W zM_@SR5Q(RaJu8Qs0h}^e_o&0)NhQsr zn`tzI3(dINirb!3aIQ~RGW(J3`&s8^I?j5)YP=qQJVA22N@`Sv6oOlX9q&&@p9MTz z?iJk@eD#Q)%cb5Y<#eQt+fD41ae!;Gw#33Lpx1tN?eG6|9#$iPrRPbXTo9fqRZWgc z!_Pn<5N$+NPKk5oDBGy8DYS(rAKMO*0<}_}54pi9ZezA82P!Wq(4n!6C-3G?vmII9 zNK%NuIS*5rdW{UZj4q4zIN#k(wL3y!j=fp-(B$|CxA_b5lh#h>WUKu?=>uX+KIBL~ z;Ftnd))uNWbrYE>a|2PNz+)L|dPGwk?<8Lw;Gw-?3@#hK8RKSyPkyu}I$U<(Lwg2k z-tpZCqbn&>W7c5a*p0=g&2q}ZBz^oknD;e29&0X$I<@^+w?xy4%uXKOz&X{FjS?Il zmL>BM&4{je+{_VSode+_U9%i{{VVgpj*`}e$)`bCid@?_?3^S?2pNx&Gi77-TnKii z!WzfRq{B!(zF_+5LJbaZ7Dlfxc?@g^=^;lY)0TL5&>_y3zYi*4;1X_sJpTX(w>ml` z2o#mLB(cOP@D+7=V|ik=e2)NxAuLt$(ejW#%EiPMG~!xG!o;}-mDAOy9Ld_nVZ{yJ zb9jZx)hV_^EPL<$#7=xj__z(tP_zlw7`mUb=A-C5Djz3s*Ar6JaWiy&=KSHkQ|;zL z+Fn>OuGQ z%=A1HV7+9b$CwI&oor%RHg|+H%bfvn@ z@mNZdz5^gLgsOczyhnjGJ@pr7hDSA9n%eeci7 z0XBDO$NxFb&0bIfO#KCFCKnJ%j?7FaidO%0Q|dRniO3F=nC9Oh1 z@2IR8e~Q(1j(rsg3g!HH##<6Wj~-Jy&WYzNetOdDa1WnI@UzaI9YG`?4cWdjo(}!G z8q>lfypWc~Uo^_ly;J3>5pfdGV<0Zq_$uM|GfjC!(%Qv-4Z1%TPKw-zvV}#FDoe|~ zSg3K(e2~fdW{Z0>fwydeilu~3P-1Gy;L_9DU#k7sX>?s`I9v0s;a;`>0GGMTfq(jQ z2K@t$S20&>_aOjACxMf|b0Dfr|3TN0Ugq>Cf?Gm5!EWPQ#jAT0&chSuXTQed=>c6; z?BzYPj0Z|<-#em|kao%NZff@kKyVsTlL1(IKoM27o2Kt@fmwyA=Hc4}wCL2IyZWCL z-iG^QTF@o&D4$gVxipM6qq0h$&NBiKtym&tK!Djila7MUX0RxCJ%LKOX$O4ccGP@U z>}nJDW5%`KOgcXIc#ZSL%*ZHV1mWS}$Dxyv0+eX-lF!_F$3&*L+T4xRVOACKyzlUy zLx-QR(MkR4dIY8}z zU3H1NDAF7Dm+(6}o5Nl_%Q(>V+;5a3vvd9xc65%L{`^d+YJ7=iI1+^h-u}R@t!jJr zSWF5Y>ojWZazeq2F>=o&YpUEj?I2_{L@*x_IJKuLGB`h4^^gSGx)Cwh&;;_3J zMRB@y&KBy>9Mz@9S%$WpNSaHK#*R(NvUVL}noHXkH0~OrYR~|h!4EEmpbN(O${1lW zQ5(5bq{J*%eBEEsD^gW%Z>xbsbf@D&_zj+G$`s*wL_A^ApZeL2W8 z;{E!_)M@Nm1M9BN56w`g_7qkVMPtryx!ke~JQn6~_6aOI#6~idZi7qN6lGV{Gm4Xz zS+xuyAWM|ocyN|SKnt%z$_!KR`P*s6;(=(#FlU=(ZE$wH;xsC->KX=Ru4Kh9_r@#N z48>+B#%d0~vFOWWK*X7TkVVB(iXvw!;`Y9~y3NNd3`-|U(2TO!;^n(Xv_pYE0Yl5d z6?ErCqZ)l}bwXlASuh1H=Rdy$w;9|whx>mThNTcNte!d$wkDRE!O;WKtEVf}!}EG@ ziUFaKmi<(*L(*i~V3%z8o_V8RmOtRW>j7f(g`ae@Y*6dQXQJ0#K6)2^wl1{P$X^Bt zMBZ{zWcX#iN5bBv?^rzHPr|EE;cbjRBA#OAw@;k+O~=at@cQ1c4P1vj9uL2ReuO~l z8ZnvzD279P>XJ7{E$W$>5;pN2NIZHxQ*6xu99wU zCS{DGI@cq^FLclQLMcCav>bZ!tHxn;6Jp3m`Y7FTib~*QgzW?wOvl$9GP=6pMU-fR zwZO@}kXO7D0Z}jvEO6fGy%TJP3{u#Ab5@P3G5`{uT_X>XoRB+=I-1SMoe^#FHB69B z;n2F6Pbcq#U0V=km(srW>bHXmbZ?vXSj$oUjUjZt|IRiaj3QDj@hHbc+2z2FIkzS5 z`y@pPzK=KVVoFWAF;0juAB(w|6r`R~StV1V4aV8&%`ohCpj}<_mjKWN2PeH8O-b;} z`lxl8m2v3w*wax~hPepK&4{@x;{zcB(aL0U+PZ}vX2pl=bu9{>Vb^OshuT22RWwK6 zy_?(pZ65nXU+ZuXF#I)udLPBTh8J$;Iee*W5CglYBEjWWWSjaiFXfrW=L^;hJMs_8 z{2KE$4R4o9&(C!Yr8=>Av6K~rFh+iM*}M24VCYB!CPVx5h~j-$d47du1*JsI#O-={ zB^2O&M%`+&$ z$tn+5DF5h-1qi+ed^cPci4Zy{q$|+>t=EM}Zanq^h_v~jnbspFmbojaB{7loGGFA? z0PGIQOF@VDsA+-y4Lp*Fvx!1A=w)}2S^4XJhVd$!s^@I7pVPXEtgdfBq+cY9-6e?}>T=k_2zyK^Ih(M)H;zpW&}$A>VET8G2@-f&K?#Y%XhW zs^098t8sHbWd|6DA$n24-D{Ns2pK52C)4I&{3HNrvv5PjMj={}NNPTAF)>jwK}hMn z$ZIq_G+cvgAmKsn>_vK_Z>$fMxyYAO#7-S1w4X!gML*-O_g|oo?*Um&Oo3dUZanMS znn!l1*PT9?9RZLQun#jTRS1}WX8Rvrnjo#*8B^NVf2X7f$km2aba4|N-08xv@ydBffRFa_6W}tMO(ud@Ee?N=Sv1)3=n65C63bN-T*qjwRtDI>LC{NLLdBL z|EO-7^#KR7zAQHLq5Q4Y6mJztK0|W12xTJIKp%X1wlq}kw1-|0M#tk|pUHF$${qE{ zroD0`Lmyvnzz08BB19zl(f$Qt^@AHhM|maZu|$T3(AyUiA?}>^+?5b@fh_X+MV>79|cM`MZbeA-yCsID;a?G83WRB=!3Y6N~m7ZRSa|8AsUDMuMKA%87C6keg%92@3 zoAyPeUBx$Mfljh{RO>rR;>rnv!>LuBm{boWZb*D{W0}I6HVR@)^AZ1*0V*eF&#Zd5 z`%%*JVbB;C;Ru&wTUYYkJWdF~zPwD9sreEQOil7<0od!4Er)#U^J&o!eCLZ?6wyoy znQX?8FzbQ!O$YDqbC?y)27!arL9)iZJ&weHL0upT7(KP`5q&_jPK@@yNAw}3C)-Y!zT!)a2G*kgs92w)DMfwUX+w?HS3@+YXmOwVeIj{*9 z!Mpfeg&PjTUhI}W|NgR6J6)nwF5sp}+zsVumhF2_i{{^smu}DcoSKa1YtXr&my;TR zBIJ~6`1(j8!u4HiAfantmR!IzN3@j1s!@aMcbKAfT%R~kH+KTuJfM-8J%aeLvN`En z6#cAn@aMUw%8Q$YoVX-iq8OXo4-K5W$HM(@VG>^4<7hjpE9I)lA)qZLhWq<#C^4tO zRwGdaWv3r?7wHsvwR77jOh*MiJ6$~0fA!>+%`IcqM{ln5wcZBqusHYq0^3Gu($(cw zdgj1+#GpUliwt5izx&-q9IebJPdg!(TAIHw$P6Gc&G3rX+jJw(>Q&_(B3ivWDQ0>x zvCWqtmf1JhZmLGLQ-8eR2@0mz#Yk6vlxsqxiX_c!nP6uV?i>rP)l$Cy$?~lyKCcHD zwO`k2XT1vFG#-a^!u3sXL~r1 zK3~3N4#)ap7FlPSea^3{r`xAMdDpnXb=&y-Wc#An`Ad!%S5L%^aLOQL;tp4I2Vf-Hw$Y1;ti~OcO{p4#t19o%&$_kIYhL0w;Vpj@w4;ungRj>j_&Q3Qu>e= ze=8S?q0Ta{0aU4fC!&}3? z{-rcGHEJPH|GqzmNsj?UE2`DdiHNphaldXo?M}Eo1hd~&s+sa|2fdo<_W9nk_ZMf! z&f#fEuk9H2DEXokEIjw|GwkO^6wrq&+bDe@d^} zVtAkzu6>fjsRLgvztwtacg7|z*Ju*ZR-j%K0B!Bji*!_)Z9VtvmThR*Cg#Rq;cvV1RmVK!JmhTT{4DPC*A2={rE7f7Pj;ERY{G>3I-^+~ zx}d=wahw{MVrPc`t>R1{-fcHs`yj0`C-mypT3;L+ed%fW2*`GMHRimfLiD71{X1Xc zgJ}7Pt*N|Hq--z^=#!*ZouRf!DPkKc(`b2D3s9GJER2%&%GX9y zL3p6nNfsI9=Pp3+LGp`A>cI-ucR!j@syO*4EPK8rv z$D5e~C3omO^sO?vM+vzsIfY?#Nm>~g?VqH`Vyi!uW87R3>%q(0U?!EBo5b{M!6b|s z0#X~*79Bqouf}QSg-VWltyh%kzn>Pt-R@k#PqdzFlPYK)hbXtS7W2A;Izc$hNbYL( zx*4lga~JSBX&M*$KVtvh+Liv6w|M($1xnKhI!{%|E+a^1bXYDF@-w`fGht4Lq=!k> z=`CunjVyk^I*LlK-{GYtKMObWV%QnkB!~KT0xrm0723^rPk=p17a}XH_}ednLvMqi zK;!9QD$|U6mP-WJhOAX|+^n|G`2vxUsu2{m3C1#zQc|q!`eGYWx{3eGrQY{sr1FIA z6@_8NkWcvPUG6z9{VbeK>B!2-)WL7IFkoqcejEb5_n}{Lcyj(kgh8oMGFGlzJQ{Ug#Ao5mP0qW2o{BX*T8-;&{t3JM^7l$b3@SQWa=%{A= zg238ymq?M!z9r z+s3~`He=Tgs{ths#tr}b($lz(9qj;01>S(p)Nsr5_~??62NE}*Fb6Qt#A3D9b`0Wo zVc$nj$Hg248ym7MU5KfV>2bqzSa2i?tXL}#;B<%EzB!h|bx~1H_F9`!=o8r?preT^ z-Ll$buFtNn$WOA~Ir<{A+ARB-@nTRjW!#IWri%BoF{Tx&lP_BIw1!^%`g6a^N;L0+ zN#xUWqpl-_!omy*&=8eX)b(osxZke_oaf1dEFPS5ms}Ee(>K@XSI1=cm76bgTr?%d z)mD>tnz_0c7>)!_|fqA?x}t!nRY8(^;=FpAL)Ff%H=4 zNQP3qT;)=rEy{J)I@4$DhyBddz(}cyBxv(jwgM6}y~&m7jr?j*w!`q~&AjLF#;bE< z*U4A*r#mxY=SKA|#plOmzILazZ-&?braP?zBv4%v7&6&(w()IeL`U|6WoLvF38RQh zB%^5cs+j9m^7jkfy7#Xdb`O?&8_JqL&00J?Q&)Y>Q`AU6A(}9!RH`3m03Q7CRJH>r z-%?7i{&h6>4}HK8@%XnpjF`ZiDfumv0v&HWz>O_d*IR?4O#`j7VlsP+`F?61$}Q{L~V&S5$W zo(nc{5cqerhf!^dhQF@}#?8GA?C-M3yNP+)`^IY`4zpXGQOvd$*Zh^%hx6?iM;=Yc zShN$IF2(Cih2B(td|L!Kl`!jz^^qJ0+MeHqe|-2P7Yu`lw6;e7S{o^h);gSTG%)qZ z4|39pXrHKY00V961LvdqIxm*dKzp4?oc|w3e)5@)4H48@NstO29G};l2<=H_hnKxZt`qu>i1=SuSh+ht1cvFK-(*9t~?$ z2jgxUxsyvz)i`7(2m8%`Y`M1EbeeI&EaP399{xe-qqxL0nR$uA^|W{_*khTd#ttC@ zQut*it4Qg4e=ppg9qR66g+5rWrp3=9cn^!Xzkm9Ka%lR`(WNU-)ytZ|3rFH!|EPD1 zlUb3Nb^&M_Q1(rD8pR}zEQMWqrrs3-TF|=rs;-(W|G4%kKoGxD&)&2e=q0N=ucTPl zZW{U(GT6FY;C@ zrONN~|`L62zLBNr{|Cgx<0JQm^gr{f&__4yV{+}u5+x~ImYBRR? z0f3&p(zSyoj&QtEK+G@nmwKZQ7P8od?5D*<9p@j{I4vq)p>MD#qmB$Z@0&qc1}^|Q z7j~mXdhT?&`i0u|18L$5^YRY>P#SM=b2vAFK`y%cHYxwugnXzNJ6xW++sL8m711<+ zrQ6z)+mCAOXX>R86nvc#w-(P%cXt7kvM?8V-RJ1gxF2i;HS@cG0Zp*y7l7_*0Ko80 zl#0 z6$R+U?PyJ}VGy{oVWBOgflVRUkbd;*v)*l>ZSe|UZ_oYshuf^8|FQxB?W51+$_AJv zE?Flaiup+e7-C@qKsbZ#K|3=IMiWQtBQt=AoAx=Kv9{;}=xADr&cy15QswLZ>GNHf zL#s-@`sa7&%x=@MDu1|=kWC|z&#B4t_@Q1K6*Zp>Y96Pd^Xs#3#sK2X0KK}J(0NGh zz-Y1lOu1#3amOc`nH;5X&8#hB1^yU%}@L0+|Xh6^;m8GrpqsqXzU%HJ=m zpQ09{jV5@@8j*n9u=4N7jh1<99?@(@)Q#F-w7VBFC%|neC=sxJs|QH-mBB9Swa!0s zWw^HzvH0Uz5af!|lN=n6gByrw?%j#HjvEd*J7V|uH-iQdyQzt)n0W#a5Z<;>)BPoF zui#3;zb^P+7sm4jdCKQ7MII$636 zN}neGDu-Nnjswluh-e$85=M9xS#>2il0o<#?7LCTR49@AdVUfm;#U4b@yj1?k)my# zV%0$UrPOAmfVPvGQrYCTCsd5fazPPfY}JUzjCOt+;+efSf6>8W}or=A?aaZFU8_?b$}^yxcB|>MA1y#o3h=*qR73CZ)Mek zO{M_to%Du-Y>-bg(L_qfnRhE^OD1Yv< zkB2=`5gd=nW&9Taz4sR&hd%`qQUrwv6bXLu7sv|K^Pu>LR{q^c|Ixl|f!)-9&Jz5E z_Ik)GzPnet@6RTucZ-%<>Ho6!Rov>0zj9Jjl>TFU!xpdWf@y6d{zYAJ!sY2H`5yiY z3;a8Q^JfFDB&XVQQ`DF4Jhk}g7C)RBKu`W@t#6?IlrHI34=AZA2bp{cuakF`fH)31 z;bS@3oy)8ZuDR-5cM}3IjF$8s_2$@jXRp&8Ee!14WJSwZYa3BFuR$p>KG!QC6JigD zJl7c@qz)1dXOH27fnqiOJjVL?4AWC?z#*1p&en3< zvEyK0A2Zv91))lXU}8diu6)2n2x9{ushxPd{EK}RPh!m2+Ie2jHT&%7_@2c#tY&x} z1UgLD4n6z&v?q`_kmS;ORPlJ@x5;8>2OHBHwnlvCej;{zJy5)Fo=M z`eGr3wR$_0u!TER;={&-gBd*-SiPAUx4G>C%@hlB0@|)9W{y#L}r6X^IcQ&b1gVXOJp=Nng~E6up7=A@lyz2dO=ySeZgLo4PWb^6)h1(GY zr&#qhtgs4Wd|(k68Fx*Y=CSbOzK+zYo{4HSbse0*@CH zb~Q6AUqtLSUiT%NV*w317Re$#H4uKS0*JO65?QnLN{z+O!_E9AKGuE&LdXxa10E?S zKqjQ6QYTfUKQR4@wE;4!&ye<-sy^99q?*aO1IxHwKhxkQT8``&ozvW^ao@L~l97^< z4w<~49#&Gdz2I5K^Jl@bhyHen8ZOCn0)Zs(^n{a<;Ie^>wW#S^CX6aF~m<6&hO9~@$D{^si zdivu1F(ZdM(uGicB7%_? zzp~^+Hm<>QG?MP$RTNAQ^hp!|+sk(W(e!J5IkA)M5S9uaGO-od+3&I~pu}L&^6~mk ztO|>oaY&Ge&K*NRBu@6niq5()CyZaST?vV);!`|E|7U*@mkPsSiWGk7<}2~e#yW;5Kww5+igRJxOktqi26qQKOV zR%nw@WA^`1w*S$(aV3rO-N0@j3e*H&&I8J2!DPBM2xK>Nb1WVObQ$nR{7kd#jAXRV zB{i(^;~|TnoVR6wP{RTGr&@)ZTYKUzM5wn&xb18Ks?@Ue`g|5X?5VuX$A;{~Fiy@s0noh}?g?POmfS z%MGb_-F`bFUs{VKN`1XEP8jw?^8dna9iZi zcv|RxR_PzEj^+952;Ltk%xRAWa%yFP?f*3p>!Z2NY~Sl zQnFVAX4*>mZ!;4AN!W#rE?W~r=^iU7_CVciK@0Dp!)#+!X;K8B!a@QLbep}=jxppN ziP*rnCYXO)FXwCt<*~ZD%Bz~h6*X^n0u~L&%F~~@Pmi;9LeOEJr{sFrq z&1y4H?{VwlOse(6!JqQbHE(d+-SIr#*(PO{usU~#|1aX+Ix5O_eFLT$N`{aSBt<|` zQb8CxRV2hfrBM+jrG{=81PMh%X;6@@0t(XIqSA^;cf){6$9KP|XYX^)cAwu`-&)`I z-*zuzn0emkx$di;VZeG0fD`ft;4pRAFYTB!QwdDatebGzw|iGAyvhRS`pbIuOW9R= zA(=|}98|>MBhjr!$itG^*Y7laAZPwFgzxdcYXN}P8O)*DqeO*NhC3>w}7T6Ay*E*AgB6xLyL)Ier0a$eyGaqeXJ-`{bU%>knLU!^*8QtRS9wy^t;^}UO7_GYv$b|vw*pn zuE~nbsvDfTY49mI%fsoEugeUeWY!1HYjOKMbjEk?4FuwN|9(~c;tx*iHc{#E>z52e zT;U}JQ^n?(Fhj>-f+|1VJtDV98$D(;saeHptGAp?4IAkb<6>&!4g+G%!SxGvP%@Q(X@QNtVCf&S>5 zDuJ*)_e=%@O<_;&mw^i3G`Rh)QA}fY~=g<83wKNgglKa_TXZJ{C1lakkLbiKX+c938c@5 zNug>Mh>McNdbYlziNqN#`7K9=!>f-Q;C#CFud*hg!$_nzr{t{J$sYs7?rs;&98}cj zzEHNz+9uvCRRgj7utA3;u}kq`u2DlGkAxnEX{+TD*h?8WPoDv1M-w((z2fUFXb?=$ zbb@)4*RNB=Fn`LzrybEh=g|H7ny+jiPVS$kDIk_&&W#1?GH)}aDb)s^Lk?Z;e?D~o z2lw!=V+i$s*^Y&>*dKiCa-jLBy?cU0XCWFDu(^Rusv*q(bka0(0D%g7i?W|8G7!NA zg$NL_Id!4T!7hS~GS0JhfEaTJ;?3a`N|6m$ewydKvu$U03bu?f_`HuunpOwgKWhT$ zVP+4zlt~2;>{cz(Kpp}6RbMKTXa*{u#o}i`O?nCUeV4pf@k4fYYRK3kgTs;REj$62H%Mm4H+p;Lm`r7(KDn4`AfT`y_b8+vb7ELCqU+RlR-% zAQ*IyEKE-WNUW}6`!{9xl2EbU@6uB&;yH@_bi!d=Uq4CC^F@3)uyg?Mol2U_Y$yLMLh&+xegf~9^@n8c0qLMp1xjH+4x(GGf z@%OUdT9wB|_HbZ6#}7MBb?0WpNjv?qRj=qV=u7^dwPgJ9tzN*3OkXxSD?Kq^-;0L~ zWU7Q9#LG*gGHLdBT_eEMlHS{!c6NRbjojWI0m~43d@>9P6#U{HyXt`CC9f4OvZwSE zc7^X?j~%9K>4nX-z_f-;M!1?d^`@>$0qA`)q90!ZCPR(_*L-<66xOOKL%l!w4I0ti zII9b~%*uG2?U!6&&D&mHe$xcwm*>GSI`zze+^Z5dI~%PB%KN+uth(EqPp_$R2_5C| zz*hyd7Fu>3L?H2apW*>r`}9nan&S8SxII5b%pdO_)b9i{3njWu3o?*aKd6V%VZQ01 z@B%X3G(`)VhZ8mTAR-tbN{eP9TPfl4RkK|gA?}>8zGjkK-^Ul1I*!@&er8utbLh-6 z>^Yxp)E7Y2Uk|6x$L1QjQeWGytOP@<8j5k6+jdt;!?oe%zZX?HV9M59R>7G_jOIIZ z;l<>1SN6j7mPBhU-iF06D|eVqtmWrjmqy<%Zy*i^r|HgDo^&J^Ug(=n4E6`@*?+US zXLkmZu-u)^9f(-pz+pvGa(qJlYGvD4F-r?2Mjcj`p+6Z-FQ`N}wQNQ948d-7TH+p} z)!aQh)s_})m}FiA91Q?z$*RDDckvHrauOWzi*IGCfrz_T$UOfrYL8VzA+vtI)umx0 z&?Ib4YZ*-08*q<+`u(_U;ixEf>fzD3eaEdP5q#YetWdQ@pD{>E)!(}U7{JS+g~gRb zl+yQ(xyF^OId#4OZsS+9Q={s8z5%3f9|B`Ufd}Pggu}Zs-a<4#!c5%_AAA&YWr=Ge z_pXILc#aZwV2r0SYKOd;mTcM{*ix@I%PrPB`8e|K+@Bv+jbEWUT38wI43-WbNhpAz zVmRtPJJEdrLthwn@RDl`GS^qDg|V1UX-e_%qHPTx-VUl%!r))@@=s|M-;;Dz%kR7W z?g$e(j$$-h#3)B$62wEx#Mezug3sZ@PL-uyOPm-eb1y*Pjgb7;Pf@RH#K}{H=q-AM z&B!A8Pe{7nEmb}-$nkch))<^7v&F!ditB!BI-H$n!I$9k8ddsKP|H*W0Up&*Zh_My z3Fp~EXglY<7-~G~ZvO7d*-q`sOohtIx7ATfrIjId+^3FL`$aJ#=u0GCfPDbS%i0hG zin!X%OYt|9?`&`6Cw#Rx4%;5wq-LGzFDse(`23;u0zjWQQS+b^7pdP&V5GFAo&XJC zm?*hE1lAJ+`@j>@QZ_h=(#$%+QFf^tn65}}<@ashI<8=~;zbl%Z?vTbo>-O|YAtfG zkaV8a-w3TFj3>{a%82I<^7=xeHQZa|Fy|(96UFkxpHtQE>kcT$kJH|65!qKR!wVe# zXxk9A+=8q5fFqB08)ir$#yKpE7Fc%Fx08_H`)V(Y;am)7*w09PN37bRi~1lgAGEIC=ur)aye?tQkhoPG|Snws44S;|#}O(OX*7 znaW(+eG-lAPFXuJYVolfrE``of4Ox$uxdaZEv%2;N%B|;s?a%_=*KIBJ{}VBJgc~AX&={~C`l<= zOC_VPmJ^Db_=%@Ru~PP_b1mN+v#aQs6pFa^)1CJl+8tc#RDY7KKe;|XEy3{5+Fncd zD=%0y=OtxF+`pUaJ5t}HTQN_%$MR4WQ3kyoiG8UbWM(z(9k*Q3G*X{rN)$gwL{Xub z`V}X7rF5m_q++)*Vfz-tU+@d*i&PUeFk8?ol2xS{6x|MS7(|Z<@hFH;4hAwCiIDFL zrnZa3o_^1`PhQF2edvgkA87+|Dw!F{N^RLl{1CyCbAmf%J$Gslv$~&Opo>+AcYIZj z$xz0?AmhS&C+FKaM;}z|f0fC0<&?L1= z4c7i8UVScOSVA>_LkcWZT65q!>=6EkP^Faw-tw=2+c7g>0h-=R+Gt-Uq zyDz*Mc=6Fkik?xZaYTWmQdGVTMJ24y6;9W(%__hzH5uQrKnpI|c$+sz;LcR#htZ6Aka9J1n5j#YLEQkHd8|iXl7)wh&9z-c^h5M1DzHb*_Ti zid8%)C6*~y;OP%G`?`t>qwM`Z6NbG|&{^CN7vlr(Y{ipTjoQ1y=Djx!JZ^}|=HHlZ zB}7=iIu_`}Zs%qvvHewkr$c&YXimyhY90>F{1cZ-Q?8Ga+TL?D|M1Kgux!sWB4Oz# z^g?!ICt`*sdug@gCro{nCcBQUC z09{MRuE>78IgsIwIeR>P^&6HG)#CV=#|5)eS=84iSnVLse~2V>U~ArGymE-(_Df)c zW5t75e0W3B9)J{e0y+M!VB9bX5(eslQ764PUgl&Y~J z`z+TuEjKQG^aP3x$0Fq>^)z6^oiN_MJ!WEf`=t8v5Nqu5Dsd7;&Ef7srV2W3oN|^T ze7Oh@!D8?f(J1e5sN<-2A|H5B*H{7>0^H3d_VDc0L;HA~_EJI#p8q2{aPc}P^b)Kd z6N)WXiWhrHN`3;~>!eZf;k$Ia5=d_!yVdXmK~>1f7;qCsUO*hIN5XEcE)5TDMzc13 zIx!J%Zd%K|sDj}oP$>2zMc030NqkVMP#R21)sBneDu1yQ20uhCoGT~N0%sAPrpcWz zLgZ(z!P~5yFud?^bx=BbBH5(MVO{rdt>j98RFKIuDd$dUEq9t|Ev<^BXs&^B=&^dl z<#Fn5>^PhmBW+g=by*uB-jVpY1pM>Yszc~Q*Bm2^9%LJdgZ&QkG+t zuGgF6Eq5G_e!4QKpB0++_R7ut5h=2Wx=X`FtfamSxY){dk}LUEqHQ%1(h4~y=TObO zybKsAJ*fg5vyb=;bHteIGhAk55p&iUB#qf`dt-!&$aCav$3on9_L5X<2q)e(U*+UK zvZ~4yZ!=$4;d?NlFe_;xV$#Rq%qFS&Ydoeml+)AK$6r9vV^vtbRg`y+>o&eRvzBx1 zoi~l&0Q*b@&bOmU{5D58Ukf_PfI-nuLLJ53Ovp`~;_E;%LS4vNOGzRmj7K-jhN6dR zHqVi>JITNH|2lG&A*1#Pke}h`hydT6Qz-ihQf_afrPQS2n(tV$CUi@E(dLaigJrtM z8~LlU1JGSwZj`1{c&|fI!84JGvokFXYjnqPw>Zal$h_|_6djyU!16SEuG0^Al`-x- zKqr1|AT?uHtY4Jdi8_cmyr{H^7A}8r+o=7a`-nj;-L<6G%0_8N3FZmu{JyTzod|nB z>>BG=w}wkInFtiC-DX#p;ktKLxH| zycLYV?f#NgrG{hH+nHol)uHs=WaK?ml#f!AEt%^G{Ydu=y;b1HeDsU0yX)PNxMyl~ zMUO(uGhc3NA2S;KGmc`ed|8OKJ??s+%!R@iZ<>7$c{`6!B%#pZ*7^N)_;N0Ohc%(gN>km*aXuV-EE(KZLko^C%F zQGQJce-WWRc8#DG5hlFHuSh81z294}KIZ@1oEDz5tOt_I{pR4Ve^G;f9oYWV-a6^t z9~+(AeVBB2xPPjI8tIoqCsY63zVSSJV&5N0!-(}mz%cixedAxVCQkF;dyN0WcK(lX z6_LsVnfl%f=^ha__WXWcN(0A0|8euEiap)$y%;g6F%J=a;%kfiz}B}6d8advB`9o< zMk0;_AeRFCJBBke5-G{~CIJ>n35<|rcO$YeqL2Y4UIZc*=`wxB2l23VOK)4j|2QySlFW3(dnWGmhbN&xSgJ?Nzyn|`juHQv< zb?2D+Bd&g+p}x-U05A2$IjiLuEvMXUzsJ`zUGOSuQFlQi>@JseU)GFw0bVl;%)=ob zh~dK@L!SJ)S)0?6S8Xqr?!WwT#O=QXu5SZ0Hy@M;rgp6C7DLb{w+f z{T<-X>jCY+oymJP*Q#5rCwNK+W)ue?j&6e|o z9GNk@%G_OCCR@3wu7Fmx_2Jq-v1Y)f)&LjNXPJ>OzP&jiUy|D_{jLlY9M^ngEcy6M zw+6h;ZAXmDdp~CCH6XL2EJ)`z{iUu4<-W~ywU7|7Th>yT$TV*NMSe4lK`PN2h`r|_ zXs4M^$9%aO6I}W&z27}JY@xM|7(T|D9`pg9iWooU*ufs}fJ`#h>K-@xir=uwy5%Lz zv}c z!V!e&bTGXAmT#GCVryl<1*Q>)xZ~EhSNdF%68tyR1O$!COX~#9n$06*uQ$*Y{E!uK z_tpIZ(R^V*%Ep=a5^upsSmM3a<$X-I(%;1Q{vN~`h)95lRj33EU-TVu+>VQ+qqwXB7IIB%f|egT1lJ%gU*3KC47`O2 znOs5vm%#0F1;fbg(?;GqK}?G=SqZCb{p0*$wwHtIq4@Y--ifQ{NOG1*y39$4Z<*() zk@XbW){+h6e0U_@@Tn$w>Do=I4Lj;##ZaarvtPo(2fazC!t0=XB65%nm)0dT1OLZ7CAQKI9gr;|mMzsJ%fJeY3jK=sw-c|E5I!^|%3k5X}H= z_Cvd7hmgU^Kr4Tfp{{lT8+{pKMZvCv{n&$x#l~CH7ni%=11DJoyUUN7|93gB!tX=ruk2qZUuF|k+EI||>Gm-3wEt4lfBR9Y%Rb3d5RtJ?t{$=q38c%ngMby=uf*pxFQi#PiSf_)sf;y zm+n1aE~k*U8>|cf2gRWlXQrApVD)BQU;{#w2k8H!S$Nnmt4&%{f$y+M2n`(-|D5B%hOC!cYGwi680vc6QI zLJSCLyC)CWe-SztKkiWqXNcfJ3OI$Glkj!S#)SwLSZ<`6U=_a}2DWs6IGj%MY$75W zBACU=dwcO%fvkZzr(Bo60)ebFO8?L6cr|_s@pvyA?|e6&_oF)6n7akdjuOGl8AYIW zF{#y^1!w(68Jq=}YFhfR24HLv8IUXIn0%Fmx)9e(P@U!w=DpZuA@MVlmsbGl z1^kmD1?9J*&4E|?{Qi;n8502}LmFUpw8EW2#)T4H(PX9sE{((Smvg@YTI{CHp56 zy|-N*mXch@S)nohy8*FXY@CFp*-B;4I?za+eNJ75S#V|_-$xKNo*H`e_L0kZc3`{~ z5Sm3JEtzgbk;7}%)cs(4H>afQ(p3gaobWJ-Mvq

1&yL`z4fr73q0c&`Qd(v>mvq3&a#Iu#g0VG4t?5d9|1Vlyv+&#txtOZw&| zdYQp!;3OOF5v88KJ`!H@V~`yQ%U+}?@_C~=gdf1_S1g(H6w`Q>S_BSY`<~Lo_VKJ- zHl=NLe}@Lji=Tq{f01n4%bC8Ursh@gEvY&z6bUi0Rrne;HGbLdjt*Z+MAZiKyc`^f zds!1+U7#(zgJ`_t5>W-8E37Onbub0Ud7rCm?a7K{Q93%hz}3jN!CVohQ><;dm+@LK z%E|M{y``IiVL4m5-Oet__@Z%2MVf%`*5rY{rEFc2n>KAyP{`QSZKULChg_FpEx#1< z>C;DmPDV#Be`G(q^nYFqIxG4VYP-Y`)0zmPcRSV~VeJt6ND@o|xaa?@+g8R&-|^|I zk&k}nl)LTildm;)2?+F>n1<=fMho8U>Z*fWn_E3w`Bg9ViO?%SQ)m7|tu2H+BG@iu zuTO7>_CX+gZW6=ZMMOn82Wgqu_wSt5YBu-<6tHMS5u4s~ZywZx*pyWmYtTag^`9@J zro?G9E;$#Uul?q?#=nE}H`Pgb^$M^b7dtx^J&|gOqaheP8scn-bWW<6z^`ku8bGgN z8xjI-GEOm-gAH8v>o#oI@yX4Jt>fFh9j&r4Y=q`K+f|U=t69T)&=ty`|ML~t2s02{ zg@A_;=?bmgi+gv^LxiK_4$t*}QR7UHPJZicRDz>jA__j7OYjyG zb~ihrtx?!byC8HjHZfr;0gPz^+@reFLa~X#ccpYH&D}7Zlw5 zY$7WoBl`OF;-sau^YdFq4&#ehl6jvhP`4Zy zs!NCgdioVN^{+X63&_iwNgMUAMH>loTit;dcX07SNfiA6-k#*JO;d05aEv?^AwtO% zr;GK1(bGR)T=)j%6HzRCdshd1Nrgf@0eAX!D1LB^`)}D25%6VR9^z>nD%SFe$BvQD z<6yG+Jm20l*V#nsCq#e^$*HMrLd2*^LP*8OO=L45HgL9k<8vK7y)cwzL}IlFKvy#y z1tcQ-O-ZS~)yFAAJHE!cDl_O<6M{hh;>B-2d|K?aV9TS46*nbf4uq~+c#T^M!n111 zG3u3!H|gyBpxE2s(s2R}15P}Mx|*J$FgSka@*2Gw^b#mt7T+~3te0`3!v;fT&Zic9 zBA7^przCU}8LaE~tp4^wWCbz7$w|ql9x|qF>d#QRLUgX%bQDTbGz zm=sB&uf&mNAR(Vr&@Xpc3%@7vQUB z#7>|)++nwmEYl~r071FMXVgDk-e{ud%UEripf!Y(a5ewuu7Oc^ve-F&9i3ja>^<6g zZ8>HQS%=h<-N*0@xcO_H!ZMt9_N~988{{z}vcXL3spDP6?w$U5JCbg{7rhft*T2I; ztl05Dd!e~P7IMld+rL60XLWY0ioF8&%+ z7ss1Wm!OiUzWH%v$)3iZTQ_N^4iXxVMtH?*-Ngl$3Uy8JF#^)h&t7U=0v{S zo|VrMS%`@?_JzGfB`<5zm;{Vw5t@Q_bR|&}q87 zpN^K1yVa$-!Gbm;BE-2Gn|H4gL_G9YBefw?J=tDT1xEOG>Elm#RNpM?#CdWi_IT4q z)1EBYoemn`X!VK75)iIQBJ$4G`#cHi{OH|O1W|9%lb_}SqD@sz8nHZhV^-88CiSFp zSlle6jn0~;s*}raylgy>FbDbDA=GnsO_8)*O}{5kT;#l#h^V3^E>n^q!&@R;spIl%jk8@-lb|^r$8?(Vlc6^vI=UFL2m#k38-* zHdl}<=}}C6^A`LT6<-LBm~t+?xL`S9yTpZR4z0V6Nb5WrGPoXR6TOS<1oM*}tLMtDu*d%ATQlpsm?Ds#;WX62Q$h9lY++rQ4#@<6&Wx8V(!8aj37qgh= z0M~3S6VAc|MN6}~r6~!ej(L>DH@T5|;N9n_r4Tu+ckf z9)NsUr7`W>Tc8Qer1o_v#(YO^F70AF=}bmJ30(aNbT<6j`}>r>C;f0TvliUGRgh(! z|D(r2u?eTMe%PQ~^X$s_-aWy)@jzEsuc1<*n5QpH1F`o}6%)BXLN6Y`j=JTZwcdV$ zvJEN z3IP`5Q6OEC?IZW~>{%(%I88aDBrfe|ATCs_74LMaLdud=nRL@42!#6^fgPBBYu4~? zgK1@@UBksU=cba!1xUCnz9$fr-?fLw9Tw{bZ(~2ZKeFn!1 zIF?qS#fdl#<&B!xz54*mDXye8+O};21+t)Mejx?$om&jb>DFrT5EEnT9S#mGz@6Z&in9K+b2)$LP1W?#cBVAr zs~rpCa&c5qOh(c(LL7ZThc{)wTHXUaVpWl zYN{2RnB2URTh?!Z2+FeFHY2@E)+aidmWE~!4zMYbcacLnt(9`H%Bz{akzx2PmX1bW z7_EM-{giyAMlqt3%nu+(^kMj!FCy)lLE_R$Y&GmH0BUgOA6=!mo4B^ zHJxab=HzHK0hNdYmD);VPY$y9hzR~b+S_C3eC!sZw!FHN4SA@OZ*?)wWO%$C^UyW= z>FwI`HPAg%E*}-0vp!%n{evhW44Bk3*=r<}ayIPe^fG^dvTU3B9Rt^1^Q#`tCDQhd zTB#ghIxLIWrY(GvDXLJeDprWa}9t88(y!>Qo0?tSiRDgZv3M;SS-1Mw$ zoiy!7{or@Ci#_1evw75Ng~NmL3Q0+FM;P*VDj!dONz{J0EJadCv3Cm-p5HpfUj-*< zWMXV=tb7Q2%JFeUkFBeNVUgG+x$AoHhItHHDLNubC;ZGv-DP5<@kzB7s@={9R*yhM zS{@n8ow!TaFyy#P@Fnk2t*HeqyEjYDk2 zgyt1j-gq=a>?_GD!rXEQUFP8!MF*}e!Tm^$rX-t|qqgtM-8l8f7 z5E|pb**kQ0-vA6KZwD?hKUe(;GK)#tq=U9rfy_zl8akUNL+<5wes?&kRnTF~r9E=J z5$|n3E%o0eOi-_6)UrHvbEPH%v1F&m-o&#A9M8#_voOu)V%9>4IwG|;Sj{P-cEV|e zz>KVhmDW8sRYA7^o&Qg@a#>I8`@i69IZdOuyICPuka{^sW< zV#wlsO(6U8@bEyA29Q4Qo;JboMl|B+9_|J$Dc?%uEoiCc{DG$D=jW%VJ9Tq}l@%0> z0O-sq`?Trg0uE zV@LB-^&(Ha-G5{o{Nd3a6#Qcu{(^3QdA9$Vc7j*}0H)B9*$~E|o*oRug^aECr*Y_t z&ssILr|?3PLCI2(8{!uu&IrwjBhe~=2mh52g~02fpS;dIVuJo+a{HUgj!2L*KlZQm z&HscU?$e}#MS!RfswG6VqngCHlGa&+2w0Vs9FQFaWzSQA1K>aMPn_7>?m1k4PUN*d z-smj!&Tv1TT_3X2VOf_GvRMLRSkwvjeL;0VPyj-vTqwG_If@jKQ6KS>aB1+1vtXL9 z9aabKf9uz8gu4Ii|JnA`UZR)7kYSG}S0f{e%f&aoTiI5r-=b zPOd==$9ABU8W%rwY-ed{X-yNN?m%j-(i-R?OYVpBEGP(*&oUpUO^vj+>P(dcyiGcG zY)l-N|IKw0?maJOI>kgo3s{wsQc{%Uje~exG6H>^qfcb=|Jk@A7OC^a?O6H*Rj7Ld-G$&rm0BE5*v$qS{Q_pR)`)7SHpbH%)Ul~ ziN-EN=TEK*I#X750qYsXbGE!o2d>QYtB{Ii;zo`%y#89UwuYGiZ3 z+dp#)2bU9z{G{BJB0C2M0OEJ(2sMyADvN%1C#VLzK=t|Q`>;_1T|oQ!`54+79=pcC z8UQlvdLMc*_@^oi{aE?g21-IAf_sV3Il$w}qqu`4Fbb>U?rRX85Pk%qCk69b^8f*v zD5X1h(tH3Nff>|<(E~onCn%sJr5eG9psWS)WMpKt8EOOo1Y<8=><(|AK;4+~vG4@0 zHF`R_mLup+kpOTgBoNm%GH0N6So+|=aQ(KQ2OZG2YUba7BUqdJw;bOg27^GeEh|`K zWA_5x`c_ev=kcsjW#{aV=ZgYu^vui~MSNO-L4=PLV2O-B!An47&aFzsAD~;!{$ayN z7OQs;+mDHdM=UeWO*}yRi$EEwQ7ND0ngVjp(bs~;(%|+He^o(rMJ=k(&8%@NP`mmbs~5_e0_Qx>u%Ct&yt&=6ft=@{x1}u`fXf zqfRb;9z=J#8uKfDlBy_r%lPOb_X)Bu>jLa~D(dDRxN0!>u#%eEb)dD2GpUP&SKD}l zgaob2ZLRQccyyGFO|vk-e<9SSJ(qz{g#QFwz2|oRwX{n9Xhm3M0v$m1C@R#-O_bFH;IsE<8z_SNg5=tkq__6!nm`+)G z+M3&dZ%qaH>LloI|Bu>-peMFmG8n<&r>XbMkmui;T`~r)6dZmyRQL zFTF?E8_+W=yW($9RXD3U&7LkK@)spdxxE=F>MTfe&kh@%bpB} zSJVH2rCZpYpci)-H#x@oVQi8yc~kc?t3kdBNYfNgpXSVB@gSXgk$dO^^~KuSH)Rc^ z_0bw~mq|ZOPi^VX`dUP<=nb(Qz0Yf+G8c4wZJ2=Ho`Q)D6sOgHNw7M?*xD2xf_Zs3 zBXz4di36YO51LPhyQ8xc0~EqROyQ)R1@Zwhqz$$}sf8bFv|Gyl9}@VF!>YNmD%1q# zMn@bOY*G3%NNf6UDX+oaQ&K+BM}R1!2|NPCfB%gMcxnDeaBCJ!^3-VrVSww>WIK?` zv+JWL@|D^GMZcZ(^lTf0`9U}XRS5oVJ!)zxXHVNbpG3$hQ~*Z7NR z;OSb1gzd0s{VxiJFo*wMdb4sv;T)74%vXGttW-F z7n;8IESEgC{`unT#;mDljT^I0y}bUivE6xRazfU+Y_cw!Vs`75{ZEP_#$?LludL_U z_{5BKKA!%-NbFV}O%@gouI0P-p4`TIXt{aKE+A4>~IY}>hJf3?uo^~DG#?V27Bzf;ysER7Buik4$v*Kdg#@YCy^Wb z+FvF4SYjef9MO8nBO|=ssB!S-28V`<8qN3T0mkH@&=M&#hx~W5VV~;ca?=&$6yQM^ z*sfDmZG`Y0brQ#^7NQilwKdI>e+3vH+5M@CJjvzt8205>*3`6!-or7LggJQNEQ$O& zditpKTKYKLAz^Ptf+hfA;14rUJvjU220a`Y@c;CTUu2NwhNL7$P$3rBCS;Lz&H0J# zH9uWT51hp#e6@fa?lM__Wvqe2%-%jFsAO(hD4IJFoAT+GoYwf8A1f;>r;T`ccu@Y` z$N0c2&S>+lP;p9H@7hJH;qUD|12yNjx;jPkp!7_LSTGOuJ(w!M@~o7E4wRdR{QU4P zS3C)Tgn(rL60g9ZAWe&a*)hg$g-x4+5vJ3k`ssO-ASUryTZ>B$4-K^#-IjMhIUb&C zLi0?V^vZ3;d9bCaCpuO}2n}?j~KJM+bT_l~Z6$%Uz(N3|2s5~(K z@&&A>tKskjLkkS=?YnnRpFZX1=SRUx^l4W1LAsaQ@rVjjV66)Di%{BP5fiik?mo+A zf}K%o+qtu6C*0|hbqYVO42`Aivb0o$c-F_E4MGT9lTZ0yg0>ev-y(93V;{Hhchar2 zA;(yu`!^Z1^_a3UAh>?S2vOdQj>2x1x{+=`P|mp(3s5dJ+Pa7*ujmyqb*N&HZ?~XYFuR_O zmDM*4GF0?v^4X0vsBx-njEt4=i91O!&C1GiCMLW4>9Djy-SRx5DDFINd3q_LWU**5 zC8eDD-IdeAgx zHabHj7Z(?@uj*v%tyZataP2co%R&zvIA2#+>hx<38UALBb>{zE+3UsRqq5<#9gWJfyq>92|Vh&qf zy!aB6A8>=YyBpoY&X2a~%4qP~LFG5h>eu|Fp3aFp{7_uVx;lblxFFu;pf&Au!1faU z;!I?NS2CaCy-fP<3^ILonO3iALhye+P0P61=?4*d#c*F#R8}58zedLisV5k9TvA%{ zxTLgn$IhKwjZQOmXDBXxrk*@*VQwBNK`l_aK0a&Y)D}FvdC+I2rP7SQ7c!0n(c<&x znVwlYILPi6;w2stOaDGO_AM%!Q|s{HT45GNih+9Yw41H%Fy$p{F>#0rd2tdSplGT^ z;2fkj30$s&D}M6P6pd-+9_7(68=wTB3H*I{q+&uR7D`h z$B_||b89>iY5{}To57l5{K3JTwGT5khjQ{0j@Q$6XN+r4S@J5H-rq8%=-aDmE2C)Y zEaR3(C-kwhzqdCHp(t>H;`nuP>cdtAQ~2n?1H=7nMe7tzi`+0x-T>-hmfo zYNNxxq%3qjd4kw2W{E-ax6-!a7;%;%I~fS+#0t4f2vH(d$HuF7kxG=vPInb?&ZI|C z9{UysvHEFFS<+;SnKS?Wp=eHdHej(f6a-1)YnD_0$E~ZRs5o)vrAL8gV%-UrSECJO zET$p*ySuukr>3U9eS@arAxGw^Q-s$WlCAJ@hb6e7q1XUhj#K=hY${Sgl9M0E&Ypt$ z?q)_!VZ**6KrJBHomcyfL2|^FR8m(r4a7}!d^}+!RR(#Cd|#osQ}hV`kQ(K^D;4`x z@539|6W_mhQT#*18u)O^@v!}i$HnrJOQdgI4&BlG{7H7~L%C66;TLjZ<+C{Rj2wdu z!3L;!9&$&~pc>2wy(gxJ5@j@?dMK(y)Y&I#O41MSLhF!^%!jFONvjpE z9{$8cI}mW}qmKN_rxrCq0RbEGj%{u})?ARQZ=(~nVZ(+Zs<(-BgUiE8t0I<;Na%^= zA?kzhCjc9>1}EI@cTX*DH`|B!b2@9sDz1E~Ibh&S?Q^zzvvpz$&!zZ|^0jxK%)Fbg zGVd?v_jYs~f$O1{o$1n*>q3X9KNM_!`buo9yoi6&W;}RRoNjm-Nx*;I&?5~|9_b>T z%Z~z&d7);3Ty6AergahKPUTR)-lPQ5J%aA{ z^YnCd8Td7Z@BT~biYua%dFwd%JbZB;`FV-EwVTXdowMm(^K}|b*&tmqf~cGg zxP^wMzkcOhc85l81eMANO|d?oy{ek;%lO1Z`a_J=EF1dRmD=jv`1VA#IB|q9KOk{4 zaI1~^;N(Ox;kNwNO`|A+KaVQIHTBU(#^DA-z77{R^IQ5+VyJc=R4?C90dV&NnesNq zdO>U>!?~E}V;eZ)_=8g)dJh7alD7YdG@L?<$+rNB98MH>gS(@M$Vg$WwdV0>V0@*TjJmzc#-@l%nbp%3 zJ{ay%+(<&D<;eAzF^~=j2q>pb)%`Ti{-XIcO`cX}FQQ0pO&o|#-y&LA45WAR^XHZ| z#IQZ2?2l^BnyF*zq?9(5oT`6ZnKesZJ)kp7y%)V&@0G9KX#r9~XmC5LEtHi~q~9M# zQsbb0UDT}=OPK(*34p29Zk!%ZyuiGm2`Z7>|oLY5lk~U z7C*YoTuLLUOcp6(H#Aq(u35-8II;4-UJ-d@Cz_r64jx== z7t7xg-RhE$W-JsAH=(Th`g+Xo$&r~|`eYx(Xl6W4QRrTPP~!mxVWUD%xtx z9>=YNEJ`#}(Wf;t%mg=4H|RQ)M+g;Cy=RiI2k2z|@bL4{I93{g6Dv(t{D}E561DpK z^2?h1QZQ?-%#7*a7OIa4YU!}sy}L$8UEPrky(8?+(y`GyChYw~ksSzde1k`mNh=BW zVc+IP`WMX+HFNMv7y8g}pJm=ZdPjMU_fy1EV3^HKRVOzA0Rgu|0Exb*6g>Am?;$Hd4p+YFZntLIho$Peod{bPn*NHnaWmgZD4C>G#5=A zP239V*6wabg(H*lqvV2Bd=H?+-eqkae@h*c=MsX7WR+{JA7+WItT>p*E&X+s{>o0} zM1Y$j9j{RL>jZFi#wbY<40PogU6$l52yonB9+YWWbbT?ZJ=t(YGcSk#tsi2_XKpYzyM6Clt zTBPlh(vQ)3_WGuCrjOI?K62zpaY~wPmp+=%T6BEJ6g}#5iCQT;;}nE#?_pZgqmg{AA0x2^dg@u@gZU1<>ZXL9;>ovFwo%x zJi46KKX9M0=Q?YLd~IHWn)E*2jOWo6cZKdmtWb<8Y}qH*1E|>Z==U8q)e)b8e>%Kp z_hgY>p7IQGEyo^Zc9wt{u~s5bh_k6S#v9_>Q4TOpSGkukUAqzF9LRmn`-`61l+NLa zuec=QJDt3bZ`t3+o>_Am#39Rc$L&LtuOmf2BV(p8^k>{`od+EX#E61JH6)1UdE#LU^(3aE>bReP1YejE8 zB>nLNga?+@4l`!FJT)K%MF@UOYNm zuxsPSjpTa>!EOCkK2~L(p6doefTcz6!=f;r6obRj+@h4YIO^=%)itucm4KOXy9=Ji zND)Ra=V2iBThxUN5Vt|H*O&Izs-t}T_^g~mU~~_Q%9Yha>_Pi6%-QxtaSys%xj+VE zI~~c0olbx;`U_rh^S*U-^(h!}ROSJ=@&mRKT$^j!y>-bdPq>Q`=K~HMLPAMu%+w3- zLJA5ApDn%nEG-9sa0^{m)={nwti64)sYzp}rlwC|O+-p-9}^9YH9W^D(qHL6C!~Wo zqEbq{G`{LU!m5@f=JCe?RX9($IYGL_9Xq(;?m8yrN~cHY&65EvC@a|0CzdzJ&19W2Oyq%i+srvctN0p3{edZWt^DENg(1?P@yAgXt$n>4Je=1XJDD#P zah5rqpD$|@d%$Jiz9OdRDUeqvlsIo#LQVT@er~RMvl~mO=2YvFR_<0)9}=rF_xEk4 zrWekh)tLo_R}eI~oTIvyd_E!EanGj-_MU|6dKSud<6Km4K70V%_EPV|QXN_*12ePU zwO$wP7-&5h;e#`|v($aSzDM$WlWx;kzYE`v#^vLdC9y4}-nC!ZGvg-O0@+ou-qdvMsm z#B$VX=LyHAg6^wyC?KaBj(n3*mz~hFdCqfC99dkxM=8JoN_46ASk68;P0}_PA&d>O z4opI)&Of9&40PCPd?OR79l5M6o`vH$62r%Tw1>9?w^|sVSEUsy`GCaj&!Be*mwspG zJ5c-Qq_Q&qKn>D34HG!sE3zYII3U4kK`MvHjguSH(r%%%!1jr^+>UQ$yzJT6`T2RG zkbP)-kdaT>8;&Pv=MHXIaR0SdP?uzr$>P!?qOT$1!(@t_0+#z3@IT;nNsDxgii+yZ z)g5Q4RY1-xaVrDHGs#x`)hk-t+8QMqOx=9*6q}O^F%B_Y_9&A1rTlT(f`ho4pIP@u zYnU~>Q-pL`T8^vBtBM7WA#8fwTun%F1WAMa!u_#0J8rYjVXqQBXyB0}#OGcC19Rll zb?T}2y@gQ*u~t@AZBOd|4lI4D)QWE~oBiaQkTAM?$H2-@mCkm+TbnR=8Kq_7Cza8h zqMTnitF!Zo^mHNop8CaoBdLWC?4nF;SkRt~ok?lE0>zCbfl|=QQG@C`&Y7-c92BH- zhHox1HTVi(*SdJcDDdvM2@|dfzKgnV|H_c=rQ@Hw4Nn?4=AUTKFZd@t@sC&a3zBd6 zxAvCo6kksLzpyj6JRLQ3vg(Samo%po85K>}#?f}wg5|LpD7xF-eUeix0quU{+85?m zwdCCLTJ3sOIk55umi--HP;6F3AO7+smyEbLtcFpSE?r7aCUTEPs>F$T=jZh8>~j1S zYPZ|i1f!rZ9F1SKcAbc62?FAWhY?O}i%+kts*-cGxGEz=WvmG(w(f-DX$)}4&gLN+ z^fh8)Xa$M3zU4&JL9@3)>(?9ov$5|$ph_^J9KCyxY@!OK2NpdeBO}R_S{g#?khc|O znBw(iqXbCDq@CZ`O;&_*bOt^E&{wkfrDEcIu=JqWVwFa`Pf$?9#|j}JU*KNbV#g%j zYA5yW-nz8|bBh-m4!l4)pSok5O6?e{BGbKrrgYJVuxI0Uc;4WrZ}l_OA8%_P0fFn# zE12>FCnx4YBPC&otLq`Lm{4HYJAsRI!GVSQ;6H%%`75WW= z;XA~|M3>)60o?oPep%|db6#k7P@>g50T6_SPg@jtE8JxFg=`RPd>w(o@Txgi;a@L~ zId+Wh@ZrPL{WuCC_}Zcgs{SjcpJC_&-z%_^K>(n7voBx%cH!3IB84{SjvE4wnAl>a zwNIOePmlMJcHFHlEp-9BOk%M71%hEES{^0V-6gWCm)D0+;l<_5T`VnMQ5uimc*g!b z1c5v6P+nf`RD!HP4UQ{JD!}ST5!TF&$!{DO(bqw`hlKscx;o28qFroW&eEAEAlwcf z%*WgXw7T$G*umq7-pI_Zjui0LQhUmH+cw=tKq?Wok)z~ggOC8l4H%reb=U6QjD{$} z9|o)o{QB5cj`aRB_+craJz+3}zFU;ti4%nA$@ zjQhCd8BssKH0x1yO7gBH>?mfcX4&qmgqBacYlKrTc=7AF+Tv)Pm1}r75WxGRCVfc z3qfa3uvYuJl+nz;#T^w2erS*gR2wBPU%8U73s4M)s1!|OfEa)INAcN_CH5WzfC$4S z?>U0q9~<@nB*UFOqi)d#P`U)IyvoF22R$&vz z>04t;uv6k?O;Zgw`oCel%8fbpU8}rh`wq(H6uS08X!m2AuU@_CENvHzLh+38 zN`t9&l%tdi7NCs+IGF{r%Z3VqvR7u8r5Dpu9{2XHX~@tI4Gs11kTjM8uu)CD%`WOTD{z*V)!TnN@1s=B9joOMMZ&&N5bx#yf(V0`HmZx?L?r&DsT`|yVW8UrE zbYfXlv*v*}myJZESLFnrQGGDXG&aA@W^25P+X@XYVZCSzrc2FDX7_tHiK_nQ7&fi3 z0XvI5$cuLg0W->}yb|3Igqu#Nwr<}_5y5d1oVj9bVL~Q!Zu9RmmFhQ4qhvCLr#DX_8GOWd!(&whw|IJRTOB1Ppb@6phpx`iX$G<5|c%S~dp>$7#$Ub$lJ zYs#)^Xh<7gxOeMTBO^fhL0$rKYS_Q$Mv|>#Fx+n{+)zhkW_H$)plf;(B&fSB)S%L>jIpa<@G8Fl^r7e_(Y7uVqR zV5Q%1_DV|qhQHDnPHuT9GQeN~3Dgg4Y9*zuFK?>I{aBn25);~*mYW-j8D7M9$3tW_H#RgxiMbl)R2a@KA%kjgU$IjA zwM&=2ERn|X_Wg?p6kSKH_NJd{4wqvK1@I#GYUJM1fn^usHZm%Iux*ZR`^^DCsO*j* z0p0-tUnVDOE?o-!{sx@XJRJ29RM|sl01hb}!nnmBO^deRq$EMa=<4WP0Tz7Z2p1RE z#s!bO!R4HA?Fq=r%9`iFPlq*VYI+Z{ji;2KdnfcyB5=6q>Q$b7))p4EaETgFaE%nC zPuwHaPH-&($OK3-#`;9>L(|Q|YzSe^I*7tRCnl&U9p~K&kB0C?^^Onk-#Zq9hp>lE z5J$AeH)6A;J&gLJ-mKI8J>LwT>|4CNmn0Y+f?l1(?z#ajC|pW?Sz@Zt0yU_st7HFR zOZvk$N-~XqhZ>&n9%883Y5@fE51^A@Yjb1nv(dudQ^XN)``=1f1_g!(4<6*Af&waZLLA1a~}LjekJEJR}7NpE7>}9;0~h8K(Zo9z6I7a>_=P@$>cb08}9q zAZB@RavlOYiZbh%N5m(gmkFd>EPZjSV+U3$Fl4R7z4(vi1rsd>qAY#^#-EBz7a*PG zW)2P-m~jVG;w)kcAn`(AdhE;_(TT~)U3gfC1;LL(v~08m*;Q9}#ZdF`ZOj-MdHot| z!ZY6YM8Up*fJ&@5_qN64LU|C3YY*zQ7e{DdW!as@gEdylD2ZZhpR#(pA*&i$S+^PS4ellhR8_PmtC&QULk zMwB1i$U8XYIUsfG6ja! zDsFkwT-joBT>oA<{J+>}*U(V4`yN$G8(DDIqPghjh!$rsX@qcwI63f4x6bL_S2bh< z6PkuLFNy3saX9a7VPhk4?iK_X2mYoqF;||vGGxSEF&i)mgSn7D+*=!MV| zYjDxwG&op|Baw(dh30)OYtCeKX2PK&DlP2Aovarnp^yI2e}xQXd8zz$@Ffn>#xk{!~ocz@USgnMZUfa@jdLls+3Sw zl<4X#4ZL)Ys2&Y%ILUW$77yIMO+<(j9V2uO=C~?|Zd%RcY2vLa?il^Ssw8Fdm;ZzPjeojZ5(fZp#?ZuHkOK7Pi` z!U86d`H4|&4UH!F;N5ZlVvpdBW2oj~bcb)=zn|+;xHnNL`RhH{2>xZoEttg?q83A5 z>viabg&DtrKC;I|YAdTpz>?6ogg=8$-+!4_7%$c(4YvPV$R+-h$4@_)SKfdN04xIJ z@lBPLbb!`HAg062xY;(u^q*>)BY60JFPd2|C?p^Qd5h-apQ|s6QqkuI5-~6MZ3VF% zNjQ8htgPZv(=aeIHaa>y1xLO%8H6n z-V?#%va+XsBh3eb5B|cw+8MMZN6{&Go9OGeVUM+_I0TNaP)&g|1i@wRXw8q~dqIY0 zO?)=yv7izz4SyR=UiNR0m+s4YV7?lV??G5dKzO*u8MsMnNTi8XLR*g>j(|CYIK(j- z@Z6e=rSANH$Hj=#t0>%xBUz7)m-pSPS164hG~h#R-6 zs_N|76OhkDaTQfoZe7&p!rg%k1+!kTuH!q_wcfR~vRdpMo=p=qGdFL}H(zz~rg4Cd zU^+)WkgmwZGm0oEXE&Sf(PQf(t_k7={NDxp>mLYn{k~K&z7~!h;!?#%!!mCiPbcT~ z_iwj&Af^H%Z)nEpdC$S{hyob|A$D1 zbwe%0n9$9m2DZYP1YI&bY2_6a&C8uJ-Vt#{n=x)vuQ>g{6z|9~QDNaRfUeeh7C1ys zqa1{T8>_zWY+Zee<&GVNhZUYliwX%f;{h}`JHcDBu&{vk0FB7kuV0mWh|Dkb&`p_w z8eu5A8xhL@;2(T^>6tTQTSj}~=b5~p{qtFpf39pr)6W+s5s(rPup90c7|$53q}H!T zvv&kIVnH!z6c5O+9UTj<2y&I+wjal$0<7IDIe_7l3-w^0?NH|l=pxsr_qxup$Ga;0R$%G!zw_D>ti0N}0ERSHs*_v>! zwW)Z5{pqNL-ccD~gN)2NsezIoZv&@}RDxiv=5XM#r|5MwLr?5ETx~JV#9>CWyUpUg z4U#1uI{Ve#NBjMrsL>I^i*uTR(ZtvoijDf(TI~%|2n{hNla}UXWn}mvISYUuAANZ} zQW^Y*%IEXtn)(PK7j#{O9uN|#yJNty&@dCJsaEG~R@}}8XMlKpF*okBXK@{voxy9< z^9t^Vn774TO;r^k&9sbBn}MhufNQ6`77sL#D}p z?B&rrdWY}_p!2#J;FJf!9EU#l+Pdm$mbg_E297u<=hdwHy}ZsD&2Pk6g@x|8G>-j9 zq=1|j-|CvoU)#O(w`wZ{GzS=#x)Wr+L37lL6M~_lrg;Q*?3y3S*1#l%ztT27#8FZyzoMtuo0! zPBM1}PkRPPG|f02n1VUE${PX~mTN?0aLtJ`_KTdTq2rpaRAmapa%UL?yKeQf)_sGfTN`V8 zL}LAM>_yZst5_xR)R}CXx(qPD{k-)akFNuLwP4x3dv1+N8_65VOis}m9L_Rt9ZcDH z`S@yAbF1@&JXpzhD$2|jE z>Di}GFWuRJ(J6qjD74LB0LY@EXz8i%3UxK#LX@z=oVynRj%*JV$4B8l^A8L(Q6phM z#;Lbg+=MQrG@t z?;X>75;SuQSUIhr;VwW+5?jf&vGf$N~+Z3jgMTpfeX3M~++v$i3&gAtp7IILX;cOo|G_;CSU zokrbe{wYqi8eMfTI`sI+_(iN*B7|G0XvlpC4KM&ZGZ#5dvRh&`x>s~VJ$=`wdgD*Q z*vQVxs;2{5i;!o!xk;vcgU?VYwkp%@he3>^_CL3>P+&YX#%gHda;UeKOrC1(1wP|m z^BvC3s#C6eAkz0~-pL!0f`F`i{D2#|`Kgzr0ciKMTSyel*WJc2@D(vMl`nvLAwH5k z=354LSe01!mSMx9Zm2c7GOX7$pM+DkprGKyi6#6TKh^P-k|dC+BEU=%3cpAbuVpn& zOiCha>>&?e4FK-wX`BIjJ+Gd}j@~IHc##`Cl9)Uq$181WG|up7^^>=A|A>$TGH2s- z7hV?ISgF#;afLf)_vH_m=?hWn`3fvfsXnTyF*@WjFgQrTBxO8;Pw2EqU5x;LKkS}P zOA5fJaht4AQ`?VxsX{Ln78cS9glR$Thsl_6q-Ij7!d?65sXWmncEifdB1vX(KE4<9 z8J2}pL`c^fu9R!nuC)~!{guot%S@(6L3lf>95WQ(#5Lk2n#UXw841)a?0V^Y^>df3 zZ<+aK8yrREI~eMRxN9G!q9N%$%AM5S-i{WzO5SZ7l%lN#(E*RjR`HL<6a*a4O$RzoQoc=O8(C6F^d@O77GgY_Vge* z2DRM#&6Vpd#C#boZxUOx98%oy?K(RzF$}Y|5O3oS@!kLo*{7 z`OwUc%|1%E3j8VphJObwp!M-PCpvSRc zDBzWjz#B%^AQKxQ!I>jz{np6Jlz;BwPq`8kgW0KsyFWfHn(GhSLVrRzb|1UdaB2Jk zILbX+D03+2(0HSXoG^BsT3V(AM0QGmQARfchkTPjSQ?NaVsOnJF4Yl@?qHjwqMH2h zBh*=S&*yz0iWP8q0gR(daYR~KbyG*jLA)(!81RTI4QMnu$}gN37pzx@?;o0RbD7!! zCRwpC#tTd$$;QuIR3&A?11*&}jiiP5V+%lSqhoHS+r2^= z@mN2sO;KFAQU~fN$UIyFuU@@MBq~fo?S|n7?C_BXlvboBBp3wtxP-z^laiQNCEr}< zeD(zN*zoHi^&1NoALHMw>a_v}Pl%7#Z^bH-x?Qq^69^Bfa)=v8;&Qa5hY&Q%uR!!n zn(YIyr|A#2E>m=W1s7LrN{rs@n{AsiH=5Y34e5cpx;ma^tQ>nm6HOKI%xRgjwmiMU zWHMJ{Cnb z&_q&LycnU5Y?DwJL03%U{K7IujmF8)6Z!P%Q+Kj8HHlrC9)_mMn*)-(JhY;wcmrG^ zE!M5RiFY7&JH#WRYB*a(Sn&p#Rz+#ia!nc}D|gUgnw!@Huq-W^C~DBKdMH1Tm}n36 z?#{$FOP))iGTkzp(E|`Gi14@7LKx z6mqB?AR=g;DHb^m`fwLam(QL(o7AaxSzbMe{1f{Uv`%O#!Q~0{Znd#lcn2zO66`d| zf(^astC4bbWYLwDld@4ST3a=&IYeTEk?t*AP%-101KMJ^o_OdxTn>I#%k0DJY0VI9 zJcC@tV(uH!o$kkwf=YyA?E-S+EQ)eQe3eBpSy(t;&^2dxBMH0U**uJg03AK2ca2s5 zRv}igw`O6Zh85^_tn`lC@$eglf}I&*Un>7kNWO+Jj+Wwc;D4IN{pb?%QM$|y2-tn4 zy?Bo9*XB*2`PT01O`?;yqvR^rNd}>Y8>rnT`hB+olyqEGzLkifyL>DK|(S=C9^TyUFkWguDNJ5p;)Vxinvof|}>9jn@7@}pV%i$nG1W7Z3LtMk=M!l73Z0MZIh?a))x2Z=svL~f&S%?)GG(s_46O9TBvR09g|byvt$64-mZH@W zs@!T^1?6;t%INA!J9>0kO3eM>yb2uK^2;_fAD?8ct&Wy^H~g#HYdRy?Q&nU}bz4vf zjxa75!;`MLJslL9*!HH0Ex6gKJG_ww$j(Tl^KRcR^`8ASh9vskXZP6~YwfJ16Su}~ zmSPJjmP-GfJ7vnar15a0%@dBe@ja9k%e_nE@o%F)4Z>!+KFfCqwM*g${5EK%1z14juX+|D|kA zzaH?hLF_+j9E#%wtrv2D>dEZy>+5@WsJ4-nM540A6l@TUnh<%jSI0&?(!?w1nn{WJ z;hM#8)czH_5UN0^4vx`z9`edu89oAm6Sj+;-#_iOz@2UN{=mRnH*TC>7j{2DZH^j# zA2>h!v(n3N)YC*QP=ON#76;;9M-+p#&*ifin5n3!C@CpXDBBKo&moq$-89j41#9Mv zm>Y2$Qz#Uiw=uN$53uAJ>rxZeN9{|RV?dfn)oZ&QwdbKB#CpUu63|dp!~$!DP>oTp zzz8#cM}p;>6Ih^)o54w%YC7y(|H7ONk)}&|d6y|lSCg^ocb13-?F;QyMLsdQ$^2mo zlOl9$#SsThMgFu6HtZ0RU%mSIxon zXGWrq5ZaDjum+j_G*t|8-DMaU8P8+p%oFFhZ+A1hJ*j2xF zy?eKOR@s42FOxr&9n*$)d_EPgXa7{Po;?h^6k{NHSy%js@As=$)yYY^{pWAq-0X0P zp>QZ>d6@eiDDxKV>yT#x;Qm?;`HMKf_|keGFA=ziALeESZw3Sw{P6K3krIT4tZbK> z=U5LjGczpAlc)Mcqt0)aHG+jPjv>8@7F&p_mPEX+4jBh!JdroMkiK>nw`d*}Yu2nu??nWhR2eA7lo z2|Bu2h&w;GTyDi1dl(s={BLjj<)eNQPB=)734tI)8%{GAB&uExOXaCA@A4=lx@%Nn zfPQ?E5W`xD-E_hav&crc1;O#v=L+uMcP@@Bp>4iWvzbubsan~F-#nh49yeqI4N0M- zSwrHrYpcyXy}XnUCkr;}s+$Btvzi~O0PRS=jEH_~>2F;tVie7nswsEj*x8n^ALeJc z19e(iAZ`O4O-&*d2XJPFs3`97Z=IZ+P;%EI4Ok&6I@+xq;I#uM37r&(Bw)M!r76m1 zx01{$($(9M1%(aUdKkZO9&(8i}wY1E@OF`;Yn%X4le!!JFClRPgzi%{;Usv!6 z*&vO4`ZM4i_UxpiFtF!(g7itdiqeWblRx{=pHV+RserNzJwrP%*_i`M#n?Ea;9Mqj z6m4uCW`@JV2x!cIN?0#jz~4yR>ZcbM2S7CeV=2M=V9@rA*UHL@KO`s!6ZC}w65jnA zGG*(Bp})g(J_9u=dgR>b{>8j;lAOY8m@Z2k14Uiw;0;i_of4hyF8t!woW}m}B$}Y9KIUW~R80F#b6c7@U5EJ<{oWqom!;5ZnosR#IXQRy3=VS;$5XMMqK`m5$G!Ky zf+YjzPJEEo#}~s7>kU04roMhCsS0%fWi^{de4ir5kKBiTv-@D_a@YmBA=L z;n~QpQFRGZQo~`_<7ki#0x@bJ>UWXAfZ=BZHH|>ATh82_1){K$0!k9Yl#JQ@_U$`% z*f~3MJp$7oBbyH16MIW(X z$-Uq)nRH(E1W^(zYq+WVd!#OLWeJB?%OZfReOv{Nb8E@Av=(=#VS}% z{6^HoSa&mC+ zxr`ivdkE~czBQ)NPl%%4FX3P6<=4H}w-Q@DNk3oGXI}L|N|Nho9}_V(Iay8uY*Lh8 z2mFF_2b;1!$EsDUFeBpTa` ziNlieMtrsMvY=z{k4Vc`C>4nAeu4(E^K5gZwu<{rU5C2e^E1;GDpi;w+F%(#NQid8 zCwL~pcj!~yXau(crN*1ruko;=FXoNHHY7o0hZyXbl+D_Qf{vquL1k2L1*JO*S_$hq zq*Y}0fm@MCYa@&u4VJW}(}Ew`hLJ8=*(c4!&C9W(rV=4-Sn(0}xRQdvMB@}P+lb*i zp#{%m+g4Bn?^Vqdn(o>21%bW@W(IXlROGQJ7WZ=WeKGg*L}FAuYDq#8KL@+>l0$mq zN66X%tOpZL(Yd?%1p;O{ap3Bn-B{`?kLAB&RnO|{tFJ@NsVSm8R^3L}o*Vo5Pm?J@ zJldhZeumm4WBz{!T9ar(Ybo|O?N7SOb6g_!4i1%5&;!6g_5iD>gKUF>hy_f*uhv1o_go+*1e5|Sd<3D%t)?6w zWtaEV*vCNzdbf1F8Xq4Y8!Z_bW}VwuMm~O5Kk*AoR{snNqPuVo#p|U)pkkq4K<m zugMGnE6Fr^s&<>;=c<0|h!-5m`R~CqpcJ=3{2gJyqF5+=gL^z#UJ?wB&X# zF~rMK-_eC(AUGwYa>dS8k3-3BP*+|5B2KcAPNA8tVQatTIv4T*vtNR1c=iVAFfD!8 z`d~W#Uq3G(EuZ@Ny*fNK$dwL7m>EuJ!^!Q5%m-qvUgNdH0+tx)hQE#O>E8plPFRAW zIkzRH_vIFb`hm$M7Pb*zPKZXSdR{b!94!F`eaNQ5|IAb1N9WDL#Du*f8rWj_79wVC z6d`PJZ82*XU$ilHjP#?;^IP29-QV1V_EMbSNP$XBP)o1#30w>kKf~utP%h8|NO_F;?-(bOHW$y^De+IQzJ|2 JRRhONw literal 0 HcmV?d00001 diff --git a/docs/src/images/spring_boot_dp_creation.png b/docs/src/images/spring_boot_dp_creation.png new file mode 100644 index 0000000000000000000000000000000000000000..08ebdaa07673ffa184ce874984a324a04dab604a GIT binary patch literal 50371 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�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 JF5i9pe*o2>i~|4w literal 0 HcmV?d00001 From 9b52701dbebd9937d0bb5a1044d41382c322d7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Sun, 17 Dec 2023 21:32:27 +0530 Subject: [PATCH 28/53] g2pc-core-lib-changes --- .../core/lib/config/G2pUnirestHelper.java | 8 +--- .../core/lib/constants/CoreConstants.java | 17 +++++-- .../lib/security/context/UnirestContext.java | 2 - .../service/AsymmetricSignatureService.java | 11 ++--- .../AsymmetricSignatureServiceImpl.java | 27 +++++------ .../serviceImpl/G2pTokenServiceImpl.java | 19 +++++--- .../lib/service/RequestBuilderService.java | 5 ++- .../RequestBuilderServiceImpl.java | 45 +++++++++++-------- 8 files changed, 75 insertions(+), 59 deletions(-) diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java index b7b0212..8b4266e 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/config/G2pUnirestHelper.java @@ -22,10 +22,6 @@ @Lazy public class G2pUnirestHelper implements G2pUnirest { - - @Autowired - UnirestContext unirestContext; - private static final String AUTH = "Authorization"; @@ -34,15 +30,13 @@ protected GetRequest setG2pHeaders(GetRequest request) { } protected GetRequest setG2pHeaders(GetRequest request, Map keyVal) { - if (null != keyVal) keyVal.forEach((k, v) -> request.header(k, v)); return request; } protected HttpRequestWithBody setG2pHeaders(HttpRequestWithBody request) { - - + UnirestContext unirestContext = new UnirestContext(); if (null != unirestContext && StringUtils.isNotBlank(unirestContext.getJwtHeader())) { request.header(AUTH, unirestContext.getJwtHeader()); } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java index 3577eca..6de59f7 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/constants/CoreConstants.java @@ -25,14 +25,25 @@ private CoreConstants() { public static final String PAGE_SIZE = "page_size"; - public static final String CLIENT_ID = "client_id"; + public static final String KEYCLOAK_URL = "keycloak_url"; - public static final String CLIENT_SECRET = "client_secret"; + public static final String KEYCLOAK_CLIENT_ID = "keycloak_client_id"; - public static final String KEYCLOAK_URL = "keycloak_url"; + public static final String KEYCLOAK_CLIENT_SECRET = "keycloak_client_secret"; public static final String HASHING_ALGORITHM = "hashing_algorithm"; public static final String IS_SIGN = "is_sign"; + public static final String DP_ID = "dp_id"; + + public static final String SUPPORT_ENCRYPTION = "support_encryption"; + + public static final String SUPPORT_SIGNATURE = "support_signature"; + + public static final String KEY_PATH = "key_path"; + + public static final String KEY_PASSWORD = "key_password"; + + public static final String DP_CLEAR_DB_URL = "dp_clear_db_url"; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java index ca37cc6..f54c8f3 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/context/UnirestContext.java @@ -9,8 +9,6 @@ import java.util.List; -@Component -@Scope(value="request", proxyMode= ScopedProxyMode.TARGET_CLASS) @Getter @Setter public class UnirestContext { diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java index e1b19fe..f344cac 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/service/AsymmetricSignatureService.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; +import java.io.InputStream; import java.security.*; import java.security.cert.CertificateException; @@ -10,13 +11,13 @@ @Service public interface AsymmetricSignatureService { - public byte[] sign(String data) throws InvalidKeyException, Exception; + public byte[] sign(String data, InputStream fis , String password) throws InvalidKeyException, Exception; - public PrivateKey getPrivate() throws Exception ; + public PrivateKey getPrivate(InputStream fis , String password) throws Exception ; - public PublicKey getPublic() throws Exception ; + public PublicKey getPublic(InputStream fis , String password) throws Exception ; - public boolean verifySignature(byte[] data, byte[] signature) throws Exception ; + public boolean verifySignature(byte[] data, byte[] signature , InputStream fis , String password) throws Exception ; - public KeyStore.PrivateKeyEntry extractP12Certificate() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException; + public KeyStore.PrivateKeyEntry extractP12Certificate(InputStream fis , String password) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException; } diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java index 4340c20..dc88ee8 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/AsymmetricSignatureServiceImpl.java @@ -26,9 +26,9 @@ public class AsymmetricSignatureServiceImpl implements AsymmetricSignatureServic * @throws Exception */ @Override - public byte[] sign(String data ) throws InvalidKeyException, Exception { + public byte[] sign(String data , InputStream fis , String password) throws InvalidKeyException, Exception { Signature signature = Signature.getInstance("SHA256withRSA"); - signature.initSign(getPrivate()); + signature.initSign(getPrivate(fis, password)); signature.update(data.getBytes()); return signature.sign(); } @@ -39,8 +39,8 @@ public byte[] sign(String data ) throws InvalidKeyException, Exception { * @throws Exception */ @Override - public PrivateKey getPrivate() throws Exception { - return extractP12Certificate().getPrivateKey(); + public PrivateKey getPrivate(InputStream fis , String password) throws Exception { + return extractP12Certificate(fis , password).getPrivateKey(); } /** @@ -49,8 +49,8 @@ public PrivateKey getPrivate() throws Exception { * @throws Exception */ @Override - public PublicKey getPublic() throws Exception { - Certificate certificate = extractP12Certificate().getCertificate(); + public PublicKey getPublic(InputStream fis , String password) throws Exception { + Certificate certificate = extractP12Certificate(fis , password).getCertificate(); return certificate.getPublicKey(); } @@ -62,9 +62,9 @@ public PublicKey getPublic() throws Exception { * @throws Exception */ @Override - public boolean verifySignature(byte[] data, byte[] signature) throws Exception { + public boolean verifySignature(byte[] data, byte[] signature , InputStream fis , String password) throws Exception { Signature sig = Signature.getInstance("SHA256withRSA"); - sig.initVerify(getPublic()); + sig.initVerify(getPublic(fis,password)); sig.update(data); return sig.verify(signature); @@ -80,16 +80,13 @@ public boolean verifySignature(byte[] data, byte[] signature) throws Exception { * @throws UnrecoverableEntryException */ @Override - public KeyStore.PrivateKeyEntry extractP12Certificate() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException { - Resource resource = resourceLoader.getResource("classpath:private.p12"); - InputStream fis = resource.getInputStream(); - + public KeyStore.PrivateKeyEntry extractP12Certificate(InputStream fis , String password) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException { KeyStore ks = KeyStore.getInstance("PKCS12"); - char[] password = "tekdi".toCharArray(); - ks.load(fis, password); + char[] passwordInChar = password.toCharArray(); + ks.load(fis, passwordInChar); - KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(password); + KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(passwordInChar); KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("1", protectionParameter); return pkEntry; diff --git a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java index 822ef41..16b717e 100644 --- a/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java +++ b/g2pc-core-lib/src/main/java/g2pc/core/lib/security/serviceImpl/G2pTokenServiceImpl.java @@ -121,6 +121,7 @@ public ArrayList> getClientByRealm(String masterAdminUrl, St , String username , String password) throws JsonProcessingException { String grantType = "password"; ObjectMapper objectMapper = new ObjectMapper(); + ArrayList> responseMap = new ArrayList<>(); HttpResponse response = Unirest.post(masterAdminUrl) .header("Content-Type", "application/x-www-form-urlencoded") .field("grant_type", grantType) @@ -130,13 +131,17 @@ public ArrayList> getClientByRealm(String masterAdminUrl, St .field("password",password) .asJson(); Map responseBody = objectMapper.readValue(response.getBody().toString(), new TypeReference>() {}); - String token = responseBody.get("access_token").toString(); - HttpResponse clientResponse = g2pUnirestHelper.g2pGet(getClientUrl) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + token) - .asString(); - ArrayList> responseMap = objectMapper.readValue(clientResponse.getBody(), ArrayList.class); - return responseMap; + if(responseBody.get("access_token")!=null){ + String token = responseBody.get("access_token").toString(); + HttpResponse clientResponse = g2pUnirestHelper.g2pGet(getClientUrl) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + token) + .asString(); + responseMap = objectMapper.readValue(clientResponse.getBody(), ArrayList.class); + + } + + return responseMap; } diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java index c7c63d4..46b141c 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/service/RequestBuilderService.java @@ -6,8 +6,10 @@ import g2pc.core.lib.dto.common.message.request.RequestMessageDTO; import g2pc.core.lib.dto.common.message.request.SearchCriteriaDTO; import g2pc.core.lib.dto.common.security.TokenExpiryDto; +import g2pc.core.lib.exceptions.G2pcError; import kong.unirest.UnirestException; import java.io.IOException; +import java.io.InputStream; import java.text.ParseException; import java.util.List; @@ -26,7 +28,8 @@ public interface RequestBuilderService { String buildRequest(List searchCriteriaDTOList, String transactionId) throws JsonProcessingException; - Integer sendRequest(String requestString, String uri, String clientId, String clientSecret , String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign) throws Exception; + G2pcError sendRequest(String requestString, String uri, String clientId, String clientSecret , + String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign , InputStream fis , String encryptedSalt , String p12Password) throws Exception; CacheDTO createCache(String data, String status); diff --git a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java index def1b0c..851860b 100644 --- a/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java +++ b/g2pc-dc-core-lib/src/main/java/g2pc/dc/core/lib/serviceimpl/RequestBuilderServiceImpl.java @@ -34,6 +34,7 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.io.IOException; +import java.io.InputStream; import java.util.*; import java.text.ParseException; @@ -57,6 +58,7 @@ public class RequestBuilderServiceImpl implements RequestBuilderService { AsymmetricSignatureService asymmetricSignatureService; + /** * Create a query from payload * @@ -196,12 +198,14 @@ public String buildRequest(List searchCriteriaDTOList, String } @Override - public Integer sendRequest(String requestString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign) throws Exception { + public G2pcError sendRequest(String requestString, String uri, String clientId, String clientSecret, + String keyClockClientTokenUrl , boolean isEncrypt, boolean isSign , InputStream fis, + String encryptedSalt , String p12Password) throws Exception { Boolean isStatusOk = true; log.info("Is encrypted ? -> "+isEncrypt); log.info("Is signed ? -> "+isSign); ObjectMapper objectMapper = new ObjectMapper(); - requestString = createSignature(isEncrypt ,isSign,requestString); + requestString = createSignature(isEncrypt ,isSign, requestString , fis , encryptedSalt , p12Password); String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); log.info("Updated Request -> "+requestString); HttpResponse response = g2pUnirestHelper.g2pPost(uri) @@ -209,28 +213,30 @@ public Integer sendRequest(String requestString, String uri, String clientId, St .header("Authorization", jwtToken) .body(requestString) .asString(); + G2pcError g2pcError = null; if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { - G2pcError g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), response.getBody()); + g2pcError = new G2pcError(ExceptionsENUM.ERROR_SERVICE_UNAVAILABLE.toValue(), response.getBody()); log.info("Exception is thrown by search endpoint", response.getStatus()); - throw new G2pHttpException(g2pcError); + } else if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). readValue(response.getBody()); - log.info("Exception is thrown by search endpoint", response.getStatus()); - throw new G2pHttpException(errorResponse.getG2PcError()); + g2pcError = errorResponse.getG2PcError(); } else if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) { - G2pcError g2pcError = new G2pcError(ExceptionsENUM.ERROR_BAD_REQUEST.toValue(), response.getBody()); - log.info("Exception is thrown by search endpoint", response.getStatus()); - throw new G2pHttpException(g2pcError); + g2pcError = new G2pcError(ExceptionsENUM.ERROR_BAD_REQUEST.toValue(), response.getBody()); } else if (response.getStatus() != HttpStatus.OK.value()) { ErrorResponse errorResponse = objectMapper.readerFor(ErrorResponse.class). readValue(response.getBody()); - log.info("Exception is thrown by search endpoint", response.getStatus()); - throw new G2pHttpException(errorResponse.getG2PcError()); - } + g2pcError= errorResponse.getG2PcError(); + } else if (response.getStatus() == HttpStatus.OK.value()) { + g2pcError = new G2pcError("succ", "request saved in cache"); + } + else { + g2pcError = new G2pcError("err.service.unavailable", "Internal service error"); + } log.info("request send response status = {}", response.getStatus()); - return response.getStatus(); + return g2pcError; } @Override @@ -304,7 +310,7 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie if (g2pTokenService.isTokenExpired(tokenExpiryDto)) { G2pTokenResponse tokenResponse = g2pTokenService.getToken(keyCloakUrl, clientId, clientSecret); jwtToken = tokenResponse.getAccess_token(); - saveToken(clientId, g2pTokenService.createTokenExpiryDto(tokenResponse)); + saveToken(clientId+"-token", g2pTokenService.createTokenExpiryDto(tokenResponse)); } else { jwtToken = tokenExpiryDto.getToken(); } @@ -319,7 +325,8 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie * @return * @throws Exception exception is thrown general because there more than 6 exception being thrown by this method. */ - private String createSignature( boolean isEncrypt , boolean isSign, String requestString) throws Exception { + private String createSignature( boolean isEncrypt , boolean isSign, + String requestString , InputStream fis , String encryptionSalt , String p12Password) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); @@ -335,13 +342,13 @@ private String createSignature( boolean isEncrypt , boolean isSign, String reque if(isSign){ if(isEncrypt){ String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - requestDTO.setMessage(encryptedMessageString); + requestDTO.setMessage(encryptionSalt+encryptedMessageString); requestDTO.getHeader().setIsMsgEncrypted(true); Map meta= (Map) requestDTO.getHeader().getMeta().getData(); meta.put(CoreConstants.IS_SIGN,true); requestDTO.getHeader().getMeta().setData(meta); requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( "salt"+requestHeaderString+encryptedMessageString); + byte[] asymmetricSignature = asymmetricSignatureService.sign( requestHeaderString+encryptedMessageString , fis , p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); log.info("Encrypted message ->"+encryptedMessageString); log.info("Hashed Signature ->"+signature); @@ -351,14 +358,14 @@ private String createSignature( boolean isEncrypt , boolean isSign, String reque meta.put(CoreConstants.IS_SIGN,true); requestDTO.getHeader().getMeta().setData(meta); requestHeaderString = objectMapper.writeValueAsString(requestDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( requestHeaderString+messageString); + byte[] asymmetricSignature = asymmetricSignatureService.sign( requestHeaderString+messageString , fis , p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); log.info("Hashed Signature ->"+signature); } } else { if(isEncrypt){ String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - requestDTO.setMessage(encryptedMessageString); + requestDTO.setMessage(encryptionSalt+encryptedMessageString); requestDTO.getHeader().setIsMsgEncrypted(true); Map meta= (Map) requestDTO.getHeader().getMeta().getData(); meta.put(CoreConstants.IS_SIGN,false); From 04c2bc453975fd97f2a87fb86cd73a5ea37235c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Sun, 17 Dec 2023 21:34:06 +0530 Subject: [PATCH 29/53] g2pc-dp-core-lib changes. --- .../lib/service/ResponseBuilderService.java | 3 +- .../ResponseBuilderServiceImpl.java | 23 +++-- .../src/main/resources/application.yml | 88 +++++++++---------- 3 files changed, 60 insertions(+), 54 deletions(-) diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java index b02dca2..9f8bf27 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/service/ResponseBuilderService.java @@ -11,6 +11,7 @@ import kong.unirest.UnirestException; import java.io.IOException; +import java.io.InputStream; import java.text.ParseException; import java.util.List; @@ -22,7 +23,7 @@ public interface ResponseBuilderService { String buildResponseString(String signatureString, ResponseHeaderDTO responseHeaderDTO, ResponseMessageDTO messageDTO) throws JsonProcessingException; - G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception; + G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl , InputStream fis , String encryptedSalt) throws Exception; public void saveToken(String cacheKey, TokenExpiryDto tokenExpiryDto) throws JsonProcessingException; diff --git a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java index 8e4fd49..48f4f7e 100644 --- a/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java +++ b/g2pc-dp-core-lib/src/main/java/g2pc/dp/core/lib/serviceimpl/ResponseBuilderServiceImpl.java @@ -34,6 +34,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; +import java.io.InputStream; import java.text.ParseException; import java.util.*; @@ -57,12 +58,14 @@ public class ResponseBuilderServiceImpl implements ResponseBuilderService { @Autowired G2pTokenService g2pTokenService; - @Value("${crypto.consumer.support_encryption}") + @Value("${crypto.to_dc.support_encryption}") private boolean isEncrypt; - @Value("${crypto.consumer.support_signature}") + @Value("${crypto.to_dc.support_signature}") private boolean isSign; + @Value("${crypto.to_dc.password}") + private String p12Password; @Autowired AsymmetricSignatureService asymmetricSignatureService; @@ -130,12 +133,13 @@ public String buildResponseString(String signatureString, ResponseHeaderDTO resp } @Override - public G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl) throws Exception { + public G2pcError sendOnSearchResponse(String responseString, String uri, String clientId, String clientSecret, String keyClockClientTokenUrl + , InputStream fis , String encryptedSalt) throws Exception { log.info("Send on-search response"); ObjectMapper objectMapper = new ObjectMapper(); log.info("Is encrypted ? -> "+isEncrypt); log.info("Is signed ? -> "+isSign); - responseString = createSignature( isEncrypt, isSign , responseString); + responseString = createSignature( isEncrypt, isSign , responseString , fis , encryptedSalt); String jwtToken = getValidatedToken(keyClockClientTokenUrl, clientId, clientSecret); HttpResponse response = g2pUnirestHelper.g2pPost(uri) .header("Content-Type", "application/json") @@ -230,7 +234,8 @@ public String getValidatedToken(String keyCloakUrl, String clientId, String clie * @return * @throws Exception */ - private String createSignature( boolean isEncrypt, boolean isSign, String responseString) throws Exception { + private String createSignature( boolean isEncrypt, boolean isSign, String responseString + , InputStream fis , String encryptedSalt) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, @@ -248,13 +253,13 @@ private String createSignature( boolean isEncrypt, boolean isSign, String respon if(isSign){ if(isEncrypt){ String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedMessageString); + responseDTO.setMessage(encryptedSalt+encryptedMessageString); responseDTO.getHeader().setIsMsgEncrypted(true); Map meta= (Map) responseDTO.getHeader().getMeta().getData(); meta.put(CoreConstants.IS_SIGN,true); responseDTO.getHeader().getMeta().setData(meta); responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+encryptedMessageString); + byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+encryptedMessageString ,fis ,p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); log.info("Encrypted message ->"+encryptedMessageString); log.info("Hashed Signature ->"+signature); @@ -264,14 +269,14 @@ private String createSignature( boolean isEncrypt, boolean isSign, String respon meta.put(CoreConstants.IS_SIGN,true); responseDTO.getHeader().getMeta().setData(meta); responseHeaderString = objectMapper.writeValueAsString(responseDTO.getHeader()); - byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+messageString); + byte[] asymmetricSignature = asymmetricSignatureService.sign( responseHeaderString+messageString,fis,p12Password); signature = Base64.getEncoder().encodeToString(asymmetricSignature); log.info("Hashed Signature ->"+signature); } } else { if(isEncrypt){ String encryptedMessageString = encryptDecrypt.g2pEncrypt(messageString,G2pSecurityConstants.SECRET_KEY); - responseDTO.setMessage(encryptedMessageString); + responseDTO.setMessage(encryptedSalt+encryptedMessageString); responseDTO.getHeader().setIsMsgEncrypted(true); Map meta= (Map) responseDTO.getHeader().getMeta().getData(); meta.put(CoreConstants.IS_SIGN,false); diff --git a/g2pc-dp-core-lib/src/main/resources/application.yml b/g2pc-dp-core-lib/src/main/resources/application.yml index a800889..40f2b74 100644 --- a/g2pc-dp-core-lib/src/main/resources/application.yml +++ b/g2pc-dp-core-lib/src/main/resources/application.yml @@ -1,44 +1,44 @@ - -spring: - mvc: - pathmatch: - matching-strategy: ANT_PATH_MATCHER - datasource: - driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/gtwop?currentSchema=dp - username: postgres - password: root - hikari: - data-source-properties: - stringtype: unspecified - cachePrepStmts: true - prepStmtCacheSize: 250 - prepStmtCacheSqlLimit: 2048 - useServerPrepStmts: true - useLocalSessionState: true - rewriteBatchedStatements: true - cacheResultSetMetadata: true - cacheServerConfiguration: true - maintainTimeStats: false - maximum-pool-size: 5 - connection-timeout: 5000 - jpa: - properties: - hibernate: - jdbc: - lob: - non_contextual_creation: true - dialect: org.hibernate.dialect.PostgreSQLDialect - hibernate.ddl-auto: none - show-sql: false - open-in-view: false - generate-ddl: false - -spring.data.redis: - repositories.enabled: false - host: localhost - password: 123456789 - port: 6376 - - - +# +#spring: +# mvc: +# pathmatch: +# matching-strategy: ANT_PATH_MATCHER +# datasource: +# driverClassName: org.postgresql.Driver +# url: jdbc:postgresql://localhost:5432/gtwop?currentSchema=dp +# username: postgres +# password: root +# hikari: +# data-source-properties: +# stringtype: unspecified +# cachePrepStmts: true +# prepStmtCacheSize: 250 +# prepStmtCacheSqlLimit: 2048 +# useServerPrepStmts: true +# useLocalSessionState: true +# rewriteBatchedStatements: true +# cacheResultSetMetadata: true +# cacheServerConfiguration: true +# maintainTimeStats: false +# maximum-pool-size: 5 +# connection-timeout: 5000 +# jpa: +# properties: +# hibernate: +# jdbc: +# lob: +# non_contextual_creation: true +# dialect: org.hibernate.dialect.PostgreSQLDialect +# hibernate.ddl-auto: none +# show-sql: false +# open-in-view: false +# generate-ddl: false +# +#spring.data.redis: +# repositories.enabled: false +# host: localhost +# password: 123456789 +# port: 6376 +# +# +# From 8208b721ee764140befd04dd38fc1222e3ba1cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cabhilash=5Fk=40tekditechnologies=2Ecom=E2=80=9D?= Date: Sun, 17 Dec 2023 21:45:14 +0530 Subject: [PATCH 30/53] g2pc-ref-dc-client changes. --- .../ref/dc/client/config/RegistryConfig.java | 72 +++++--- .../client/controller/rest/DcController.java | 132 +++++++------- .../rest/DcDashboardController.java | 34 +++- .../entity/RegistryTransactionsEntity.java | 47 ----- .../RegistryTransactionsRepository.java | 17 -- .../security/dc_to_farmer/farmer_search.p12 | Bin 0 -> 2429 bytes .../security/dc_to_farmer/private-key.pem | 30 ++++ .../client/security/dc_to_farmer/private.crt | 20 +++ .../client/security/dc_to_farmer/private.csr | 17 ++ .../security/dc_to_farmer/public-key.pem | 9 + .../security/dc_to_mobile/mobile_search.p12 | Bin 0 -> 2365 bytes .../security/dc_to_mobile/private-key.pem | 30 ++++ .../client/security/dc_to_mobile/private.crt | 18 ++ .../client/security/dc_to_mobile/private.csr | 16 ++ .../security/dc_to_mobile/public-key.pem | 9 + .../farmer_to_dc/farmer_on_search.p12 | Bin 0 -> 2397 bytes .../security/farmer_to_dc/private-key.pem | 30 ++++ .../client/security/farmer_to_dc/private.crt | 19 ++ .../client/security/farmer_to_dc/private.csr | 17 ++ .../security/farmer_to_dc/public-key.pem | 9 + .../mobile_to_dc/mobile_on_search.p12 | Bin 0 -> 2365 bytes .../security/mobile_to_dc/private-key.pem | 30 ++++ .../client/security/mobile_to_dc/private.crt | 18 ++ .../client/security/mobile_to_dc/private.csr | 17 ++ .../security/mobile_to_dc/public-key.pem | 9 + .../service/DcRequestBuilderService.java | 9 +- .../DcRequestBuilderServiceImpl.java | 139 +++++---------- .../serviceimpl/DcValidationServiceImpl.java | 74 +++++++- .../ref/dc/client/utils/DcCommonUtils.java | 57 ++++++ .../src/main/resources/1693731657.p12 | Bin 0 -> 6013 bytes .../src/main/resources/application-local.yml | 99 ++++++----- .../src/main/resources/application.yml | 99 ++++++----- .../src/main/resources/farmer_on_search.p12 | Bin 0 -> 2397 bytes .../src/main/resources/farmer_search.p12 | Bin 0 -> 2429 bytes .../src/main/resources/mobile_on_search.p12 | Bin 0 -> 2365 bytes .../src/main/resources/mobile_search.p12 | Bin 0 -> 2365 bytes .../src/main/resources/private.p12 | Bin 0 -> 2547 bytes .../src/main/webapp/WEB-INF/jsp/dashboard.jsp | 162 ++++-------------- .../src/main/webapp/resources/css/styles.css | 108 ++++++++++++ 39 files changed, 880 insertions(+), 467 deletions(-) delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java delete mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/farmer_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private.crt create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/private.csr create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/public-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/mobile_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.crt create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.csr create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/public-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/farmer_on_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.crt create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.csr create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/public-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/mobile_on_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private.crt create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/private.csr create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/public-key.pem create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/1693731657.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/farmer_on_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/farmer_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/mobile_on_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/mobile_search.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/private.p12 create mode 100644 g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/resources/css/styles.css diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java index e94fd2f..dbd9c85 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/config/RegistryConfig.java @@ -20,34 +20,57 @@ public class RegistryConfig { @Value("${registry.api_urls.mobile_search_api}") private String mobileSearchURL; - @Value("${registry.api_urls.farmer_search_public_api}") - private String farmerSearchPublicURL; + @Value("${dashboard.clear_dp1_db_endpoint_url}") + private String farmerClearDbURL; - @Value("${registry.api_urls.mobile_search_public_api}") - private String mobileSearchPublicURL; + @Value("${dashboard.clear_dp2_db_endpoint_url}") + private String mobileClearDbURL; - @Value("${keycloak.farmer.clientId}") + @Value("${keycloak.from_dp.farmer.clientId}") private String farmerClientId; - @Value("${keycloak.farmer.clientSecret}") + @Value("${keycloak.from_dp.farmer.clientSecret}") private String farmerClientSecret; - @Value("${keycloak.farmer.url}") + @Value("${keycloak.from_dp.farmer.url}") private String keycloakFarmerTokenUrl; - @Value("${keycloak.mobile.clientId}") + @Value("${keycloak.from_dp.mobile.url}") + private String keycloakMobileTokenUrl; + + @Value("${keycloak.from_dp.mobile.clientId}") private String mobileClientId; - @Value("${keycloak.mobile.clientSecret}") + @Value("${keycloak.from_dp.mobile.clientSecret}") private String mobileClientSecret; - @Value("${keycloak.mobile.url}") - private String keycloakMobileTokenUrl; + @Value("${crypto.to_dp_farmer.support_encryption}") + private boolean isFarmerEncrypt; + + @Value("${crypto.to_dp_farmer.support_signature}") + private boolean isFarmerSign; + + @Value("${crypto.to_dp_mobile.support_encryption}") + private boolean isMobileEncrypt; + + @Value("${crypto.to_dp_mobile.support_signature}") + private boolean isMobileSign; + + @Value("${crypto.to_dp_mobile.key_path}") + private String mobileKeyPath; + + @Value("${crypto.to_dp_farmer.key_path}") + private String farmerKeyPath; + + @Value("${crypto.to_dp_farmer.password}") + private String farmerKeyPassword; + + @Value("${crypto.to_dp_mobile.password}") + private String mobileKeyPassword; /** * Map to represent which query params are required for which registry * - * * @return query params specific to registry */ public Map getQueryParamsConfig() { @@ -70,7 +93,6 @@ public Map getQueryParamsConfig() { /** * Map to represent which common values to be used to generate request for a registry * - * * @return Map to represent registry specific config values */ public Map getRegistrySpecificConfig() { @@ -87,13 +109,11 @@ public Map getRegistrySpecificConfig() { /** * Set farmer registry specific config values * - * * @return Map to represent registry specific config values for farmer */ private Map getFarmerRegistryMap() { Map farmerRegistryMap = new HashMap<>(); farmerRegistryMap.put(CoreConstants.QUERY_NAME, "paid_farmer"); - farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchPublicURL); farmerRegistryMap.put(CoreConstants.REG_TYPE, "ns:FARMER_REGISTRY"); farmerRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); farmerRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); @@ -101,22 +121,26 @@ private Map getFarmerRegistryMap() { farmerRegistryMap.put(CoreConstants.SORT_ORDER, SortOrderEnum.ASC.toValue()); farmerRegistryMap.put(CoreConstants.PAGE_NUMBER, "1"); farmerRegistryMap.put(CoreConstants.PAGE_SIZE, "10"); - farmerRegistryMap.put(CoreConstants.CLIENT_ID, farmerClientId); - farmerRegistryMap.put(CoreConstants.CLIENT_SECRET, farmerClientSecret); farmerRegistryMap.put(CoreConstants.KEYCLOAK_URL, keycloakFarmerTokenUrl); + farmerRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_ID, farmerClientId); + farmerRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_SECRET, farmerClientSecret); + farmerRegistryMap.put(CoreConstants.SUPPORT_ENCRYPTION, "" + isFarmerEncrypt); + farmerRegistryMap.put(CoreConstants.SUPPORT_SIGNATURE, "" + isFarmerSign); + farmerRegistryMap.put(CoreConstants.KEY_PATH, farmerKeyPath); + farmerRegistryMap.put(CoreConstants.KEY_PASSWORD, farmerKeyPassword); + farmerRegistryMap.put(CoreConstants.DP_SEARCH_URL, farmerSearchURL); + farmerRegistryMap.put(CoreConstants.DP_CLEAR_DB_URL, farmerClearDbURL); return farmerRegistryMap; } /** * Set mobile registry specific config values * - * * @return Map to represent registry specific config values for mobile */ private Map getMobileRegistryMap() { Map mobileRegistryMap = new HashMap<>(); mobileRegistryMap.put(CoreConstants.QUERY_NAME, "mobile_registered"); - mobileRegistryMap.put(CoreConstants.DP_SEARCH_URL, mobileSearchPublicURL); mobileRegistryMap.put(CoreConstants.REG_TYPE, "ns:MOBILE_REGISTRY"); mobileRegistryMap.put(CoreConstants.REG_SUB_TYPE, ""); mobileRegistryMap.put(CoreConstants.QUERY_TYPE, "namedQuery"); @@ -124,9 +148,15 @@ private Map getMobileRegistryMap() { mobileRegistryMap.put(CoreConstants.SORT_ORDER, SortOrderEnum.ASC.toValue()); mobileRegistryMap.put(CoreConstants.PAGE_NUMBER, "1"); mobileRegistryMap.put(CoreConstants.PAGE_SIZE, "10"); - mobileRegistryMap.put(CoreConstants.CLIENT_ID, mobileClientId); - mobileRegistryMap.put(CoreConstants.CLIENT_SECRET, mobileClientSecret); mobileRegistryMap.put(CoreConstants.KEYCLOAK_URL, keycloakMobileTokenUrl); + mobileRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_ID, mobileClientId); + mobileRegistryMap.put(CoreConstants.KEYCLOAK_CLIENT_SECRET, mobileClientSecret); + mobileRegistryMap.put(CoreConstants.SUPPORT_ENCRYPTION, "" + isMobileEncrypt); + mobileRegistryMap.put(CoreConstants.SUPPORT_SIGNATURE, "" + isMobileSign); + mobileRegistryMap.put(CoreConstants.KEY_PATH, mobileKeyPath); + mobileRegistryMap.put(CoreConstants.KEY_PASSWORD, mobileKeyPassword); + mobileRegistryMap.put(CoreConstants.DP_SEARCH_URL, mobileSearchURL); + mobileRegistryMap.put(CoreConstants.DP_CLEAR_DB_URL, mobileClearDbURL); return mobileRegistryMap; } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java index 91e6c44..95eab69 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcController.java @@ -2,39 +2,45 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import g2pc.core.lib.config.G2pUnirestHelper; +import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; import g2pc.core.lib.dto.common.header.RequestHeaderDTO; import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.response.ResponseDTO; import g2pc.core.lib.dto.common.message.response.ResponseMessageDTO; -import g2pc.core.lib.enums.ExceptionsENUM; import g2pc.core.lib.exceptionhandler.ErrorResponse; import g2pc.core.lib.exceptionhandler.ValidationErrorResponse; import g2pc.core.lib.exceptions.G2pHttpException; import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.exceptions.G2pcValidationException; -import g2pc.core.lib.security.BearerTokenUtil; import g2pc.core.lib.security.service.G2pEncryptDecrypt; -import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.dc.core.lib.repository.ResponseDataRepository; +import g2pc.dc.core.lib.service.RequestBuilderService; +import g2pc.ref.dc.client.config.RegistryConfig; import g2pc.ref.dc.client.constants.Constants; import g2pc.ref.dc.client.service.DcRequestBuilderService; import g2pc.ref.dc.client.service.DcResponseHandlerService; import g2pc.ref.dc.client.service.DcValidationService; +import g2pc.ref.dc.client.utils.DcCommonUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import kong.unirest.HttpRequestWithBody; +import kong.unirest.HttpResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; +import java.io.IOException; +import java.text.ParseException; import java.util.*; @RestController @@ -56,37 +62,19 @@ public class DcController { G2pEncryptDecrypt encryptDecrypt; @Autowired - G2pTokenService g2pTokenService; + private DcCommonUtils commonUtils; - @Value("${keycloak.realm}") - private String keycloakRealm; - - @Value("${keycloak.url}") - private String keycloakURL; - - @Value("${keycloak.consumer.admin-url}") - private String masterAdminUrl; - - @Value("${keycloak.consumer.get-client-url}") - private String getClientUrl; - - @Value("${keycloak.admin.realm.client-id}") - private String adminRealmClientId; - - @Value("${keycloak.admin.realm.client-secret}") - private String adminRealmClientSecret; - - @Value("${keycloak.admin.client-id}") - private String adminClientId; + @Autowired + private ResponseDataRepository responseDataRepository; - @Value("${keycloak.admin.client-secret}") - private String adminClientSecret; + @Autowired + private RequestBuilderService requestBuilderService; - @Value("${keycloak.admin.username}") - private String adminUsername; + @Autowired + private RegistryConfig registryConfig; - @Value("${keycloak.admin.password}") - private String adminPassword; + @Autowired + private G2pUnirestHelper g2pUnirestHelper; /** * Get consumer search request @@ -101,13 +89,13 @@ public class DcController { @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/public/api/v1/consumer/search/payload") - public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { + public Map createSearchRequestsFromPayload(@RequestBody Map payloadMap) throws Exception { log.info("Payload received from postman"); - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); + Map acknowledgement = new HashMap<>(); if (ObjectUtils.isNotEmpty(payloadMap)) { - acknowledgementDTO = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); + acknowledgement = dcRequestBuilderService.generateRequest(Collections.singletonList(payloadMap)); } - return acknowledgementDTO; + return acknowledgement; } /** @@ -123,23 +111,14 @@ public AcknowledgementDTO createSearchRequestsFromPayload(@RequestBody Map createSearchRequestsFromCsv(@RequestPart(value = "file") MultipartFile payloadFile) throws Exception { + Map acknowledgement = new HashMap<>(); log.info("Payload received from csv file"); if (ObjectUtils.isNotEmpty(payloadFile)) { - acknowledgementDTO = dcRequestBuilderService.generatePayloadFromCsv(payloadFile); + acknowledgement = dcRequestBuilderService.generatePayloadFromCsv(payloadFile); } - return acknowledgementDTO; - } - - @PostMapping("/public/api/v1/registry/on-search") - public AcknowledgementDTO demoOnSearch(@RequestBody String responseString) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerSubtypes(RequestHeaderDTO.class, - ResponseHeaderDTO.class, HeaderDTO.class); - ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). - readValue(responseString); - return dcResponseHandlerService.getResponse(responseDTO); + //TODO: convert returning map to acknowledgementDTO + return acknowledgement; } /** @@ -148,6 +127,7 @@ public AcknowledgementDTO demoOnSearch(@RequestBody String responseString) throw * @param responseString required * @return On-Search response received acknowledgement */ + @SuppressWarnings("unchecked") @Operation(summary = "Listen to registry response") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = Constants.ON_SEARCH_RESPONSE_RECEIVED), @@ -155,38 +135,25 @@ public AcknowledgementDTO demoOnSearch(@RequestBody String responseString) throw @ApiResponse(responseCode = "403", description = Constants.INVALID_RESPONSE), @ApiResponse(responseCode = "500", description = Constants.CONFLICT)}) @PostMapping("/private/api/v1/registry/on-search") - public AcknowledgementDTO createSearchRequests(@RequestBody String responseString) throws Exception { - String token = BearerTokenUtil.getBearerTokenHeader(); - String introspect = keycloakURL + "/realms/" + keycloakRealm + "/protocol/openid-connect/token/introspect"; - ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, adminRealmClientId, adminRealmClientSecret); - if (introspectResponse.getStatusCode().value() == 401) { - throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); - } - if(!g2pTokenService.validateToken(masterAdminUrl,getClientUrl , g2pTokenService.decodeToken(token) , adminClientId , adminClientSecret , adminUsername , adminPassword)){ - throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); - } - + public AcknowledgementDTO handleOnSearchResponse(@RequestBody String responseString) throws Exception { + commonUtils.handleToken(); AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerSubtypes(RequestHeaderDTO.class, ResponseHeaderDTO.class, HeaderDTO.class); ResponseDTO responseDTO = objectMapper.readerFor(ResponseDTO.class). readValue(responseString); - ResponseMessageDTO messageDTO = null; - Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); - messageDTO= dcValidationService.signatureValidation(metaData, responseDTO); + ResponseMessageDTO messageDTO; + Map metaData = (Map) responseDTO.getHeader().getMeta().getData(); + messageDTO = dcValidationService.signatureValidation(metaData, responseDTO); responseDTO.setMessage(messageDTO); try { dcValidationService.validateResponseDto(responseDTO); if (ObjectUtils.isNotEmpty(responseDTO)) { acknowledgementDTO = dcResponseHandlerService.getResponse(responseDTO); } - } catch (JsonProcessingException e) { + } catch (JsonProcessingException | IllegalArgumentException e) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); - } catch (IllegalArgumentException e) { - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); - } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } return acknowledgementDTO; } @@ -220,4 +187,33 @@ public ErrorResponse handleG2pHttpStatusException(G2pHttpException ex) { return new ErrorResponse(ex.getG2PcError()); } + + /** + * Clear transaction tracker DB + */ + @SuppressWarnings("unchecked") + @GetMapping("/private/api/v1/registry/clear-db") + public void clearDb() throws G2pHttpException, IOException { + commonUtils.handleToken(); + responseDataRepository.deleteAll(); + log.info("DC DB cleared"); + + for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { + try { + Map registrySpecificConfigMap = (Map) registryConfig.getRegistrySpecificConfig().get(configEntryMap.getKey()); + String jwtToken = requestBuilderService.getValidatedToken(registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString()); + log.info("jwtToken: {}", jwtToken); + log.info("url: {}", registrySpecificConfigMap.get(CoreConstants.DP_CLEAR_DB_URL).toString()); + HttpResponse response = g2pUnirestHelper.g2pGet(registrySpecificConfigMap.get(CoreConstants.DP_CLEAR_DB_URL).toString()) + .header("Content-Type", "application/json") + .header("Authorization", jwtToken) + .asString(); + log.info("DP " + registrySpecificConfigMap.get(CoreConstants.REG_TYPE) + " DB cleared with response " + response.getStatus()); + } catch (Exception e) { + log.error("Exception in clearDb: {}", e); + } + } + } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java index 157e328..d63dc8c 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/controller/rest/DcDashboardController.java @@ -1,11 +1,20 @@ package g2pc.ref.dc.client.controller.rest; +import g2pc.core.lib.dto.common.security.G2pTokenResponse; +import g2pc.core.lib.security.service.G2pTokenService; +import g2pc.dc.core.lib.service.RequestBuilderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import java.io.IOException; +import java.text.ParseException; + @Controller +@Slf4j public class DcDashboardController { @Value("${dashboard.left_panel_url}") @@ -17,11 +26,34 @@ public class DcDashboardController { @Value("${dashboard.bottom_panel_url}") private String bottomPanelUrl; + @Value("${dashboard.post_endpoint_url}") + private String postEndpointUrl; + + @Value("${dashboard.clear_dc_db_endpoint_url}") + private String clearDcDbEndpointUrl; + + @Value("${keycloak.dc.client.url}") + private String dcKeyCloakUrl; + + @Value("${keycloak.dc.client.clientId}") + private String dcClientId; + + @Value("${keycloak.dc.client.clientSecret}") + private String dcClientSecret; + + @Autowired + private RequestBuilderService requestBuilderService; + @GetMapping("/dashboard") - public String showDashboardPage(Model model) { + public String showDashboardPage(Model model) throws IOException, ParseException { + String jwtToken = requestBuilderService.getValidatedToken(dcKeyCloakUrl, dcClientId, dcClientSecret); + model.addAttribute("left_panel_url", leftPanelUrl); model.addAttribute("right_panel_url", rightPanelUrl); model.addAttribute("bottom_panel_url", bottomPanelUrl); + model.addAttribute("post_endpoint_url", postEndpointUrl); + model.addAttribute("clear_dc_db_endpoint_url", clearDcDbEndpointUrl); + model.addAttribute("jwtToken", jwtToken); return "dashboard"; } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java deleted file mode 100644 index 5bce42f..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/entity/RegistryTransactionsEntity.java +++ /dev/null @@ -1,47 +0,0 @@ -package g2pc.ref.dc.client.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.sql.Timestamp; - -@Builder -@Data -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Table(name = "registry_transactions") -public class RegistryTransactionsEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - private String transactionId; - - private String farmerId; - - private String farmerName; - - private String mobileNumber; - - private String season; - - private String paymentStatus; - - private String paymentDate; - - private Double paymentAmount; - - private String mobileStatus; - - @Column(insertable = false, updatable = false) - private Timestamp createdDate; - - @Column(insertable = false) - private Timestamp lastUpdatedDate; -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java deleted file mode 100644 index e325097..0000000 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/repository/RegistryTransactionsRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package g2pc.ref.dc.client.repository; - -import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface RegistryTransactionsRepository extends JpaRepository { - - Optional getByTransactionId(String transactionId); - - Optional getByTransactionIdAndFarmerId(String transactionId,String farmerId); - - Optional getByTransactionIdAndMobileNumber(String transactionId,String mobileNumber); -} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/farmer_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_farmer/farmer_search.p12 new file mode 100644 index 0000000000000000000000000000000000000000..1873f5da742adb97bda01180f5e42fbd50b5bcb7 GIT binary patch literal 2429 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 jWogr#r!E8te@FGEGUGJp0s;sC8$Dp4 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private-key.pem new file mode 100644 index 0000000..9464a41 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private-key.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,C62382ECC7C02A70 + +uIlXPPB9YWzUkGvyhfoiRiZzo+d9dRZrZNfaE+pLruXwaza63XDWA2tB3wcSc5v7 +c9K9Qe/XmCcRoqf6psIehOrJA24x2EmAgIOTad8rQN5ovCU0wdUgoP8HqinJDEwN +tHKNiitmBx42gGMKBAxbHs7nNUIwYxwQmbPX8T5Qif/G8rV44P1tOX6VUnMQ2gkJ +sBv12HJzQPAfOmqqqWQfzcHcjseOlhNEOkvnu2IMMxjojoBmp9pnGFFmucuqdUgt ++UY5/8geLuDFTCGEItK+uTOO5CqHpEeqdL9C714Y79XJShOOqYFX/L24m35CJjFc +UeMBvQERTWWmkW97iw6P40WEm55YEqN4UOfUG0WF8NycbtvUEa5krxUN0f2uyVir +XG0D2Dbz5l8J4TMmEV07COP5HFsy4y4AG/dGPBm5IdzEM4aFd1F4RrmlXZ0joVP3 +LLf4GkyJquQJqOsstLR5OHwIbAojUYIp8asHXBjKQAdHx3SEtpBI3YkQgFOTzgQA +gXAzIG9tdShQUkxEHjGowZ9sKKc6YhkAypt5a/RhCWpyRvbj3uYe/wH6IrqYZjuF +sJoAE35ggiJw2B+FM6yBQgkMUdi9Z4vKdgEBlQhJ9wMOhul/enAtZi6n3jpyhrrX +jpJUL91nCnwn5hoCuK799KF6S7QbTnlDZ/fHWGLkNZ9FwNdbW3dbdxjs34eOM9ON +C5UCGc2LBN2/rj0iCLK+5LfbbYISjeV5aUKjmiQBbfUvGMjU9/tNRxHrr5IJMRZ5 +8b6OwsjBdI6y43+Dix+l1I3giKW72hsbP1zWmkW50QwgwQu130IuCOEBuESHngEs +flQB4Hd99NTd46Fd+t8o932zquJQDtUZmWlk8y3DSzhHAA7QAiIY+XvMtcWPM4V7 +7kz/hd5fZe2tllXVz08T7rWgP+t6/gW/RmLJA4wjgZOYMBA0YTfY7uwHPL/kC9T/ +5hYo6DHNKq+wy8NKzknY6B9Y+J37t+Drr6cNzQ+EWG5oM1VJPKAwpu/GTf6TLJht +o76QTUQLhqe0ILV0r/szL4JMoqaiSNq2rS9poMMl8TCkGkNyV/IQ152egmjtkHj1 +N7vYOCXe53MlJnOPIqmgzLQH/3aBaY2A2iJFTxguDGf40hMDGugUJipe8kSliUJ9 +g1FrUZL6sycnpg5mvhNmxw4K/mE0SR6hea30QlwcE6FjbhrlQa68WqoerL1pcgkD +raGfvy+DHu0WDr3B9E5JEp4Ewo/krL715mP/Tfp2PhtCQ48DQGTkmDAUZ3M1BknS +dQUtB0w2eCLoIK+udzKZYdVPwTEM1R90QdHuU8zyUGjsyyETJjZsOH82/q85Gudl +Z9goag+J+9SF3ygYAc3rcACqm1AUKpUZlj05+c00/JlLgH3S7D3NFAVPgV3XgNbY +hWE9n3kdFI4NMCJABUvuOpgZr7poBruaIJv/05dJ+OAUjnLB2i1u96EbfP60H0AU +/7sOZ/g42GTmghSTI4juphejxBByIqtpdqBDOD0tARzbvzx+CEh5Bd0781ws/l1N +h+QkE2CTLBfECprV1jcC/9xMTSiK2KRmCY6JPl/jItJNjQt71Ooh7g== +-----END RSA PRIVATE KEY----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.crt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.crt new file mode 100644 index 0000000..ce6e43a --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+TCCAeECFAWDmxnPiPlFVZjueTpP+7b7nIXAMA0GCSqGSIb3DQEBCwUAMDkx +CzAJBgNVBAYTAklOMQswCQYDVQQIDAJNSDENMAsGA1UEBwwEUHVuZTEOMAwGA1UE +CgwFdGVrZGkwHhcNMjMxMjEyMDg1MDQwWhcNMjQxMjExMDg1MDQwWjA5MQswCQYD +VQQGEwJJTjELMAkGA1UECAwCTUgxDTALBgNVBAcMBFB1bmUxDjAMBgNVBAoMBXRl +a2RpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqm3yRXloOhZgSgTy +/2d3mtklyr6sAzjHonoVZqZAASV/CvVtw4gWYkXMPM8k4U7n7LoeITGniWlL9oP/ +eJpBZpbcWh8YChJjLGZKuFhfdIRAHh0YEQSLQ90j9XkN2Z7wfNEaCfnacs21/zWB +VdA+jmLoVWOeR+Ewpi76v7NmEmAThptMRlj5qX8K+OzyJ4WrRhu2z63lJep2+gMr +iTO4BwWG8YhtSi8NBzLcs0fceUSdhQktEw++XJY2vwAFInNXCDig4Nlc7oVZlRyf +2/25aUKnflw1V1t64m3i2OskuOfVLm0tH+y2bjpBHDfOfl7RDg5tBcCaWmc12QoQ +9Gnu+wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmuYAYZ75LqsG7/ezFgk5BVxiR +ihF5oHMC8n4ySm2Jcx3ONUEgwQISamzVsUtRz+x7ecUae5z0LrmMus5MXFdskBFG +DifCruqJsUISNy0XCSKINSRwpS22xkQGSpaPqR4aov67JvE3QqYxdCxFQAb6IQeZ +Q0wVGILtqLpdaIRtUihrAcqZHZsLj9idMj9wWajIKZpNnqQyRajJ2IOM+wD9PNMY +emfV8L0/uCT9qkN9LZvgwbRB25S8HEfgN1qhX8VSFIwMFzc3tU9wIS6A9kaosUW/ +FvSpjBrGqzAzkZsofnf4XUAmHQOY6wCxTRqfehW3ve84Dk7Rm5/yCMyDmxk8 +-----END CERTIFICATE----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.csr b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.csr new file mode 100644 index 0000000..92f05cc --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/private.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICnDCCAYQCAQAwOTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAk1IMQ0wCwYDVQQH +DARQdW5lMQ4wDAYDVQQKDAV0ZWtkaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKpt8kV5aDoWYEoE8v9nd5rZJcq+rAM4x6J6FWamQAElfwr1bcOIFmJF +zDzPJOFO5+y6HiExp4lpS/aD/3iaQWaW3FofGAoSYyxmSrhYX3SEQB4dGBEEi0Pd +I/V5Ddme8HzRGgn52nLNtf81gVXQPo5i6FVjnkfhMKYu+r+zZhJgE4abTEZY+al/ +Cvjs8ieFq0Ybts+t5SXqdvoDK4kzuAcFhvGIbUovDQcy3LNH3HlEnYUJLRMPvlyW +Nr8ABSJzVwg4oODZXO6FWZUcn9v9uWlCp35cNVdbeuJt4tjrJLjn1S5tLR/stm46 +QRw3zn5e0Q4ObQXAmlpnNdkKEPRp7vsCAwEAAaAeMBwGCSqGSIb3DQEJBzEPDA1t +b2JpbGVfc2VhcmNoMA0GCSqGSIb3DQEBCwUAA4IBAQB8F6gXRTL/CVBjmlSK/a1t +zHQjGiFnB3J7cSwWm+es9Nsi069x8tq7P6feroMz9g4feNe0I8o38HwtqsN87XZ4 +G2VGS5iy6HBb+cgYUHT9nJJmRr2CrBQwdae7e/AVzNCW8vhddP8ua2Au9goWl47w +VTmjJpJQSuUk3niv4FSRS0rddPPSEx5Ingjg4nQNyGSR4baICYC23RT+p7+oYY9l +GgMeAVAoiT830ecVAgnC5htoaf6E733DBT9n6kzIy+SpQIHk4WbBXMh1uauJPW0S +JhhLrUQ+mL34T0MeM1TQGTTUkaoM8r4W0w7PhPB1ZoJwXT+68pki0lEku6DzX1MZ +-----END CERTIFICATE REQUEST----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/public-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/public-key.pem new file mode 100644 index 0000000..322245d --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/dc_to_mobile/public-key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqm3yRXloOhZgSgTy/2d3 +mtklyr6sAzjHonoVZqZAASV/CvVtw4gWYkXMPM8k4U7n7LoeITGniWlL9oP/eJpB +ZpbcWh8YChJjLGZKuFhfdIRAHh0YEQSLQ90j9XkN2Z7wfNEaCfnacs21/zWBVdA+ +jmLoVWOeR+Ewpi76v7NmEmAThptMRlj5qX8K+OzyJ4WrRhu2z63lJep2+gMriTO4 +BwWG8YhtSi8NBzLcs0fceUSdhQktEw++XJY2vwAFInNXCDig4Nlc7oVZlRyf2/25 +aUKnflw1V1t64m3i2OskuOfVLm0tH+y2bjpBHDfOfl7RDg5tBcCaWmc12QoQ9Gnu ++wIDAQAB +-----END PUBLIC KEY----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/farmer_on_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/farmer_on_search.p12 new file mode 100644 index 0000000000000000000000000000000000000000..9a83fcbb282dad532e0b96c39ed79e4042d6812e GIT binary patch literal 2397 zcmV-j38MBef(cmy0Ru3C2_FUtDuzgg_YDCD0ic2j5Cnn=3^0NT2rz;JzXk~^hDe6@ z4FLxRpn?OiFoFZD0s#Opf&--n2`Yw2hW8Bt2LUh~1_~;MNQUfWpepWZny!qW29%CU)nSr@3Y~hl#TCw`-R}dZ>|od(A$ZeH-78JhGMOvlAU3Y0sfAQ3JVz%9OfpQ7$(RyK~=M! zuqQ%jtS@?So$;W>5y6A;&$QaFKiVh}uS7#!^BHHkMCAn$;qXon@lT5=YZ&@ABT_() zIN9%57}%iHCV>21IU@igX<{DrhsSlfID`|2kCpS#3jA8SQ*}xmtG-^b*bV*wDJSV- zE2ZvIO&y*Uvb$V6N{Ki#eZ5r<9X@r3Ft?x(m3e1Hk_}!aLioa)JjawB7vKT~45hDG z4-3bNMmz`q(WWK~A1%gKkg?M3N&{Aer@gn(e94P8f|}y?NCcG&vuVy`MX-+R$!bFs zSgYau@qGZEj5QN95w9q(r1Hf4&h+S8;skDu4r|!@?G3ejXUJHB^>`i!?tISW{Hqrc zM9({{l!N$C?kQh|nDUjuZH-g4jSrM~sXci}?K8*j?cy=!dyqBVF zMZ}-?AixTOjt+t_1<#IAccs;wt1tISynqxSNs0<+&q<^0B5d{i z7ymoO^W@c|lI&?ZouG0It}g5F6KAeZRdv1Ca~{Oyq{4bnSH8r=&xbfKxsuS`oDFf zSvNxd2sAl}#Zf*NEn5Lg@Y4_h3PgSyU!TJ2LxgVa^qobKS}A}5kuD(WVZ~wtnOg_E zOzbcuv^oud`4_veA`^Ir9*9#@FTz2+-ex^}0%k~qN>nedmO!Hb3wsCo;_o&TT|_rN zNu9dnae)X=t6$iKO<*Fn-O@1xYEhv%jJ!-Fv!G#CnP)As__7teeD@Hc3ly)qn5ud| z@gPmnSYjRAZFoFd^1_>&LNQUX7DyKTUfO0WqJn4M z=}wzIP5e~4zxlIE+HsekxQ(37qXNu4-rR>Yd~A4`z&roAn-O0h9sC6Te62mhP`3rq z+cv#tu%DLn3-{5l9_LY%0#BPNtN%QtVjL!%AOUt(sTMDNt;0*iKx2-Qi9_V*${ z-%uw8S0kTOpHdR~NuNE26ncQ%=mLN~Mlh9R9)0V@T(U4I}jcfvy8X$)e*X7-l4+@Amn&!IHI(_*PY}yBht^;@j00 z$FFFq+rrxWZpq|!tui24B^|!4E%YEvDa`(X_u3o#VAB77jk4$e)CQ5n9GjJ6y$7&X z!=ENp0%M6#;|2vTt>nz>^+csYV0Z6bRNKcOLnL>04g{&n+VJ3fXtNtFHFq4&y>L@j zi?ZX5tYbvwNm(lAqF256*9BtiOE`qBJSNe$hh;n2TiOHS#hhMyUxGD7se>Jy_n3I6 zJHY4}_D_L23Vw#!RH+0yaJ{;Ff*JRaxLPaOfk-3pu}O#y%f$VKv^t|p0}CuiR5`j0 z{?GGToYDw{AL(fiWUFGUZ1_B$dj;aH$F_#D5VSU773b75um4}wWRks9i7P4OUNGq{ zRtQkdZ{Ta#6~&PLSUTip5kcZi`p$Nwo_VzN$?B2Y9Us9$!gvlBOeh*+uSU?=phU6! zGgGrTSZ(S7Amrc5zZT>M)2ra>=XyuSqOwE{nJ<4MR#bi*Y1PLi*-M2yI3jPJ^L-d7 zFlDV?7McfOv3+M@sD@5cI7+C>vR{)J1Lh=yw4|YGPr3_&Aa;*Z$3qPQF6mi3V^ADoAKL0B> z+FK3~8_|qRS0YeU!$5qlWv&SdOhhe!KFm2;HP;#&>p}=Q=c{oM3z~j@0yk^$sfHYj z%G#x92aICFXcpE&1hU<5sbi`yRh8y(T7CD;nkDxvCjY0%u?Io!$%t{1dLT0cwP*qB z%9^h6E>`2vRbo8l_J^|UZR8)DbX*9$80;}6Fe3&DDuzgg_YDCF6)_eB6zT01Hj}s~ zu@gM!ETaNo74eAutIB1uG5%0vZJX1QZE|B4*J41mh%hh-Oh8=+fqze6|D# PX?{W`p|9P40s;sCefMD0 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private-key.pem new file mode 100644 index 0000000..8ae3b08 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private-key.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,B138F6DC5A16BD51 + +NH8voiaeQHTJ/QvtVCXv/0cas0/hSymPWdP8mWkyEfpGx2DTCdLF+RTC6vpnoc1L +jSl9/PqDf8dedkysf9DHeGGg6ygjf7l4h5mH9Orm1JHF+41d2yEpNy9nQs8yCriA +OB1IOT2que6lWM4E7T78mpUro4SCXSExMmKDLaDllCwfYjtP12a6SCIgf/j1FpKy +Fmre6mr8t0QW5rlx8QVi/00OWrcnfUhfT05tofp7eaJA2EMDMU+lB8mKra0ALiIe +3+M31zAfsRLJqyIQuWah2zpuT5sO1srOPxtBhV/0E5wedJAtnSUlpL6mNhWhKo72 +teVbm9uSXA7dUHBp6kfUC1X4Whf9mV2LMfVttWcqTwlkMOBOzGReIiRrAO575zQ7 +wtaYEEt2DskzTacitBXEI9JPWiJUQ7ojGDKpDGoEfdShaoLy+8RxFeHXcBlGlkbk +IKlRPX/c8lU2jfC65if21z3FWWMZIIB3wD6FNus6aLTM3zxbOXCOZ5qSDo6UeT2p +EaG0AFBBXnDZvR+/V6iMprpG19eRCz2cgmvpJbnDTNzkQwTai9fAVjj2hlERjAB5 +YIBgyRlrsEVEiZNXyaMIUp4NWa2yrFGRxQFp6vniV3q4GY3c4vhJ/6DGmYI+d/BL +gI8/V29xZQ9DFgORuxkQky3v6vbkkFAKmWL32wKsOu3TzM+zQkcVjDmrKAP5XZNi +rNHJYA8wrNSVkQ54f9Gyy483VegxAEK8iKmMviuNAJX4+yWuxLP4Wh8drhH/1K63 +xtkMa2e0drZQZkH1EJBtqUedw6bOAJjrYewb0Fod3KxG8KTjNJqMLLZ7G2HAihbY +jbWnVu6YIv2K2iXH021SYoKOpLMijb0oRNtWTAReGwWAz4V6kup4Er6W1p3OkaHC +pGFuqozRd3gbKnjqst5H/SNg92+YJI1xdGl1JdQDsSFbsVu5QFI+1FLxL43cKjOR +A4S6agUPeg7f9GS1ruTtMpQ74jfS6f0VKTETeaBci6rlcouWiSFrdrBdNaOiIEhh +JIFeJG3HFSeWFp3DifkdsN/qZ2PPW4NS0Sego0V0AVpAAsGQlp1VUPa/YoNmYRfY +r1QxvDy5BP+Wq2IhOA3XaIwpSa5hfbtLoXAlsxco8YxoDBoB577tPha5xrHtQAvt +cZwgFNnkHyFm6r/C2xWowzLGJcfrmVehvmrJ7hKlRPp5X3fe1SDhand+czpLnYXl +0zkrYBPvpy7okHi8uf4zc2woYCtfPnQmMcQNi+v1Z4LUxtd7BFNRpTyt7R8UK342 +mU7LgshUCLr5mTxcHXDQtJ5JQlQiWvdXW5tvznpyI1L1kh3xKrFklX/XZ/OsHs3S +obcXkYrsrQ3TRpBkXVSVXGB11loqI7f12rg91VZa28cqUBvcFJJvoEtxE5xwCl4N +6dkQ2RU4NbnXobc/Gghoibxh2f4fajyXynlQCLXZyHPGpmsjoUTpG7YShpPuf/h5 +Lkea2MAcY3NkhkFf6fPOURjQzAYCl66ofB47RK+7jxJkLY8oD3df3ZpPkeq+ZnIb +LBBT7x/cB42TCYRcJ0vDBRcRaPkHE7TZD6Ux86rLqmIG8E7V/nUyDg== +-----END RSA PRIVATE KEY----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.crt b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.crt new file mode 100644 index 0000000..09da22f --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGTCCAgECFBg5xnLIBS/v50Tomil7QnEPtmkNMA0GCSqGSIb3DQEBCwUAMEkx +CzAJBgNVBAYTAklOMQswCQYDVQQIDAJNSDENMAsGA1UEBwwEUHVuZTEOMAwGA1UE +CgwFdGVrZGkxDjAMBgNVBAsMBXRla2RpMB4XDTIzMTIxMjA4NTgyNloXDTI0MTIx +MTA4NTgyNlowSTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAk1IMQ0wCwYDVQQHDARQ +dW5lMQ4wDAYDVQQKDAV0ZWtkaTEOMAwGA1UECwwFdGVrZGkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDM4EmZs820e8DioL/nAV7mrrzzIZIa+fQCo/kR +5znNp2FL+5LO1bbNAy6s/5jBEnYUVXAVsfAE3Qt+gIO90Jl2kdpvx4JD8GKqwGS0 +4EZUBXAQRYAFW5hHzZsRQZPKFKWK7Dis1J2fZ8KMVoGMnFceSmj2vmAsgdoIl3s7 +fMcboKtnb0+0yi8ZC87fNAGZ8otcXorSY3CzLiLg3u17s7/UrZ8FnvkQ0yMzCwUw +Cq0TYQusJ8mLzbzXQlsAA+qFPOl+MrWp95iVFGM97ZC5I+8Kj6gMoMhUyr3sWPTV +fRLNY1wce4rR8lJ4TsosqdHVJrTgd1m5RdntaO3lVayYUfyxAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBAKlfm7uoO61/8iePiZNpgUMvVtn604Cluzx3x/w0GBoDp1qK +6tfkgjPeRZL6WJW5kQsKuhMfckNvdCwU0sacQEflydldpQR316ssfxeeqcokDh0S +SorJKbfsWaiVTmmxn53mSps5kduDhrzAeTyPkL4Oks4rlo0BoBdVkrNg7mpH7dCd +g1b3VGuFSiQWWqoCEhLpFEF/4s44Cj8JJemfcf6RXKfHDnylOMHEAM3fsd2D2STl +29mv2Ze9ouBBJqpa40HR60S/chEgTP2hfoudje6xpkEhWjzwG13pGCwCKA0YZvUK +lLWFUJYfxr0E0UvYpIHrKgy3rmO7RXxY4HqMru8= +-----END CERTIFICATE----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.csr b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.csr new file mode 100644 index 0000000..f21d893 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/private.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICrzCCAZcCAQAwSTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAk1IMQ0wCwYDVQQH +DARQdW5lMQ4wDAYDVQQKDAV0ZWtkaTEOMAwGA1UECwwFdGVrZGkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM4EmZs820e8DioL/nAV7mrrzzIZIa+fQC +o/kR5znNp2FL+5LO1bbNAy6s/5jBEnYUVXAVsfAE3Qt+gIO90Jl2kdpvx4JD8GKq +wGS04EZUBXAQRYAFW5hHzZsRQZPKFKWK7Dis1J2fZ8KMVoGMnFceSmj2vmAsgdoI +l3s7fMcboKtnb0+0yi8ZC87fNAGZ8otcXorSY3CzLiLg3u17s7/UrZ8FnvkQ0yMz +CwUwCq0TYQusJ8mLzbzXQlsAA+qFPOl+MrWp95iVFGM97ZC5I+8Kj6gMoMhUyr3s +WPTVfRLNY1wce4rR8lJ4TsosqdHVJrTgd1m5RdntaO3lVayYUfyxAgMBAAGgITAf +BgkqhkiG9w0BCQcxEgwQZmFybWVyX29uX3NlYXJjaDANBgkqhkiG9w0BAQsFAAOC +AQEAD2Hah9cJwoNoDryZG6dtXmaJ05ZYLuskgH3zntbHpOzD+LoqDZmy07YcQa+f +X4N4qPx9YW5Z8qOglqjgM4ww+ohMTDghjKlBu390Yxc0IMot5mBQV/U4q+Qijztw +was/KoaHpMvCWlDK3NIcMykPbhkaASfAf1slc4EbPXAN0RgDEOanHkO9xxz2fra+ +YY+sFnkNvJEAjpQOC1Sfwmz1p9qmQU+GLugq/GsgSaFDhyAXQZnJdb5SQzVkshUt +GCzVmxD9dFaXuOKYhBCkr34WvM/zLUrf/qWsJGlmgdVjmaAyPv+N/Ua5JXYIW+Sh +TpEUQl//nYglp2HJRIAJBMyHhA== +-----END CERTIFICATE REQUEST----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/public-key.pem b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/public-key.pem new file mode 100644 index 0000000..ab1b37a --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/farmer_to_dc/public-key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzOBJmbPNtHvA4qC/5wFe +5q688yGSGvn0AqP5Eec5zadhS/uSztW2zQMurP+YwRJ2FFVwFbHwBN0LfoCDvdCZ +dpHab8eCQ/BiqsBktOBGVAVwEEWABVuYR82bEUGTyhSliuw4rNSdn2fCjFaBjJxX +Hkpo9r5gLIHaCJd7O3zHG6CrZ29PtMovGQvO3zQBmfKLXF6K0mNwsy4i4N7te7O/ +1K2fBZ75ENMjMwsFMAqtE2ELrCfJi82810JbAAPqhTzpfjK1qfeYlRRjPe2QuSPv +Co+oDKDIVMq97Fj01X0SzWNcHHuK0fJSeE7KLKnR1Sa04HdZuUXZ7Wjt5VWsmFH8 +sQIDAQAB +-----END PUBLIC KEY----- diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/mobile_on_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/security/mobile_to_dc/mobile_on_search.p12 new file mode 100644 index 0000000000000000000000000000000000000000..43fe9ea8f40af2758e660706e8749dc418160506 GIT binary patch literal 2365 zcmV-D3BvX;f(bbS0Ru3C2>%8NDuzgg_YDCD0ic2i@C1Sg>@b1|=rDoqY ztof!z0s;sCfPw>9SVDgqn6hyIglgFzeKNj5uR*x*hrP!&e>d7yEkE)NONVYQ|9pJnAPw#?S(9*^~5*JaVQ7xB)H!dSQt)&(mM^~ zS^vo7Q{LsBfHJG=KO*X^3Y;85lbfTu_sePySUndYR{;+!d3>o(W#Y1%v`9FN7y+YK z9@?@P*rJGcyeC^v_e+s_^Vub-k-0(-{{?;YnsQ(TP}1`5+n2{LO6&zy(B zO6r9xv8bEbz}S@6(&h`MvJFYbHpY?Yq+5`z^P)Uc`vsXd1F35N-Hs(3(ntz8uD}+3 zTLc;xSUaiS2R1M7C~p3NGOUb%Vxr9UMdSrF6=5Pi2=#g;ZW`TYConzjlm)X-)z%tFS>^C!&hCa)$lv=i>_*lqhn0 zBf199=Nx(cE}BDkz=R=+Wnn62_I;WKM>0al))o;lyXPAST7shT#b8M0@@kf`lU;Qn zqaXFPUut%-xwAc?uR$@CS>x z6Odg4i&DMNfaLL01hcj)h~Buu;^wc$_*eCk$}G(zcBAb2q*Ei|UghOb)O>~#+Usw_oa)F$k+!1EbXSSNpbXwLAF>z-FoFd^1_>&LNQUc4@cI za4nlmAkh(!1K!n}xlfeHO>Cbt^nE(NHtZAR-|5VE6OwgLxt3@GL{1l<9 z*Us5Q#}%l2r({+7_sC_Xlm*`Nh+{9+2M$^Mbuljz))ce&fB}zciz3(a!jzQ5h+ll3 zPB70C-B$@~V|V3=FVC}f>spsVUnLcneWM?Ay|^#*0+ds}6v`(CHvFo>)Rj(IQ78Ww zp*U$a(Yo)+B4FUv?%i?i)=Z*<)|ERIq#Yvm%Wg7}VnxA|5J+u>bZEIAD(VOq;xra@_2$WuoVGZ0B{_kD;OW~{v%}z~$1O0s z46w(!T1J9UhC1|46x845ONOj55mRhgGp#V^5dc#}_$1q>TW@1a?Sqn8ow<*+!qy9F z6O!*R0QO#J4UmyREJDdJ#xUAfWHVEZa|M2F?q#=S7ww)Cty2$65I*evpukCvFS|!Z z|I$gEv<54>dS>Ze%#8xjZdz-E+KJuwU9ZCIelj4?b|C(oJgwe=qsFn135VHCsjDsS z3sg+;20kMG=j0mXbavnzqX?+-s&6AX&)byRaJJ{px>izqikA8bW`vap&V|^J_O|Kq z{oMa^__$@Zds6I{hGzdWZh7Z>xs5W29>^#;8fD8iP z={=w!-94!4L*Re&hc1H270_SACQ}Ls+Wc@om0j0N57MsG!L)lJ+R^}wpKonIaLgCC zLAqr88Ds%(Hi5Bu5-|2A{%1cJ1#_qo8%eD(|HWwz4w*Z+Cvf;f{fE5JJIYD)t-hA9 zi=5=7&&r&&*164UUFTNWJ`9Bil)_}B@7O@K@B)gq z;QKfl@gF>2VNbr6sz=7{6I`w3KIcl19L~M|+%wvtgCF8x=I_Wf*A8*I^l3jt5v&#Q z7a8Y!P2d#RpzDPuI>WIX5alw1E(F6-3h%)Z^D^MMv3oui4xJB*AScBXLgk3+J!c@K zlpMKCciAutIB1uG5%0vZJX1Qdrh jtSu}wD7z=6r&}|My60_g_Jjlo7 generateRequest(List> payloadMapList) throws Exception; - AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception; - - AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception; + //TODO: use acknowledgementDTO instead of Map + Map generatePayloadFromCsv(MultipartFile payloadFile) throws Exception; } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java index 5b31606..e2c6a5e 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcRequestBuilderServiceImpl.java @@ -1,7 +1,5 @@ package g2pc.ref.dc.client.serviceimpl; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import g2pc.core.lib.constants.CoreConstants; import g2pc.core.lib.dto.common.AcknowledgementDTO; import g2pc.core.lib.dto.common.header.HeaderDTO; @@ -9,13 +7,11 @@ import g2pc.core.lib.dto.common.header.ResponseHeaderDTO; import g2pc.core.lib.dto.common.message.request.*; import g2pc.core.lib.enums.HeaderStatusENUM; +import g2pc.core.lib.exceptions.G2pcError; import g2pc.core.lib.utils.CommonUtils; import g2pc.dc.core.lib.service.RequestBuilderService; import g2pc.dc.core.lib.service.TxnTrackerService; import g2pc.ref.dc.client.config.RegistryConfig; -import g2pc.ref.dc.client.constants.Constants; -import g2pc.ref.dc.client.entity.RegistryTransactionsEntity; -import g2pc.ref.dc.client.repository.RegistryTransactionsRepository; import g2pc.ref.dc.client.service.DcRequestBuilderService; import kong.unirest.HttpResponse; import kong.unirest.Unirest; @@ -23,30 +19,27 @@ import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import org.springframework.core.io.Resource; +import java.io.InputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.Reader; import java.util.*; import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.ObjectMapper; + @Service @Slf4j public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { - @Autowired - private RegistryTransactionsRepository registryTransactionsRepository; - - @Value("${registry.api_urls.farmer_search_api}") - private String farmerSearchURL; - - @Value("${registry.api_urls.mobile_search_api}") - private String mobileSearchURL; - @Autowired private RequestBuilderService requestBuilderService; @@ -56,51 +49,8 @@ public class DcRequestBuilderServiceImpl implements DcRequestBuilderService { @Autowired TxnTrackerService txnTrackerService; - @Value("${keycloak.farmer.clientId}") - private String farmerClientId; - - @Value("${keycloak.farmer.clientSecret}") - private String farmerClientSecret; - - @Value("${keycloak.mobile.clientId}") - private String mobileClientId; - - @Value("${keycloak.mobile.clientSecret}") - private String mobileClientSecret; - - @Value("${keycloak.farmer.url}") - private String keycloakFarmerTokenUrl; - - @Value("${keycloak.mobile.url}") - private String keycloakMobileTokenUrl; - - @Value("${crypto.farmer.support_encryption}") - private boolean isFarmerEncrypt; - - @Value("${crypto.farmer.support_signature}") - private boolean isFarmerSign; - - @Value("${crypto.mobile.support_encryption}") - private boolean isMobileEncrypt; - - @Value("${crypto.mobile.support_signature}") - private boolean isMobileSign; - - - /** - * Create initial transaction in DB - * - * @param transactionId unique transaction id - */ - @Override - public void createInitialTransactionInDB(String transactionId) { - Optional entityOptional = registryTransactionsRepository.getByTransactionId(transactionId); - if (entityOptional.isEmpty()) { - RegistryTransactionsEntity entity = new RegistryTransactionsEntity(); - entity.setTransactionId(transactionId); - registryTransactionsRepository.save(entity); - } - } + @Autowired + private ResourceLoader resourceLoader; /** * Create, save and send a request from payload @@ -108,11 +58,14 @@ public void createInitialTransactionInDB(String transactionId) { * @param payloadMapList required query params data * @return acknowledgement of the request */ + @SuppressWarnings("unchecked") @Override - public AcknowledgementDTO generateRequest(List> payloadMapList) throws Exception { - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - acknowledgementDTO.setMessage(Constants.SEARCH_REQUEST_RECEIVED); - acknowledgementDTO.setStatus(HeaderStatusENUM.RCVD.toValue()); + public Map generateRequest(List> payloadMapList) throws Exception { + Map g2pcErrorMap = new HashMap<>(); + + String transactionId = CommonUtils.generateUniqueId("T"); + + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); List> queryMapList = requestBuilderService.createQueryMap(payloadMapList, registryConfig.getQueryParamsConfig().entrySet()); for (Map.Entry configEntryMap : registryConfig.getRegistrySpecificConfig().entrySet()) { @@ -128,30 +81,32 @@ public AcknowledgementDTO generateRequest(List> payloadMapLi searchCriteriaDTOList.add(searchCriteriaDTO); } - String transactionId = CommonUtils.generateUniqueId("T"); - //txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); - String requestString = requestBuilderService.buildRequest(searchCriteriaDTOList, transactionId); - txnTrackerService.saveRequestTransaction(requestString, - registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); - txnTrackerService.saveRequestInDB(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); - log.info("requestString = {}", requestString); - sendRequestDemo(requestString, registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString()); - /* requestBuilderService.sendRequest(requestString, - registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), - registrySpecificConfigMap.get(CoreConstants.CLIENT_ID).toString(), - registrySpecificConfigMap.get(CoreConstants.CLIENT_SECRET).toString(), - registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString());*/ - - log.info("Initial Request String for " + configEntryMap.getKey() + ": {}", requestString); - - /*if (configEntryMap.getKey().equals("mobile_registry")) { - requestBuilderService.sendRequest(requestString, mobileSearchURL, mobileClientId, mobileClientSecret, keycloakMobileTokenUrl, isMobileEncrypt, isMobileSign); - } else { - requestBuilderService.sendRequest(requestString, farmerSearchURL, farmerClientId, farmerClientSecret, keycloakFarmerTokenUrl, isFarmerEncrypt, isFarmerSign); - }*/ + try { + Resource resource = resourceLoader.getResource(registrySpecificConfigMap.get(CoreConstants.KEY_PATH).toString()); + String encryptedSalt = ""; + InputStream fis = resource.getInputStream(); + G2pcError g2pcError = requestBuilderService.sendRequest(requestString, + registrySpecificConfigMap.get(CoreConstants.DP_SEARCH_URL).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_ID).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_CLIENT_SECRET).toString(), + registrySpecificConfigMap.get(CoreConstants.KEYCLOAK_URL).toString(), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_ENCRYPTION).toString()), + Boolean.parseBoolean(registrySpecificConfigMap.get(CoreConstants.SUPPORT_SIGNATURE).toString()), + fis, encryptedSalt, + registrySpecificConfigMap.get(CoreConstants.KEY_PASSWORD).toString()); + g2pcErrorMap.put(configEntryMap.getKey(), g2pcError); + + txnTrackerService.saveInitialTransaction(payloadMapList, transactionId, HeaderStatusENUM.RCVD.toValue()); + txnTrackerService.saveRequestTransaction(requestString, + registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString(), transactionId); + txnTrackerService.saveRequestInDB(requestString, registrySpecificConfigMap.get(CoreConstants.REG_TYPE).toString()); + } catch (Exception e) { + log.error("Exception in generateRequest: {}", e); + } } - return acknowledgementDTO; + //TODO: convert returning map to acknowledgementDTO + return g2pcErrorMap; } /** @@ -161,16 +116,16 @@ public AcknowledgementDTO generateRequest(List> payloadMapLi * @return acknowledgement of the request */ @Override - public AcknowledgementDTO generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { - AcknowledgementDTO acknowledgementDTO = new AcknowledgementDTO(); - acknowledgementDTO.setMessage(Constants.SEARCH_REQUEST_RECEIVED); - acknowledgementDTO.setStatus("RECEIVED"); - + public Map generatePayloadFromCsv(MultipartFile payloadFile) throws Exception { Reader reader = new BufferedReader(new InputStreamReader(payloadFile.getInputStream())); CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); List> payloadMapList = getPayloadMapList(csvParser); - acknowledgementDTO = generateRequest(payloadMapList); - return acknowledgementDTO; + Map acknowledgement = new HashMap<>(); + if (ObjectUtils.isNotEmpty(payloadMapList)) { + acknowledgement = generateRequest(payloadMapList); + } + //TODO: convert returning map to acknowledgementDTO + return acknowledgement; } private static List> getPayloadMapList(CSVParser csvParser) { diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java index 430d26b..93ed0f7 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/serviceimpl/DcValidationServiceImpl.java @@ -23,14 +23,16 @@ import g2pc.dc.core.lib.service.ResponseHandlerService; import g2pc.ref.dc.client.constants.Constants; import g2pc.ref.dc.client.service.DcValidationService; -import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import java.io.IOException; import java.io.InputStream; +import java.security.SignatureException; import java.util.*; @@ -48,11 +50,38 @@ public class DcValidationServiceImpl implements DcValidationService { @Autowired private AsymmetricSignatureService asymmetricSignatureService; - @Value("${crypto.consumer.support_encryption}") - private boolean isEncrypt; + @Autowired + private ResourceLoader resourceLoader; + + @Value("${crypto.from_dp_farmer.support_encryption}") + private boolean isFarmerEncrypt; + + @Value("${crypto.from_dp_farmer.support_signature}") + private boolean isFarmerSign; + + @Value("${crypto.from_dp_farmer.password}") + private String farmerp12Password; + + @Value("${crypto.from_dp_farmer.key_path}") + private String farmerKeyPath; + + @Value("${crypto.from_dp_farmer.id}") + private String farmerID; + + @Value("${crypto.from_dp_mobile.support_encryption}") + private boolean isMobileEncrypt; + + @Value("${crypto.from_dp_mobile.support_signature}") + private boolean isMobileSign; + + @Value("${crypto.from_dp_mobile.password}") + private String mobilep12Password; - @Value("${crypto.consumer.support_signature}") - private boolean isSign; + @Value("${crypto.from_dp_mobile.key_path}") + private String mobileKeyPath; + + @Value("${crypto.from_dp_mobile.id}") + private String mobileID; /** * Validate response dto. @@ -135,14 +164,35 @@ public void validateRegRecords(ResponseMessageDTO messageDTO) throws G2pcValidat @Override public ResponseMessageDTO signatureValidation(Map metaData, ResponseDTO responseDTO) throws Exception { + + String p12Password =""; + boolean isEncrypt = false; + boolean isSign=false; + String keyPath=""; + if(metaData.get(CoreConstants.DP_ID).equals(farmerID)){ + p12Password = farmerp12Password; + isEncrypt = isFarmerEncrypt; + isSign = isFarmerSign; + keyPath = farmerKeyPath; + } else if(metaData.get(CoreConstants.DP_ID).equals(mobileID)){ + p12Password = mobilep12Password; + isEncrypt=isMobileEncrypt; + isSign = isMobileSign; + keyPath = mobileKeyPath; + } log.info("Is encrypted ? -> "+isEncrypt); log.info("Is signed ? -> "+isSign); ObjectMapper objectMapper = new ObjectMapper(); ResponseMessageDTO messageDTO; + + if(isSign){ if(!metaData.get(CoreConstants.IS_SIGN).equals(true)){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); } + Resource resource = resourceLoader.getResource(keyPath); + InputStream fis = resource.getInputStream(); + if(isEncrypt){ if(!responseDTO.getHeader().getIsMsgEncrypted()){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_VERSION_NOT_VALID.toValue(), Constants.CONFIGURATION_MISMATCH_ERROR)); @@ -152,8 +202,13 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp String responseSignature = responseDTO.getSignature(); String messageString = responseDTO.getMessage().toString(); String data = responseHeaderString+messageString; - if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature)) ){ + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + } catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); } if(responseDTO.getHeader().getIsMsgEncrypted()){ String deprecatedMessageString; @@ -179,8 +234,13 @@ public ResponseMessageDTO signatureValidation(Map metaData, Resp String messageString = objectMapper.writeValueAsString(messageDTO); String data = responseHeaderString+messageString; log.info("Signature ->"+responseSignature); - if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature)) ){ + try{if(! asymmetricSignatureService.verifySignature(data.getBytes(), Base64.getDecoder().decode(responseSignature) , fis , p12Password) ){ + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }}catch(SignatureException e){ throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), "signature is not valid ")); + }catch(IOException e){ + log.info("Rejecting the on-search request in signature is not valid"); + throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_SIGNATURE_INVALID.toValue(), e.getMessage())); } } diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java new file mode 100644 index 0000000..d9ba882 --- /dev/null +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/java/g2pc/ref/dc/client/utils/DcCommonUtils.java @@ -0,0 +1,57 @@ +package g2pc.ref.dc.client.utils; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import g2pc.core.lib.enums.ExceptionsENUM; +import g2pc.core.lib.exceptions.G2pHttpException; +import g2pc.core.lib.exceptions.G2pcError; +import g2pc.core.lib.security.BearerTokenUtil; +import g2pc.core.lib.security.service.G2pTokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +@Service +public class DcCommonUtils { + + @Autowired + G2pTokenService g2pTokenService; + + @Value("${keycloak.dc.client.realm}") + private String keycloakRealm; + + @Value("${keycloak.dc.client.url}") + private String keycloakURL; + + @Value("${keycloak.dc.master.url}") + private String masterUrl; + + @Value("${keycloak.dc.master.getClientUrl}") + private String getClientUrl; + + @Value("${keycloak.dc.client.clientId}") + private String masterClientId; + + @Value("${keycloak.dc.client.clientSecret}") + private String masterClientSecret; + + @Value("${keycloak.dc.username}") + private String adminUsername; + + @Value("${keycloak.dc.password}") + private String adminPassword; + + public void handleToken() throws G2pHttpException, JsonProcessingException { + String token = BearerTokenUtil.getBearerTokenHeader(); + String introspect = keycloakURL + "/introspect"; + ResponseEntity introspectResponse = g2pTokenService.getInterSpectResponse(introspect, token, masterClientId, masterClientSecret); + if (introspectResponse.getStatusCode().value() == 401) { + throw new G2pHttpException(new G2pcError(introspectResponse.getStatusCode().toString(), introspectResponse.getBody())); + } + if (!g2pTokenService.validateToken(masterUrl, getClientUrl, g2pTokenService.decodeToken(token), masterClientId, masterClientSecret, adminUsername, adminPassword)) { + //TODO:check this + //throw new G2pHttpException(new G2pcError(ExceptionsENUM.ERROR_USER_UNAUTHORIZED.toValue(), "User is not authorized")); + } + } +} diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/1693731657.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/1693731657.p12 new file mode 100644 index 0000000000000000000000000000000000000000..9a827995e886b9ea6e2ffa05e3563f75d41f6ad3 GIT binary patch literal 6013 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 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml index 25315d8..e7e9667 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application-local.yml @@ -9,9 +9,10 @@ spring: datasource: driverClassName: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/dc1?currentSchema=g2pc - username: postgres - password: postgres + url: not_set + username: not_set + password: not_set + hikari: data-source-properties: stringtype: unspecified @@ -56,49 +57,65 @@ spring.data.redis: password: 123456789 port: 6379 - api_urls: - #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" - #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - keycloak: - farmer: - url: "http://127.0.0.1:8081/auth/realms/dp-farmer/protocol/openid-connect/token" - clientId: "dp-farmer-client" - clientSecret: "EaXspS2bAcCmh5XrDWYrAzWP1Q1uQEIA" - mobile: - url: "http://127.0.0.1:8081/auth/realms/dp-mobile/protocol/openid-connect/token" - clientId: "dp-mobile-client" - clientSecret: "d544H8DTnZXREmX6jgmAfoFCeFXQ1oVV" - consumer: - admin-url: http://127.0.0.1:8081/auth/realms/master/protocol/openid-connect/token - get-client-url: http://127.0.0.1:8081/auth/admin/realms/data-consumer/clients - realm: data-consumer - url: http://127.0.0.1:8081/auth - admin: - client-id: admin-cli - client-secret: qCyT7XM24KGjb5j6ZU5YC68H5OiI6LRm + from_dp: + farmer: + url: not_set + clientId: not_set + clientSecret: not_set + mobile: + url: not_set + clientId: not_set + clientSecret: not_set + dc: + url: not_set + username: not_set + password: not_set + master: + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set + client: + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: - farmer: - support_encryption: true - support_signature: true - mobile: - support_encryption: true - support_signature: true - consumer: - support_encryption: true - support_signature: true + to_dp_farmer: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + to_dp_mobile: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + from_dp_farmer: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set + from_dp_mobile: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set registry: api_urls: - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - farmer_search_public_api: "http://localhost:9001/public/api/v1/registry/search" - mobile_search_public_api: "http://localhost:9002/public/api/v1/registry/search" + farmer_search_api: not_set + mobile_search_api: not_set dashboard: - left_panel_url: "http://localhost:3005/d-solo/abce6fd2-95ac-49a6-b743-23d785b3a080/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - right_panel_url: "http://localhost:3005/d-solo/eddad923-f089-4225-a72b-57967e263937/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - bottom_panel_url: "http://localhost:3005/d-solo/e36b8bf2-4bf9-4391-b687-a026426db65f/botton-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" + left_panel_url: not_set + right_panel_url: not_set + bottom_panel_url: not_set + post_endpoint_url: not_set + clear_dc_db_endpoint_url: not_set + clear_dp1_db_endpoint_url: not_set + clear_dp2_db_endpoint_url: not_set \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml index 31c1007..e7e9667 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/application.yml @@ -10,7 +10,7 @@ spring: datasource: driverClassName: org.postgresql.Driver url: not_set - username: postgres + username: not_set password: not_set hikari: @@ -57,56 +57,65 @@ spring.data.redis: password: 123456789 port: 6379 - api_urls: - #farmer_search_api: "https://webhook.site/9043a6a4-1710-4af6-a7c6-25a55c6fc903" - #mobile_search_api: "http://localhost:9200/private/api/v1/registry/mobile/info" - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - keycloak: - farmer: - url: "https://g2pc-dp1-lab.cdpi.dev/auth/realms/dp-farmer/protocol/openid-connect/token" - clientId: "dp-farmer-client" - clientSecret: not_set - mobile: - url: "https://g2pc-dp2-lab.cdpi.dev/auth/realms/dp-mobile/protocol/openid-connect/token" - clientId: "dp-mobile-client" - clientSecret: not_set - consumer: - admin-url: https://g2pc-dc-lab.cdpi.dev/auth/realms/master/protocol/openid-connect/token - get-client-url: https://g2pc-dc-lab.cdpi.dev/auth/admin/realms/data-consumer/clients - realm: data-consumer - url: https://g2pc-dc-lab.cdpi.dev/auth/ - admin: - realm: - client-id: admin-cli - client-secret: not_set - client-id: admin-cli - client-secret: not_set # In realm In master admin-cli -> secret key - username: admin + from_dp: + farmer: + url: not_set + clientId: not_set + clientSecret: not_set + mobile: + url: not_set + clientId: not_set + clientSecret: not_set + dc: + url: not_set + username: not_set password: not_set + master: + url: not_set + getClientUrl: not_set + clientId: not_set + clientSecret: not_set + client: + url: not_set + realm: not_set + clientId: not_set + clientSecret: not_set crypto: - farmer: - support_encryption: true - support_signature: true - mobile: - support_encryption: true - support_signature: true - consumer: - support_encryption: true - support_signature: true - + to_dp_farmer: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + to_dp_mobile: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + from_dp_farmer: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set + from_dp_mobile: + support_encryption: not_set + support_signature: not_set + password: not_set + key_path: not_set + id: not_set registry: api_urls: - farmer_search_api: "http://localhost:9001/private/api/v1/registry/search" - mobile_search_api: "http://localhost:9002/private/api/v1/registry/search" - farmer_search_public _api: "http://localhost:9001/public/api/v1/registry/search" - mobile_search_public_api: "http://localhost:9002/public/api/v1/registry/search" + farmer_search_api: not_set + mobile_search_api: not_set dashboard: - left_panel_url: "http://localhost:3005/d-solo/abce6fd2-95ac-49a6-b743-23d785b3a080/left-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - right_panel_url: "http://localhost:3005/d-solo/eddad923-f089-4225-a72b-57967e263937/right-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - bottom_panel_url: "http://localhost:3005/d-solo/e36b8bf2-4bf9-4391-b687-a026426db65f/botton-panel-data?orgId=1&refresh=5s&from=1701984074137&to=1702005674137&panelId=1" - + left_panel_url: not_set + right_panel_url: not_set + bottom_panel_url: not_set + post_endpoint_url: not_set + clear_dc_db_endpoint_url: not_set + clear_dp1_db_endpoint_url: not_set + clear_dp2_db_endpoint_url: not_set \ No newline at end of file diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/farmer_on_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/farmer_on_search.p12 new file mode 100644 index 0000000000000000000000000000000000000000..9a83fcbb282dad532e0b96c39ed79e4042d6812e GIT binary patch literal 2397 zcmV-j38MBef(cmy0Ru3C2_FUtDuzgg_YDCD0ic2j5Cnn=3^0NT2rz;JzXk~^hDe6@ z4FLxRpn?OiFoFZD0s#Opf&--n2`Yw2hW8Bt2LUh~1_~;MNQUfWpepWZny!qW29%CU)nSr@3Y~hl#TCw`-R}dZ>|od(A$ZeH-78JhGMOvlAU3Y0sfAQ3JVz%9OfpQ7$(RyK~=M! zuqQ%jtS@?So$;W>5y6A;&$QaFKiVh}uS7#!^BHHkMCAn$;qXon@lT5=YZ&@ABT_() zIN9%57}%iHCV>21IU@igX<{DrhsSlfID`|2kCpS#3jA8SQ*}xmtG-^b*bV*wDJSV- zE2ZvIO&y*Uvb$V6N{Ki#eZ5r<9X@r3Ft?x(m3e1Hk_}!aLioa)JjawB7vKT~45hDG z4-3bNMmz`q(WWK~A1%gKkg?M3N&{Aer@gn(e94P8f|}y?NCcG&vuVy`MX-+R$!bFs zSgYau@qGZEj5QN95w9q(r1Hf4&h+S8;skDu4r|!@?G3ejXUJHB^>`i!?tISW{Hqrc zM9({{l!N$C?kQh|nDUjuZH-g4jSrM~sXci}?K8*j?cy=!dyqBVF zMZ}-?AixTOjt+t_1<#IAccs;wt1tISynqxSNs0<+&q<^0B5d{i z7ymoO^W@c|lI&?ZouG0It}g5F6KAeZRdv1Ca~{Oyq{4bnSH8r=&xbfKxsuS`oDFf zSvNxd2sAl}#Zf*NEn5Lg@Y4_h3PgSyU!TJ2LxgVa^qobKS}A}5kuD(WVZ~wtnOg_E zOzbcuv^oud`4_veA`^Ir9*9#@FTz2+-ex^}0%k~qN>nedmO!Hb3wsCo;_o&TT|_rN zNu9dnae)X=t6$iKO<*Fn-O@1xYEhv%jJ!-Fv!G#CnP)As__7teeD@Hc3ly)qn5ud| z@gPmnSYjRAZFoFd^1_>&LNQUX7DyKTUfO0WqJn4M z=}wzIP5e~4zxlIE+HsekxQ(37qXNu4-rR>Yd~A4`z&roAn-O0h9sC6Te62mhP`3rq z+cv#tu%DLn3-{5l9_LY%0#BPNtN%QtVjL!%AOUt(sTMDNt;0*iKx2-Qi9_V*${ z-%uw8S0kTOpHdR~NuNE26ncQ%=mLN~Mlh9R9)0V@T(U4I}jcfvy8X$)e*X7-l4+@Amn&!IHI(_*PY}yBht^;@j00 z$FFFq+rrxWZpq|!tui24B^|!4E%YEvDa`(X_u3o#VAB77jk4$e)CQ5n9GjJ6y$7&X z!=ENp0%M6#;|2vTt>nz>^+csYV0Z6bRNKcOLnL>04g{&n+VJ3fXtNtFHFq4&y>L@j zi?ZX5tYbvwNm(lAqF256*9BtiOE`qBJSNe$hh;n2TiOHS#hhMyUxGD7se>Jy_n3I6 zJHY4}_D_L23Vw#!RH+0yaJ{;Ff*JRaxLPaOfk-3pu}O#y%f$VKv^t|p0}CuiR5`j0 z{?GGToYDw{AL(fiWUFGUZ1_B$dj;aH$F_#D5VSU773b75um4}wWRks9i7P4OUNGq{ zRtQkdZ{Ta#6~&PLSUTip5kcZi`p$Nwo_VzN$?B2Y9Us9$!gvlBOeh*+uSU?=phU6! zGgGrTSZ(S7Amrc5zZT>M)2ra>=XyuSqOwE{nJ<4MR#bi*Y1PLi*-M2yI3jPJ^L-d7 zFlDV?7McfOv3+M@sD@5cI7+C>vR{)J1Lh=yw4|YGPr3_&Aa;*Z$3qPQF6mi3V^ADoAKL0B> z+FK3~8_|qRS0YeU!$5qlWv&SdOhhe!KFm2;HP;#&>p}=Q=c{oM3z~j@0yk^$sfHYj z%G#x92aICFXcpE&1hU<5sbi`yRh8y(T7CD;nkDxvCjY0%u?Io!$%t{1dLT0cwP*qB z%9^h6E>`2vRbo8l_J^|UZR8)DbX*9$80;}6Fe3&DDuzgg_YDCF6)_eB6zT01Hj}s~ zu@gM!ETaNo74eAutIB1uG5%0vZJX1QZE|B4*J41mh%hh-Oh8=+fqze6|D# PX?{W`p|9P40s;sCefMD0 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/farmer_search.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/farmer_search.p12 new file mode 100644 index 0000000000000000000000000000000000000000..1873f5da742adb97bda01180f5e42fbd50b5bcb7 GIT binary patch literal 2429 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 jWogr#r!E8te@FGEGUGJp0s;sC8$Dp4 literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/private.p12 b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/resources/private.p12 new file mode 100644 index 0000000000000000000000000000000000000000..c067bffa1a9b5b24e01686440e6eb38f44699e81 GIT binary patch literal 2547 zcmai$X*3iH8^_Itk&!j~*w+k08pe{C5=o&UvNUQYX+{jP(-@&7g+Z?+WeE{Ymc|ld z=$1XwbupRj>x|UR?sd=mp89w{Jm)$8|NlJa`SkqGgQP+^KmZ6yg%8J zfG1RFCYTCM+qYAYR50*&#QTH_T0>Gn`$IhYZSHphKwkJIATZ=xrr6e@^P zH%r^wIxQP#`i-&aS=W$nm(?I-l_$@zy4Pph>+}fIme})T=q34{XhRd91uY_+8JSqq zyo;$VY9xJ5?9km93FAYBgi7kE(UDwjoPULfF^9c#=`DyLS0sxXd8#lF*+35XaW3Ny zH>myR? zype(#xCOd}EAJ{OyEk{*@z&VlO6M`d>mx(7q>Z7-yk%XI>OkjE$+x2^S6rO{I)_zt z4J&8`jq{K%$JpM{1xv4^%d+lG!yheRCj=$9=9-#oDQ5x^{?%PWYb>Dpv7w>8H|Cjm zpA9Ma`NVY*LbsH<3R!s6ncY$tC?=h!+N^MC(a-pe){4sBg}0*9{fo7`f_Qi$9EZ+Y(D+Cg8kLcf!{He0E?j0{Nks#6GO}R3rWrFMK6b zmw47o%DweS7Y+)1V*Z**lj;CQq2)@(dwYQ;$)f$Sld3>U%I$z}8N^G?#BroB)RNH? z+rt{gOJjQAA;RMbNpit_@}7DyyHzT$d~R;;v=n6n{MM#ls|uQEG1E6@@zn}GimHs7 z6Cvg+%Bd=1$R#0TKz#|hPI$1UF4BSoECU+qFL)R2cVALpaEz}{?Q zuj4*J^hZZB23j#a>KBY-;Ale$-%`RuS&*;bg56Y+0(81G3foVn5(@W=qfR$)&$~1vO%T3h{&^ z%L_@F#61X-3c3C3p8o*^ z|M--`RzdC~9z%Uo+x033ctNqhln;xcojU>sK1>({mH4>3FEOioL*&Oj<}|Ll2$Xph z*2~KZjgu$iI6S2&tpk5?S6J>1=l^mMx95tv#b_TMv^0*(df>Br^>c)oU4Qkgg7kc;d2*5{IgM*Z@HCSx{Bp*?cEmBQC&H**fHLu zHkn|9GG#>345`sY)-o|yUfYw`xyI=?tJMR-KZhxVP1q_=U<+6Ui{%{>|7kYIEmv5 z#;j%;z)du#nZD?QdqpH)bHp&g9_^8m*;r5ZUCU#(HJ+uu$pJTb5v|cE$+)x~O(f{4 z{i&jpM2wKv^i;W0qX?m>eHaCv{UYi@Qr_q%gA~L&s#zOP~9N zRgrphrmgK{^Yvy9O8jh$>rkr)W$Z*d9~nqwDoK9v3e(o)cb>2VG1T1(hxdU%%WhjqjGw>F#X_?&qiq~6tU zMWmmkL|yk&X)YA_Z20->^>y2*#}RA<$1z$VmZjbKymS6PGdF9GMohAw9oHA|9tv#(i6TkD?_$oo#P?YE5J4S*_6hF6eYl)t>gr0HrD z((-Bo+RWdP1OQb#$_Ul~&0w6qK#SDk3 uNiVYG!VsooVaCX^wiOm_+jQ(iBL@0C!2EXfk5QM*3KaiA?SBC%Gnn`Q literal 0 HcmV?d00001 diff --git a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp index 4eab858..d8a16e9 100644 --- a/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp +++ b/g2pc-reference-apps/g2pc-ref-dc-client/src/main/webapp/WEB-INF/jsp/dashboard.jsp @@ -4,116 +4,7 @@ Dashboard - +