diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 795b625220..5f7cd1b567 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,36 @@ # Release Notes +## Version 47.7.0 +_6 January 2025_ + +### Features +* Add endpoints to retrieve data for the Developer Attestation PowerBI report +* Allow changing of ATL using upload-to-update +* Save activity for created and deleted invitations +* Add job to update Service Base URL report items + +### Bug Fixes +* Give ONC users ability to edit listing via upload + +--- + +## Version 47.6.0 +_18 December 2024_ + +### Features +* Add endpoint to return data for Service Base URL List report +* Allow existing user to be granted access to additional Organizations +* Return generic msg if invitation token is bad +* Create /developers//insights endpoint to fetch insights data + +### Bug Fixes +* Add all required standards to listing as of cert day + current day +* Give appropriate error if addt'l software group name too long +* Give pending change request report user correct cognito group +* Send API Key deletion warning if key was created and never used + +--- + ## Version 47.5.0 _9 December 2024_ diff --git a/chpl/chpl-api/e2e/collections/activity-controller.postman_collection.json b/chpl/chpl-api/e2e/collections/activity-controller.postman_collection.json index 74e852613c..ffa5098f09 100644 --- a/chpl/chpl-api/e2e/collections/activity-controller.postman_collection.json +++ b/chpl/chpl-api/e2e/collections/activity-controller.postman_collection.json @@ -233,15 +233,16 @@ "name": "ROLE_ACB", "item": [ { - "name": "GET /activity/metadata/svaps - ACB user gets 401 status and no response", + "name": "GET /activity/metadata/svaps - ACB user gets 200 status and valid response", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test(\"ACB user gets 401 status and valid response from /activity/metadata/svaps end point\", function () {\r", + "pm.test(\"ACB user gets 200 status and valid response from /activity/metadata/svaps end point\", function () {\r", " var actualResponseBody = pm.response.json();\r", - " pm.response.to.have.status(401);\r", + " pm.response.to.have.status(200);\r", + " pm.expect(actualResponseBody).not.eql(null);\r", "});" ], "type": "text/javascript", @@ -285,20 +286,21 @@ "svaps" ] }, - "description": "GET /activity/metadata/svaps - ACB user gets 401 status and no response" + "description": "GET /activity/metadata/svaps - ACB user gets 200 status and valid response" }, "response": [] }, { - "name": "GET /activity/metadata/standards - ACB user gets 401 status and no response", + "name": "GET /activity/metadata/standards - ACB user gets 200 status and valid response", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test(\"ACB user gets 401 status and valid response from /activity/metadata/standards end point\", function () {\r", + "pm.test(\"ACB user gets 200 status and valid response from /activity/metadata/standards end point\", function () {\r", " var actualResponseBody = pm.response.json();\r", - " pm.response.to.have.status(401);\r", + " pm.response.to.have.status(200);\r", + " pm.expect(actualResponseBody).not.eql(null);\r", "});" ], "type": "text/javascript", @@ -342,20 +344,21 @@ "standards" ] }, - "description": "GET /activity/metadata/standards - ACB user gets 401 status and no response" + "description": "GET /activity/metadata/standards - ACB user gets 200 status and valid response" }, "response": [] }, { - "name": "GET /activity/metadata/functionalities-tested - ACB user gets 401 status and no response", + "name": "GET /activity/metadata/functionalities-tested - ACB user gets 200 status and valid response", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test(\"ACB user gets 401 status and valid response from /activity/metadata/functionalities-tested end point\", function () {\r", + "pm.test(\"ACB user gets 200 status and valid response from /activity/metadata/functionalities-tested end point\", function () {\r", " var actualResponseBody = pm.response.json();\r", - " pm.response.to.have.status(401);\r", + " pm.response.to.have.status(200);\r", + " pm.expect(actualResponseBody).not.eql(null);\r", "});" ], "type": "text/javascript", @@ -399,7 +402,7 @@ "functionalities-tested" ] }, - "description": "GET /activity/metadata/functionalities-tested - ACB user gets 401 status and no response" + "description": "GET /activity/metadata/functionalities-tested - ACB user gets 200 status and valid response" }, "response": [] } diff --git a/chpl/chpl-api/e2e/collections/test-tools-controller.postman_collection.json b/chpl/chpl-api/e2e/collections/test-tools-controller.postman_collection.json index 22711c9341..deecb35652 100644 --- a/chpl/chpl-api/e2e/collections/test-tools-controller.postman_collection.json +++ b/chpl/chpl-api/e2e/collections/test-tools-controller.postman_collection.json @@ -225,7 +225,7 @@ "name": "ROLE_ONC", "item": [ { - "name": "POST /test-tools - ROLE_ONC user should get 401 status and access denied error", + "name": "POST /test-tools - ROLE_ONC user should get 200 status", "event": [ { "listen": "prerequest", @@ -240,8 +240,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"create test tools request by ROLE_ONC user should return Status code 401\", function () {\r", - " pm.response.to.have.status(401);\r", + "pm.test(\"create test tools request by ROLE_ONC user should return Status code 200\", function () {\r", + " pm.response.to.have.status(200);\r", "});\r" ], "type": "text/javascript" @@ -274,7 +274,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"id\":1,\r\n \"value\": \"test\",\r\n \"regulatoryTextCitation\": \"test1\",\r\n \"startDay\": \"2023-07-12\",\r\n \"endDay\": \"2023-07-12\",\r\n \"requiredDay\": \"2023-07-12\",\r\n \"criteria\": [\r\n {\r\n \"id\": 0,\r\n \"number\": \"string\",\r\n \"title\": \"string\",\r\n \"certificationEditionId\": 0,\r\n \"certificationEdition\": \"string\",\r\n \"description\": \"string\",\r\n \"removed\": true\r\n }\r\n ],\r\n \"rule\": {\r\n \"id\": 0,\r\n \"name\": \"string\"\r\n }\r\n}" + "raw": "{\r\n \"value\": \"new tt\",\r\n \"regulatoryTextCitation\": \"newtt\",\r\n \"startDay\": \"2025-01-01\",\r\n \"endDay\": \"\",\r\n \"criteria\": [\r\n {\r\n \"id\": 165,\r\n \"number\": \"170.315 (b)(1)\",\r\n \"title\": \"Transitions of Care\",\r\n \"certificationEditionId\": 3,\r\n \"certificationEdition\": \"2015\",\r\n \"description\": null,\r\n \"removed\": false\r\n }\r\n ],\r\n \"rule\": {\r\n \"id\": 5,\r\n \"name\": \"HTI-1\"\r\n }\r\n}" }, "url": { "raw": "{{url}}/rest/test-tools", @@ -286,19 +286,19 @@ "test-tools" ] }, - "description": "ROLE_ONC user should get 401 status and access denied error to create Test Tools" + "description": "ROLE_ONC user should get 200 status to create Test Tools" }, "response": [] }, { - "name": "PUT /test-tools - ROLE_ONC user should get 401 status and access denied error", + "name": "PUT /test-tools - ROLE_ONC user should get 200 status", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test(\"edit test tools request by ROLE_ONC user should return Status code 401\", function () {\r", - " pm.response.to.have.status(401);\r", + "pm.test(\"edit test tools request by ROLE_ONC user should return Status code 200\", function () {\r", + " pm.response.to.have.status(200);\r", "});\r" ], "type": "text/javascript" @@ -331,7 +331,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"id\": 1,\r\n \"value\": \"test2\",\r\n \"regulatoryTextCitation\": \"test3\",\r\n \"startDay\": \"2023-07-13\",\r\n \"endDay\": \"2023-07-13\",\r\n \"requiredDay\": \"2023-07-13\",\r\n \"criteria\": [\r\n {\r\n \"id\": 0,\r\n \"number\": \"string\",\r\n \"title\": \"string\",\r\n \"certificationEditionId\": 0,\r\n \"certificationEdition\": \"string\",\r\n \"description\": \"string\",\r\n \"removed\": true\r\n }\r\n ],\r\n \"rule\": {\r\n \"id\": 0,\r\n \"name\": \"string\"\r\n }\r\n}" + "raw": "{\r\n \"id\":33,\r\n \"value\": \"Drummond G10+ FHIR API powered by Touchstone\",\r\n \"regulatoryTextCitation\": \"Touchstone\",\r\n \"startDay\": \"\",\r\n \"endDay\": \"\",\r\n \"criteria\": [\r\n {\r\n \"id\": 182,\r\n \"number\": \"170.315 (g)(10)\",\r\n \"title\": \"Standardized API for Patient and Population Services\",\r\n \"certificationEditionId\": 3,\r\n \"certificationEdition\": \"2015\",\r\n \"description\": null,\r\n \"removed\": false,\r\n \"rule\": {\r\n \"id\": 4,\r\n \"name\": \"Cures\"\r\n },\r\n \"startDay\": \"2020-06-30\",\r\n \"endDay\": null\r\n }\r\n ],\r\n \"rule\": {\r\n \"id\": 5,\r\n \"name\": \"HTI-1\"\r\n }\r\n}" }, "url": { "raw": "{{url}}/rest/test-tools", @@ -343,7 +343,7 @@ "test-tools" ] }, - "description": "PUT /test-tools - ROLE_ONC user should get 401 status and access denied error" + "description": "PUT /test-tools - ROLE_ONC user should get 200 status" }, "response": [] }, diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ActivityController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ActivityController.java index 2de2d21e8e..988190abfb 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ActivityController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ActivityController.java @@ -339,7 +339,6 @@ public List metadataForProductById(@PathVariable("id") final L }) @RequestMapping(value = "/metadata/versions", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/versions", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForVersions(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -354,7 +353,6 @@ public ActivityMetadataPage metadataForVersions(@RequestParam(required = false) }) @RequestMapping(value = "/metadata/versions/{id:^-?\\d+$}", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/versions/{id}", responseClass = ActivityMetadata.class) public List metadataForVersionById(@PathVariable("id") final Long id, @RequestParam(required = false) final Long start, @RequestParam(required = false) final Long end) throws JsonParseException, IOException, EntityRetrievalException, ValidationException { @@ -390,7 +388,6 @@ public List metadataForVersionById(@PathVariable("id") final L }) @RequestMapping(value = "/metadata/acbs", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/acbs", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForAcbs(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -405,7 +402,6 @@ public ActivityMetadataPage metadataForAcbs(@RequestParam(required = false) Long }) @RequestMapping(value = "/metadata/acbs/{id:^-?\\d+$}", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/acbs/{id}", responseClass = ActivityMetadata.class) public List metadataForAcbById(@PathVariable("id") final Long id, @RequestParam(required = false) final Long start, @RequestParam(required = false) final Long end) throws JsonParseException, IOException, EntityRetrievalException, ValidationException { @@ -438,7 +434,6 @@ public List metadataForAcbById(@PathVariable("id") final Long }) @RequestMapping(value = "/metadata/atls", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/atls", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForAtls(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -453,7 +448,6 @@ public ActivityMetadataPage metadataForAtls(@RequestParam(required = false) Long }) @RequestMapping(value = "/metadata/atls/{id:^-?\\d+$}", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/atls/{id}", responseClass = ActivityMetadata.class) public List metadataForAtlById(@PathVariable("id") final Long id, @RequestParam(required = false) final Long start, @RequestParam(required = false) final Long end) throws JsonParseException, IOException, EntityRetrievalException, ValidationException { @@ -485,7 +479,6 @@ public List metadataForAtlById(@PathVariable("id") final Long @SecurityRequirement(name = SwaggerSecurityRequirement.BEARER) }) @RequestMapping(value = "/metadata/users", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/users", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForUsers(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -504,7 +497,6 @@ public ActivityMetadataPage metadataForUsers(@RequestParam(required = false) Lon }) @RequestMapping(value = "/metadata/announcements", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/announcements", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForAnnouncements(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -520,7 +512,6 @@ public ActivityMetadataPage metadataForAnnouncements(@RequestParam(required = fa @SecurityRequirement(name = SwaggerSecurityRequirement.BEARER) }) @RequestMapping(value = "/metadata/complaints", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/complaints", responseClass = ActivityMetadata.class) public List metadataForComplaints(@RequestParam final Long start, @RequestParam final Long end) throws JsonParseException, IOException, ValidationException { Date startDate = new Date(start); @@ -538,7 +529,6 @@ public List metadataForComplaints(@RequestParam final Long sta @SecurityRequirement(name = SwaggerSecurityRequirement.BEARER) }) @RequestMapping(value = "/metadata/quarterly-reports", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/quarterly-reports", responseClass = ActivityMetadata.class) public List metadataForQuarterlyReports(@RequestParam final Long start, @RequestParam final Long end) throws JsonParseException, IOException, ValidationException { @@ -566,7 +556,6 @@ public List metadataForQuarterlyReports(@RequestParam final Lo @SecurityRequirement(name = SwaggerSecurityRequirement.BEARER) }) @RequestMapping(value = "/metadata/annual-reports", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/annual-reports", responseClass = ActivityMetadata.class) public List metadataForAnnualReports(@RequestParam final Long start, @RequestParam final Long end) throws JsonParseException, IOException, ValidationException { Date startDate = new Date(start); @@ -583,7 +572,6 @@ public List metadataForAnnualReports(@RequestParam final Long @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) }) @RequestMapping(value = "/metadata/corrective-action-plans", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/corrective-action-plans", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForCorrectiveActionPlans(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -601,7 +589,6 @@ public ActivityMetadataPage metadataForCorrectiveActionPlans(@RequestParam(requi }) @RequestMapping(value = "/metadata/pending-surveillances", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/pending-surveillances", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForPendingSurveillances(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -618,7 +605,6 @@ public ActivityMetadataPage metadataForPendingSurveillances(@RequestParam(requir }) @RequestMapping(value = "/metadata/change-requests", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/change-requests", responseClass = ActivityMetadata.class) public List metadataForChangeRequests(@RequestParam final Long start, @RequestParam final Long end) throws JsonParseException, IOException, ValidationException { Date startDate = new Date(start); @@ -637,7 +623,6 @@ public List metadataForChangeRequests(@RequestParam final Long }) @RequestMapping(value = "/metadata/api-keys", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/api-keys", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForApiKeys(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -654,7 +639,6 @@ public ActivityMetadataPage metadataForApiKeys(@RequestParam(required = false) L }) @RequestMapping(value = "/metadata/functionalities-tested", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/functionalities-tested", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForFunctionalitiesTested(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -671,7 +655,6 @@ public ActivityMetadataPage metadataForFunctionalitiesTested(@RequestParam(requi }) @RequestMapping(value = "/metadata/standards", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/standards", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForStandards(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { @@ -688,7 +671,6 @@ public ActivityMetadataPage metadataForStandards(@RequestParam(required = false) }) @RequestMapping(value = "/metadata/svaps", method = RequestMethod.GET, produces = "application/json; charset=utf-8") - @DeprecatedApiResponseFields(friendlyUrl = "/activity/metadata/svaps", responseClass = ActivityMetadataPage.class) public ActivityMetadataPage metadataForSvaps(@RequestParam(required = false) Long start, @RequestParam(required = false) Long end, @RequestParam(required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize) throws JsonParseException, IOException, ValidationException { diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java index 50208effbe..2ac3e538b9 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java @@ -28,6 +28,7 @@ import gov.healthit.chpl.report.surveillance.CapCounts; import gov.healthit.chpl.report.surveillance.NonconformityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceActivityCounts; +import gov.healthit.chpl.scheduler.job.report.attestation.AttestationReport; import gov.healthit.chpl.scheduler.job.summarystatistics.data.CertificationBodyStatistic; import gov.healthit.chpl.search.domain.ListingSearchResult; import gov.healthit.chpl.util.SwaggerSecurityRequirement; @@ -462,4 +463,14 @@ public ReportDataController(ReportDataManager reportDataManager, DeveloperSearch return reportDataManager.getDirectReviewCounts(); } + @Operation(summary = "Retrieves the data used to generate the Attestations report.", + description = "Retrieves the data used to generate the Attestations report.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @RequestMapping(value = "/attestations", method = RequestMethod.GET, produces = "application/json; charset=utf-8") + public @ResponseBody List getAttestationReports() { + return reportDataManager.getAttestationReports(); + } + } diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/UserManagementController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/UserManagementController.java index b42e0d8579..6f5f5a8331 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/UserManagementController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/UserManagementController.java @@ -185,6 +185,8 @@ public CognitoUserInvitation inviteUser(@RequestBody CognitoUserInvitation invit case CognitoGroups.CHPL_CMS_STAFF: createdInvitiation = cognitoInvitationManager.inviteCmsUser(invitation); break; + default: + LOGGER.error("Invitation group name not handled: " + invitation.getGroupName()); } return createdInvitiation; } diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders-console.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders-console.xml index 736c008c0e..ddc8e4ef8e 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders-console.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders-console.xml @@ -475,4 +475,18 @@ + + + + + + + + + + + + + + diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml index 770530f568..c4bddca963 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml @@ -811,6 +811,30 @@ + + %d{ISO8601} %-5p (%t) [%C{1}(%M:%L)] %m%n + + + + + + + + %d{ISO8601} %-5p (%t) [%C{1}(%M:%L)] %m%n + + + + + + %d{ISO8601} %-5p (%t) [%C{1}(%M:%L)] %m%n diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml index 28311a9265..e4d82bed4a 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml @@ -208,4 +208,10 @@ + + + + + + diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml index 7d861ceac0..8a828d5224 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml @@ -266,4 +266,12 @@ + + + + + + + + diff --git a/chpl/chpl-api/src/main/webapp/WEB-INF/web.xml b/chpl/chpl-api/src/main/webapp/WEB-INF/web.xml index fc9c765848..4cde1a8dd4 100644 --- a/chpl/chpl-api/src/main/webapp/WEB-INF/web.xml +++ b/chpl/chpl-api/src/main/webapp/WEB-INF/web.xml @@ -39,26 +39,4 @@ /ff4j-console/* /static/* - - - - quartz:config-file - quartz.properties - - - quartz:shutdown-on-unload - true - - - quartz:wait-on-shutdown - true - - - quartz:start-on-load - false - - - org.quartz.ee.servlet.QuartzInitializerListener - - diff --git a/chpl/chpl-resources/src/main/resources/environment.properties b/chpl/chpl-resources/src/main/resources/environment.properties index 94cec072b7..0f1a00c34c 100644 --- a/chpl/chpl-resources/src/main/resources/environment.properties +++ b/chpl/chpl-resources/src/main/resources/environment.properties @@ -17,7 +17,7 @@ chplUrlBegin=https://chpl.healthit.gov developerUrlPart=/#/organizations/developers/%s jndiName=java:comp/env/jdbc/openchpl persistenceUnitName=openchpl -api.version=47.5.0 +api.version=47.7.0 api.description=Created by CHPL Development Team. Please submit any questions using the Health IT \ Feedback Form and select the "Certified Health IT Products List (CHPL)" category.
\ See more at %s @@ -360,6 +360,7 @@ apiCriteriaKeys=criterion.170_315_g_7,\ ###### Attestations ###### attestationExceptionWindowInDays=5 +attestationApprovalWindowInDays=30 #################################### ###### Redis Connection Properties ###### diff --git a/chpl/chpl-resources/src/main/resources/errors.properties b/chpl/chpl-resources/src/main/resources/errors.properties index e5156aa4ed..b47d3633c7 100644 --- a/chpl/chpl-resources/src/main/resources/errors.properties +++ b/chpl/chpl-resources/src/main/resources/errors.properties @@ -127,7 +127,7 @@ pendingListing.alreadyProcessing=The listing is already being processed. upload.emptyFile=You cannot upload an empty file! upload.notCSV=File must be a CSV document. listing.upload.badMerge=The CHPL Product Number in the uploaded file, '%s', does not match the expected CHPL Product Number of \ -the listing after updates are applied: '%s'. ACB code, ATL code, Product code, Version code, and Certified Date code \ +the listing after updates are applied: '%s'. ACB code, Product code, Version code, and Certified Date code \ may not be changed via upload file. listing.upload.noHeadingFound=No records with allowed heading values were found in the file. listing.upload.unrecognizedHeading=The heading '%s' was found in the upload file but is not recognized. diff --git a/chpl/chpl-resources/src/main/resources/jobs.xml b/chpl/chpl-resources/src/main/resources/jobs.xml index 3c1361f8db..50fb2c2872 100644 --- a/chpl/chpl-resources/src/main/resources/jobs.xml +++ b/chpl/chpl-resources/src/main/resources/jobs.xml @@ -930,5 +930,23 @@ true false + + + attestationReportCreatorJob + systemJobs + Generate data for the Attestation Report (Power BI) + gov.healthit.chpl.scheduler.job.report.attestation.AttestationReportCreatorJob + true + false + + + + fixDatadogUrlUptimeAssertionsJob + systemJobs + Fix Datadog Url Uptime Assertions Job (BEWARE - THIS JOB CAN ONLY BE RUN ONCE IN EACH DATADOG ENVITRONMENT!) + gov.healthit.chpl.scheduler.job.urluptime.FixDatadogUrlUptimeAssertionsJob + true + false + diff --git a/chpl/chpl-resources/src/main/resources/system-triggers.xml b/chpl/chpl-resources/src/main/resources/system-triggers.xml index 5a67f8452f..93546b7dd7 100644 --- a/chpl/chpl-resources/src/main/resources/system-triggers.xml +++ b/chpl/chpl-resources/src/main/resources/system-triggers.xml @@ -256,5 +256,15 @@ + + + attestationReportCreator + AttestationReportCreatorJobTrigger + attestationReportCreatorJob + systemJobs + MISFIRE_INSTRUCTION_DO_NOTHING + 0 30 10 * * ? + + diff --git a/chpl/chpl-service/pom.xml b/chpl/chpl-service/pom.xml index 8ec1b28ffe..c42a0084b2 100644 --- a/chpl/chpl-service/pom.xml +++ b/chpl/chpl-service/pom.xml @@ -379,7 +379,7 @@ org.apache.tomcat tomcat-catalina - 10.1.30 + 10.1.34 provided diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java index bbaf7aa89b..0e267f697c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java @@ -27,8 +27,10 @@ import org.apache.http.conn.ssl.TrustStrategy; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.quartz.spi.JobFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; +import org.springframework.context.ApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; @@ -39,6 +41,7 @@ import org.springframework.context.annotation.PropertySources; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.task.TaskExecutor; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; @@ -52,6 +55,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.client.RestTemplate; @@ -96,6 +100,9 @@ public class CHPLServiceConfig implements WebMvcConfigurer, EnvironmentAware { @Autowired private Environment env; + @Autowired + private ApplicationContext applicationContext; + @Override public void setEnvironment(final Environment environment) { this.env = environment; @@ -279,4 +286,21 @@ private int getRequestTimeout() { } return requestTimeout; } + + @Bean + public JobFactory jobFactory() { + QuartzJobFactory jobFactory = new QuartzJobFactory(applicationContext); + return jobFactory; + } + + @Bean + public SchedulerFactoryBean schedulerFactory() { + SchedulerFactoryBean factory = new SchedulerFactoryBean(); + factory.setAutoStartup(true); + factory.setConfigLocation(new ClassPathResource("quartz.properties")); + factory.setJobFactory(jobFactory()); + + return factory; + } + } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/QuartzJobFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/QuartzJobFactory.java new file mode 100644 index 0000000000..5991dfdb44 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/QuartzJobFactory.java @@ -0,0 +1,28 @@ +package gov.healthit.chpl; + +import org.quartz.spi.TriggerFiredBundle; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; + +import gov.healthit.chpl.scheduler.JobAspect; + +public class QuartzJobFactory extends SpringBeanJobFactory { + private AutowireCapableBeanFactory beanFactory; + + public QuartzJobFactory(ApplicationContext applicationContext) { + beanFactory = applicationContext.getAutowireCapableBeanFactory(); + } + + @Override + protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception { + final Object job = super.createJobInstance(bundle); + beanFactory.autowireBean(job); + + AspectJProxyFactory pFactory = new AspectJProxyFactory(job); + pFactory.addAspect(new JobAspect()); + + return pFactory.getProxy(); + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/SpringContext.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/SpringContext.java index 032b97bb2e..05294f4f84 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/SpringContext.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/SpringContext.java @@ -3,10 +3,11 @@ import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; import org.springframework.stereotype.Component; @Component -public class SpringContext implements ApplicationContextAware { +public class SpringContext extends SpringBeanJobFactory implements ApplicationContextAware { private static ApplicationContext context; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ActivityMetadataBuilder.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ActivityMetadataBuilder.java index 112eff90d8..597eec1c7b 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ActivityMetadataBuilder.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ActivityMetadataBuilder.java @@ -45,7 +45,6 @@ protected void addConceptSpecificMetadata(ActivityDTO dto, ActivityMetadata meta protected void addGenericMetadata(ActivityDTO dto, ActivityMetadata metadata) { metadata.setId(dto.getId()); metadata.setDate(dto.getActivityDate()); - metadata.setObjectId(dto.getActivityObjectId()); metadata.setObject(ActivityObject.builder() .id(dto.getActivityObjectId()) .build()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/DeveloperActivityMetadataBuilder.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/DeveloperActivityMetadataBuilder.java index 0743b716d5..59da4d1923 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/DeveloperActivityMetadataBuilder.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/DeveloperActivityMetadataBuilder.java @@ -1,12 +1,9 @@ package gov.healthit.chpl.activity; -import java.util.List; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.fasterxml.jackson.databind.ObjectMapper; - +import gov.healthit.chpl.dao.DeveloperDAO; import gov.healthit.chpl.domain.Developer; import gov.healthit.chpl.domain.activity.ActivityCategory; import gov.healthit.chpl.domain.activity.ActivityMetadata; @@ -18,12 +15,14 @@ @Log4j2 @Component("developerActivityMetadataBuilder") public class DeveloperActivityMetadataBuilder extends ActivityMetadataBuilder { - private ObjectMapper jsonMapper; + + private DeveloperDAO developerDao; @Autowired - public DeveloperActivityMetadataBuilder(ChplUserToCognitoUserUtil chplUserToCognitoUserUtil) { + public DeveloperActivityMetadataBuilder(ChplUserToCognitoUserUtil chplUserToCognitoUserUtil, + DeveloperDAO developerDao) { super(chplUserToCognitoUserUtil); - jsonMapper = new ObjectMapper(); + this.developerDao = developerDao; } @Override @@ -31,114 +30,17 @@ protected void addConceptSpecificMetadata(final ActivityDTO activity, final Acti if (!(metadata instanceof DeveloperActivityMetadata)) { return; } - DeveloperActivityMetadata developerMetadata = (DeveloperActivityMetadata) metadata; - - //parse developer specific metadata - //for merges, the original data is a list of developers. - //for other developer activities it's just a single developer. - Developer origDeveloper = null; - List origDevelopers = null; - if (activity.getOriginalData() != null) { - try { - origDeveloper = - jsonMapper.readValue(activity.getOriginalData(), Developer.class); - } catch (final Exception ignore) { - } - - //if we couldn't parse it as a Developer - //try to parse it as a List. - if (origDeveloper == null) { - try { - origDevelopers = jsonMapper.readValue(activity.getOriginalData(), - jsonMapper.getTypeFactory().constructCollectionType(List.class, Developer.class)); - } catch (final Exception ignore) { - } - } - - //if the orig data is not a developer or a list, log an error - if (origDeveloper == null && origDevelopers == null) { - LOGGER.error("Could not parse activity ID " + activity.getId() + " original data as " - + "a Developer or List. JSON was: " + activity.getOriginalData()); - } - } - - Developer newDeveloper = null; - List newDevelopers = null; - if (activity.getNewData() != null) { - try { - newDeveloper = - jsonMapper.readValue(activity.getNewData(), Developer.class); - } catch (final Exception ignore) { - } - - //if we couldn't parse it as a Developer - //try to parse it as a List. - if (newDeveloper == null) { - try { - newDevelopers = jsonMapper.readValue(activity.getNewData(), - jsonMapper.getTypeFactory().constructCollectionType(List.class, Developer.class)); - } catch (final Exception ignore) { - } - } - - //if the new data is not a developer or a list, log an error - if (newDeveloper == null && newDevelopers == null) { - LOGGER.error("Could not parse activity ID " + activity.getId() + " new data as " - + "a Developer or List. JSON was: " + activity.getNewData()); - } - } - - if (newDeveloper != null && origDeveloper != null - && newDevelopers == null && origDevelopers == null) { - //if there is a single new developer and single original developer - //that means the activity was editing the developer - parseDeveloperMetadata(developerMetadata, newDeveloper); - } else if (origDeveloper != null && newDeveloper == null - && newDevelopers == null && origDevelopers == null) { - //if there is an original developer but no new developer - //then the developer was deleted - pull its info from the orig object - parseDeveloperMetadata(developerMetadata, origDeveloper); - } else if (newDeveloper != null && origDeveloper == null - && newDevelopers == null && origDevelopers == null) { - //if there is a new developer but no original developer - //then the developer was just created - parseDeveloperMetadata(developerMetadata, newDeveloper); - } else if (newDevelopers != null && origDeveloper != null - && newDeveloper == null && origDevelopers == null) { - //multiple new developers and a single original developer - //means the activity was a split - parseDeveloperMetadata(developerMetadata, activity, newDevelopers); - } else if (origDevelopers != null && newDeveloper != null - && origDeveloper == null && newDevelopers == null) { - //multiple original developers and a single new developer - //means the activity was a merge - parseDeveloperMetadata(developerMetadata, newDeveloper); - } + DeveloperActivityMetadata developerMetadata = (DeveloperActivityMetadata) metadata; developerMetadata.getCategories().add(ActivityCategory.DEVELOPER); - } - private void parseDeveloperMetadata(DeveloperActivityMetadata developerMetadata, Developer developer) { - developerMetadata.setDeveloperName(developer.getName()); - developerMetadata.getObject().setName(developer.getName()); - developerMetadata.setDeveloperCode(developer.getDeveloperCode()); - } - - /** - * Find the developer in the list that matches the id of the developer - * the activity was recorded for. Parse activity metadata from that developer. - * @param developerMetadata - * @param activity - * @param developers - */ - private void parseDeveloperMetadata( - DeveloperActivityMetadata developerMetadata, ActivityDTO activity, - List developers) { - Long idToFind = activity.getActivityObjectId(); - for (Developer currDev : developers) { - if (currDev != null && currDev.getId().equals(idToFind)) { - parseDeveloperMetadata(developerMetadata, currDev); - break; + if (metadata.getObject() != null && metadata.getObject().getId() != null) { + Developer dev = null; + try { + dev = developerDao.getById(metadata.getObject().getId(), true); + metadata.getObject().setName(dev.getName()); + } catch (Exception ex) { + LOGGER.error("Could not find developer " + metadata.getObject().getId() + " for activity metadata.", ex); } } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ListingActivityMetadataBuilder.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ListingActivityMetadataBuilder.java index ab0bcb68dc..098e2bfd04 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ListingActivityMetadataBuilder.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ListingActivityMetadataBuilder.java @@ -1,35 +1,28 @@ package gov.healthit.chpl.activity; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections4.MapUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.fasterxml.jackson.databind.ObjectMapper; - -import gov.healthit.chpl.certifiedproduct.service.CertificationStatusEventsService; -import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import gov.healthit.chpl.domain.activity.ActivityCategory; import gov.healthit.chpl.domain.activity.ActivityMetadata; import gov.healthit.chpl.domain.activity.ListingActivityMetadata; -import gov.healthit.chpl.domain.surveillance.Surveillance; import gov.healthit.chpl.dto.ActivityDTO; +import gov.healthit.chpl.search.ListingSearchService; +import gov.healthit.chpl.search.domain.ListingSearchResult; import gov.healthit.chpl.util.ChplUserToCognitoUserUtil; +import lombok.extern.log4j.Log4j2; +@Log4j2 @Component("listingActivityMetadataBuilder") public class ListingActivityMetadataBuilder extends ActivityMetadataBuilder { - private static final Logger LOGGER = LogManager.getLogger(ListingActivityMetadataBuilder.class); - private CertificationStatusEventsService cseService; - private ObjectMapper jsonMapper; + private ListingSearchService listingSearchService; @Autowired - public ListingActivityMetadataBuilder(CertificationStatusEventsService cseService, ChplUserToCognitoUserUtil chplUserToCognitoUserUtil) { + public ListingActivityMetadataBuilder(ChplUserToCognitoUserUtil chplUserToCognitoUserUtil, + ListingSearchService listingSearchService) { super(chplUserToCognitoUserUtil); - this.cseService = cseService; - jsonMapper = new ObjectMapper(); + this.listingSearchService = listingSearchService; } @Override @@ -38,149 +31,15 @@ protected void addConceptSpecificMetadata(final ActivityDTO dto, final ActivityM return; } ListingActivityMetadata listingMetadata = (ListingActivityMetadata) metadata; - - //parse listing specific metadata - CertifiedProductSearchDetails origListing = null; - if (dto.getOriginalData() != null) { - try { - origListing = - jsonMapper.readValue(dto.getOriginalData(), CertifiedProductSearchDetails.class); - } catch (final Exception ex) { - LOGGER.error("Could not parse activity ID " + dto.getId() + " original data. " - + "JSON was: " + dto.getOriginalData(), ex); - } - } - - CertifiedProductSearchDetails newListing = null; - if (dto.getNewData() != null) { - try { - newListing = - jsonMapper.readValue(dto.getNewData(), CertifiedProductSearchDetails.class); - } catch (final Exception ex) { - LOGGER.error("Could not parse activity ID " + dto.getId() + " new data. " - + "JSON was: " + dto.getNewData(), ex); - } - } - - if (newListing != null) { - //for listing activity newListing should really never be null since listings can't be deleted - parseListingMetadata(listingMetadata, newListing); - } else if (origListing != null) { - //adding this here just in case in some future circumstance the newListing could have been null - parseListingMetadata(listingMetadata, origListing); - } - - categorizeActivity(listingMetadata, origListing, newListing); - } - - private void parseListingMetadata( - final ListingActivityMetadata listingMetadata, final CertifiedProductSearchDetails listing) { - listingMetadata.setChplProductNumber(listing.getChplProductNumber()); - listingMetadata.getObject().setName(listing.getChplProductNumber()); - if (listing.getCertifyingBody() != null - && listing.getCertifyingBody().get(CertifiedProductSearchDetails.ACB_NAME_KEY) != null - && listing.getCertifyingBody().get(CertifiedProductSearchDetails.ACB_ID_KEY) != null) { - listingMetadata.setAcbName(listing.getCertifyingBody().get(CertifiedProductSearchDetails.ACB_NAME_KEY).toString()); - listingMetadata.setAcbId(Long.valueOf(listing.getCertifyingBody().get(CertifiedProductSearchDetails.ACB_ID_KEY).toString())); - } - - //there is at least one activity record for listing ID 4801 - //that has a null certification date field due to a bug in the system at the time of the activity - listingMetadata.setCertificationDate(listing.getCertificationDate()); - if (listing.getDeveloper() != null) { - listingMetadata.setDeveloperName(listing.getDeveloper().getName()); - } - if (listing.getEdition() != null) { - listingMetadata.setEdition(listing.getEdition().getName()); - } else if (listing.getCertificationEdition() != null - && MapUtils.getString(listing.getCertificationEdition(), CertifiedProductSearchDetails.EDITION_NAME_KEY) != null) { - listingMetadata.setEdition(MapUtils.getString(listing.getCertificationEdition(), CertifiedProductSearchDetails.EDITION_NAME_KEY)); - } - if (listing.getCuresUpdate() != null) { - listingMetadata.setCuresUpdate(listing.getCuresUpdate()); - } - if (listing.getProduct() != null) { - listingMetadata.setProductName(listing.getProduct().getName()); - } - } - - private void categorizeActivity(final ListingActivityMetadata listingMetadata, - final CertifiedProductSearchDetails origListing, final CertifiedProductSearchDetails newListing) { listingMetadata.getCategories().add(ActivityCategory.LISTING); - if (origListing == null && newListing != null) { - listingMetadata.getCategories().add(ActivityCategory.LISTING_UPLOAD); - } else if (origListing != null && newListing != null) { - //status change? - if (origListing.getCertificationStatus() != null && newListing.getCertificationStatus() != null - && origListing.getCertificationStatus().getId() != newListing.getCertificationStatus().getId()) { - //check the legacy certificationStatus field for older activities - listingMetadata.getCategories().add(ActivityCategory.LISTING_STATUS_CHANGE); - } else if (!CollectionUtils.isEmpty(origListing.getCertificationEvents()) - && !CollectionUtils.isEmpty(newListing.getCertificationEvents()) - && (!CollectionUtils.isEmpty(cseService.getAddedCertificationStatusEvents(origListing, newListing)) - || !CollectionUtils.isEmpty(cseService.getRemovedCertificationStatusEvents(origListing, newListing)))) { - listingMetadata.getCategories().add(ActivityCategory.LISTING_STATUS_CHANGE); - } - //surveillance change? - //check for surveillance added or removed - if ((origListing.getSurveillance() != null && newListing.getSurveillance() == null) - || (origListing.getSurveillance() == null && newListing.getSurveillance() != null)) { - listingMetadata.getCategories().add(ActivityCategory.SURVEILLANCE); - } else if (origListing.getSurveillance() != null && newListing.getSurveillance() != null) { - if (origListing.getSurveillance().size() != newListing.getSurveillance().size()) { - listingMetadata.getCategories().add(ActivityCategory.SURVEILLANCE); - } else { - //there are the same amount of surveillances for both orig and - //new listing activity; - //check for new surveillance, deleted surveillance, or any updates - - //look for surveillance added - for (Surveillance newSurv : newListing.getSurveillance()) { - boolean foundInOrigListing = false; - for (Surveillance origSurv : origListing.getSurveillance()) { - if (origSurv.getId().longValue() == newSurv.getId().longValue()) { - foundInOrigListing = true; - } - } - if (!foundInOrigListing) { - //surv is in the new listing but not the original one = was added - listingMetadata.getCategories().add(ActivityCategory.SURVEILLANCE); - } - } - - //if there's a surveillance change already detected we don't need to look any farther - //if not keep looking for one - look for surveillance deleted - if (!listingMetadata.getCategories().contains(ActivityCategory.SURVEILLANCE)) { - for (Surveillance origSurv : origListing.getSurveillance()) { - boolean foundInNewListing = false; - for (Surveillance newSurv : newListing.getSurveillance()) { - if (origSurv.getId().longValue() == newSurv.getId().longValue()) { - foundInNewListing = true; - } - } - if (!foundInNewListing) { - //surv is in the original listing but not the new one = was deleted - listingMetadata.getCategories().add(ActivityCategory.SURVEILLANCE); - } - } - } - - //if there's a surveillance change already detected we don't need to look any farther - //if not keep looking for one - look for surveillance updated - if (!listingMetadata.getCategories().contains(ActivityCategory.SURVEILLANCE)) { - for (Surveillance origSurv : origListing.getSurveillance()) { - for (Surveillance newSurv : newListing.getSurveillance()) { - if (origSurv.getId().longValue() == newSurv.getId().longValue() - && !origSurv.matches(newSurv)) { - listingMetadata.getCategories().add(ActivityCategory.SURVEILLANCE); - //if we add a surveillance category there's no need to keep looking - //for more differences. - return; - } - } - } - } + if (metadata.getObject() != null && metadata.getObject().getId() != null) { + try { + ListingSearchResult listingSearchResult = listingSearchService.findListing(metadata.getObject().getId()); + if (listingSearchResult != null) { + metadata.getObject().setName(listingSearchResult.getChplProductNumber()); } + } catch (Exception ex) { + LOGGER.error("Could not find listing " + metadata.getObject().getId() + " for activity metadata.", ex); } } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ProductActivityMetadataBuilder.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ProductActivityMetadataBuilder.java index 3a3683811e..e33acc7004 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ProductActivityMetadataBuilder.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/activity/ProductActivityMetadataBuilder.java @@ -1,15 +1,9 @@ package gov.healthit.chpl.activity; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.fasterxml.jackson.databind.ObjectMapper; - -import gov.healthit.chpl.dao.DeveloperDAO; -import gov.healthit.chpl.domain.Developer; +import gov.healthit.chpl.dao.ProductDAO; import gov.healthit.chpl.domain.Product; import gov.healthit.chpl.domain.activity.ActivityCategory; import gov.healthit.chpl.domain.activity.ActivityMetadata; @@ -21,14 +15,14 @@ @Log4j2 @Component("productActivityMetadataBuilder") public class ProductActivityMetadataBuilder extends ActivityMetadataBuilder { - private ObjectMapper jsonMapper; - private DeveloperDAO developerDao; + + private ProductDAO productDao; @Autowired - public ProductActivityMetadataBuilder(DeveloperDAO developerDao, ChplUserToCognitoUserUtil chplUserToCognitoUserUtil) { + public ProductActivityMetadataBuilder(ChplUserToCognitoUserUtil chplUserToCognitoUserUtil, + ProductDAO productDao) { super(chplUserToCognitoUserUtil); - jsonMapper = new ObjectMapper(); - this.developerDao = developerDao; + this.productDao = productDao; } @Override @@ -37,120 +31,15 @@ protected void addConceptSpecificMetadata(ActivityDTO activity, ActivityMetadata return; } ProductActivityMetadata productMetadata = (ProductActivityMetadata) metadata; - - //parse product specific metadata - //for merges, original data is a list of products - //for splits, new data is a list of products - //otherwise we expect orig/new data to be a single product - Product origProduct = null; - List origProducts = null; - if (activity.getOriginalData() != null) { - try { - origProduct = - jsonMapper.readValue(activity.getOriginalData(), Product.class); - } catch (Exception ignore) { } - - if (origProduct == null) { - try { - origProducts = jsonMapper.readValue(activity.getOriginalData(), - jsonMapper.getTypeFactory().constructCollectionType(List.class, Product.class)); - } catch (Exception ignore) { } - } - - if (origProduct == null && origProducts == null) { - LOGGER.error("Could not parse activity ID " + activity.getId() + " original data " - + " as ProductDTO or List. JSON was: " + activity.getOriginalData()); - } - } - - Product newProduct = null; - List newProducts = null; - if (activity.getNewData() != null) { - try { - newProduct = - jsonMapper.readValue(activity.getNewData(), Product.class); - } catch (Exception ignore) { } - - if (newProduct == null) { - try { - newProducts = jsonMapper.readValue(activity.getNewData(), - jsonMapper.getTypeFactory().constructCollectionType(List.class, Product.class)); - } catch (Exception ignore) { } - } - - if (newProduct == null && newProducts == null) { - LOGGER.error("Could not parse activity ID " + activity.getId() + " new data " - + "as ProductDTO or List. JSON was: " + activity.getNewData()); - } - } - - if (newProduct != null && origProduct != null - && newProducts == null && origProducts == null) { - //if there is a single new product and single original product - //that means the activity was editing the product - parseProductMetadata(productMetadata, newProduct); - } else if (origProduct != null && newProduct == null - && newProducts == null && origProducts == null) { - //if there is an original product but no new product - //then the product was deleted - pull its info from the orig object - parseProductMetadata(productMetadata, origProduct); - } else if (newProduct != null && origProduct == null - && newProducts == null && origProducts == null) { - //if there is a new product but no original product - //then the product was just created - parseProductMetadata(productMetadata, newProduct); - } else if (newProducts != null && origProduct != null - && newProduct == null && origProducts == null) { - //multiple new products and a single original product - //means the activity was a split - parseProductMetadata(productMetadata, activity, newProducts); - } else if (origProducts != null && newProduct != null - && origProduct == null && newProducts == null) { - //multiple original products and a single new product - //means the activity was a merge - parseProductMetadata(productMetadata, newProduct); - } - productMetadata.getCategories().add(ActivityCategory.PRODUCT); - } - private void parseProductMetadata( - ProductActivityMetadata productMetadata, Product product) { - //Developer id is always filled in the activity object - //but the name does not seem to be. If the name is available - //use it but if not look up the developer by ID - if (product.getOwner() != null && !StringUtils.isEmpty(product.getOwner().getName())) { - productMetadata.setDeveloperName(product.getOwner().getName()); - } else if (product.getOwner() != null && product.getOwner().getId() != null) { + if (metadata.getObject() != null && metadata.getObject().getId() != null) { + Product product = null; try { - Developer developer = developerDao.getSimpleDeveloperById(product.getOwner().getId(), true); - productMetadata.setDeveloperName(developer.getName()); + product = productDao.getById(metadata.getObject().getId(), false); + metadata.getObject().setName(product.getName()); } catch (Exception ex) { - LOGGER.error("Unable to find developer with ID " + product.getOwner().getId() + " referenced " - + "in activity for product " + product.getId()); - } - } else if (!StringUtils.isEmpty(product.getDeveloperName())) { - productMetadata.setDeveloperName(product.getDeveloperName()); - } else if (product.getDeveloperId() != null) { - try { - Developer developer = developerDao.getSimpleDeveloperById(product.getDeveloperId(), true); - productMetadata.setDeveloperName(developer.getName()); - } catch (Exception ex) { - LOGGER.error("Unable to find developer with ID " + product.getDeveloperId() + " referenced " - + "in activity for product " + product.getId()); - } - } - productMetadata.setProductName(product.getName()); - productMetadata.getObject().setName(product.getName()); - } - - private void parseProductMetadata(ProductActivityMetadata productMetadata, ActivityDTO activity, - List products) { - Long idToFind = activity.getActivityObjectId(); - for (Product currProduct : products) { - if (currProduct != null && currProduct.getId().equals(idToFind)) { - parseProductMetadata(productMetadata, currProduct); - break; + LOGGER.error("Could not find product " + metadata.getObject().getId() + " for activity metadata.", ex); } } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/auth/authentication/CognitoJwtUserConverter.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/auth/authentication/CognitoJwtUserConverter.java index 67cf818a9d..e21c1a9667 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/auth/authentication/CognitoJwtUserConverter.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/auth/authentication/CognitoJwtUserConverter.java @@ -74,7 +74,7 @@ private DecodedJWT decodeJwt(String jwt) { RSAKeyProvider keyProvider = new CognitoRsaKeyProvider(region, userPoolId, tokenizeRsaKeyUrl); Algorithm algorithm = Algorithm.RSA256(keyProvider); JWTVerifier jwtVerifier = JWT.require(algorithm) - //.withAudience(clientId) + .acceptLeeway(30000) //allows for the CHPL server clock and AWS server clock to be off by 30 seconds .build(); DecodedJWT decodedJwt = jwtVerifier.verify(jwt); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/ListingMergeService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/ListingMergeService.java index 2e19b67903..61b30bef2f 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/ListingMergeService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/ListingMergeService.java @@ -94,7 +94,6 @@ public void mergeWithListingFromChpl(CertifiedProductSearchDetails updatedListin updatedListing.setEdition(currentListing.getEdition()); updatedListing.setCuresUpdate(currentListing.getCuresUpdate()); updatedListing.setSurveillance(currentListing.getSurveillance()); - updatedListing.setTestingLabs(currentListing.getTestingLabs()); updatedListing.setCountClosedNonconformities(currentListing.getCountClosedNonconformities()); updatedListing.setCountClosedSurveillance(currentListing.getCountClosedSurveillance()); updatedListing.setCountOpenNonconformities(currentListing.getCountOpenNonconformities()); @@ -123,6 +122,7 @@ public void mergeWithListingFromChpl(CertifiedProductSearchDetails updatedListin private String applyUpdatesToChplProductNumber(CertifiedProductSearchDetails updatedListing, CertifiedProductSearchDetails currentListing) { ChplProductNumberParts currChplProductNumberParts = chplProductNumberUtil.parseChplProductNumber(currentListing.getChplProductNumber()); + currChplProductNumberParts.setAtlCode(chplProductNumberUtil.deriveTestingLabCodeFromListing(updatedListing)); currChplProductNumberParts.setIcsCode(chplProductNumberUtil.deriveIcsCodeFromListing(updatedListing)); currChplProductNumberParts.setAdditionalSoftwareCode(chplProductNumberUtil.deriveAdditionalSoftwareCodeFromListing(updatedListing)); return chplProductNumberUtil.getChplProductNumber(currChplProductNumberParts); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/dao/DeveloperCertificationBodyMapDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/dao/DeveloperCertificationBodyMapDAO.java index 2cd665b69c..2c8be77852 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/dao/DeveloperCertificationBodyMapDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/changerequest/dao/DeveloperCertificationBodyMapDAO.java @@ -1,6 +1,9 @@ package gov.healthit.chpl.changerequest.dao; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.springframework.stereotype.Repository; @@ -47,4 +50,26 @@ public List getDevelopersForCertificationBody(Long certificationBodyI .map(item -> item.getDeveloper().toDomain()) .collect(Collectors.toList()); } + + public Map> getCertificationBodiesForAllDeveloper() { + String hql = "SELECT main " + + "FROM DeveloperCertificationBodyMapEntity main " + + "JOIN FETCH main.developer dev " + + "JOIN FETCH main.certificationBody cb " + + "LEFT JOIN FETCH cb.address "; + + List entities = entityManager + .createQuery(hql, DeveloperCertificationBodyMapEntity.class) + .getResultList(); + + Map> map = new HashMap>(); + + entities.forEach(e -> { + if (!map.containsKey(e.getDeveloperId())) { + map.put(e.getDeveloperId(), new ArrayList()); + } + map.get(e.getDeveloperId()).add(e.getCertificationBody().toDomain()); + }); + return map; + } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityConcept.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityConcept.java index fe887cb725..97aaabd4cd 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityConcept.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityConcept.java @@ -13,6 +13,7 @@ public enum ActivityConcept implements Serializable { CORRECTIVE_ACTION_PLAN, DEVELOPER, FUNCTIONALITY_TESTED, + INVITATION, LISTING_UPLOAD, PENDING_CERTIFIED_PRODUCT, PENDING_SURVEILLANCE, diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadata.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadata.java index 11a18b87fa..835a149faf 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadata.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadata.java @@ -6,7 +6,6 @@ import java.util.Objects; import java.util.Set; -import gov.healthit.chpl.api.deprecatedUsage.DeprecatedResponseField; import gov.healthit.chpl.domain.auth.User; import gov.healthit.chpl.util.Util; import lombok.AccessLevel; @@ -17,7 +16,6 @@ import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.Setter; -import lombok.Singular; import lombok.ToString; @Builder @@ -31,18 +29,16 @@ public class ActivityMetadata implements Serializable { private Long id; private ActivityConcept concept; - @Singular + + @Builder.Default private Set categories = new HashSet(); + @Setter(AccessLevel.NONE) @Getter(AccessLevel.NONE) private Date date; - @DeprecatedResponseField(message = "This field is deprecated and will be removed. Use object.id", - removalDate = "2024-10-31") - @Deprecated - private Long objectId; - private ActivityObject object; + private User responsibleUser; private String description; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadataPage.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadataPage.java index 77f2bafadd..efc3e47360 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadataPage.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ActivityMetadataPage.java @@ -3,12 +3,6 @@ import java.io.Serializable; import java.util.Set; -/** - * A page of activity metadata including the page number, page size, - * and total result set size. - * @author kekey - * - */ public class ActivityMetadataPage implements Serializable { private static final long serialVersionUID = -3855142961571461535L; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/DeveloperActivityMetadata.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/DeveloperActivityMetadata.java index 5971f212b5..5bfb967c2d 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/DeveloperActivityMetadata.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/DeveloperActivityMetadata.java @@ -1,5 +1,6 @@ package gov.healthit.chpl.domain.activity; +import gov.healthit.chpl.api.deprecatedUsage.DeprecatedResponseField; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -10,6 +11,11 @@ public class DeveloperActivityMetadata extends ActivityMetadata { private static final long serialVersionUID = 9069117187928313180L; + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String developerName; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String developerCode; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ListingActivityMetadata.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ListingActivityMetadata.java index dd1ed5e74c..bdb92526cd 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ListingActivityMetadata.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ListingActivityMetadata.java @@ -1,5 +1,6 @@ package gov.healthit.chpl.domain.activity; +import gov.healthit.chpl.api.deprecatedUsage.DeprecatedResponseField; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -12,12 +13,35 @@ public class ListingActivityMetadata extends ActivityMetadata { private static final long serialVersionUID = 5473773376581297578L; + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String chplProductNumber; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String acbName; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private Long acbId; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String developerName; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String productName; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String edition; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private Boolean curesUpdate; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private Long certificationDate; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ProductActivityMetadata.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ProductActivityMetadata.java index 44044743b6..2b2a0f8b1c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ProductActivityMetadata.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/activity/ProductActivityMetadata.java @@ -1,5 +1,6 @@ package gov.healthit.chpl.domain.activity; +import gov.healthit.chpl.api.deprecatedUsage.DeprecatedResponseField; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -10,7 +11,12 @@ public class ProductActivityMetadata extends ActivityMetadata { private static final long serialVersionUID = 9069117187924463180L; + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String developerName; + + @Deprecated + @DeprecatedResponseField(message = "This field is deprecated and will be removed.", removalDate = "2025-06-01") private String productName; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/ListingUploadDomainPerissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/ListingUploadDomainPerissions.java index 988a91967b..22acaf45ea 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/ListingUploadDomainPerissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/ListingUploadDomainPerissions.java @@ -11,11 +11,13 @@ import gov.healthit.chpl.permissions.domains.listingUpload.GetByIdActionPermissions; import gov.healthit.chpl.permissions.domains.listingUpload.GetListingUploadAsListingPermissions; import gov.healthit.chpl.permissions.domains.listingUpload.GetUploadedCsvActionPermissions; +import gov.healthit.chpl.permissions.domains.listingUpload.ParseActionPermissions; import gov.healthit.chpl.permissions.domains.listingUpload.ValidateByIdsActionPermissions; @Component public class ListingUploadDomainPerissions extends DomainPermissions { public static final String CREATE = "CREATE"; + public static final String PARSE = "PARSE"; public static final String GET_ALL = "GET_ALL"; public static final String GET_BY_ID = "GET_BY_ID"; public static final String GET_UPLOAD_AS_LISTING = "GET_UPLOAD_AS_LISTING"; @@ -27,6 +29,7 @@ public class ListingUploadDomainPerissions extends DomainPermissions { @Autowired public ListingUploadDomainPerissions( @Qualifier("createListingUploadActionPermissions") CreateActionPermissions createActionPermissions, + @Qualifier("parseListingUploadActionPermissions") ParseActionPermissions parseActionPermissions, @Qualifier("getAllListingUploadsActionPermissions") GetAllActionPermissions getAllActionPermissions, @Qualifier("getListingUploadByIdActionPermissions") GetByIdActionPermissions getByIdActionPermissions, @Qualifier("getListingUploadAsListingActionPermissions") GetListingUploadAsListingPermissions getListingUploadAsListingPermissions, @@ -35,6 +38,7 @@ public ListingUploadDomainPerissions( @Qualifier("deleteListingUploadActionPermissions") DeleteActionPermissions deleteActionPermissions, @Qualifier("getUploadedCsvActionPermissions") GetUploadedCsvActionPermissions getUploadedCsvActionPermissions) { getActionPermissions().put(CREATE, createActionPermissions); + getActionPermissions().put(PARSE, parseActionPermissions); getActionPermissions().put(GET_ALL, getAllActionPermissions); getActionPermissions().put(GET_BY_ID, getByIdActionPermissions); getActionPermissions().put(GET_UPLOAD_AS_LISTING, getListingUploadAsListingPermissions); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetActivityDetailsActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetActivityDetailsActionPermissions.java index 7db210c0c1..b587b36e8b 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetActivityDetailsActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetActivityDetailsActionPermissions.java @@ -130,7 +130,7 @@ public boolean hasAccess(Object obj) { case FUNCTIONALITY_TESTED: case SVAP: case STANDARD: - return false; + return getResourcePermissions().isUserRoleAcbAdmin(); default: // all other types of activity // are accessible to any logged-in or anonymous user diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetFunctionalityTestedMetadataActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetFunctionalityTestedMetadataActionPermissions.java index 89b5b172ec..7aaaa3cd75 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetFunctionalityTestedMetadataActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetFunctionalityTestedMetadataActionPermissions.java @@ -8,11 +8,13 @@ public class GetFunctionalityTestedMetadataActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc(); + return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); } @Override public boolean hasAccess(Object obj) { - return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc(); + return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetStandardMetadataActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetStandardMetadataActionPermissions.java index 8feb5a384d..65a0409f73 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetStandardMetadataActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetStandardMetadataActionPermissions.java @@ -8,11 +8,13 @@ public class GetStandardMetadataActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc(); + return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); } @Override public boolean hasAccess(Object obj) { - return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc(); + return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetSvapMetadataActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetSvapMetadataActionPermissions.java index 336967b735..5ebf1b47bf 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetSvapMetadataActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/activity/GetSvapMetadataActionPermissions.java @@ -8,11 +8,13 @@ public class GetSvapMetadataActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc(); + return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); } @Override public boolean hasAccess(Object obj) { - return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc(); + return getResourcePermissions().isUserRoleAdmin() || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/CreateActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/CreateActionPermissions.java index 00c9697099..e76e27d106 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/CreateActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/CreateActionPermissions.java @@ -8,7 +8,8 @@ public class CreateActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/DeleteActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/DeleteActionPermissions.java index b67478e7fd..bcffcf6f13 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/DeleteActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/DeleteActionPermissions.java @@ -9,7 +9,8 @@ public class DeleteActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/UpdateActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/UpdateActionPermissions.java index 75a39f679a..ecd2365cf1 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/UpdateActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/functionalitytested/UpdateActionPermissions.java @@ -9,7 +9,8 @@ public class UpdateActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/GetListingUploadAsListingPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/GetListingUploadAsListingPermissions.java index 3187d86894..31e282b4ab 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/GetListingUploadAsListingPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/GetListingUploadAsListingPermissions.java @@ -23,7 +23,8 @@ public boolean hasAccess(Object obj) { } private boolean hasAccess(ListingUpload uploadedMetadata) { - if (getResourcePermissions().isUserRoleAdmin()) { + if (getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc()) { return true; } else if (getResourcePermissions().isUserRoleAcbAdmin()) { return isAcbValidForCurrentUser(uploadedMetadata.getAcb().getId()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/ParseActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/ParseActionPermissions.java new file mode 100644 index 0000000000..0e85601896 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/listingUpload/ParseActionPermissions.java @@ -0,0 +1,46 @@ +package gov.healthit.chpl.permissions.domains.listingUpload; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import gov.healthit.chpl.domain.ListingUpload; +import gov.healthit.chpl.permissions.domains.ActionPermissions; + +@Component("parseListingUploadActionPermissions") +public class ParseActionPermissions extends ActionPermissions { + + @Override + public boolean hasAccess() { + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc() + || getResourcePermissions().isUserRoleAcbAdmin(); + } + + @Override + public boolean hasAccess(Object obj) { + if (obj instanceof ListingUpload) { + return hasAccess((ListingUpload) obj); + } else if (obj instanceof List) { + boolean hasAccessToAll = true; + for (Object listItem : (List) obj) { + if (listItem instanceof ListingUpload) { + hasAccessToAll = hasAccessToAll && hasAccess((ListingUpload) listItem); + } else { + hasAccessToAll = false; + } + } + return hasAccessToAll; + } + return false; + } + + private boolean hasAccess(ListingUpload uploadedMetadata) { + if (getResourcePermissions().isUserRoleAdmin()) { + return true; + } else if (getResourcePermissions().isUserRoleAcbAdmin() && uploadedMetadata.getAcb() != null) { + return isAcbValidForCurrentUser(uploadedMetadata.getAcb().getId()); + } + return false; + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/CreateActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/CreateActionPermissions.java index 105b406779..b89d96ceba 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/CreateActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/CreateActionPermissions.java @@ -8,7 +8,8 @@ public class CreateActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/DeleteActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/DeleteActionPermissions.java index fc68749436..4d5959e1f2 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/DeleteActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/DeleteActionPermissions.java @@ -9,7 +9,8 @@ public class DeleteActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/UpdateActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/UpdateActionPermissions.java index 35e460648c..3856f606bb 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/UpdateActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/standard/UpdateActionPermissions.java @@ -9,7 +9,8 @@ public class UpdateActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/CreateActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/CreateActionPermissions.java index 4154876b9b..dec5835d1a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/CreateActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/CreateActionPermissions.java @@ -8,7 +8,8 @@ public class CreateActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/DeleteActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/DeleteActionPermissions.java index bd9aa519f5..2c19bd0188 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/DeleteActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/DeleteActionPermissions.java @@ -9,7 +9,8 @@ public class DeleteActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/UpdateActionPermissions.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/UpdateActionPermissions.java index 32aa85b7df..7b0ceb7957 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/UpdateActionPermissions.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/permissions/domains/testtool/UpdateActionPermissions.java @@ -9,7 +9,8 @@ public class UpdateActionPermissions extends ActionPermissions { @Override public boolean hasAccess() { - return getResourcePermissions().isUserRoleAdmin(); + return getResourcePermissions().isUserRoleAdmin() + || getResourcePermissions().isUserRoleOnc(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java index 7f01f990ae..faefb01a52 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Component; import gov.healthit.chpl.developer.search.DeveloperSearchResult; +import gov.healthit.chpl.report.attestation.AttestationReportService; import gov.healthit.chpl.report.criteriaattribute.StandardListingReport; import gov.healthit.chpl.report.criteriaattribute.StandardReport; import gov.healthit.chpl.report.criteriaattribute.StandardReportService; @@ -31,6 +32,7 @@ import gov.healthit.chpl.report.surveillance.NonconformityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceActivityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceReportsService; +import gov.healthit.chpl.scheduler.job.report.attestation.AttestationReport; import gov.healthit.chpl.scheduler.job.summarystatistics.data.CertificationBodyStatistic; import gov.healthit.chpl.search.domain.ListingSearchResult; import lombok.Synchronized; @@ -50,6 +52,7 @@ public class ReportDataManager { private ServiceBaseUrlListReportService serviceBaseUrlListReportService; private StandardReportService standardReportService; private DirectReviewReportsService directReviewReportsService; + private AttestationReportService attestationReportService; private ReportMetadataDAO reportMetadataDAO; private RealWorldTestingReportDataService realWorldTestingReportDataService; @@ -62,6 +65,7 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS ListingReportsService listingReportsService, TestToolReportService testToolReportService, DirectReviewReportsService directReviewReportsService, + AttestationReportService attestationReportService, ReportMetadataDAO reportMetadataDAO, ServiceBaseUrlListReportService serviceBaseUrlListReportService, StandardReportService standardReportService, @@ -76,6 +80,7 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS this.serviceBaseUrlListReportService = serviceBaseUrlListReportService; this.standardReportService = standardReportService; this.directReviewReportsService = directReviewReportsService; + this.attestationReportService = attestationReportService; this.reportMetadataDAO = reportMetadataDAO; this.realWorldTestingReportDataService = realWorldTestingReportDataService; } @@ -283,4 +288,10 @@ public DirectReviewCounts getDirectReviewCounts() { public RealWorldTestingReportDataService getRealWorldTestingReportDataService() { return realWorldTestingReportDataService; } + + @Synchronized("lock") + public List getAttestationReports() { + return attestationReportService.getAttestationReports(); + } + } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/attestation/AttestationReportService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/attestation/AttestationReportService.java new file mode 100644 index 0000000000..94d67bb5d0 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/attestation/AttestationReportService.java @@ -0,0 +1,30 @@ +package gov.healthit.chpl.report.attestation; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import gov.healthit.chpl.attestation.manager.AttestationPeriodService; +import gov.healthit.chpl.scheduler.job.report.attestation.AttestationReport; +import gov.healthit.chpl.scheduler.job.report.attestation.AttestationReportDAO; + +@Component +public class AttestationReportService { + + private AttestationReportDAO attestationReportDAO; + private AttestationPeriodService attestationPeriodService; + + @Autowired + public AttestationReportService(AttestationReportDAO attestationReportDAO, AttestationPeriodService attestationPeriodService) { + this.attestationReportDAO = attestationReportDAO; + this.attestationPeriodService = attestationPeriodService; + } + + @Transactional + public List getAttestationReports() { + return attestationReportDAO.getAttestationReportByAttestationPeriod( + attestationPeriodService.getMostRecentPastAttestationPeriod()); + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/ChplSchedulerReference.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/ChplSchedulerReference.java index 59b1880c51..9de02c5e93 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/ChplSchedulerReference.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/ChplSchedulerReference.java @@ -1,11 +1,9 @@ package gov.healthit.chpl.scheduler; -import jakarta.annotation.PostConstruct; - import org.quartz.Scheduler; -import org.quartz.SchedulerException; -import org.quartz.impl.StdSchedulerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Component; /** @@ -18,15 +16,9 @@ public class ChplSchedulerReference { private Scheduler scheduler; - /** - * Initializes the Quartz Scheduler when this object is created. - * @throws SchedulerException if thrown - */ - @PostConstruct - public void init() throws SchedulerException { - StdSchedulerFactory sf = new StdSchedulerFactory(); - sf.initialize(); - this.scheduler = sf.getScheduler(); + @Autowired + public ChplSchedulerReference(SchedulerFactoryBean schedulerFactory) { + this.scheduler = schedulerFactory.getScheduler(); } public Scheduler getScheduler() { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/JobAspect.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/JobAspect.java new file mode 100644 index 0000000000..d09bfda06d --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/JobAspect.java @@ -0,0 +1,16 @@ +package gov.healthit.chpl.scheduler; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.security.core.context.SecurityContextHolder; + +@Aspect +public class JobAspect { + + @After("execution(public void org.quartz.Job.execute(..))") + public void afterExecute(JoinPoint joinPoint) { + SecurityContextHolder.clearContext(); + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/QuartzJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/QuartzJob.java index 3677a0347e..e7838e1137 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/QuartzJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/QuartzJob.java @@ -8,7 +8,9 @@ import org.springframework.core.env.Environment; import gov.healthit.chpl.scheduler.AuthenticatedUserAwareJob; +import lombok.extern.log4j.Log4j2; +@Log4j2 public abstract class QuartzJob extends AuthenticatedUserAwareJob implements Job { public static final String JOB_DATA_KEY_EMAIL = "email"; public static final String JOB_DATA_KEY_ACB = "acb"; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/downloadfile/GenerateListingDownloadFilesAspect.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/downloadfile/GenerateListingDownloadFilesAspect.java index d6c366a0de..eaf285ad87 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/downloadfile/GenerateListingDownloadFilesAspect.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/downloadfile/GenerateListingDownloadFilesAspect.java @@ -64,15 +64,20 @@ private void scheduleDownloadFileJob(ListingSet listingSet) { if (!isJobAlreadyScheduled(info)) { ChplOneTimeTrigger downloadFileTrigger = new ChplOneTimeTrigger(); - ChplJob downloadFileJob = getDownloadFileJob(listingSetToJobNameMap.get(listingSet).jobName); - downloadFileTrigger.setJob(downloadFileJob); - downloadFileTrigger.setRunDateMillis(listingSetToJobNameMap.get(listingSet).getRunDateTime().toInstant().toEpochMilli()); - downloadFileTrigger = addTriggerToScheduler(downloadFileTrigger); - - LOGGER.info("System job {}/{} has been scheduled for {}", - downloadFileTrigger.getJob().getGroup(), - downloadFileTrigger.getJob().getName(), - DateUtil.toLocalDateTime(downloadFileTrigger.getRunDateMillis()).toString()); + String downloadFileJobName = listingSetToJobNameMap.get(listingSet).jobName; + ChplJob downloadFileJob = getDownloadFileJob(downloadFileJobName); + if (downloadFileJob != null) { + downloadFileTrigger.setJob(downloadFileJob); + downloadFileTrigger.setRunDateMillis(listingSetToJobNameMap.get(listingSet).getRunDateTime().toInstant().toEpochMilli()); + downloadFileTrigger = addTriggerToScheduler(downloadFileTrigger); + + LOGGER.info("System job {}/{} has been scheduled for {}", + downloadFileTrigger.getJob().getGroup(), + downloadFileTrigger.getJob().getName(), + DateUtil.toLocalDateTime(downloadFileTrigger.getRunDateMillis()).toString()); + } else { + LOGGER.error("No job found with name " + downloadFileJobName); + } } } @@ -89,7 +94,7 @@ private ChplJob getDownloadFileJob(String jobName) { return getAllJobs().stream() .filter(job -> job.getName().equals(jobName)) .findAny() - .get(); + .orElse(null); } private List getAllJobs() { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReport.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReport.java new file mode 100644 index 0000000000..3ab7cc3895 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReport.java @@ -0,0 +1,41 @@ +package gov.healthit.chpl.scheduler.job.report.attestation; + +import java.time.LocalDate; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import gov.healthit.chpl.attestation.domain.AttestationPeriod; +import gov.healthit.chpl.domain.CertificationBody; +import gov.healthit.chpl.util.LocalDateDeserializer; +import gov.healthit.chpl.util.LocalDateSerializer; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AttestationReport { + private Long id; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate reportDate; + + private CertificationBody certificationBody; + private AttestationPeriod attestationPeriod; + + @Builder.Default + private Long developerCount = 0L; + + @Builder.Default + private Long approvedCount = 0L; + + @Builder.Default + private Long pendingAcbActionCount = 0L; + + @Builder.Default + private Long pendingDeveloperActionCount = 0L; + + @Builder.Default + private Long noSubmissionCount = 0L; +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportCreatorJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportCreatorJob.java new file mode 100644 index 0000000000..56490266aa --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportCreatorJob.java @@ -0,0 +1,201 @@ +package gov.healthit.chpl.scheduler.job.report.attestation; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import gov.healthit.chpl.attestation.domain.AttestationPeriod; +import gov.healthit.chpl.attestation.manager.AttestationManager; +import gov.healthit.chpl.attestation.manager.AttestationPeriodService; +import gov.healthit.chpl.attestation.service.AttestationCertificationBodyService; +import gov.healthit.chpl.changerequest.dao.ChangeRequestAttestationDAO; +import gov.healthit.chpl.changerequest.domain.ChangeRequest; +import gov.healthit.chpl.changerequest.manager.ChangeRequestManager; +import gov.healthit.chpl.domain.CertificationBody; +import gov.healthit.chpl.domain.Developer; +import gov.healthit.chpl.manager.CertificationBodyManager; +import gov.healthit.chpl.scheduler.SchedulerSecurityContextService; +import gov.healthit.chpl.scheduler.job.QuartzJob; +import gov.healthit.chpl.scheduler.job.developer.attestation.DeveloperAttestationPeriodCalculator; +import gov.healthit.chpl.util.DateUtil; +import lombok.extern.log4j.Log4j2; + +@Log4j2(topic = "attestationReportCreatorJobLogger") +public class AttestationReportCreatorJob extends QuartzJob { + + @Autowired + private JpaTransactionManager txManager; + + @Autowired + private Environment env; + + @Autowired + private AttestationPeriodService attestationPeriodService; + + @Autowired + private AttestationManager attestationManager; + + @Autowired + private DeveloperAttestationPeriodCalculator developerAttestationPeriodCalculator; + + @Autowired + private CertificationBodyManager certificationBodyManager; + + @Autowired + private AttestationReportDAO attestationReportDAO; + + @Autowired + private ChangeRequestAttestationDAO changeRequestAttestationDAO; + + @Autowired + private ChangeRequestManager changeRequestManager; + + @Autowired + private AttestationCertificationBodyService attestationCertificationBodyService; + + @Autowired + private SchedulerSecurityContextService securityContextService; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + + LOGGER.info("********* Starting Attestation Report Creator job. *********"); + securityContextService.setAdminSecurityContext(); + + // We need to manually create a transaction in this case because of how + // AOP works. When a method is annotated with @Transactional, the transaction + // wrapper is only added if the object's proxy is called. The object's proxy is + // not called when the method is called from within this class. The object's + // proxy is called when the method is public and is called from a different + // object. + // https://stackoverflow.com/questions/3037006/starting-new-transaction-in-spring-bean + TransactionTemplate txTemplate = new TransactionTemplate(txManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + + try { + if (inSubmissionPlusApprovalPeriod()) { + if (!CollectionUtils.isEmpty(attestationReportDAO.getAttestationReportByDate(LocalDate.now()))) { + attestationReportDAO.deleteAttestationReportByDate(LocalDate.now()); + } + + AttestationPeriod mostRecentPastAttestationPeriod = attestationPeriodService.getMostRecentPastAttestationPeriod(); + Map attestationReportsByAcbId = new HashMap(); + List activeAcbs = certificationBodyManager.getAllActive(); + List applicableDevelopers = getDevelopersActiveListingsDuringMostRecentPastAttestationPeriod(); + + applicableDevelopers.forEach(developer -> { + LOGGER.info("Processing Developer: {} ({})", developer.getName(), developer.getId()); + try { + + ChangeRequest cr = getMostRecentChangeRequest(developer, mostRecentPastAttestationPeriod); + + activeAcbs.forEach(acb -> { + if (isDeveloperManagedByAcb(developer, acb, mostRecentPastAttestationPeriod)) { + if (!attestationReportsByAcbId.containsKey(acb.getId())) { + attestationReportsByAcbId.put(acb.getId(), + AttestationReport.builder() + .attestationPeriod(mostRecentPastAttestationPeriod) + .certificationBody(acb) + .reportDate(LocalDate.now()) + .build()); + } + AttestationReport report = attestationReportsByAcbId.get(acb.getId()); + report.setDeveloperCount(report.getDeveloperCount() + 1); + + updateCountsBasedOnChangeRequestStatus(cr, report); + } + }); + } catch (Exception e) { + LOGGER.error("Could not collect Developer Attestation Report data for Developer: {} ", developer.getName(), e); + } + }); + + attestationReportsByAcbId.entrySet().forEach(entry -> attestationReportDAO.insert(entry.getValue())); + + } else { + LOGGER.info("Not within submission plus approval window"); + } + } catch (Exception e) { + LOGGER.error(e); + } + } + + private void updateCountsBasedOnChangeRequestStatus(ChangeRequest cr, AttestationReport report) { + if (cr == null) { + report.setNoSubmissionCount(report.getNoSubmissionCount() + 1); + } else { + switch (cr.getCurrentStatus().getChangeRequestStatusType().getName()) { + case "Accepted": + report.setApprovedCount(report.getApprovedCount() + 1); + break; + case "Pending Developer Action": + report.setPendingDeveloperActionCount(report.getPendingDeveloperActionCount() + 1); + break; + case "Pending ONC-ACB Action": + report.setPendingAcbActionCount(report.getPendingAcbActionCount() + 1); + break; + default: + break; + } + } + } + }); + LOGGER.info("********* Completed Attestation Report Creator job. *********"); + } + + private boolean inSubmissionPlusApprovalPeriod() { + AttestationPeriod attestationPeriod = attestationPeriodService.getMostRecentPastAttestationPeriod(); + return DateUtil.isDateBetweenInclusive( + Pair.of(attestationPeriod.getSubmissionStart(), attestationPeriod.getSubmissionEnd().plusDays(getDaysInApprovalPeriod())), + LocalDate.now()); + } + + private List getDevelopersActiveListingsDuringMostRecentPastAttestationPeriod() { + AttestationPeriod mostRecentPastPeriod = attestationManager.getMostRecentPastAttestationPeriod(); + return developerAttestationPeriodCalculator.getDevelopersWithActiveListingsDuringAttestationPeriod(mostRecentPastPeriod, LOGGER); + } + + private ChangeRequest getMostRecentChangeRequest(Developer developer, AttestationPeriod period) { + Long crId = changeRequestAttestationDAO.getIdOfMostRecentAttestationChangeRequest(developer.getId(), period.getId()); + if (crId == null) { + LOGGER.warn("No change request was found for developer " + developer.getId() + " and attestation period " + period.getId()); + return null; + } + ChangeRequest changeRequest = null; + try { + changeRequest = changeRequestManager.getChangeRequest(crId); + } catch (Exception ex) { + LOGGER.error("Error getting change request with ID " + crId, ex); + } + return changeRequest; + } + + private Boolean isDeveloperManagedByAcb(Developer developer, CertificationBody certificationBody, AttestationPeriod attestationPeriod) { + return attestationCertificationBodyService.getAssociatedCertificationBodies(developer.getId(), attestationPeriod.getId()).stream() + .filter(acb -> acb.getId().equals(certificationBody.getId())) + .findAny() + .isPresent(); + } + + private Integer getDaysInApprovalPeriod() { + return Integer.valueOf(env.getProperty("attestationApprovalWindowInDays")); + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportDAO.java new file mode 100644 index 0000000000..42ae0f8fe6 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportDAO.java @@ -0,0 +1,66 @@ +package gov.healthit.chpl.scheduler.job.report.attestation; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.stereotype.Component; + +import gov.healthit.chpl.attestation.domain.AttestationPeriod; +import gov.healthit.chpl.attestation.entity.AttestationPeriodEntity; +import gov.healthit.chpl.dao.impl.BaseDAOImpl; +import gov.healthit.chpl.entity.CertificationBodyEntity; +import jakarta.persistence.Query; + +@Component +public class AttestationReportDAO extends BaseDAOImpl { + + public void insert(AttestationReport attestationReport) { + create(AttestationReportEntity.builder() + .approvedCount(attestationReport.getApprovedCount()) + .reportDate(attestationReport.getReportDate()) + .attestationPeriod(AttestationPeriodEntity.builder() + .id(attestationReport.getAttestationPeriod().getId()) + .build()) + .certificationBody(CertificationBodyEntity.builder() + .id(attestationReport.getCertificationBody().getId()) + .build()) + .developerCount(attestationReport.getDeveloperCount()) + .noSubmissionCount(attestationReport.getNoSubmissionCount()) + .pendingAcbActionCount(attestationReport.getPendingAcbActionCount()) + .pendingDeveloperActionCount(attestationReport.getPendingDeveloperActionCount()) + .build()); + } + + public List getAttestationReportByDate(LocalDate date) { + return getEntitiesByDate(date).stream() + .map(entity -> entity.toDomain()) + .toList(); + } + + public List getAttestationReportByAttestationPeriod(AttestationPeriod period) { + return getEntitiesByAttestationPeriod(period).stream() + .map(entity -> entity.toDomain()) + .toList(); + } + + public void deleteAttestationReportByDate(LocalDate date) { + getEntitiesByDate(date).forEach(entity -> { + entity.setDeleted(true); + update(entity); + }); + } + + private List getEntitiesByDate(LocalDate date) { + Query query = entityManager.createQuery( + "from AttestationReportEntity where (NOT deleted = true) and reportDate = :date", AttestationReportEntity.class); + query.setParameter("date", date); + return query.getResultList(); + } + + private List getEntitiesByAttestationPeriod(AttestationPeriod period) { + Query query = entityManager.createQuery( + "from AttestationReportEntity where (NOT deleted = true) and attestationPeriod.id = :attestationPeriodId", AttestationReportEntity.class); + query.setParameter("attestationPeriodId", period.getId()); + return query.getResultList(); + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportEntity.java new file mode 100644 index 0000000000..cd38ec3748 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/report/attestation/AttestationReportEntity.java @@ -0,0 +1,80 @@ +package gov.healthit.chpl.scheduler.job.report.attestation; + +import java.time.LocalDate; + +import gov.healthit.chpl.attestation.entity.AttestationPeriodEntity; +import gov.healthit.chpl.entity.CertificationBodyEntity; +import gov.healthit.chpl.entity.EntityAudit; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@ToString +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "attestation_report") +public class AttestationReportEntity extends EntityAudit { + private static final long serialVersionUID = -3139285302653689705L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "report_date") + private LocalDate reportDate; + + @OneToOne(optional = true, fetch = FetchType.LAZY) + @JoinColumn(name = "attestation_period_id") + private AttestationPeriodEntity attestationPeriod; + + @OneToOne(optional = true, fetch = FetchType.LAZY) + @JoinColumn(name = "certification_body_id") + private CertificationBodyEntity certificationBody; + + @Column(name = "developer_count") + private Long developerCount; + + @Column(name = "approved_count") + private Long approvedCount; + + @Column(name = "pending_acb_action_count") + private Long pendingAcbActionCount; + + @Column(name = "pending_developer_action_count") + private Long pendingDeveloperActionCount; + + @Column(name = "no_submission_count") + private Long noSubmissionCount; + + public AttestationReport toDomain() { + return AttestationReport.builder() + .id(id) + .reportDate(reportDate) + .attestationPeriod(attestationPeriod.toDomain()) + .certificationBody(certificationBody.toDomain()) + .developerCount(developerCount) + .approvedCount(approvedCount) + .pendingAcbActionCount(pendingAcbActionCount) + .pendingDeveloperActionCount(pendingDeveloperActionCount) + .noSubmissionCount(noSubmissionCount) + .build(); + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/DatadogSyntheticsTestService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/DatadogSyntheticsTestService.java index 2ac7b1cc0a..ca5535ea31 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/DatadogSyntheticsTestService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/DatadogSyntheticsTestService.java @@ -60,6 +60,10 @@ public DatadogSyntheticsTestService(DatadogSyntheticsTestApiProvider apiProvider this.datadogTestLocation = datadogTestLocation; } + protected DatadogSyntheticsTestApiProvider getApiProvider() { + return apiProvider; + } + public List getAllSyntheticsTests() { try { return apiProvider.getApiInstance().listTests().getTests(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/FixDatadogUrlUptimeAssertionsJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/FixDatadogUrlUptimeAssertionsJob.java new file mode 100644 index 0000000000..abaebd3eb5 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/urluptime/FixDatadogUrlUptimeAssertionsJob.java @@ -0,0 +1,147 @@ +package gov.healthit.chpl.scheduler.job.urluptime; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import com.datadog.api.client.ApiException; +import com.datadog.api.client.v1.model.SyntheticsAPITest; +import com.datadog.api.client.v1.model.SyntheticsAPITestConfig; +import com.datadog.api.client.v1.model.SyntheticsAPITestType; +import com.datadog.api.client.v1.model.SyntheticsAssertion; +import com.datadog.api.client.v1.model.SyntheticsAssertionOperator; +import com.datadog.api.client.v1.model.SyntheticsAssertionTarget; +import com.datadog.api.client.v1.model.SyntheticsAssertionType; +import com.datadog.api.client.v1.model.SyntheticsTestOptions; +import com.datadog.api.client.v1.model.SyntheticsTestOptionsHTTPVersion; +import com.datadog.api.client.v1.model.SyntheticsTestOptionsScheduling; +import com.datadog.api.client.v1.model.SyntheticsTestOptionsSchedulingTimeframe; +import com.datadog.api.client.v1.model.SyntheticsTestRequest; + +import gov.healthit.chpl.scheduler.job.QuartzJob; +import gov.healthit.chpl.util.DateUtil; +import lombok.extern.log4j.Log4j2; + +@Log4j2(topic = "fixDatadogUrlUptimeAssertionsJobLogger") +public class FixDatadogUrlUptimeAssertionsJob extends QuartzJob { + + private static final Integer HTTP_STATUS_OK = 200; + private static final String HTTP_METHOD_GET = "GET"; + private static final Long SECONDS_IN_A_MINUTE = 60L; + + + @Autowired + private DatadogSyntheticsTestService datadogSyntheticsTestService; + + @Value("${datadog.syntheticsTest.startTime}") + private String datadogTestStartTime; + + @Value("${datadog.syntheticsTest.endTime}") + private String datadogTestEndTime; + + @Value("${datadog.syntheticsTest.checkEveryMinutes}") + private Long datadogCheckEveryMinutes; + + @Value("${datadog.syntheticsTest.timeout}") + private Integer datadogTestTimeout; + + @Value("${datadog.syntheticsTest.location}") + private String datadogTestLocation; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + LOGGER.info("********* Starting the Fix Datadog Url Uptime Assertions Job *********"); + + datadogSyntheticsTestService.getAllSyntheticsTests().forEach(test -> { + Optional contentHeaderAssertion = getContentHeaderAssertionExist(test.getConfig().getAssertions()); + String url = test.getConfig().getRequest().getUrl(); + + if (contentHeaderAssertion.isPresent()) { + + SyntheticsAPITest body = new SyntheticsAPITest() + .config(new SyntheticsAPITestConfig() + .assertions(Arrays.asList( + new SyntheticsAssertion(new SyntheticsAssertionTarget() + .operator(SyntheticsAssertionOperator.LESS_THAN) + .target(datadogTestTimeout) + .type(SyntheticsAssertionType.RESPONSE_TIME)), + new SyntheticsAssertion(new SyntheticsAssertionTarget() + .operator(SyntheticsAssertionOperator.IS) + .target(HTTP_STATUS_OK) + .type(SyntheticsAssertionType.STATUS_CODE)), + new SyntheticsAssertion(new SyntheticsAssertionTarget() + .operator(SyntheticsAssertionOperator.MATCHES) + .target("/[\\S\s]+[\\S]+/") + .type(SyntheticsAssertionType.BODY)))) + .request(new SyntheticsTestRequest() + .url(url) + .method(HTTP_METHOD_GET))) + .options(new SyntheticsTestOptions() + .httpVersion(SyntheticsTestOptionsHTTPVersion.ANY) + .minFailureDuration(0L) + .minLocationFailed(1L) + .scheduling(new SyntheticsTestOptionsScheduling() + .timezone(DateUtil.ET_ZONE_ID) + .addTimeframesItem(new SyntheticsTestOptionsSchedulingTimeframe() + .day(DatadogDayOfWeek.MONDAY) + .from(datadogTestStartTime) + .to(datadogTestEndTime)) + .addTimeframesItem(new SyntheticsTestOptionsSchedulingTimeframe() + .day(DatadogDayOfWeek.TUESDAY) + .from(datadogTestStartTime) + .to(datadogTestEndTime)) + .addTimeframesItem(new SyntheticsTestOptionsSchedulingTimeframe() + .day(DatadogDayOfWeek.WEDNESDAY) + .from(datadogTestStartTime) + .to(datadogTestEndTime)) + .addTimeframesItem(new SyntheticsTestOptionsSchedulingTimeframe() + .day(DatadogDayOfWeek.THURSDAY) + .from(datadogTestStartTime) + .to(datadogTestEndTime)) + .addTimeframesItem(new SyntheticsTestOptionsSchedulingTimeframe() + .day(DatadogDayOfWeek.FRIDAY) + .from(datadogTestStartTime) + .to(datadogTestEndTime))) + .tickEvery(convertMinutesToSeconds(datadogCheckEveryMinutes))) + .locations(Collections.singletonList(datadogTestLocation)) + .message("Failed: " + url) + .type(SyntheticsAPITestType.API) + .name(url) + .tags(test.getTags()); + + try { + datadogSyntheticsTestService.getApiProvider().getApiInstance().updateAPITest(test.getPublicId(), body); + LOGGER.info("Test updated: {}", url); + } catch (ApiException e) { + LOGGER.error("Could not update test for URL: {}", url, e); + } + } else { + LOGGER.info("Test NOT updated: {}", url); + } + }); + + LOGGER.info("********* Completed the Fix Datadog Url Uptime Assertions Job *********"); + } + + + private Optional getContentHeaderAssertionExist(List assertions) { + return assertions.stream() + .filter(assertion -> assertion.getSyntheticsAssertionTarget().getType().equals(SyntheticsAssertionType.HEADER) + && assertion.getSyntheticsAssertionTarget().getProperty().equals("content-length")) + .findAny(); + + } + + private Long convertMinutesToSeconds(Long minutes) { + return minutes * SECONDS_IN_A_MINUTE; + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/ListingUploadManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/ListingUploadManager.java index fb19928537..4327c69087 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/ListingUploadManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/upload/listing/ListingUploadManager.java @@ -113,7 +113,7 @@ public ListingUploadManager(ListingDetailsUploadHandler listingDetailsHandler, @Transactional @PreAuthorize("@permissions.hasAccess(T(gov.healthit.chpl.permissions.Permissions).LISTING_UPLOAD, " - + "T(gov.healthit.chpl.permissions.domains.ListingUploadDomainPerissions).CREATE)") + + "T(gov.healthit.chpl.permissions.domains.ListingUploadDomainPerissions).PARSE)") public List parseUploadFile(MultipartFile file) throws ValidationException { List allCsvRecords = getFileAsCsvRecords(file); int headingRowIndex = uploadUtil.getHeadingRecordIndex(allCsvRecords); @@ -218,7 +218,7 @@ public CertifiedProductSearchDetails getListingUploadAsListingForUpdate(ListingU LOGGER.debug("Converting listing upload into CertifiedProductSearchDetails object"); CertifiedProductSearchDetails listing = listingDetailsHandler.parseAsListing(headingRecord, allListingRecords); LOGGER.debug("Converted listing upload into CertifiedProductSearchDetails object"); - listingNormalizer.normalize(listing, List.of(baselineStandardAsOfTodayNormalizer)); + listingNormalizer.normalize(listing, List.of(baselineStandardAsOfCertificationDayNormalizer, baselineStandardAsOfTodayNormalizer)); LOGGER.debug("Normalized listing upload"); return listing; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoApiWrapper.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoApiWrapper.java index 39ab515f0e..4c4f5c8efd 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoApiWrapper.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoApiWrapper.java @@ -168,18 +168,22 @@ public AuthenticationResultType respondToNewPasswordRequiredChallenge(CognitoNew } } - @Cacheable(CacheNames.COGNITO_USERS_BY_UUID) + @Cacheable(value = CacheNames.COGNITO_USERS_BY_UUID, unless = "#result == null") public User getUserInfo(UUID cognitoId) throws UserRetrievalException { AdminGetUserRequest request = AdminGetUserRequest.builder() .userPoolId(userPoolId) .username(cognitoId.toString()) .build(); - AdminGetUserResponse response = cognitoClient.adminGetUser(request); - if (response == null || response.sdkHttpResponse() == null || !response.sdkHttpResponse().isSuccessful()) { + try { + AdminGetUserResponse response = cognitoClient.adminGetUser(request); + if (response == null || response.sdkHttpResponse() == null || !response.sdkHttpResponse().isSuccessful()) { + return null; + } + return createUserFromGetUserResponse(response); + } catch (Exception e) { return null; } - return createUserFromGetUserResponse(response); } @Cacheable(value = CacheNames.COGNITO_USERS_BY_EMAIL, unless = "#result == null") @@ -244,7 +248,7 @@ public CognitoCredentials createUser(CreateUserRequest userRequest) throws UserC public AuthenticationResultType refreshToken(String refreshToken, UUID cognitoId) { Map authParams = new LinkedHashMap(); authParams.put("REFRESH_TOKEN", refreshToken); - authParams.put("SECRET_HASH", calculateSecretHash(cognitoId.toString())); + authParams.put("SECRET_HASH", calculateSecretHash(cognitoId.toString())); AdminInitiateAuthRequest authRequest = AdminInitiateAuthRequest.builder() .authFlow(AuthFlowType.REFRESH_TOKEN_AUTH) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoUserManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoUserManager.java index 4f511aac10..2cfa71d0a7 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoUserManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/CognitoUserManager.java @@ -133,7 +133,7 @@ public UUID createUser(CreateUserFromInvitationRequest userInfo) } else { cognitoApiWrapper.addUserToGroup(userInfo.getUser().getEmail(), groupNameForEnvironment); } - cognitoInvitationManager.deleteToken(UUID.fromString(userInfo.getHash())); + cognitoInvitationManager.deleteInvitation(invitation); cognitoConfirmEmailEmailer.sendConfirmationEmail(credentials); User createdUser = cognitoApiWrapper.getUserNoCache(credentials.getCognitoId()); @@ -199,7 +199,7 @@ public User addOrganizationToUser(UUID invitationToken, String accessToken) thro } cognitoApiWrapper.addOrgToUser(originalUser, invitation.getOrganizationId()); - cognitoInvitationManager.deleteToken(invitationToken); + cognitoInvitationManager.deleteInvitation(invitation); User updatedUser = cognitoApiWrapper.getUserNoCache(originalUser.getCognitoId()); activityManager.addUserActivity(updatedUser.getCognitoId(), diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/invitation/CognitoInvitationManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/invitation/CognitoInvitationManager.java index 29d0fce0fd..d4b55c3b58 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/invitation/CognitoInvitationManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/user/cognito/invitation/CognitoInvitationManager.java @@ -9,10 +9,13 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import gov.healthit.chpl.domain.activity.ActivityConcept; +import gov.healthit.chpl.exception.ActivityException; import gov.healthit.chpl.exception.UserCreationException; import gov.healthit.chpl.exception.UserPermissionRetrievalException; import gov.healthit.chpl.exception.UserRetrievalException; import gov.healthit.chpl.exception.ValidationException; +import gov.healthit.chpl.manager.ActivityManager; import gov.healthit.chpl.service.InvitationEmailer; import lombok.extern.log4j.Log4j2; @@ -23,14 +26,17 @@ public class CognitoInvitationManager { private CognitoUserInvitationDAO userInvitationDAO; private InvitationEmailer invitationEmailer; private CognitoInvitationValidator cognitoInvitationValidator; + private ActivityManager activityManager; @Autowired public CognitoInvitationManager(CognitoUserInvitationDAO userInvitationDAO, InvitationEmailer invitationEmailer, - CognitoInvitationValidator cognitoInvitationValidator) { + CognitoInvitationValidator cognitoInvitationValidator, + ActivityManager activityManager) { this.userInvitationDAO = userInvitationDAO; this.invitationEmailer = invitationEmailer; this.cognitoInvitationValidator = cognitoInvitationValidator; + this.activityManager = activityManager; } @Transactional @@ -111,8 +117,15 @@ public CognitoUserInvitation inviteCmsUser(CognitoUserInvitation invitation) } @Transactional - public void deleteToken(UUID invitationToken) { - userInvitationDAO.deleteByToken(invitationToken); + public void deleteInvitation(CognitoUserInvitation invitation) { + userInvitationDAO.deleteByToken(invitation.getInvitationToken()); + + try { + activityManager.addActivity(ActivityConcept.INVITATION, invitation.getId(), + "Deleted invitation for " + invitation.getEmail(), invitation, null); + } catch (ActivityException ex) { + LOGGER.error("Could not write activity about creating an invitation for " + invitation.getEmail()); + } } public CognitoUserInvitation getByToken(UUID invitationToken) { @@ -124,6 +137,13 @@ private CognitoUserInvitation createUserInvitation(CognitoUserInvitation origInv CognitoUserInvitation invitation = userInvitationDAO.create(origInvitation); + try { + activityManager.addActivity(ActivityConcept.INVITATION, invitation.getId(), + "Created invitation for " + invitation.getEmail(), null, invitation); + } catch (ActivityException ex) { + LOGGER.error("Could not write activity about creating an invitation for " + invitation.getEmail()); + } + invitationEmailer.emailInvitedUser(invitation); return invitation; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/ChplProductNumberUtil.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/ChplProductNumberUtil.java index b1498950ca..7659a245c3 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/ChplProductNumberUtil.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/util/ChplProductNumberUtil.java @@ -205,6 +205,15 @@ public boolean hasIcsConflict(String uniqueId, Boolean hasIcs) { return hasIcsConflict; } + public String deriveTestingLabCodeFromListing(CertifiedProductSearchDetails listing) { + if (listing.getTestingLabs() != null && listing.getTestingLabs().size() > 1) { + return "99"; + } else if (listing.getTestingLabs() != null) { + return listing.getTestingLabs().get(0).getTestingLab().getAtlCode(); + } + return ""; + } + public String deriveIcsCodeFromListing(CertifiedProductSearchDetails listing) { if (listing.getIcs() == null || !listing.getIcs().getInherits() || CollectionUtils.isEmpty(listing.getIcs().getParents())) {