From fa653db1c14dc2a461683566d3741ac7b447e633 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 6 Mar 2025 12:21:16 -0500 Subject: [PATCH] fix!: Put back /criterion_product endpoint accidentally removed [#OCD-4843] --- .../web/controller/ReportDataController.java | 21 ++- .../web/controller/StatisticsController.java | 28 ++++ .../CriterionProductStatisticsResult.java | 24 ++++ ...log4j2-xinclude-file-appenders-console.xml | 7 + .../log4j2-xinclude-file-appenders.xml | 12 ++ .../log4j2-xinclude-loggers-local.xml | 3 + .../resources/log4j2-xinclude-loggers.xml | 4 + .../src/main/resources/jobs.xml | 14 +- .../dao/CriterionProductStatisticsDAO.java | 78 +++++++++++ .../domain/CriterionProductStatistics.java | 23 ++++ .../CriterionProductStatisticsEntity.java | 77 +++++++++++ .../chpl/manager/StatisticsManager.java | 38 ++++++ .../job/chartdata/ChartDataCreatorJob.java | 54 ++++++++ .../chartdata/CriterionProductDataFilter.java | 30 +++++ .../CriterionProductStatisticsCalculator.java | 125 ++++++++++++++++++ 15 files changed, 533 insertions(+), 5 deletions(-) create mode 100644 chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/results/CriterionProductStatisticsResult.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CriterionProductStatisticsDAO.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CriterionProductStatistics.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/entity/statistics/CriterionProductStatisticsEntity.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/manager/StatisticsManager.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/ChartDataCreatorJob.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductDataFilter.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductStatisticsCalculator.java 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 87ea7b0855..a63c4e7140 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 @@ -12,6 +12,7 @@ import gov.healthit.chpl.developer.search.DeveloperSearchRequest; import gov.healthit.chpl.developer.search.DeveloperSearchResult; import gov.healthit.chpl.developer.search.DeveloperSearchService; +import gov.healthit.chpl.manager.StatisticsManager; import gov.healthit.chpl.report.ReportDataManager; import gov.healthit.chpl.report.ReportMetadata; import gov.healthit.chpl.report.criteriamigrationreport.CriteriaMigrationReportDenormalized; @@ -28,6 +29,7 @@ import gov.healthit.chpl.scheduler.job.summarystatistics.data.CertificationBodyStatistic; import gov.healthit.chpl.search.domain.ListingSearchResult; import gov.healthit.chpl.util.SwaggerSecurityRequirement; +import gov.healthit.chpl.web.controller.results.CriterionProductStatisticsResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -41,11 +43,15 @@ @RequestMapping("/report-data") public class ReportDataController { private ReportDataManager reportDataManager; + private StatisticsManager statisticsManager; private DeveloperSearchService developerSearchService; @Autowired - public ReportDataController(ReportDataManager reportDataManager, DeveloperSearchService developerSearchService) { + public ReportDataController(ReportDataManager reportDataManager, + StatisticsManager statisticsManager, + DeveloperSearchService developerSearchService) { this.reportDataManager = reportDataManager; + this.statisticsManager = statisticsManager; this.developerSearchService = developerSearchService; } @@ -431,4 +437,17 @@ public ReportDataController(ReportDataManager reportDataManager, DeveloperSearch public @ResponseBody List getAttestationReports() { return reportDataManager.getAttestationReports(); } + + @Operation(summary = "Get count of Criteria certified to by unique Product.", + description = "Retrieves and returns the Criterion/Product counts.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @RequestMapping(value = "/criterion-product", method = RequestMethod.GET, + produces = "application/json; charset=utf-8") + public @ResponseBody CriterionProductStatisticsResult getCriterionProductStatistics() { + CriterionProductStatisticsResult response = new CriterionProductStatisticsResult(); + response.setCriterionProductStatisticsResult(statisticsManager.getCriterionProductStatisticsResult()); + return response; + } } diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/StatisticsController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/StatisticsController.java index ce0c99997d..d76593121f 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/StatisticsController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/StatisticsController.java @@ -4,13 +4,17 @@ import java.util.Date; import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.manager.StatisticsManager; import gov.healthit.chpl.util.SwaggerSecurityRequirement; import gov.healthit.chpl.web.controller.annotation.DeprecatedApi; +import gov.healthit.chpl.web.controller.results.CriterionProductStatisticsResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -21,6 +25,30 @@ @RestController @RequestMapping("/statistics") public class StatisticsController { + private StatisticsManager statisticsManager; + + @Autowired + public StatisticsController(StatisticsManager statisticsManager) { + this.statisticsManager = statisticsManager; + } + + @Deprecated + @DeprecatedApi(friendlyUrl = "/statistics/criterion_product", + message = "This endpoint is deprecated and will be removed in a future release. Please use /report-data/criterion-product as a replacement.", + removalDate = "2025-09-01") + @Operation(summary = "Get count of Criteria certified to by unique Product.", + description = "Retrieves and returns the Criterion/Product counts.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @RequestMapping(value = "/criterion_product", method = RequestMethod.GET, + produces = "application/json; charset=utf-8") + public @ResponseBody CriterionProductStatisticsResult getCriterionProductStatistics() { + CriterionProductStatisticsResult response = new CriterionProductStatisticsResult(); + response.setCriterionProductStatisticsResult(statisticsManager.getCriterionProductStatisticsResult()); + return response; + } + @Deprecated @DeprecatedApi(friendlyUrl = "/statistics/nonconformity_criteria_count", diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/results/CriterionProductStatisticsResult.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/results/CriterionProductStatisticsResult.java new file mode 100644 index 0000000000..e8e7ad60bc --- /dev/null +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/results/CriterionProductStatisticsResult.java @@ -0,0 +1,24 @@ +package gov.healthit.chpl.web.controller.results; + +import java.util.ArrayList; +import java.util.List; + +import gov.healthit.chpl.domain.CriterionProductStatistics; + +public class CriterionProductStatisticsResult { + private List criterionProductStatisticsResult; + + public CriterionProductStatisticsResult() { + this.criterionProductStatisticsResult = new ArrayList(); + } + + public List getCriterionProductStatisticsResult() { + return criterionProductStatisticsResult; + } + + public void setCriterionProductStatisticsResult( + final List criterionProductStatisticsResult) { + this.criterionProductStatisticsResult = criterionProductStatisticsResult; + } + +} \ No newline at end of file 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 37c158fead..2de3d170a1 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 @@ -1,5 +1,12 @@ + + + + + + + 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 727434af89..a0ada8d90b 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 @@ -1,5 +1,17 @@ + + + %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{dd.trace_id} %X{dd.span_id} - %m%n + + + + + + + + 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 652ee99405..b18593f507 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml @@ -32,6 +32,10 @@ + + + + diff --git a/chpl/chpl-resources/src/main/resources/jobs.xml b/chpl/chpl-resources/src/main/resources/jobs.xml index 40413b8550..c016042180 100644 --- a/chpl/chpl-resources/src/main/resources/jobs.xml +++ b/chpl/chpl-resources/src/main/resources/jobs.xml @@ -7,10 +7,6 @@ triggerJob interruptJob - - chartDataCreator - systemJobs - @@ -237,6 +233,16 @@ + + + chartDataCreator + systemJobs + Generates the chart data + gov.healthit.chpl.scheduler.job.chartdata.ChartDataCreatorJob + true + false + + standardsUpdateJob systemJobs diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CriterionProductStatisticsDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CriterionProductStatisticsDAO.java new file mode 100644 index 0000000000..7fc4890170 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/dao/CriterionProductStatisticsDAO.java @@ -0,0 +1,78 @@ +package gov.healthit.chpl.dao; + +import java.util.List; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import gov.healthit.chpl.dao.impl.BaseDAOImpl; +import gov.healthit.chpl.domain.CriterionProductStatistics; +import gov.healthit.chpl.entity.statistics.CriterionProductStatisticsEntity; +import gov.healthit.chpl.exception.EntityCreationException; +import gov.healthit.chpl.exception.EntityRetrievalException; +import jakarta.persistence.Query; + + +@Repository("criterionProductStatisticsDAO") +public class CriterionProductStatisticsDAO extends BaseDAOImpl { + public List findAll() { + return this.findAllEntities().stream() + .map(entity -> entity.toDomain()) + .toList(); + } + + @Transactional + public void delete(Long id) throws EntityRetrievalException { + CriterionProductStatisticsEntity toDelete = getEntityById(id); + + if (toDelete != null) { + toDelete.setDeleted(true); + entityManager.merge(toDelete); + } + } + + @Transactional + public CriterionProductStatisticsEntity create(CriterionProductStatistics criteirCriterionProductStatistics) + throws EntityCreationException, EntityRetrievalException { + + CriterionProductStatisticsEntity entity = new CriterionProductStatisticsEntity(); + entity.setProductCount(criteirCriterionProductStatistics.getProductCount()); + entity.setCertificationCriterionId(criteirCriterionProductStatistics.getCertificationCriterionId()); + + entityManager.persist(entity); + entityManager.flush(); + return entity; + } + + private List findAllEntities() { + Query query = entityManager.createQuery("from CriterionProductStatisticsEntity cpse " + + "LEFT OUTER JOIN FETCH cpse.certificationCriterion cce " + + "LEFT OUTER JOIN FETCH cce.certificationEdition " + + "LEFT JOIN FETCH cce.rule " + + "WHERE (cpse.deleted = false)", + CriterionProductStatisticsEntity.class); + return query.getResultList(); + } + + private CriterionProductStatisticsEntity getEntityById(Long id) throws EntityRetrievalException { + CriterionProductStatisticsEntity entity = null; + + Query query = entityManager.createQuery("from CriterionProductStatisticsEntity cpse " + + "LEFT OUTER JOIN FETCH cpse.certificationCriterion cce " + + "LEFT OUTER JOIN FETCH cce.certificationEdition " + + "LEFT JOIN FETCH cce.rule " + + "WHERE (cpse.deleted = false) " + + "AND (cpse.id = :entityid)", + CriterionProductStatisticsEntity.class); + query.setParameter("entityid", id); + List result = query.getResultList(); + + if (result.size() == 1) { + entity = result.get(0); + } else { + throw new EntityRetrievalException("Data error. Did not find only one entity."); + } + + return entity; + } +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CriterionProductStatistics.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CriterionProductStatistics.java new file mode 100644 index 0000000000..f22c8ddb7d --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CriterionProductStatistics.java @@ -0,0 +1,23 @@ +package gov.healthit.chpl.domain; + +import java.io.Serializable; + +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +@Builder +public class CriterionProductStatistics implements Serializable { + private static final long serialVersionUID = -1648513956784683632L; + + private Long id; + private Long productCount; + private Long certificationCriterionId; + private CertificationCriterion criterion; + private Integer sortOrder; +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/entity/statistics/CriterionProductStatisticsEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/entity/statistics/CriterionProductStatisticsEntity.java new file mode 100644 index 0000000000..3d9d19d68b --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/entity/statistics/CriterionProductStatisticsEntity.java @@ -0,0 +1,77 @@ +package gov.healthit.chpl.entity.statistics; + +import gov.healthit.chpl.certificationCriteria.CertificationCriterionEntity; +import gov.healthit.chpl.domain.CriterionProductStatistics; +import gov.healthit.chpl.entity.EntityAudit; +import gov.healthit.chpl.entity.lastmodifieduserstrategy.CurrentUserThenSystemUserStrategy; +import gov.healthit.chpl.entity.lastmodifieduserstrategy.LastModifiedUserStrategy; +import jakarta.persistence.Basic; +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 jakarta.persistence.Transient; +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 = "criterion_product_statistics") +public class CriterionProductStatisticsEntity extends EntityAudit { + private static final long serialVersionUID = -4258273713908999510L; + + @Override + public LastModifiedUserStrategy getLastModifiedUserStrategy() { + return new CurrentUserThenSystemUserStrategy(); + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Basic(optional = false) + @Column(name = "id", nullable = false) + private Long id; + + @Basic(optional = false) + @Column(name = "product_count", nullable = false) + private Long productCount; + + @Basic(optional = false) + @Column(name = "certification_criterion_id", nullable = false) + private Long certificationCriterionId; + + @OneToOne(optional = true, fetch = FetchType.LAZY) + @JoinColumn(name = "certification_criterion_id", insertable = false, updatable = false) + private CertificationCriterionEntity certificationCriterion; + + public CriterionProductStatisticsEntity(final Long id) { + this.id = id; + } + + @Transient + public Class getClassType() { + return CriterionProductStatisticsEntity.class; + } + + public CriterionProductStatistics toDomain() { + return CriterionProductStatistics.builder() + .id(this.id) + .productCount(this.productCount) + .certificationCriterionId(this.certificationCriterionId) + .criterion(this.certificationCriterion.toDomain()) + .build(); + } +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/manager/StatisticsManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/manager/StatisticsManager.java new file mode 100644 index 0000000000..00ebb6020e --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/manager/StatisticsManager.java @@ -0,0 +1,38 @@ +package gov.healthit.chpl.manager; + +import java.util.Comparator; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.support.ApplicationObjectSupport; +import org.springframework.stereotype.Service; + +import gov.healthit.chpl.certificationCriteria.CertificationCriterionComparator; +import gov.healthit.chpl.dao.CriterionProductStatisticsDAO; +import gov.healthit.chpl.domain.CriterionProductStatistics; + +@Service +public class StatisticsManager extends ApplicationObjectSupport { + + private CriterionProductStatisticsDAO criterionProductStatisticsDAO; + private CertificationCriterionComparator certificationCriterionComparator; + + @Autowired + public StatisticsManager(CriterionProductStatisticsDAO criterionProductStatisticsDAO, + CertificationCriterionComparator certificationCriterionComparator) { + + this.criterionProductStatisticsDAO = criterionProductStatisticsDAO; + this.certificationCriterionComparator = certificationCriterionComparator; + } + + public List getCriterionProductStatisticsResult() { + List criterionProductStatistics = criterionProductStatisticsDAO.findAll().stream() + .sorted(Comparator.comparing(CriterionProductStatistics::getCriterion, certificationCriterionComparator)) + .toList(); + + for (int i = 0; i < criterionProductStatistics.size(); i++) { + criterionProductStatistics.get(i).setSortOrder(i); + } + return criterionProductStatistics; + } +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/ChartDataCreatorJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/ChartDataCreatorJob.java new file mode 100644 index 0000000000..f799b01e68 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/ChartDataCreatorJob.java @@ -0,0 +1,54 @@ +package gov.healthit.chpl.scheduler.job.chartdata; + +import java.util.List; +import java.util.Map; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import gov.healthit.chpl.exception.EntityRetrievalException; +import gov.healthit.chpl.scheduler.job.QuartzJob; +import gov.healthit.chpl.search.ListingSearchManager; +import gov.healthit.chpl.search.domain.ListingSearchResult; +import lombok.extern.log4j.Log4j2; + +@Log4j2(topic = "chartDataCreatorJobLogger") +@DisallowConcurrentExecution +public final class ChartDataCreatorJob extends QuartzJob { + + @Autowired + private ListingSearchManager listingSearchManager; + + public ChartDataCreatorJob() throws Exception { + super(); + } + + @Override + public void execute(JobExecutionContext arg0) throws JobExecutionException { + LOGGER.info("*****Chart Data Generator is starting now.*****"); + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + List listings = listingSearchManager.getAllListings(); + LOGGER.info("Certified Product Count: " + listings.size()); + + try { + analyzeProducts(listings); + } catch (Exception e) { + LOGGER.error("Problem analyzing products " + e.getMessage(), e); + } + + listings = null; + LOGGER.info("*****Chart Data Generator is done running.*****"); + } + + private void analyzeProducts(List listings) throws NumberFormatException, EntityRetrievalException { + CriterionProductDataFilter criterionProductDataFilter = new CriterionProductDataFilter(); + CriterionProductStatisticsCalculator criterionProductStatisticsCalculator = new CriterionProductStatisticsCalculator(); + List filteredListings = criterionProductDataFilter.filterData(listings); + Map productCounts = criterionProductStatisticsCalculator.getCounts(filteredListings); + criterionProductStatisticsCalculator.logCounts(productCounts); + criterionProductStatisticsCalculator.save(productCounts); + } +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductDataFilter.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductDataFilter.java new file mode 100644 index 0000000000..5d03f21c6e --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductDataFilter.java @@ -0,0 +1,30 @@ +package gov.healthit.chpl.scheduler.job.chartdata; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import gov.healthit.chpl.search.domain.ListingSearchResult; + +public class CriterionProductDataFilter { + private static final Logger LOGGER = LogManager.getLogger("chartDataCreatorJobLogger"); + private static final String EDITION_2015 = "2015"; + + public CriterionProductDataFilter() { + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + } + + public List filterData(List certifiedProducts) { + List results = new ArrayList(); + for (ListingSearchResult result : certifiedProducts) { + if (result.getEdition() == null || result.getEdition().getName().equalsIgnoreCase(EDITION_2015)) { + results.add(result); + } + } + LOGGER.info("Number of filtered Listings: " + results.size()); + return results; + } +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductStatisticsCalculator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductStatisticsCalculator.java new file mode 100644 index 0000000000..d845c0b408 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/chartdata/CriterionProductStatisticsCalculator.java @@ -0,0 +1,125 @@ + +package gov.healthit.chpl.scheduler.job.chartdata; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.dao.CertificationCriterionDAO; +import gov.healthit.chpl.dao.CriterionProductStatisticsDAO; +import gov.healthit.chpl.domain.CriterionProductStatistics; +import gov.healthit.chpl.exception.EntityCreationException; +import gov.healthit.chpl.exception.EntityRetrievalException; +import gov.healthit.chpl.search.domain.ListingSearchResult; +import gov.healthit.chpl.search.domain.ListingSearchResult.CertificationCriterionSearchResult; + +public class CriterionProductStatisticsCalculator { + private static final Logger LOGGER = LogManager.getLogger("chartDataCreatorJobLogger"); + + @Autowired + private CertificationCriterionDAO certificationCriterionDAO; + @Autowired + private CriterionProductStatisticsDAO criterionProductStatisticsDAO; + + public CriterionProductStatisticsCalculator() { + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + } + + /** + * criterionMap maps the certification criterion id to the count of unique + * Products that certify to that criterion. + * + * uniqueProductSet contains strings of the form + * "--" iff that combination of + * criterion and product have already been counted in the criterion map + * + * @param listings + * listings to parse + * @return map of criteria to counts + */ + public Map getCounts(List listings) { + Map criterionMap = new HashMap(); + HashSet uniqueProductSet = new HashSet(); + for (ListingSearchResult listing : listings) { + if (listing.getCriteriaMet() != null && !listing.getCriteriaMet().isEmpty()) { + for (CertificationCriterionSearchResult cert : listing.getCriteriaMet()) { + String key = cert.getId() + "-" + listing.getDeveloper().getName() + '-' + listing.getProduct().getName(); + if (!uniqueProductSet.contains(key)) { + if (!criterionMap.containsKey(cert.getId())) { + criterionMap.put(cert.getId(), 0L); + } + criterionMap.put(cert.getId(), criterionMap.get(cert.getId()) + 1); + uniqueProductSet.add(key); + } + } + } + } + return criterionMap; + } + + public void logCounts(Map productCounts) { + for (Entry entry : productCounts.entrySet()) { + LOGGER.info("Certification Criteria count: [" + entry.getKey() + " : " + entry.getValue() + "]"); + } + } + + public void save(Map productCounts) throws NumberFormatException, EntityRetrievalException { + try { + deleteExistingCriterionProductStatistics(); + } catch (EntityRetrievalException e) { + LOGGER.error("Error occured while deleting existing CriterionProductStatistics.", e); + return; + } + + convertProductCountMapToListOfCriterionProductStatistics(productCounts).forEach(statistic -> { + saveCriterionProductStatistic(statistic); + }); + } + + private List convertProductCountMapToListOfCriterionProductStatistics( + Map productCounts) throws NumberFormatException, EntityRetrievalException { + + List stats = new ArrayList(); + for (Entry entry : productCounts.entrySet()) { + CertificationCriterion criterion = certificationCriterionDAO.getById(entry.getKey()); + if (!criterion.isRemoved()) { + CriterionProductStatistics criterionProductStatistics = new CriterionProductStatistics(); + criterionProductStatistics.setProductCount(entry.getValue()); + criterionProductStatistics.setCertificationCriterionId(criterion.getId()); + stats.add(criterionProductStatistics); + } + } + return stats; + } + + private void deleteExistingCriterionProductStatistics() throws EntityRetrievalException { + criterionProductStatisticsDAO.findAll().forEach(stat -> { + try { + criterionProductStatisticsDAO.delete(stat.getId()); + } catch (EntityRetrievalException e) { + LOGGER.error("Could not not delete criterionProductStatistics: {}", stat.getId(), e); + } + LOGGER.info("Deleted: " + stat.getId()); + }); + } + + private void saveCriterionProductStatistic(CriterionProductStatistics criterionProductStatistics) { + try { + criterionProductStatisticsDAO.create(criterionProductStatistics); + LOGGER.info("Saved CriterionProductStatisticsDTO [Certification Criteria Id: " + + criterionProductStatistics.getCertificationCriterionId() + ", Count:" + criterionProductStatistics.getProductCount() + "]"); + } catch (EntityCreationException | EntityRetrievalException e) { + LOGGER.error("Error occured while inserting counts.", e); + return; + } + } +}