From 665b025b19ac6bceed02c4cd297272e9fa16609a Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Tue, 9 Jul 2024 17:19:48 +0200 Subject: [PATCH 01/10] [Fix partially #36] Add dependency on POI --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index afbbfc74..8fc84ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,13 @@ 2.6 + + + org.apache.poi + poi-ooxml + 5.2.5 + + org.hamcrest From 90f3c3cbb32f05efb77010b4a37a986457565b1c Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Tue, 9 Jul 2024 17:21:59 +0200 Subject: [PATCH 02/10] [Fix partially #36] Add excel template for record export --- .../templates/record-export-template.xlsx | Bin 0 -> 25146 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/templates/record-export-template.xlsx diff --git a/src/main/resources/templates/record-export-template.xlsx b/src/main/resources/templates/record-export-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9fcbe46401f7b577248b4848f9c535d951db17d8 GIT binary patch literal 25146 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 literal 0 HcmV?d00001 From 2e3dd4ed1e01715fa734a6273ae389d52e10803e Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Tue, 9 Jul 2024 17:40:06 +0200 Subject: [PATCH 03/10] [Fix partially #36] Add excel export implementation - add sparql query - add export related entities - add dao layer code to retrieve exported record data - add ExcelRecordExporter service exporting records to excel --- .../kbss/study/model/export/ExportRecord.java | 301 ++++++++++++++++++ .../kbss/study/model/export/NamedItem.java | 23 ++ .../cz/cvut/kbss/study/model/export/Path.java | 67 ++++ .../kbss/study/model/export/RawRecord.java | 289 +++++++++++++++++ .../persistence/dao/CodeListValuesDao.java | 185 +++++++++++ .../persistence/dao/PatientRecordDao.java | 16 + .../study/service/ExcelRecordExporter.java | 165 ++++++++++ .../resources/query/find-raw-records.sparql | 220 +++++++++++++ 8 files changed, 1266 insertions(+) create mode 100644 src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java create mode 100644 src/main/java/cz/cvut/kbss/study/model/export/NamedItem.java create mode 100644 src/main/java/cz/cvut/kbss/study/model/export/Path.java create mode 100644 src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java create mode 100644 src/main/java/cz/cvut/kbss/study/persistence/dao/CodeListValuesDao.java create mode 100644 src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java create mode 100644 src/main/resources/query/find-raw-records.sparql diff --git a/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java b/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java new file mode 100644 index 00000000..ef0c88e2 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java @@ -0,0 +1,301 @@ +package cz.cvut.kbss.study.model.export; + +import java.net.URI; +import java.util.Date; +import java.util.List; + +public class ExportRecord { + + private URI uri; + + + private Date created; + + private String label; + + private String phase; + + private String institution; + + private String aircraftType; + + private String fuselage; + + private URI ac_comp; + + private String ac_compName; + + + private List path; + + private String failDate; + + + private String flightHours; + + private Integer numberOfAirframeOverhauls; + + private String classificationOfOccurrence; + + private String failureAscertainmentCircumstances; + + private String repeatedFailure; + + private String failureCause; + + private String consequence; + + private String mission; + + private String repair; + + private String repairDuration; + + private Double averageNumberOfMenDuringRepairment; + + private String failureDescription; + + private String descriptionOfCorrectiveAction; + + private String yearOfProductionOfDefectiveEquipment; + + private Integer numberOfOverhaulsOfDefectiveEquipment; + + private String serialNoOf; + + private String notes; + + private String fhaEvent; + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getPhase() { + return phase; + } + + public void setPhase(String phase) { + this.phase = phase; + } + + public String getInstitution() { + return institution; + } + + public void setInstitution(String institution) { + this.institution = institution; + } + + public String getAircraftType() { + return aircraftType; + } + + public void setAircraftType(String aircraftType) { + this.aircraftType = aircraftType; + } + + public String getFuselage() { + return fuselage; + } + + public void setFuselage(String fuselage) { + this.fuselage = fuselage; + } + + public URI getAc_comp() { + return ac_comp; + } + + public void setAc_comp(URI ac_comp) { + this.ac_comp = ac_comp; + } + + public String getAc_compName() { + return ac_compName; + } + + public void setAc_compName(String ac_compName) { + this.ac_compName = ac_compName; + } + + public List getPath() { + return path; + } + + public void setPath(List path) { + this.path = path; + } + + public String getFailDate() { + return failDate; + } + + public void setFailDate(String failDate) { + this.failDate = failDate; + } + + public String getFlightHours() { + return flightHours; + } + + public void setFlightHours(String flightHours) { + this.flightHours = flightHours; + } + + public Integer getNumberOfAirframeOverhauls() { + return numberOfAirframeOverhauls; + } + + public void setNumberOfAirframeOverhauls(Integer numberOfAirframeOverhauls) { + this.numberOfAirframeOverhauls = numberOfAirframeOverhauls; + } + + public String getClassificationOfOccurrence() { + return classificationOfOccurrence; + } + + public void setClassificationOfOccurrence(String classificationOfOccurrence) { + this.classificationOfOccurrence = classificationOfOccurrence; + } + + public String getFailureAscertainmentCircumstances() { + return failureAscertainmentCircumstances; + } + + public void setFailureAscertainmentCircumstances(String failureAscertainmentCircumstances) { + this.failureAscertainmentCircumstances = failureAscertainmentCircumstances; + } + + public String getRepeatedFailure() { + return repeatedFailure; + } + + public void setRepeatedFailure(String repeatedFailure) { + this.repeatedFailure = repeatedFailure; + } + + public String getFailureCause() { + return failureCause; + } + + public void setFailureCause(String failureCause) { + this.failureCause = failureCause; + } + + public String getConsequence() { + return consequence; + } + + public void setConsequence(String consequence) { + this.consequence = consequence; + } + + public String getMission() { + return mission; + } + + public void setMission(String mission) { + this.mission = mission; + } + + public String getRepair() { + return repair; + } + + public void setRepair(String repair) { + this.repair = repair; + } + + public String getRepairDuration() { + return repairDuration; + } + + public void setRepairDuration(String repairDuration) { + this.repairDuration = repairDuration; + } + + public Double getAverageNumberOfMenDuringRepairment() { + return averageNumberOfMenDuringRepairment; + } + + public void setAverageNumberOfMenDuringRepairment(Double averageNumberOfMenDuringRepairment) { + this.averageNumberOfMenDuringRepairment = averageNumberOfMenDuringRepairment; + } + + public String getFailureDescription() { + return failureDescription; + } + + public void setFailureDescription(String failureDescription) { + this.failureDescription = failureDescription; + } + + public String getDescriptionOfCorrectiveAction() { + return descriptionOfCorrectiveAction; + } + + public void setDescriptionOfCorrectiveAction(String descriptionOfCorrectiveAction) { + this.descriptionOfCorrectiveAction = descriptionOfCorrectiveAction; + } + + public String getYearOfProductionOfDefectiveEquipment() { + return yearOfProductionOfDefectiveEquipment; + } + + public void setYearOfProductionOfDefectiveEquipment(String yearOfProductionOfDefectiveEquipment) { + this.yearOfProductionOfDefectiveEquipment = yearOfProductionOfDefectiveEquipment; + } + + public Integer getNumberOfOverhaulsOfDefectiveEquipment() { + return numberOfOverhaulsOfDefectiveEquipment; + } + + public void setNumberOfOverhaulsOfDefectiveEquipment(Integer numberOfOverhaulsOfDefectiveEquipment) { + this.numberOfOverhaulsOfDefectiveEquipment = numberOfOverhaulsOfDefectiveEquipment; + } + + public String getSerialNoOf() { + return serialNoOf; + } + + public void setSerialNoOf(String serialNoOf) { + this.serialNoOf = serialNoOf; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public String getFhaEvent() { + return fhaEvent; + } + + public void setFhaEvent(String fhaEvent) { + this.fhaEvent = fhaEvent; + } +} diff --git a/src/main/java/cz/cvut/kbss/study/model/export/NamedItem.java b/src/main/java/cz/cvut/kbss/study/model/export/NamedItem.java new file mode 100644 index 00000000..f83bebf1 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/model/export/NamedItem.java @@ -0,0 +1,23 @@ +package cz.cvut.kbss.study.model.export; + +import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.study.model.AbstractEntity; + +@SparqlResultSetMappings(value = +@SparqlResultSetMapping(name = "NamedItem", entities = { + @EntityResult(entityClass = NamedItem.class) +}) +) +@OWLClass(iri = "http://named-item") +public class NamedItem extends AbstractEntity { + @OWLDataProperty(iri = "http://name") + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/cz/cvut/kbss/study/model/export/Path.java b/src/main/java/cz/cvut/kbss/study/model/export/Path.java new file mode 100644 index 00000000..3b0d51ca --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/model/export/Path.java @@ -0,0 +1,67 @@ +package cz.cvut.kbss.study.model.export; + + +import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.study.model.AbstractEntity; + +import java.net.URI; + +@SparqlResultSetMappings(value = +@SparqlResultSetMapping(name = "Path", entities = { + @EntityResult(entityClass = Path.class) +}) +) +@OWLClass(iri = "http://path") +public class Path extends AbstractEntity { + + @OWLDataProperty(iri = "http://l1") + protected URI l1; + @OWLDataProperty(iri = "http://l2") + protected URI l2; + @OWLDataProperty(iri = "http://l3") + protected URI l3; + @OWLDataProperty(iri = "http://l4") + protected URI l4; + @OWLDataProperty(iri = "http://l5") + protected URI l5; + + public URI getL1() { + return l1; + } + + public void setL1(URI l1) { + this.l1 = l1; + } + + public URI getL2() { + return l2; + } + + public void setL2(URI l2) { + this.l2 = l2; + } + + public URI getL3() { + return l3; + } + + public void setL3(URI l3) { + this.l3 = l3; + } + + public URI getL4() { + return l4; + } + + public void setL4(URI l4) { + this.l4 = l4; + } + + public URI getL5() { + return l5; + } + + public void setL5(URI l5) { + this.l5 = l5; + } +} diff --git a/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java b/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java new file mode 100644 index 00000000..25ce1c1f --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java @@ -0,0 +1,289 @@ +package cz.cvut.kbss.study.model.export; + +import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.study.model.Vocabulary; + +import java.net.URI; +import java.util.Date; + +@SparqlResultSetMappings(value = +@SparqlResultSetMapping(name = "RawRecord", entities = { + @EntityResult(entityClass = RawRecord.class) +}) +) +@OWLClass(iri = Vocabulary.s_c_patient_record) +public class RawRecord { + @Id + private URI uri; + + @OWLDataProperty(iri = "http://created") + private Date created; + @OWLDataProperty(iri = "http://label") + private String label; + @OWLObjectProperty(iri = "http://phase") + private URI phase; + @OWLObjectProperty(iri = "http://institution") + private URI institution; + @OWLObjectProperty(iri = "http://aircraftType") + private URI aircraftType; + @OWLObjectProperty(iri = "http://fuselage") + private String fuselage; + @OWLObjectProperty(iri = "http://ac_comp") + private URI ac_comp; + @OWLDataProperty(iri = "http://failDate") + private String failDate; + + @OWLDataProperty(iri = "http://flightHours") + private String flightHours; + @OWLDataProperty(iri = "http://numberOfAirframeOverhauls") + private Integer numberOfAirframeOverhauls; + @OWLObjectProperty(iri = "http://classificationOfOccurrence") + private URI classificationOfOccurrence; + @OWLObjectProperty(iri = "http://failureAscertainmentCircumstances") + private URI failureAscertainmentCircumstances; + @OWLObjectProperty(iri = "http://repeatedFailure") + private URI repeatedFailure; + @OWLObjectProperty(iri = "http://failureCause") + private URI failureCause; + @OWLObjectProperty(iri = "http://consequence") + private URI consequence; + @OWLObjectProperty(iri = "http://mission") + private URI mission; + @OWLObjectProperty(iri = "http://repair") + private URI repair; + @OWLDataProperty(iri = "http://repairDuration") + private String repairDuration; + @OWLDataProperty(iri = "http://averageNumberOfMenDuringRepairment") + private Double averageNumberOfMenDuringRepairment; + @OWLDataProperty(iri = "http://failureDescription") + private String failureDescription; + @OWLDataProperty(iri = "http://descriptionOfCorrectiveAction") + private String descriptionOfCorrectiveAction; + @OWLDataProperty(iri = "http://yearOfProductionOfDefectiveEquipment") + private String yearOfProductionOfDefectiveEquipment; + @OWLDataProperty(iri = "http://numberOfOverhaulsOfDefectiveEquipment") + private Integer numberOfOverhaulsOfDefectiveEquipment; + @OWLDataProperty(iri = "http://serialNoOf") + private String serialNoOf; + @OWLDataProperty(iri = "http://notes") + private String notes; + @OWLObjectProperty(iri = "http://fhaEvent") + private URI fhaEvent; + + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public URI getPhase() { + return phase; + } + + public void setPhase(URI phase) { + this.phase = phase; + } + + public URI getInstitution() { + return institution; + } + + public void setInstitution(URI institution) { + this.institution = institution; + } + + public URI getAircraftType() { + return aircraftType; + } + + public void setAircraftType(URI aircraftType) { + this.aircraftType = aircraftType; + } + + public String getFuselage() { + return fuselage; + } + + public void setFuselage(String fuselage) { + this.fuselage = fuselage; + } + + public URI getAc_comp() { + return ac_comp; + } + + public void setAc_comp(URI ac_comp) { + this.ac_comp = ac_comp; + } + + public String getFailDate() { + return failDate; + } + + public void setFailDate(String failDate) { + this.failDate = failDate; + } + + public String getFlightHours() { + return flightHours; + } + + public void setFlightHours(String flightHours) { + this.flightHours = flightHours; + } + + public Integer getNumberOfAirframeOverhauls() { + return numberOfAirframeOverhauls; + } + + public void setNumberOfAirframeOverhauls(Integer numberOfAirframeOverhauls) { + this.numberOfAirframeOverhauls = numberOfAirframeOverhauls; + } + + public URI getClassificationOfOccurrence() { + return classificationOfOccurrence; + } + + public void setClassificationOfOccurrence(URI classificationOfOccurrence) { + this.classificationOfOccurrence = classificationOfOccurrence; + } + + public URI getFailureAscertainmentCircumstances() { + return failureAscertainmentCircumstances; + } + + public void setFailureAscertainmentCircumstances(URI failureAscertainmentCircumstances) { + this.failureAscertainmentCircumstances = failureAscertainmentCircumstances; + } + + public URI getRepeatedFailure() { + return repeatedFailure; + } + + public void setRepeatedFailure(URI repeatedFailure) { + this.repeatedFailure = repeatedFailure; + } + + public URI getFailureCause() { + return failureCause; + } + + public void setFailureCause(URI failureCause) { + this.failureCause = failureCause; + } + + public URI getConsequence() { + return consequence; + } + + public void setConsequence(URI consequence) { + this.consequence = consequence; + } + + public URI getMission() { + return mission; + } + + public void setMission(URI mission) { + this.mission = mission; + } + + public URI getRepair() { + return repair; + } + + public void setRepair(URI repair) { + this.repair = repair; + } + + public String getRepairDuration() { + return repairDuration; + } + + public void setRepairDuration(String repairDuration) { + this.repairDuration = repairDuration; + } + + public Double getAverageNumberOfMenDuringRepairment() { + return averageNumberOfMenDuringRepairment; + } + + public void setAverageNumberOfMenDuringRepairment(Double averageNumberOfMenDuringRepairment) { + this.averageNumberOfMenDuringRepairment = averageNumberOfMenDuringRepairment; + } + + public String getFailureDescription() { + return failureDescription; + } + + public void setFailureDescription(String failureDescription) { + this.failureDescription = failureDescription; + } + + public String getDescriptionOfCorrectiveAction() { + return descriptionOfCorrectiveAction; + } + + public void setDescriptionOfCorrectiveAction(String descriptionOfCorrectiveAction) { + this.descriptionOfCorrectiveAction = descriptionOfCorrectiveAction; + } + + public String getYearOfProductionOfDefectiveEquipment() { + return yearOfProductionOfDefectiveEquipment; + } + + public void setYearOfProductionOfDefectiveEquipment(String yearOfProductionOfDefectiveEquipment) { + this.yearOfProductionOfDefectiveEquipment = yearOfProductionOfDefectiveEquipment; + } + + public Integer getNumberOfOverhaulsOfDefectiveEquipment() { + return numberOfOverhaulsOfDefectiveEquipment; + } + + public void setNumberOfOverhaulsOfDefectiveEquipment(Integer numberOfOverhaulsOfDefectiveEquipment) { + this.numberOfOverhaulsOfDefectiveEquipment = numberOfOverhaulsOfDefectiveEquipment; + } + + public String getSerialNoOf() { + return serialNoOf; + } + + public void setSerialNoOf(String serialNoOf) { + this.serialNoOf = serialNoOf; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public URI getFhaEvent() { + return fhaEvent; + } + + public void setFhaEvent(URI fhaEvent) { + this.fhaEvent = fhaEvent; + } +} diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/CodeListValuesDao.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/CodeListValuesDao.java new file mode 100644 index 00000000..416df4c1 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/CodeListValuesDao.java @@ -0,0 +1,185 @@ +package cz.cvut.kbss.study.persistence.dao; + +import cz.cvut.kbss.jopa.model.EntityManager; +import cz.cvut.kbss.study.model.Vocabulary; +import cz.cvut.kbss.study.model.export.NamedItem; +import cz.cvut.kbss.study.model.export.Path; +import org.springframework.stereotype.Repository; + +import java.net.URI; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Repository +public class CodeListValuesDao { + + private final EntityManager em; + + public CodeListValuesDao(EntityManager em) { + this.em = em; + } + + + + public List findClassificationOfOccurrence() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/classification-of-occurrence")); + } + + + public List findConsequence() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/consequence")); + } + + + public List findFailureAscertainmentCircumstances() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/failure-ascertainment-circumstances")); + } + + + public List findFailureCause() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/failure-cause")); + } + + + public List findFhaEvent() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/fha-event")); + } + + + public List findMission() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/mission")); + } + + + public List findRepair() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/repair")); + } + + + public List findRepeatedFailure() { + return findItems(URI.create("http://vfn.cz/ontologies/ava-study/model/repeated-failure")); + } + + public List findInstitutions(){ + return em.createNativeQuery(""" + PREFIX rm: + PREFIX rdfs: + + SELECT ?uri (str(?_name) as ?name) { + ?uri a ?type. + ?uri rdfs:label ?_name + } + """, NamedItem.class.getSimpleName()) + .setParameter("type", URI.create(Vocabulary.s_c_institution)) + .getResultList(); + } + + public List findItems(URI type){ + return em.createNativeQuery(""" + PREFIX rm: + PREFIX rdfs: + + SELECT ?uri (str(?_name) as ?name) { + SERVICE { + ?uri a ?type. + ?uri rdfs:label ?_name + } + } + """, NamedItem.class.getSimpleName()) + .setParameter("type", type) + .getResultList(); + } + + public List findItems(Collection items){ + String queryString = """ + PREFIX rm: + PREFIX rdfs: + + SELECT ?uri (str(?_name) as ?name) { + SERVICE { + ?uri rdfs:label ?_name + } + }VALUES ?uri { + ###items### + } + """; + if(items != null) { + queryString = queryString.replaceFirst( + "###items###", + items.stream().map(u -> "<%s>".formatted(u)).collect(Collectors.joining("\n")) + ); + } + return em.createNativeQuery(queryString, NamedItem.class.getSimpleName()) + .getResultList(); + } + + + + public List findAircraft(){ + return em.createNativeQuery(""" + PREFIX rdfs: + PREFIX form: + + SELECT ?uri ?name { + SERVICE { + ?uri a form:form-template. + ?uri rdfs:label ?name. + } + } + """, NamedItem.class.getSimpleName()).getResultList(); + } + + public List findAircraftParts(){ + return em.createNativeQuery(""" + PREFIX rdfs: + PREFIX form: + + SELECT ?uri ?name { + SERVICE { + ?uri a form:form-template. + ?uri rdfs:label ?name. + } + } + """, NamedItem.class.getSimpleName()).getResultList(); + } + + public List getBroaderPath(Collection elements){ + String queryString = """ + PREFIX skos: + SELECT ?l1 ?l2 ?l3 ?l4 ?l5 (?p as ?uri) { + SERVICE { + OPTIONAL{ + ?p skos:broader+ ?l1. + FILTER NOT EXISTS{ + ?l1 skos:broader ?l0 . + } + OPTIONAL{ + ?l2 skos:broader ?l1 . + ?p skos:broader+ ?l2. + OPTIONAL{ + ?l3 skos:broader ?l2 . + ?p skos:broader+ ?l3. + OPTIONAL{ + ?l4 skos:broader ?l3 . + ?p skos:broader+ ?l4. + OPTIONAL{ + ?l5 skos:broader ?l4 . + ?p skos:broader+ ?l5. + } + } + } + } + } + } + }VALUES ?p { + %s + } + """; + queryString = queryString.formatted(elements.stream().map(u -> "<%s>".formatted(u.toString())) + .collect(Collectors.joining("\n"))); + return em.createNativeQuery(queryString, Path.class.getSimpleName()) + .getResultList(); + } + +} diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java index 6fd233ef..c901ddca 100644 --- a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java +++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java @@ -5,6 +5,7 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.model.query.TypedQuery; import cz.cvut.kbss.ontodriver.model.LangString; import cz.cvut.kbss.study.dto.PatientRecordDto; @@ -14,11 +15,13 @@ import cz.cvut.kbss.study.model.PatientRecord; import cz.cvut.kbss.study.model.User; import cz.cvut.kbss.study.model.Vocabulary; +import cz.cvut.kbss.study.model.export.RawRecord; import cz.cvut.kbss.study.persistence.dao.util.QuestionSaver; import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; import cz.cvut.kbss.study.persistence.dao.util.RecordSort; import cz.cvut.kbss.study.util.Constants; import cz.cvut.kbss.study.util.IdentificationUtils; +import cz.cvut.kbss.study.util.Utils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -38,6 +41,9 @@ @Repository public class PatientRecordDao extends OwlKeySupportingDao { + public static final String FIND_ALL_RAW_PATIENT_RECORDS = "find-raw-records.sparql"; + public static final String RAW_RECORDS_FILTER_CLAUSE_PATTERN = "###FILTER_CLAUSES###"; + public PatientRecordDao(EntityManager em) { super(PatientRecord.class, em); } @@ -237,6 +243,16 @@ private Page findRecords(RecordFilterParams filters, Pageable pageSpec, C return new PageImpl<>(records, pageSpec, totalCount); } + public List findAllRecordsRaw(RecordFilterParams filters){ + final Map queryParams = new HashMap<>(); + final String filterClauseExtension = mapParamsToQuery(filters, queryParams); + final String queryString = Utils.loadQuery(FIND_ALL_RAW_PATIENT_RECORDS) + .replaceAll(RAW_RECORDS_FILTER_CLAUSE_PATTERN, filterClauseExtension); + Query query = em.createNativeQuery(queryString, RawRecord.class.getSimpleName()); + queryParams.forEach(query::setParameter); + return query.getResultList(); + } + private void setQueryParameters(TypedQuery query, Map queryParams) { query.setParameter("type", typeUri) .setParameter("hasPhase", URI.create(Vocabulary.s_p_has_phase)) diff --git a/src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java b/src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java new file mode 100644 index 00000000..72f12184 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java @@ -0,0 +1,165 @@ +package cz.cvut.kbss.study.service; + +import cz.cvut.kbss.study.model.export.ExportRecord; +import cz.cvut.kbss.study.model.export.Path; +import cz.cvut.kbss.study.model.export.RawRecord; +import cz.cvut.kbss.study.persistence.dao.CodeListValuesDao; +import cz.cvut.kbss.study.persistence.dao.PatientRecordDao; +import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; +import cz.cvut.kbss.study.util.Utils; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +public class ExcelRecordExporter { + + private final PatientRecordDao patientRecordDao; + private final CodeListValuesDao codeListValuesDao; + + public ExcelRecordExporter(PatientRecordDao patientRecordDao, CodeListValuesDao codeListValuesDao) { + this.patientRecordDao = patientRecordDao; + this.codeListValuesDao = codeListValuesDao; + } + + public InputStream exportRecords(RecordFilterParams filters){ + try { + XSSFWorkbook workbook = new XSSFWorkbook(Utils.class.getClassLoader().getResourceAsStream("templates/record-export-template.xlsx")); + List exportRecords = findExportRecords(filters); + addDataToExcel(workbook, exportRecords); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + workbook.write(output); + return new ByteArrayInputStream(output.toByteArray()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private List findExportRecords(RecordFilterParams filters){ + List rawRecords = patientRecordDao.findAllRecordsRaw(filters); + Map translatorMap = new HashMap<>(); + List paths = codeListValuesDao.getBroaderPath(rawRecords.stream().map(r -> r.getAc_comp()).collect(Collectors.toSet())); + Set uris = rawRecords.stream().flatMap(r -> Stream.of( + r.getClassificationOfOccurrence(), r.getConsequence(), r.getFailureAscertainmentCircumstances(), + r.getFailureCause(), r.getAc_comp(), r.getFhaEvent(), r.getMission(), r.getRepair(), + r.getRepeatedFailure())) + .distinct() + .filter(u -> u != null) + .collect(Collectors.toSet()); + + uris.addAll(paths.stream() + .flatMap(p -> Stream.of(p.getL1(), p.getL2(), p.getL3(), p.getL4(), p.getL5())) + .filter(u -> u != null).collect(Collectors.toSet()) + ); + + + codeListValuesDao.findItems(uris).forEach(i -> translatorMap.put(i.getUri(), i.getName())); + codeListValuesDao.findAircraft().forEach(i -> translatorMap.put(i.getUri(), i.getName())); + codeListValuesDao.findInstitutions().forEach(i -> translatorMap.put(i.getUri(), i.getName())); + + Map> pathMap = new HashMap<>(); + paths.forEach(p -> pathMap.put(p.getUri(), Arrays.asList( + translatorMap.get(p.getL1()), + translatorMap.get(p.getL2()), + translatorMap.get(p.getL3()), + translatorMap.get(p.getL4()), + translatorMap.get(p.getL5()) + ))); + + List exportRecords = new ArrayList<>(); + + for(RawRecord r : rawRecords){ + ExportRecord er = new ExportRecord(); + exportRecords.add(er); + er.setPath(pathMap.get(r.getAc_comp())); + er.setInstitution(translatorMap.get(r.getInstitution())); + er.setAircraftType(translatorMap.get(r.getAircraftType())); + er.setAc_compName(translatorMap.get(r.getAc_comp())); + er.setClassificationOfOccurrence(translatorMap.get(r.getClassificationOfOccurrence())); + er.setConsequence(translatorMap.get(r.getConsequence())); + er.setFailureCause(translatorMap.get(r.getFailureCause())); + er.setFhaEvent(translatorMap.get(r.getFhaEvent())); + er.setMission(translatorMap.get(r.getMission())); + er.setRepair(translatorMap.get(r.getRepair())); + er.setRepeatedFailure(translatorMap.get(r.getRepeatedFailure())); + + er.setFuselage(r.getFuselage()); + er.setUri(r.getUri()); + er.setCreated(r.getCreated()); + er.setLabel(r.getLabel()); + er.setFailDate(r.getFailDate()); + er.setFlightHours(r.getFlightHours()); + er.setNumberOfAirframeOverhauls(r.getNumberOfAirframeOverhauls()); + er.setRepairDuration(r.getRepairDuration()); + er.setAverageNumberOfMenDuringRepairment(r.getAverageNumberOfMenDuringRepairment()); + er.setFailureDescription(r.getFailureDescription()); + er.setDescriptionOfCorrectiveAction(r.getDescriptionOfCorrectiveAction()); + er.setNumberOfOverhaulsOfDefectiveEquipment(r.getNumberOfOverhaulsOfDefectiveEquipment()); + er.setSerialNoOf(r.getSerialNoOf()); + er.setNotes(r.getNotes()); + } + + return exportRecords; + } + + + private void addDataToExcel(XSSFWorkbook workbook, List data) throws IOException { + XSSFSheet s = workbook.getSheetAt(1); + + int rowIndex = 1; + for(ExportRecord rec : data) { + XSSFRow r = s.createRow(rowIndex++); + r.createCell(0).setCellValue(rowIndex); +// r.createCell(1).setCellValue(rowIndex); + r.createCell(2).setCellValue(rec.getCreated()); +// r.createCell(3).setCellValue(rec.ed); +// r.createCell(4).setCellValue(rec.); +// r.createCell(5).setCellValue(rec.); + r.createCell(6).setCellValue(rec.getLabel()); + r.createCell(7).setCellValue(rec.getInstitution()); + r.createCell(8).setCellValue(rec.getAircraftType()); + r.createCell(10).setCellValue(rec.getFuselage()); + r.createCell(11).setCellValue(rec.getFailDate()); + r.createCell(12).setCellValue(rec.getFlightHours()); + r.createCell(13).setCellValue(rec.getNumberOfAirframeOverhauls()); + r.createCell(14).setCellValue(rec.getClassificationOfOccurrence()); + r.createCell(15).setCellValue(rec.getFailureAscertainmentCircumstances()); + r.createCell(16).setCellValue(rec.getRepeatedFailure()); + r.createCell(17).setCellValue(rec.getFailureCause()); + r.createCell(18).setCellValue(rec.getConsequence()); + r.createCell(19).setCellValue(rec.getMission()); + r.createCell(20).setCellValue(rec.getRepair()); + r.createCell(21).setCellValue(rec.getRepairDuration()); + r.createCell(22).setCellValue(rec.getAverageNumberOfMenDuringRepairment()); + r.createCell(23).setCellValue(rec.getFailureDescription()); + r.createCell(24).setCellValue(rec.getDescriptionOfCorrectiveAction()); + r.createCell(25).setCellValue(rec.getAc_compName()); + if (rec.getPath() != null){ + r.createCell(26).setCellValue(rec.getPath().get(0)); + r.createCell(27).setCellValue(rec.getPath().get(1)); + r.createCell(28).setCellValue(rec.getPath().get(2)); + r.createCell(29).setCellValue(rec.getPath().get(3)); + r.createCell(30).setCellValue(rec.getPath().get(4)); + } +// r.createCell(31).setCellValue(rec.get()); +// r.createCell(32).setCellValue(rec.get()); + r.createCell(31).setCellValue(rec.getYearOfProductionOfDefectiveEquipment()); + + Optional.ofNullable(rec.getNumberOfOverhaulsOfDefectiveEquipment()).ifPresent(v -> r.createCell(29).setCellValue(v)); + r.createCell(33).setCellValue(rec.getSerialNoOf()); + r.createCell(34).setCellValue(rec.getNotes()); + r.createCell(35).setCellValue(rec.getFhaEvent()); + } + } + +} diff --git a/src/main/resources/query/find-raw-records.sparql b/src/main/resources/query/find-raw-records.sparql new file mode 100644 index 00000000..cfb8870b --- /dev/null +++ b/src/main/resources/query/find-raw-records.sparql @@ -0,0 +1,220 @@ +PREFIX av: +PREFIX dc: +PREFIX doc: +PREFIX form: +PREFIX form-lt: +PREFIX owl: +PREFIX rdfs: +PREFIX skos: +PREFIX vs-f: +PREFIX vs-m: +PREFIX xsd: +PREFIX avadom: + +PREFIX avamod: +PREFIX rm: +PREFIX dcterms: +PREFIX spif: + +SELECT (?rec as ?uri) +?created ?label ?phase ?institution (IRI(str(?formTemplate)) as ?aircraftType) (str(?fus) as ?fuselage) ?ac_comp (str(?failDateStr) as ?failDate) +?flightHours ?numberOfAirframeOverhauls ?classificationOfOccurrence ?failureAscertainmentCircumstances (?repeatedFailureCode as ?repeatedFailure) ?failureCause ?consequence ?mission ?repair (str(?repairDurationStr) as ?repairDuration) ?averageNumberOfMenDuringRepairment ?failureDescription ?descriptionOfCorrectiveAction ?yearOfProductionOfDefectiveEquipment ?numberOfOverhaulsOfDefectiveEquipment ?serialNoOf ?notes ?fhaEvent +{ + + ?rec a rm:patient-record; + dcterms:created ?created; + rdfs:label ?label . + ?rec rm:was-treated-at ?institution. + ?institution rm:key ?institutionKey . + + ?rec rm:has-form-template ?formTemplate. + + OPTIONAL { + ?rec dcterms:modified ?lastModified . + } + + OPTIONAL{ + ?rec rm:has-phase ?phase. #rm:completed-record-phase. + } + + ?rec rm:has-question ?f. + ?f doc:has_related_question ?s1. + ?f doc:has_related_question ?s2 + FILTER(?s2 != ?s1) + ?s2 doc:has_related_question ?fhaEventQ. + ?fhaEventQ form:has-question-origin avamod:fha-event. + + ?s1 doc:has_related_question ?FUSq. + ?FUSq form:has-question-origin avamod:fuselage-no. + + ?s1 doc:has_related_question ?Cq. + ?Cq form:has-question-origin ?componentQuestionOrigin. + FILTER(contains(str(?componentQuestionOrigin), "http://vfn.cz/ontologies/ava-study/model/system-equipment-block-part-")) + + ?s1 doc:has_related_question ?failDateq. + ?failDateq form:has-question-origin avamod:date-of-failure-ascertainment. + + ?s1 doc:has_related_question ?FHq. + ?FHq form:has-question-origin avamod:flight-hours-of-airframe-since-the-service-beginning. + + ?s1 doc:has_related_question ?classificationOfOccurrenceQ. + ?classificationOfOccurrenceQ form:has-question-origin avamod:classification-of-occurrence. + + ?s1 doc:has_related_question ?failureAscertainmentCircumstancesQ. + ?failureAscertainmentCircumstancesQ form:has-question-origin avamod:failure-ascertainment-circumstances. + + ?s1 doc:has_related_question ?repeatedFailureQ. + ?repeatedFailureQ form:has-question-origin avamod:repeated-failure. + + ?s1 doc:has_related_question ?failureCauseQ. + ?failureCauseQ form:has-question-origin avamod:failure-cause . + + ?s1 doc:has_related_question ?consequenceQ. + ?consequenceQ form:has-question-origin avamod:consequence. + + ?s1 doc:has_related_question ?missionQ. + ?missionQ form:has-question-origin avamod:mission. + + ?s1 doc:has_related_question ?repairQ. + ?repairQ form:has-question-origin avamod:repair. + + ?s1 doc:has_related_question ?repairDurationQ. + ?repairDurationQ form:has-question-origin avamod:repair-duration. + + ?s1 doc:has_related_question ?averageNumberOfMenDuringRepairmentQ. + ?averageNumberOfMenDuringRepairmentQ form:has-question-origin avamod:average-number-of-men-during-repairment. + + ?s1 doc:has_related_question ?failureDescriptionQ. + ?failureDescriptionQ form:has-question-origin avamod:failure-description. + + ?s1 doc:has_related_question ?descriptionOfCorrectiveActionQ. + ?descriptionOfCorrectiveActionQ form:has-question-origin avamod:description-of-corrective-action. + + ?s1 doc:has_related_question ?numberOfAirframeOverhaulsQ. + ?numberOfAirframeOverhaulsQ form:has-question-origin avamod:number-of-airframe-overhauls. + + ?s1 doc:has_related_question ?yearOfProductionOfDefectiveEquipmentQ. + ?yearOfProductionOfDefectiveEquipmentQ form:has-question-origin avamod:year-of-production-of-defective-equipment. + + ?s1 doc:has_related_question ?numberOfOverhaulsOfDefectiveEquipmentQ. + ?numberOfOverhaulsOfDefectiveEquipmentQ form:has-question-origin avamod:number-of-overhauls-of-defective-equipment. + +# ?s1 doc:has_related_question ?serialNoOfQ. +# ?serialNoOfQ form:has-question-origin avamod:serial-no-of. + + ?s1 doc:has_related_question ?notesQ. + ?notesQ form:has-question-origin avamod:notes. + + # avamod:fuselage-no + ?FUSq doc:has_answer ?FUSa. + ?FUSa doc:has_data_value ?fus. + + #avamod:system-equipment-block-part-l-39ng.a1 + # ?comp - label of general component/system + # ?comp_iri - iri of component type specific to the aircraft + ?Cq doc:has_answer ?Ca. + ?Ca doc:has_object_value ?ac_comp. + + #avamod:date-of-failure-ascertainment + # ?failDate - creation date filter + ?failDateq doc:has_answer ?failDatea. + ?failDatea doc:has_data_value ?failDateStr. + # BIND(spif:parseDate(?failDateStr, "DD-MM-yyyy") as ?failDate) + # FILTER((!BOUND(?createdBefore) || ?createdBefore < ?failDate ) && (!BOUND(?createdAfter) || ?createdAfter >= ?failDate )) + + #avamod:flight-hours-of-airframe-since-the-service-beginning + + ?FHq doc:has_answer ?FHa. + ?FHa doc:has_data_value ?flightHours. + + #avamod:classification-of-occurrence + ?classificationOfOccurrenceQ doc:has_answer ?classificationOfOccurrenceA. + ?classificationOfOccurrenceA doc:has_object_value ?classificationOfOccurrence. + + #avamod:failure-ascertainment-circumstances + + ?failureAscertainmentCircumstancesQ doc:has_answer ?failureAscertainmentCircumstancesA. + ?failureAscertainmentCircumstancesA doc:has_object_value ?failureAscertainmentCircumstances. + + # during flight + # BIND(IF(BOUND(?failureAscertainmentCircumstances) && ?failureAscertainmentCircumstances = avadom:during-flight, 1, 0) as ?duringFlight) + + #avamod:repeated-failure + ?repeatedFailureQ doc:has_answer ?repeatedFailureA. + ?repeatedFailureA doc:has_object_value ?repeatedFailureCode. + + OPTIONAL{ + #avamod:failure-cause + ?failureCauseQ doc:has_answer ?failureCauseA. + ?failureCauseA doc:has_object_value ?failureCause. + } + #avamod:consequence + + ?consequenceQ doc:has_answer ?consequenceA. + ?consequenceA doc:has_object_value ?consequence. + + #avamod:mission + ?missionQ doc:has_answer ?missionA. + ?missionA doc:has_object_value ?mission. + + #avamod:repair + ?repairQ doc:has_answer ?repairA. + ?repairA doc:has_object_value ?repair. + + #avamod:repair-duration + ?repairDurationQ doc:has_answer ?repairDurationA. + ?repairDurationA doc:has_data_value ?repairDurationStr. + + #avamod:average-number-of-men-during-repairment + ?averageNumberOfMenDuringRepairmentQ doc:has_answer ?averageNumberOfMenDuringRepairmentA. + ?averageNumberOfMenDuringRepairmentA doc:has_data_value ?averageNumberOfMenDuringRepairmentStr. + BIND(xsd:decimal(str(?averageNumberOfMenDuringRepairmentStr)) as ?averageNumberOfMenDuringRepairment) + + #avamod:failure-description + ?failureDescriptionQ doc:has_answer ?failureDescriptionA. + ?failureDescriptionA doc:has_data_value ?failureDescription. + + #avamod:description-of-corrective-action + ?descriptionOfCorrectiveActionQ doc:has_answer ?descriptionOfCorrectiveActionA. + ?descriptionOfCorrectiveActionA doc:has_data_value ?descriptionOfCorrectiveAction. + + OPTIONAL { + #avamod:number-of-airframe-overhauls + ?numberOfAirframeOverhaulsQ doc:has_answer ?numberOfAirframeOverhaulsA. + ?numberOfAirframeOverhaulsA doc:has_data_value ?numberOfAirframeOverhaulsStr. + BIND(xsd:integer(str(?numberOfAirframeOverhaulsStr)) as ?numberOfAirframeOverhauls) + } + + OPTIONAL { + #avamod:year-of-production-of-defective-equipment + ?yearOfProductionOfDefectiveEquipmentQ doc:has_answer ?yearOfProductionOfDefectiveEquipmentA. + ?yearOfProductionOfDefectiveEquipmentA doc:has_data_value ?yearOfProductionOfDefectiveEquipment.# TODO - transform to more suitable datatype + FILTER(str(spif:trim(?yearOfProductionOfDefectiveEquipment)) != "" ) + } + + OPTIONAL { + #avamod:number-of-overhauls-of-defective-equipment + ?numberOfOverhaulsOfDefectiveEquipmentQ doc:has_answer ?numberOfOverhaulsOfDefectiveEquipmentA. + ?numberOfOverhaulsOfDefectiveEquipmentA doc:has_data_value ?numberOfOverhaulsOfDefectiveEquipment.# TODO - transform to more suitable datatype + FILTER(str(spif:trim(?numberOfOverhaulsOfDefectiveEquipment)) != "" ) +# } +# OPTIONAL{ +# #avamod:serial-no-of +# ?serialNoOfQ doc:has_answer ?serialNoOfA. +# ?serialNoOfA doc:has_data_value ?serialNoOf. + } + OPTIONAL{ + #avamod:notes + ?notesQ doc:has_answer ?notesA. + ?notesA doc:has_data_value ?notes. + FILTER(str(spif:trim(?notes)) != "" ) + } + OPTIONAL{ + #avamod:fha-event + ?fhaEventQ doc:has_answer ?fhaEventA. + ?fhaEventA doc:has_object_value ?fhaEvent. + } + BIND (COALESCE(?lastModified, ?created) AS ?date) + + ###FILTER_CLAUSES### +} From e5a70489c36fcf298d5e7ff01527b3c495c72b47 Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Tue, 9 Jul 2024 17:42:16 +0200 Subject: [PATCH 04/10] [Fix partially #36] Extend rest api to support record export in Excel --- .../study/rest/PatientRecordController.java | 68 +++++++++++++------ .../study/rest/util/RecordFilterMapper.java | 8 ++- .../study/service/PatientRecordService.java | 3 + .../RepositoryPatientRecordService.java | 11 ++- .../cz/cvut/kbss/study/util/Constants.java | 10 +++ 5 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java index 5bc4d9ff..ab89c7ea 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java @@ -5,34 +5,27 @@ import cz.cvut.kbss.study.exception.NotFoundException; import cz.cvut.kbss.study.model.PatientRecord; import cz.cvut.kbss.study.model.RecordPhase; +import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent; import cz.cvut.kbss.study.rest.exception.BadRequestException; import cz.cvut.kbss.study.rest.util.RecordFilterMapper; import cz.cvut.kbss.study.rest.util.RestUtils; import cz.cvut.kbss.study.security.SecurityConstants; import cz.cvut.kbss.study.service.PatientRecordService; +import cz.cvut.kbss.study.util.Constants; import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.io.InputStreamResource; import org.springframework.data.domain.Page; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriComponentsBuilder; +import java.io.InputStream; import java.util.List; +import java.util.Optional; @RestController @PreAuthorize("hasRole('" + SecurityConstants.ROLE_USER + "')") @@ -42,10 +35,12 @@ public class PatientRecordController extends BaseController { private final PatientRecordService recordService; private final ApplicationEventPublisher eventPublisher; + private final HttpServletResponse httpServletResponse; - public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher) { + public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher, HttpServletResponse httpServletResponse) { this.recordService = recordService; this.eventPublisher = eventPublisher; + this.httpServletResponse = httpServletResponse; } @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)") @@ -62,15 +57,48 @@ public List getRecords( @PreAuthorize( "hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)") - @GetMapping(value = "/export", produces = MediaType.APPLICATION_JSON_VALUE) - public List exportRecords( + @GetMapping(value = "/export", produces = {MediaType.APPLICATION_JSON_VALUE, Constants.MEDIA_TYPE_EXCEL}) + public ResponseEntity exportRecords( @RequestParam(name = "institution", required = false) String institutionKey, - @RequestParam MultiValueMap params, + @RequestParam(required = false) MultiValueMap params, UriComponentsBuilder uriBuilder, HttpServletResponse response) { + + String exportType = Optional.ofNullable(params) + .map(p -> p.getFirst(Constants.EXPORT_TYPE_PARAM)) + .orElse(MediaType.APPLICATION_JSON_VALUE); + + return switch (exportType){ + case Constants.MEDIA_TYPE_EXCEL -> exportRecordsExcel(params, response); + case MediaType.APPLICATION_JSON_VALUE -> exportRecordsAsJson(institutionKey, params, uriBuilder, response); + default -> throw new IllegalArgumentException("Unsupported export type: " + exportType); + }; + } + + protected ResponseEntity> exportRecordsAsJson( + @RequestParam(name = "institution", required = false) String institutionKey, + @RequestParam MultiValueMap params, + UriComponentsBuilder uriBuilder, HttpServletResponse response){ final Page result = recordService.findAllFull(RecordFilterMapper.constructRecordFilter(params), - RestUtils.resolvePaging(params)); + RestUtils.resolvePaging(params)); eventPublisher.publishEvent(new PaginatedResultRetrievedEvent(this, uriBuilder, response, result)); - return result.getContent(); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(result.getContent()); + } + + public ResponseEntity exportRecordsExcel(@RequestParam(required = false) MultiValueMap params, HttpServletResponse response){ + RecordFilterParams filterParams = new RecordFilterParams(); + filterParams.setMinModifiedDate(null); + filterParams.setMaxModifiedDate(null); + RecordFilterMapper.constructRecordFilter(filterParams, params); + + InputStream stream = recordService.exportRecords(filterParams); + ContentDisposition contentDisposition = ContentDisposition.attachment().filename("export.xlsx").build(); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(Constants.MEDIA_TYPE_EXCEL)) + .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()) + .body(new InputStreamResource(stream)); + } @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isRecordInUsersInstitution(#key)") diff --git a/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java b/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java index a3473cd0..4b13df8a 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java +++ b/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java @@ -45,16 +45,18 @@ public class RecordFilterMapper { public static RecordFilterParams constructRecordFilter(String param, String value) { return constructRecordFilter(new LinkedMultiValueMap<>(Map.of(param, List.of(value)))); } - + public static RecordFilterParams constructRecordFilter(MultiValueMap params) { + final RecordFilterParams result = new RecordFilterParams(); + return constructRecordFilter(result, new LinkedMultiValueMap<>(params)); + } /** * Maps the specified parameters to a new {@link RecordFilterParams} instance. * * @param params Request parameters to map * @return New {@code RecordFilterParams} instance */ - public static RecordFilterParams constructRecordFilter(MultiValueMap params) { + public static RecordFilterParams constructRecordFilter(final RecordFilterParams result, MultiValueMap params) { Objects.requireNonNull(params); - final RecordFilterParams result = new RecordFilterParams(); getSingleValue(MIN_DATE_PARAM, params).ifPresent(s -> { try { result.setMinModifiedDate(LocalDate.parse(s)); diff --git a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java index 91322af1..ddd38c23 100644 --- a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java +++ b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java @@ -8,6 +8,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import java.io.InputStream; import java.util.List; public interface PatientRecordService extends BaseService { @@ -77,4 +78,6 @@ public interface PatientRecordService extends BaseService { * repository */ RecordImportResult importRecords(List records, RecordPhase targetPhase); + + InputStream exportRecords(RecordFilterParams filters); } diff --git a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java index 8aaf2c9c..7a60200f 100644 --- a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java +++ b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java @@ -9,6 +9,7 @@ import cz.cvut.kbss.study.persistence.dao.OwlKeySupportingDao; import cz.cvut.kbss.study.persistence.dao.PatientRecordDao; import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; +import cz.cvut.kbss.study.service.ExcelRecordExporter; import cz.cvut.kbss.study.service.PatientRecordService; import cz.cvut.kbss.study.service.UserService; import cz.cvut.kbss.study.service.security.SecurityUtils; @@ -20,6 +21,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.InputStream; import java.util.Date; import java.util.List; import java.util.Objects; @@ -36,12 +38,14 @@ public class RepositoryPatientRecordService extends KeySupportingRepositoryServi private final SecurityUtils securityUtils; private final UserService userService; + private final ExcelRecordExporter excelRecordExporter; public RepositoryPatientRecordService(PatientRecordDao recordDao, SecurityUtils securityUtils, - UserService userService) { + UserService userService, ExcelRecordExporter excelRecordExporter) { this.recordDao = recordDao; this.securityUtils = securityUtils; this.userService = userService; + this.excelRecordExporter = excelRecordExporter; } @Override @@ -125,4 +129,9 @@ public RecordImportResult importRecords(List records, RecordPhase LOG.debug("Importing records to target phase '{}'.", targetPhase); return importRecordsImpl(records, Optional.ofNullable(targetPhase)); } + + @Override + public InputStream exportRecords(RecordFilterParams filters) { + return excelRecordExporter.exportRecords(filters); + } } diff --git a/src/main/java/cz/cvut/kbss/study/util/Constants.java b/src/main/java/cz/cvut/kbss/study/util/Constants.java index 45369d2f..58289c76 100644 --- a/src/main/java/cz/cvut/kbss/study/util/Constants.java +++ b/src/main/java/cz/cvut/kbss/study/util/Constants.java @@ -53,9 +53,19 @@ private Constants() { */ public static final String SORT_PARAM = "sort"; + /** + * Name of the request parameter specifying record the export type. + */ + public static final String EXPORT_TYPE_PARAM = "exportType"; + /** * Represents the X-Total-Count HTTP header used to convey the total number of items in paged or otherwise * restricted response. */ public static final String X_TOTAL_COUNT_HEADER = "X-Total-Count"; + + /** + * Excel MIME type + */ + public static final String MEDIA_TYPE_EXCEL = "application/vnd.ms-excel"; } From 397569dc78c91127feef544ba22b7d69dc234d93 Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Wed, 10 Jul 2024 10:54:34 +0200 Subject: [PATCH 05/10] [Fix partially kbss-cvut/record-manager-ui#36] Remove injected httpServletResponse --- .../java/cz/cvut/kbss/study/rest/PatientRecordController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java index ab89c7ea..31234eeb 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java @@ -35,12 +35,10 @@ public class PatientRecordController extends BaseController { private final PatientRecordService recordService; private final ApplicationEventPublisher eventPublisher; - private final HttpServletResponse httpServletResponse; - public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher, HttpServletResponse httpServletResponse) { + public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher) { this.recordService = recordService; this.eventPublisher = eventPublisher; - this.httpServletResponse = httpServletResponse; } @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)") From 1918810357e24a9dfa848ca4b1c80d9d0c20ffa1 Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Wed, 10 Jul 2024 10:57:00 +0200 Subject: [PATCH 06/10] [Fix partially kbss-cvut/record-manager-ui#36] Consider Accept header when extracting exportType from request. exportType uri parameter takes precedence over Accept header. --- .../study/rest/PatientRecordController.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java index 31234eeb..03bae9c5 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java @@ -13,6 +13,7 @@ import cz.cvut.kbss.study.security.SecurityConstants; import cz.cvut.kbss.study.service.PatientRecordService; import cz.cvut.kbss.study.util.Constants; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.io.InputStreamResource; @@ -24,8 +25,10 @@ import org.springframework.web.util.UriComponentsBuilder; import java.io.InputStream; +import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; @RestController @PreAuthorize("hasRole('" + SecurityConstants.ROLE_USER + "')") @@ -59,13 +62,19 @@ public List getRecords( public ResponseEntity exportRecords( @RequestParam(name = "institution", required = false) String institutionKey, @RequestParam(required = false) MultiValueMap params, - UriComponentsBuilder uriBuilder, HttpServletResponse response) { - - String exportType = Optional.ofNullable(params) - .map(p -> p.getFirst(Constants.EXPORT_TYPE_PARAM)) - .orElse(MediaType.APPLICATION_JSON_VALUE); - - return switch (exportType){ + UriComponentsBuilder uriBuilder, HttpServletRequest request, HttpServletResponse response) { + MediaType exportType = Stream.of( + Optional.ofNullable(params).map(p -> p.getFirst(Constants.EXPORT_TYPE_PARAM)), + Optional.ofNullable(request.getHeader(HttpHeaders.ACCEPT)) + ).filter(Optional::isPresent) + .findFirst() + .orElse(Optional.empty()) + .flatMap(s -> MediaType.parseMediaTypes(s).stream() + .max(Comparator.comparing(MediaType::getQualityValue)) + ).orElse(MediaType.APPLICATION_JSON) + .removeQualityValue(); + + return switch (exportType.toString()){ case Constants.MEDIA_TYPE_EXCEL -> exportRecordsExcel(params, response); case MediaType.APPLICATION_JSON_VALUE -> exportRecordsAsJson(institutionKey, params, uriBuilder, response); default -> throw new IllegalArgumentException("Unsupported export type: " + exportType); From 0b985cdefe9022e83db64f7431bc64c6399d8bd6 Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Wed, 10 Jul 2024 11:19:12 +0200 Subject: [PATCH 07/10] [Fix partially kbss-cvut/record-manager-ui#36] Refactor handling of institution request parameter. - remove unnecessary annotations from method parameters --- .../cz/cvut/kbss/study/rest/PatientRecordController.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java index 03bae9c5..493c3f7a 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java @@ -76,14 +76,13 @@ public ResponseEntity exportRecords( return switch (exportType.toString()){ case Constants.MEDIA_TYPE_EXCEL -> exportRecordsExcel(params, response); - case MediaType.APPLICATION_JSON_VALUE -> exportRecordsAsJson(institutionKey, params, uriBuilder, response); + case MediaType.APPLICATION_JSON_VALUE -> exportRecordsAsJson(params, uriBuilder, response); default -> throw new IllegalArgumentException("Unsupported export type: " + exportType); }; } protected ResponseEntity> exportRecordsAsJson( - @RequestParam(name = "institution", required = false) String institutionKey, - @RequestParam MultiValueMap params, + MultiValueMap params, UriComponentsBuilder uriBuilder, HttpServletResponse response){ final Page result = recordService.findAllFull(RecordFilterMapper.constructRecordFilter(params), RestUtils.resolvePaging(params)); @@ -93,7 +92,7 @@ protected ResponseEntity> exportRecordsAsJson( .body(result.getContent()); } - public ResponseEntity exportRecordsExcel(@RequestParam(required = false) MultiValueMap params, HttpServletResponse response){ + public ResponseEntity exportRecordsExcel(MultiValueMap params, HttpServletResponse response){ RecordFilterParams filterParams = new RecordFilterParams(); filterParams.setMinModifiedDate(null); filterParams.setMaxModifiedDate(null); @@ -105,7 +104,6 @@ public ResponseEntity exportRecordsExcel(@RequestParam(requ .contentType(MediaType.parseMediaType(Constants.MEDIA_TYPE_EXCEL)) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()) .body(new InputStreamResource(stream)); - } @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isRecordInUsersInstitution(#key)") From d05e5dc8e2e63547b8bccbcb11ac8dd54dd4d248 Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Wed, 10 Jul 2024 15:03:44 +0200 Subject: [PATCH 08/10] [Fix partially kbss-cvut/record-manager-ui#36] Implement paging request parameters support for excel export --- .../persistence/dao/PatientRecordDao.java | 39 ++- .../study/rest/PatientRecordController.java | 16 +- ...xporter.java => ExcelRecordConverter.java} | 15 +- .../study/service/PatientRecordService.java | 10 +- .../RepositoryPatientRecordService.java | 13 +- .../resources/query/find-raw-records.sparql | 245 ++++++++---------- 6 files changed, 176 insertions(+), 162 deletions(-) rename src/main/java/cz/cvut/kbss/study/service/{ExcelRecordExporter.java => ExcelRecordConverter.java} (92%) diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java index c901ddca..a64d6e19 100644 --- a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java +++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java @@ -42,7 +42,7 @@ public class PatientRecordDao extends OwlKeySupportingDao { public static final String FIND_ALL_RAW_PATIENT_RECORDS = "find-raw-records.sparql"; - public static final String RAW_RECORDS_FILTER_CLAUSE_PATTERN = "###FILTER_CLAUSES###"; + public static final String RECORDS_CLAUSE_TEMPLATE_VAR = "###RECORD_CLAUSE###"; public PatientRecordDao(EntityManager em) { super(PatientRecord.class, em); @@ -243,17 +243,40 @@ private Page findRecords(RecordFilterParams filters, Pageable pageSpec, C return new PageImpl<>(records, pageSpec, totalCount); } - public List findAllRecordsRaw(RecordFilterParams filters){ + public Page findAllRecordsRaw(RecordFilterParams filters, Pageable pageSpec){ final Map queryParams = new HashMap<>(); - final String filterClauseExtension = mapParamsToQuery(filters, queryParams); - final String queryString = Utils.loadQuery(FIND_ALL_RAW_PATIENT_RECORDS) - .replaceAll(RAW_RECORDS_FILTER_CLAUSE_PATTERN, filterClauseExtension); - Query query = em.createNativeQuery(queryString, RawRecord.class.getSimpleName()); + final String whereClause = constructWhereClause(filters, queryParams); + + final String queryStringNoPaging = Utils.loadQuery(FIND_ALL_RAW_PATIENT_RECORDS) + .replaceFirst(RECORDS_CLAUSE_TEMPLATE_VAR, whereClause); + final String queryString = queryStringNoPaging + (pageSpec.isPaged() + ? resolveOrderBy(pageSpec.getSortOr(RecordSort.defaultSort())) + : "" + ); + + Query query = em.createNativeQuery(queryString, RawRecord.class.getSimpleName()); queryParams.forEach(query::setParameter); - return query.getResultList(); + + if (pageSpec.isPaged()) { + query.setFirstResult((int) pageSpec.getOffset()); + query.setMaxResults(pageSpec.getPageSize()); + } + setQueryParameters(query, queryParams); + List result = query.getResultList(); + Integer totalCount = result.size(); + if(pageSpec.isPaged()){ + TypedQuery countQuery = em.createNativeQuery( + "SELECT (COUNT(?r) as ?cnt) WHERE {%s}".formatted(whereClause), + Integer.class); + + setQueryParameters(countQuery, queryParams); + totalCount = countQuery.getSingleResult(); + } + + return new PageImpl<>(result, pageSpec, totalCount); } - private void setQueryParameters(TypedQuery query, Map queryParams) { + private void setQueryParameters(Query query, Map queryParams) { query.setParameter("type", typeUri) .setParameter("hasPhase", URI.create(Vocabulary.s_p_has_phase)) .setParameter("hasFormTemplate", URI.create(Vocabulary.s_p_has_form_template)) diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java index 493c3f7a..7a5ad4a7 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java @@ -5,12 +5,14 @@ import cz.cvut.kbss.study.exception.NotFoundException; import cz.cvut.kbss.study.model.PatientRecord; import cz.cvut.kbss.study.model.RecordPhase; +import cz.cvut.kbss.study.model.export.RawRecord; import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent; import cz.cvut.kbss.study.rest.exception.BadRequestException; import cz.cvut.kbss.study.rest.util.RecordFilterMapper; import cz.cvut.kbss.study.rest.util.RestUtils; import cz.cvut.kbss.study.security.SecurityConstants; +import cz.cvut.kbss.study.service.ExcelRecordConverter; import cz.cvut.kbss.study.service.PatientRecordService; import cz.cvut.kbss.study.util.Constants; import jakarta.servlet.http.HttpServletRequest; @@ -38,10 +40,12 @@ public class PatientRecordController extends BaseController { private final PatientRecordService recordService; private final ApplicationEventPublisher eventPublisher; + private final ExcelRecordConverter excelRecordConverter; - public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher) { + public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher, ExcelRecordConverter excelRecordConverter) { this.recordService = recordService; this.eventPublisher = eventPublisher; + this.excelRecordConverter = excelRecordConverter; } @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)") @@ -75,7 +79,7 @@ public ResponseEntity exportRecords( .removeQualityValue(); return switch (exportType.toString()){ - case Constants.MEDIA_TYPE_EXCEL -> exportRecordsExcel(params, response); + case Constants.MEDIA_TYPE_EXCEL -> exportRecordsExcel(params, uriBuilder, response); case MediaType.APPLICATION_JSON_VALUE -> exportRecordsAsJson(params, uriBuilder, response); default -> throw new IllegalArgumentException("Unsupported export type: " + exportType); }; @@ -92,13 +96,17 @@ protected ResponseEntity> exportRecordsAsJson( .body(result.getContent()); } - public ResponseEntity exportRecordsExcel(MultiValueMap params, HttpServletResponse response){ + public ResponseEntity exportRecordsExcel(MultiValueMap params, + UriComponentsBuilder uriBuilder, HttpServletResponse response){ RecordFilterParams filterParams = new RecordFilterParams(); filterParams.setMinModifiedDate(null); filterParams.setMaxModifiedDate(null); RecordFilterMapper.constructRecordFilter(filterParams, params); - InputStream stream = recordService.exportRecords(filterParams); + Page result = recordService.exportRecords(filterParams, RestUtils.resolvePaging(params)); + + InputStream stream = excelRecordConverter.convert(result.getContent()); + eventPublisher.publishEvent(new PaginatedResultRetrievedEvent(this, uriBuilder, response, result)); ContentDisposition contentDisposition = ContentDisposition.attachment().filename("export.xlsx").build(); return ResponseEntity.ok() .contentType(MediaType.parseMediaType(Constants.MEDIA_TYPE_EXCEL)) diff --git a/src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java b/src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java similarity index 92% rename from src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java rename to src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java index 72f12184..f4fa7877 100644 --- a/src/main/java/cz/cvut/kbss/study/service/ExcelRecordExporter.java +++ b/src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java @@ -4,8 +4,6 @@ import cz.cvut.kbss.study.model.export.Path; import cz.cvut.kbss.study.model.export.RawRecord; import cz.cvut.kbss.study.persistence.dao.CodeListValuesDao; -import cz.cvut.kbss.study.persistence.dao.PatientRecordDao; -import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; import cz.cvut.kbss.study.util.Utils; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; @@ -22,20 +20,18 @@ import java.util.stream.Stream; @Service -public class ExcelRecordExporter { +public class ExcelRecordConverter { - private final PatientRecordDao patientRecordDao; private final CodeListValuesDao codeListValuesDao; - public ExcelRecordExporter(PatientRecordDao patientRecordDao, CodeListValuesDao codeListValuesDao) { - this.patientRecordDao = patientRecordDao; + public ExcelRecordConverter(CodeListValuesDao codeListValuesDao) { this.codeListValuesDao = codeListValuesDao; } - public InputStream exportRecords(RecordFilterParams filters){ + public InputStream convert(List rawRecords){ try { XSSFWorkbook workbook = new XSSFWorkbook(Utils.class.getClassLoader().getResourceAsStream("templates/record-export-template.xlsx")); - List exportRecords = findExportRecords(filters); + List exportRecords = findExportRecordsData(rawRecords); addDataToExcel(workbook, exportRecords); ByteArrayOutputStream output = new ByteArrayOutputStream(); workbook.write(output); @@ -45,8 +41,7 @@ public InputStream exportRecords(RecordFilterParams filters){ } } - private List findExportRecords(RecordFilterParams filters){ - List rawRecords = patientRecordDao.findAllRecordsRaw(filters); + private List findExportRecordsData(List rawRecords){ Map translatorMap = new HashMap<>(); List paths = codeListValuesDao.getBroaderPath(rawRecords.stream().map(r -> r.getAc_comp()).collect(Collectors.toSet())); Set uris = rawRecords.stream().flatMap(r -> Stream.of( diff --git a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java index ddd38c23..83eb8965 100644 --- a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java +++ b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java @@ -4,11 +4,11 @@ import cz.cvut.kbss.study.dto.RecordImportResult; import cz.cvut.kbss.study.model.PatientRecord; import cz.cvut.kbss.study.model.RecordPhase; +import cz.cvut.kbss.study.model.export.RawRecord; import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import java.io.InputStream; import java.util.List; public interface PatientRecordService extends BaseService { @@ -79,5 +79,11 @@ public interface PatientRecordService extends BaseService { */ RecordImportResult importRecords(List records, RecordPhase targetPhase); - InputStream exportRecords(RecordFilterParams filters); + /** + * + * @param filters Record filtering criteria + * @param pageSpec Specification of page and sorting to retrieve + * @return List of matching records + */ + Page exportRecords(RecordFilterParams filters, Pageable pageSpec); } diff --git a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java index 7a60200f..d524a105 100644 --- a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java +++ b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java @@ -6,10 +6,10 @@ import cz.cvut.kbss.study.model.PatientRecord; import cz.cvut.kbss.study.model.RecordPhase; import cz.cvut.kbss.study.model.User; +import cz.cvut.kbss.study.model.export.RawRecord; import cz.cvut.kbss.study.persistence.dao.OwlKeySupportingDao; import cz.cvut.kbss.study.persistence.dao.PatientRecordDao; import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams; -import cz.cvut.kbss.study.service.ExcelRecordExporter; import cz.cvut.kbss.study.service.PatientRecordService; import cz.cvut.kbss.study.service.UserService; import cz.cvut.kbss.study.service.security.SecurityUtils; @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.InputStream; import java.util.Date; import java.util.List; import java.util.Objects; @@ -38,14 +37,14 @@ public class RepositoryPatientRecordService extends KeySupportingRepositoryServi private final SecurityUtils securityUtils; private final UserService userService; - private final ExcelRecordExporter excelRecordExporter; + private final PatientRecordDao patientRecordDao; public RepositoryPatientRecordService(PatientRecordDao recordDao, SecurityUtils securityUtils, - UserService userService, ExcelRecordExporter excelRecordExporter) { + UserService userService, PatientRecordDao patientRecordDao) { this.recordDao = recordDao; this.securityUtils = securityUtils; this.userService = userService; - this.excelRecordExporter = excelRecordExporter; + this.patientRecordDao = patientRecordDao; } @Override @@ -131,7 +130,7 @@ public RecordImportResult importRecords(List records, RecordPhase } @Override - public InputStream exportRecords(RecordFilterParams filters) { - return excelRecordExporter.exportRecords(filters); + public Page exportRecords(RecordFilterParams filters, Pageable pageSpec){ + return patientRecordDao.findAllRecordsRaw(filters, pageSpec); } } diff --git a/src/main/resources/query/find-raw-records.sparql b/src/main/resources/query/find-raw-records.sparql index cfb8870b..902cf11a 100644 --- a/src/main/resources/query/find-raw-records.sparql +++ b/src/main/resources/query/find-raw-records.sparql @@ -16,30 +16,15 @@ PREFIX rm: PREFIX dcterms: PREFIX spif: -SELECT (?rec as ?uri) -?created ?label ?phase ?institution (IRI(str(?formTemplate)) as ?aircraftType) (str(?fus) as ?fuselage) ?ac_comp (str(?failDateStr) as ?failDate) +SELECT ?r (?r as ?uri) +?created ?label ?lastModified ?phase ?institution ?institutionKey ?formTemplate (IRI(str(?formTemplate)) as ?aircraftType) (str(?fus) as ?fuselage) ?ac_comp (str(?failDateStr) as ?failDate) ?flightHours ?numberOfAirframeOverhauls ?classificationOfOccurrence ?failureAscertainmentCircumstances (?repeatedFailureCode as ?repeatedFailure) ?failureCause ?consequence ?mission ?repair (str(?repairDurationStr) as ?repairDuration) ?averageNumberOfMenDuringRepairment ?failureDescription ?descriptionOfCorrectiveAction ?yearOfProductionOfDefectiveEquipment ?numberOfOverhaulsOfDefectiveEquipment ?serialNoOf ?notes ?fhaEvent { + ###RECORD_CLAUSE### - ?rec a rm:patient-record; - dcterms:created ?created; - rdfs:label ?label . - ?rec rm:was-treated-at ?institution. - ?institution rm:key ?institutionKey . - - ?rec rm:has-form-template ?formTemplate. - - OPTIONAL { - ?rec dcterms:modified ?lastModified . - } - - OPTIONAL{ - ?rec rm:has-phase ?phase. #rm:completed-record-phase. - } - - ?rec rm:has-question ?f. + ?r rm:has-question ?f. ?f doc:has_related_question ?s1. - ?f doc:has_related_question ?s2 + ?f doc:has_related_question ?s2. FILTER(?s2 != ?s1) ?s2 doc:has_related_question ?fhaEventQ. ?fhaEventQ form:has-question-origin avamod:fha-event. @@ -104,117 +89,115 @@ SELECT (?rec as ?uri) ?s1 doc:has_related_question ?notesQ. ?notesQ form:has-question-origin avamod:notes. - - # avamod:fuselage-no - ?FUSq doc:has_answer ?FUSa. - ?FUSa doc:has_data_value ?fus. - - #avamod:system-equipment-block-part-l-39ng.a1 - # ?comp - label of general component/system - # ?comp_iri - iri of component type specific to the aircraft - ?Cq doc:has_answer ?Ca. - ?Ca doc:has_object_value ?ac_comp. - - #avamod:date-of-failure-ascertainment - # ?failDate - creation date filter - ?failDateq doc:has_answer ?failDatea. - ?failDatea doc:has_data_value ?failDateStr. - # BIND(spif:parseDate(?failDateStr, "DD-MM-yyyy") as ?failDate) - # FILTER((!BOUND(?createdBefore) || ?createdBefore < ?failDate ) && (!BOUND(?createdAfter) || ?createdAfter >= ?failDate )) - - #avamod:flight-hours-of-airframe-since-the-service-beginning - - ?FHq doc:has_answer ?FHa. - ?FHa doc:has_data_value ?flightHours. - - #avamod:classification-of-occurrence - ?classificationOfOccurrenceQ doc:has_answer ?classificationOfOccurrenceA. - ?classificationOfOccurrenceA doc:has_object_value ?classificationOfOccurrence. - - #avamod:failure-ascertainment-circumstances - - ?failureAscertainmentCircumstancesQ doc:has_answer ?failureAscertainmentCircumstancesA. - ?failureAscertainmentCircumstancesA doc:has_object_value ?failureAscertainmentCircumstances. - - # during flight - # BIND(IF(BOUND(?failureAscertainmentCircumstances) && ?failureAscertainmentCircumstances = avadom:during-flight, 1, 0) as ?duringFlight) - - #avamod:repeated-failure - ?repeatedFailureQ doc:has_answer ?repeatedFailureA. - ?repeatedFailureA doc:has_object_value ?repeatedFailureCode. - - OPTIONAL{ - #avamod:failure-cause - ?failureCauseQ doc:has_answer ?failureCauseA. - ?failureCauseA doc:has_object_value ?failureCause. - } - #avamod:consequence - - ?consequenceQ doc:has_answer ?consequenceA. - ?consequenceA doc:has_object_value ?consequence. - - #avamod:mission - ?missionQ doc:has_answer ?missionA. - ?missionA doc:has_object_value ?mission. - - #avamod:repair - ?repairQ doc:has_answer ?repairA. - ?repairA doc:has_object_value ?repair. - - #avamod:repair-duration - ?repairDurationQ doc:has_answer ?repairDurationA. - ?repairDurationA doc:has_data_value ?repairDurationStr. - - #avamod:average-number-of-men-during-repairment - ?averageNumberOfMenDuringRepairmentQ doc:has_answer ?averageNumberOfMenDuringRepairmentA. - ?averageNumberOfMenDuringRepairmentA doc:has_data_value ?averageNumberOfMenDuringRepairmentStr. - BIND(xsd:decimal(str(?averageNumberOfMenDuringRepairmentStr)) as ?averageNumberOfMenDuringRepairment) - - #avamod:failure-description - ?failureDescriptionQ doc:has_answer ?failureDescriptionA. - ?failureDescriptionA doc:has_data_value ?failureDescription. - - #avamod:description-of-corrective-action - ?descriptionOfCorrectiveActionQ doc:has_answer ?descriptionOfCorrectiveActionA. - ?descriptionOfCorrectiveActionA doc:has_data_value ?descriptionOfCorrectiveAction. - OPTIONAL { - #avamod:number-of-airframe-overhauls - ?numberOfAirframeOverhaulsQ doc:has_answer ?numberOfAirframeOverhaulsA. - ?numberOfAirframeOverhaulsA doc:has_data_value ?numberOfAirframeOverhaulsStr. - BIND(xsd:integer(str(?numberOfAirframeOverhaulsStr)) as ?numberOfAirframeOverhauls) - } - - OPTIONAL { - #avamod:year-of-production-of-defective-equipment - ?yearOfProductionOfDefectiveEquipmentQ doc:has_answer ?yearOfProductionOfDefectiveEquipmentA. - ?yearOfProductionOfDefectiveEquipmentA doc:has_data_value ?yearOfProductionOfDefectiveEquipment.# TODO - transform to more suitable datatype - FILTER(str(spif:trim(?yearOfProductionOfDefectiveEquipment)) != "" ) - } - - OPTIONAL { - #avamod:number-of-overhauls-of-defective-equipment - ?numberOfOverhaulsOfDefectiveEquipmentQ doc:has_answer ?numberOfOverhaulsOfDefectiveEquipmentA. - ?numberOfOverhaulsOfDefectiveEquipmentA doc:has_data_value ?numberOfOverhaulsOfDefectiveEquipment.# TODO - transform to more suitable datatype - FILTER(str(spif:trim(?numberOfOverhaulsOfDefectiveEquipment)) != "" ) -# } -# OPTIONAL{ -# #avamod:serial-no-of -# ?serialNoOfQ doc:has_answer ?serialNoOfA. -# ?serialNoOfA doc:has_data_value ?serialNoOf. - } - OPTIONAL{ - #avamod:notes - ?notesQ doc:has_answer ?notesA. - ?notesA doc:has_data_value ?notes. - FILTER(str(spif:trim(?notes)) != "" ) + # avamod:fuselage-no + ?FUSq doc:has_answer ?FUSa. + ?FUSa doc:has_data_value ?fus. + + #avamod:system-equipment-block-part-l-39ng.a1 + # ?comp - label of general component/system + # ?comp_iri - iri of component type specific to the aircraft + ?Cq doc:has_answer ?Ca. + ?Ca doc:has_object_value ?ac_comp. + + #avamod:date-of-failure-ascertainment + # ?failDate - creation date filter + ?failDateq doc:has_answer ?failDatea. + ?failDatea doc:has_data_value ?failDateStr. + # BIND(spif:parseDate(?failDateStr, "DD-MM-yyyy") as ?failDate) + # FILTER((!BOUND(?createdBefore) || ?createdBefore < ?failDate ) && (!BOUND(?createdAfter) || ?createdAfter >= ?failDate )) + + #avamod:flight-hours-of-airframe-since-the-service-beginning + + ?FHq doc:has_answer ?FHa. + ?FHa doc:has_data_value ?flightHours. + + #avamod:classification-of-occurrence + ?classificationOfOccurrenceQ doc:has_answer ?classificationOfOccurrenceA. + ?classificationOfOccurrenceA doc:has_object_value ?classificationOfOccurrence. + + #avamod:failure-ascertainment-circumstances + + ?failureAscertainmentCircumstancesQ doc:has_answer ?failureAscertainmentCircumstancesA. + ?failureAscertainmentCircumstancesA doc:has_object_value ?failureAscertainmentCircumstances. + + # during flight + # BIND(IF(BOUND(?failureAscertainmentCircumstances) && ?failureAscertainmentCircumstances = avadom:during-flight, 1, 0) as ?duringFlight) + + #avamod:repeated-failure + ?repeatedFailureQ doc:has_answer ?repeatedFailureA. + ?repeatedFailureA doc:has_object_value ?repeatedFailureCode. + + OPTIONAL{ + #avamod:failure-cause + ?failureCauseQ doc:has_answer ?failureCauseA. + ?failureCauseA doc:has_object_value ?failureCause. + } + #avamod:consequence + + ?consequenceQ doc:has_answer ?consequenceA. + ?consequenceA doc:has_object_value ?consequence. + + #avamod:mission + ?missionQ doc:has_answer ?missionA. + ?missionA doc:has_object_value ?mission. + + #avamod:repair + ?repairQ doc:has_answer ?repairA. + ?repairA doc:has_object_value ?repair. + + #avamod:repair-duration + ?repairDurationQ doc:has_answer ?repairDurationA. + ?repairDurationA doc:has_data_value ?repairDurationStr. + + #avamod:average-number-of-men-during-repairment + ?averageNumberOfMenDuringRepairmentQ doc:has_answer ?averageNumberOfMenDuringRepairmentA. + ?averageNumberOfMenDuringRepairmentA doc:has_data_value ?averageNumberOfMenDuringRepairmentStr. + BIND(xsd:decimal(str(?averageNumberOfMenDuringRepairmentStr)) as ?averageNumberOfMenDuringRepairment) + + #avamod:failure-description + ?failureDescriptionQ doc:has_answer ?failureDescriptionA. + ?failureDescriptionA doc:has_data_value ?failureDescription. + + #avamod:description-of-corrective-action + ?descriptionOfCorrectiveActionQ doc:has_answer ?descriptionOfCorrectiveActionA. + ?descriptionOfCorrectiveActionA doc:has_data_value ?descriptionOfCorrectiveAction. + + OPTIONAL { + #avamod:number-of-airframe-overhauls + ?numberOfAirframeOverhaulsQ doc:has_answer ?numberOfAirframeOverhaulsA. + ?numberOfAirframeOverhaulsA doc:has_data_value ?numberOfAirframeOverhaulsStr. + BIND(xsd:integer(str(?numberOfAirframeOverhaulsStr)) as ?numberOfAirframeOverhauls) + } + + OPTIONAL { + #avamod:year-of-production-of-defective-equipment + ?yearOfProductionOfDefectiveEquipmentQ doc:has_answer ?yearOfProductionOfDefectiveEquipmentA. + ?yearOfProductionOfDefectiveEquipmentA doc:has_data_value ?yearOfProductionOfDefectiveEquipment.# TODO - transform to more suitable datatype + FILTER(str(spif:trim(?yearOfProductionOfDefectiveEquipment)) != "" ) + } + + OPTIONAL { + #avamod:number-of-overhauls-of-defective-equipment + ?numberOfOverhaulsOfDefectiveEquipmentQ doc:has_answer ?numberOfOverhaulsOfDefectiveEquipmentA. + ?numberOfOverhaulsOfDefectiveEquipmentA doc:has_data_value ?numberOfOverhaulsOfDefectiveEquipment.# TODO - transform to more suitable datatype + FILTER(str(spif:trim(?numberOfOverhaulsOfDefectiveEquipment)) != "" ) + # } + # OPTIONAL{ + # #avamod:serial-no-of + # ?serialNoOfQ doc:has_answer ?serialNoOfA. + # ?serialNoOfA doc:has_data_value ?serialNoOf. + } + OPTIONAL{ + #avamod:notes + ?notesQ doc:has_answer ?notesA. + ?notesA doc:has_data_value ?notes. + FILTER(str(spif:trim(?notes)) != "" ) + } + OPTIONAL{ + #avamod:fha-event + ?fhaEventQ doc:has_answer ?fhaEventA. + ?fhaEventA doc:has_object_value ?fhaEvent. + } } - OPTIONAL{ - #avamod:fha-event - ?fhaEventQ doc:has_answer ?fhaEventA. - ?fhaEventA doc:has_object_value ?fhaEvent. - } - BIND (COALESCE(?lastModified, ?created) AS ?date) - - ###FILTER_CLAUSES### } From 077c00779f84d28ad6f1f9f04c72ec6d5d27b73a Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Wed, 10 Jul 2024 15:07:53 +0200 Subject: [PATCH 09/10] [Fix partially kbss-cvut/record-manager-ui#36] Export modified date --- .../cz/cvut/kbss/study/model/export/ExportRecord.java | 10 ++++++++++ .../cz/cvut/kbss/study/model/export/RawRecord.java | 10 ++++++++++ .../cvut/kbss/study/service/ExcelRecordConverter.java | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java b/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java index ef0c88e2..fd861648 100644 --- a/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java +++ b/src/main/java/cz/cvut/kbss/study/model/export/ExportRecord.java @@ -11,6 +11,8 @@ public class ExportRecord { private Date created; + private Date modified; + private String label; private String phase; @@ -83,6 +85,14 @@ public void setCreated(Date created) { this.created = created; } + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + public String getLabel() { return label; } diff --git a/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java b/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java index 25ce1c1f..843f5cde 100644 --- a/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java +++ b/src/main/java/cz/cvut/kbss/study/model/export/RawRecord.java @@ -18,6 +18,8 @@ public class RawRecord { @OWLDataProperty(iri = "http://created") private Date created; + @OWLDataProperty(iri = "http://modified") + private Date modified; @OWLDataProperty(iri = "http://label") private String label; @OWLObjectProperty(iri = "http://phase") @@ -87,6 +89,14 @@ public void setCreated(Date created) { this.created = created; } + public Date getModified() { + return modified; + } + + public void setModified(Date modified) { + this.modified = modified; + } + public String getLabel() { return label; } diff --git a/src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java b/src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java index f4fa7877..dfea8f4c 100644 --- a/src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java +++ b/src/main/java/cz/cvut/kbss/study/service/ExcelRecordConverter.java @@ -91,6 +91,7 @@ private List findExportRecordsData(List rawRecords){ er.setFuselage(r.getFuselage()); er.setUri(r.getUri()); er.setCreated(r.getCreated()); + er.setModified(r.getModified()); er.setLabel(r.getLabel()); er.setFailDate(r.getFailDate()); er.setFlightHours(r.getFlightHours()); @@ -119,7 +120,7 @@ private void addDataToExcel(XSSFWorkbook workbook, List data) thro r.createCell(2).setCellValue(rec.getCreated()); // r.createCell(3).setCellValue(rec.ed); // r.createCell(4).setCellValue(rec.); -// r.createCell(5).setCellValue(rec.); + r.createCell(5).setCellValue(rec.getModified()); r.createCell(6).setCellValue(rec.getLabel()); r.createCell(7).setCellValue(rec.getInstitution()); r.createCell(8).setCellValue(rec.getAircraftType()); From 483c5860a7fa9bec0e2c4a8d98940ed8f8069ec0 Mon Sep 17 00:00:00 2001 From: Bogdan Kostov Date: Wed, 10 Jul 2024 15:08:25 +0200 Subject: [PATCH 10/10] [Fix partially kbss-cvut/record-manager-ui#36] Update record-export-template.xlsx file --- .../templates/record-export-template.xlsx | Bin 25146 -> 22912 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/templates/record-export-template.xlsx b/src/main/resources/templates/record-export-template.xlsx index 9fcbe46401f7b577248b4848f9c535d951db17d8..942bcbecd830db8fed944c979a276b5ed8a8853b 100644 GIT binary patch delta 14434 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

iw*5Wd_t3vcA?4b02<4f7_@_;(uL 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