From 432a1c7d515f5bd0f6f7615a666612e5a8be3e29 Mon Sep 17 00:00:00 2001 From: SilverD3 Date: Mon, 2 Dec 2024 10:19:38 +0100 Subject: [PATCH 1/5] feat:OH2-419 - Add endpoints to map ORTHANC APIs | OH2-420 - Add Gateway for ORTHANC resources consuming --- .../org/isf/generaldata/OrthancConfig.java | 75 +++++++ .../isf/orthanc/client/OrthancAPIClient.java | 74 +++++++ .../client/OrthancFeignClientFactory.java | 89 ++++++++ .../org/isf/orthanc/model/BaseResponse.java | 98 +++++++++ .../org/isf/orthanc/model/FindRequest.java | 70 +++++++ .../isf/orthanc/model/FindRequestLevel.java | 30 +++ .../java/org/isf/orthanc/model/Instance.java | 127 ++++++++++++ .../isf/orthanc/model/InstanceResponse.java | 120 +++++++++++ .../java/org/isf/orthanc/model/Patient.java | 101 ++++++++++ .../java/org/isf/orthanc/model/Query.java | 77 +++++++ .../java/org/isf/orthanc/model/Series.java | 140 +++++++++++++ .../org/isf/orthanc/model/SeriesResponse.java | 87 ++++++++ .../java/org/isf/orthanc/model/Study.java | 155 ++++++++++++++ .../org/isf/orthanc/model/StudyResponse.java | 76 +++++++ .../service/OrthancAPIClientService.java | 190 ++++++++++++++++++ .../service/OrthancAPIClientServiceTest.java | 146 ++++++++++++++ src/test/resources/orthanc.properties | 5 + 17 files changed, 1660 insertions(+) create mode 100644 src/main/java/org/isf/generaldata/OrthancConfig.java create mode 100644 src/main/java/org/isf/orthanc/client/OrthancAPIClient.java create mode 100644 src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java create mode 100644 src/main/java/org/isf/orthanc/model/BaseResponse.java create mode 100644 src/main/java/org/isf/orthanc/model/FindRequest.java create mode 100644 src/main/java/org/isf/orthanc/model/FindRequestLevel.java create mode 100644 src/main/java/org/isf/orthanc/model/Instance.java create mode 100644 src/main/java/org/isf/orthanc/model/InstanceResponse.java create mode 100644 src/main/java/org/isf/orthanc/model/Patient.java create mode 100644 src/main/java/org/isf/orthanc/model/Query.java create mode 100644 src/main/java/org/isf/orthanc/model/Series.java create mode 100644 src/main/java/org/isf/orthanc/model/SeriesResponse.java create mode 100644 src/main/java/org/isf/orthanc/model/Study.java create mode 100644 src/main/java/org/isf/orthanc/model/StudyResponse.java create mode 100644 src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java create mode 100644 src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java create mode 100644 src/test/resources/orthanc.properties diff --git a/src/main/java/org/isf/generaldata/OrthancConfig.java b/src/main/java/org/isf/generaldata/OrthancConfig.java new file mode 100644 index 000000000..111dba202 --- /dev/null +++ b/src/main/java/org/isf/generaldata/OrthancConfig.java @@ -0,0 +1,75 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.generaldata; + +/** + * @author Silevester D. + * @since 1.15 + */ +public final class OrthancConfig extends ConfigurationProperties { + + private static final String FILE_PROPERTIES = "orthanc.properties"; + + /** + * Enable ORTHANC integration + */ + public static boolean ORTHANC_ENABLED; + + /** + * Base URL of the ORTHANC server + */ + public static String ORTHANC_BASE_URL; + + /** + * Name of the user to use for ORTHANC APIs calls + */ + public static String ORTHANC_USERNAME; + + /** + * Password of the user to use for ORTHANC APIs calls + */ + public static String ORTHANC_PASSWORD; + + /** + * Full URL of the ORTHANC explorer + */ + public static String ORTHANC_EXPLORER_URL; + + private static final boolean DEFAULT_ORTHANC_ENABLED = false; + private static final String DEFAULT_ORTHANC_BASE_URL = ""; + private static final String DEFAULT_ORTHANC_USERNAME = "admin"; + private static final String DEFAULT_ORTHANC_PASSWORD = "adminADMIN!123#"; + private static final String DEFAULT_ORTHANC_EXPLORER_URL = ""; + + private OrthancConfig(String fileProperties) { + super(fileProperties); + ORTHANC_ENABLED = myGetProperty("orthanc.enabled", DEFAULT_ORTHANC_ENABLED); + ORTHANC_BASE_URL = myGetProperty("orthanc.base-url", DEFAULT_ORTHANC_BASE_URL); + ORTHANC_USERNAME = myGetProperty("orthanc.username", DEFAULT_ORTHANC_USERNAME); + ORTHANC_PASSWORD = myGetProperty("orthanc.password", DEFAULT_ORTHANC_PASSWORD); + ORTHANC_EXPLORER_URL = myGetProperty("orthanc.explorer-url", DEFAULT_ORTHANC_EXPLORER_URL); + } + + public static void initialize() { + new OrthancConfig(FILE_PROPERTIES); + } +} diff --git a/src/main/java/org/isf/orthanc/client/OrthancAPIClient.java b/src/main/java/org/isf/orthanc/client/OrthancAPIClient.java new file mode 100644 index 000000000..711da96a7 --- /dev/null +++ b/src/main/java/org/isf/orthanc/client/OrthancAPIClient.java @@ -0,0 +1,74 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.client; + +import java.util.List; + +import org.isf.orthanc.model.FindRequest; +import org.isf.orthanc.model.InstanceResponse; +import org.isf.orthanc.model.SeriesResponse; +import org.isf.orthanc.model.StudyResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +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.RequestBody; + +import feign.Response; + +/** + * @author Silevester D. + * @since 1.15 + */ +@FeignClient(name = "orthanc-rest-api-client") +public interface OrthancAPIClient { + @GetMapping(value = "/tools/now", produces = MediaType.TEXT_PLAIN_VALUE) + String testConnection(); + + @GetMapping(value = "/studies?expand=true", produces = MediaType.APPLICATION_JSON_VALUE) + List getAllStudies(); + + @PostMapping(value = "/tools/find", produces = MediaType.APPLICATION_JSON_VALUE) + List getPatientStudiesById(@RequestBody FindRequest request); + + @GetMapping(value = "/patients/{id}/studies", produces = MediaType.APPLICATION_JSON_VALUE) + List getPatientStudiesByUuid(@PathVariable("id") String patientUuid); + + @GetMapping(value = "/studies/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + StudyResponse getStudyById(@PathVariable("id") String id); + + @GetMapping(value = "/studies/{id}/series", produces = MediaType.APPLICATION_JSON_VALUE) + List getStudySeries(@PathVariable("id") String id); + + @GetMapping(value = "/series/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + SeriesResponse getSeriesById(@PathVariable("id") String id); + + @GetMapping(value = "/series/{id}/instances", produces = MediaType.APPLICATION_JSON_VALUE) + List getSeriesInstances(@PathVariable("id") String id); + + @GetMapping(value = "/instances/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + InstanceResponse getInstanceById(@PathVariable("id") String id); + + @GetMapping(value = "/instances/{id}/preview", produces = MediaType.IMAGE_PNG_VALUE) + Response getInstancePreview(@PathVariable("id") String id); +} \ No newline at end of file diff --git a/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java b/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java new file mode 100644 index 000000000..8e948c417 --- /dev/null +++ b/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java @@ -0,0 +1,89 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.client; + +import org.isf.generaldata.OrthancConfig; +import org.isf.utils.exception.OHServiceException; +import org.isf.utils.exception.model.OHExceptionMessage; +import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.stereotype.Component; + +import feign.Feign; +import feign.Logger; +import feign.auth.BasicAuthRequestInterceptor; +import feign.gson.GsonDecoder; +import feign.gson.GsonEncoder; +import feign.slf4j.Slf4jLogger; + +/** + * @author Silevester D. + * @since 1.15 + */ +@Component +public class OrthancFeignClientFactory { + + private OrthancAPIClient client; + + public OrthancFeignClientFactory() { + OrthancConfig.initialize(); + } + + /** + * Create a client to perform request + * + * @return an instance of {@link OrthancAPIClient} + * @throws OHServiceException When ORTHANC not properly configured + */ + public OrthancAPIClient createClient() throws OHServiceException { + if (!validateConfiguration()) { + throw new OHServiceException(new OHExceptionMessage("ORTHANC server is not properly configured")); + } + + if (client != null) return client; + + client = Feign.builder() + .encoder(new GsonEncoder()) + .decoder(new GsonDecoder()) + .requestInterceptor(new BasicAuthRequestInterceptor(OrthancConfig.ORTHANC_USERNAME, OrthancConfig.ORTHANC_PASSWORD)) + .logger(new Slf4jLogger(OrthancAPIClient.class)) + .logLevel(Logger.Level.FULL) + .contract(new SpringMvcContract()) + .target(OrthancAPIClient.class, OrthancConfig.ORTHANC_BASE_URL); + + return client; + } + + /** + * Validate ORTHANC configuration + * This method only ensures that necessary parameters have been set + * + * @return true if the configuration is valid, false otherwise + */ + public boolean validateConfiguration() { + return OrthancConfig.ORTHANC_BASE_URL != null + && !OrthancConfig.ORTHANC_BASE_URL.isEmpty() + && OrthancConfig.ORTHANC_USERNAME != null + && !OrthancConfig.ORTHANC_USERNAME.isEmpty() + && OrthancConfig.ORTHANC_PASSWORD != null + && !OrthancConfig.ORTHANC_PASSWORD.isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/org/isf/orthanc/model/BaseResponse.java b/src/main/java/org/isf/orthanc/model/BaseResponse.java new file mode 100644 index 000000000..1ba51fdf3 --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/BaseResponse.java @@ -0,0 +1,98 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class BaseResponse { + @SerializedName("ID") + private String id; + + @SerializedName("IsStable") + private Boolean isStable; + + @SerializedName("Type") + private String objectType; + + @SerializedName("Labels") + private List labels; + + @SerializedName("LastUpdate") + private String lastUpdate; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean getStable() { + return isStable; + } + + public void setStable(Boolean stable) { + isStable = stable; + } + + public String getObjectType() { + return objectType; + } + + public void setObjectType(String objectType) { + this.objectType = objectType; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + public String getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(String lastUpdate) { + this.lastUpdate = lastUpdate; + } + + /** + * Parse last update date into LocalDateTime + * + * @return {@link LocalDateTime} instance + */ + public LocalDateTime getLastUpdateInstance() { + return LocalDateTime.parse(lastUpdate, DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss")); + } +} diff --git a/src/main/java/org/isf/orthanc/model/FindRequest.java b/src/main/java/org/isf/orthanc/model/FindRequest.java new file mode 100644 index 000000000..651285e83 --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/FindRequest.java @@ -0,0 +1,70 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class FindRequest { + @SerializedName("Level") + private String level; + + @SerializedName("Expand") + private boolean expand; + + @SerializedName("Query") + private Query query; + + public String getLevel() { + return level; + } + + public FindRequest(String level, boolean expand, Query query) { + this.level = level; + this.expand = expand; + this.query = query; + } + + public void setLevel(String level) { + this.level = level; + } + + public boolean getExpand() { + return expand; + } + + public void setExpand(boolean expand) { + this.expand = expand; + } + + public Query getQuery() { + return query; + } + + public void setQuery(Query query) { + this.query = query; + } +} + diff --git a/src/main/java/org/isf/orthanc/model/FindRequestLevel.java b/src/main/java/org/isf/orthanc/model/FindRequestLevel.java new file mode 100644 index 000000000..37903aeb5 --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/FindRequestLevel.java @@ -0,0 +1,30 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +/** + * @author Silevester D. + * @since 1.15 + */ +public enum FindRequestLevel { + Patient, Study, Series, Instance +} diff --git a/src/main/java/org/isf/orthanc/model/Instance.java b/src/main/java/org/isf/orthanc/model/Instance.java new file mode 100644 index 000000000..ada9715ef --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/Instance.java @@ -0,0 +1,127 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class Instance { + @SerializedName("AcquisitionNumber") + private String acquisitionNumber; + + @SerializedName("ImageOrientationPatient") + private String imageOrientationPatient; + + @SerializedName("ImagePositionPatient") + private String imagePositionPatient; + + @SerializedName("InstanceCreationDate") + private String creationDate; + + @SerializedName("InstanceCreationTime") + private String creationTime; + + @SerializedName("InstanceNumber") + private String instanceNumber; + + @SerializedName("SOPInstanceUID") + private String instanceUID; + + public String getAcquisitionNumber() { + return acquisitionNumber; + } + + public void setAcquisitionNumber(String acquisitionNumber) { + this.acquisitionNumber = acquisitionNumber; + } + + public String getImageOrientationPatient() { + return imageOrientationPatient; + } + + public void setImageOrientationPatient(String imageOrientationPatient) { + this.imageOrientationPatient = imageOrientationPatient; + } + + public String getImagePositionPatient() { + return imagePositionPatient; + } + + public void setImagePositionPatient(String imagePositionPatient) { + this.imagePositionPatient = imagePositionPatient; + } + + public String getCreationDate() { + return creationDate; + } + + public void setCreationDate(String creationDate) { + this.creationDate = creationDate; + } + + public String getCreationTime() { + return creationTime; + } + + public void setCreationTime(String creationTime) { + this.creationTime = creationTime; + } + + public String getInstanceNumber() { + return instanceNumber; + } + + public void setInstanceNumber(String instanceNumber) { + this.instanceNumber = instanceNumber; + } + + public String getInstanceUID() { + return instanceUID; + } + + public void setInstanceUID(String instanceUID) { + this.instanceUID = instanceUID; + } + + /** + * Parse instance creation date into LocalDate + * @return {@link LocalDate} instance + */ + public LocalDate getCreationDateInstance() { + return LocalDate.parse(creationDate, DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + /** + * Parse instance creation time into LocalTime + * @return {@link LocalTime} instance + */ + public LocalTime getCreationTimeInstance() { + return LocalTime.parse(creationTime, DateTimeFormatter.ofPattern("HHmmss")); + } +} diff --git a/src/main/java/org/isf/orthanc/model/InstanceResponse.java b/src/main/java/org/isf/orthanc/model/InstanceResponse.java new file mode 100644 index 000000000..a892f1efa --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/InstanceResponse.java @@ -0,0 +1,120 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class InstanceResponse { + @SerializedName("ID") + private String id; + + @SerializedName("FileSize") + private Integer fileSize; + + @SerializedName("FileUuid") + private String fileUuid; + + @SerializedName("IndexInSeries") + private Integer indexInSeries; + + @SerializedName("Labels") + private List labels; + + @SerializedName("MainDicomTags") + private Instance instance; + + @SerializedName("ParentSeries") + private String parentSeriesId; + + @SerializedName("Type") + private String objectType; + + public Instance getInstance() { + return instance; + } + + public void setInstance(Instance instance) { + this.instance = instance; + } + + public String getParentSeriesId() { + return parentSeriesId; + } + + public void setParentSeriesId(String parentSeriesId) { + this.parentSeriesId = parentSeriesId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getFileSize() { + return fileSize; + } + + public void setFileSize(Integer fileSize) { + this.fileSize = fileSize; + } + + public String getFileUuid() { + return fileUuid; + } + + public void setFileUuid(String fileUuid) { + this.fileUuid = fileUuid; + } + + public Integer getIndexInSeries() { + return indexInSeries; + } + + public void setIndexInSeries(Integer indexInSeries) { + this.indexInSeries = indexInSeries; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + public String getObjectType() { + return objectType; + } + + public void setObjectType(String objectType) { + this.objectType = objectType; + } +} diff --git a/src/main/java/org/isf/orthanc/model/Patient.java b/src/main/java/org/isf/orthanc/model/Patient.java new file mode 100644 index 000000000..fc9e094bf --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/Patient.java @@ -0,0 +1,101 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class Patient { + @SerializedName("PatientBirthDate") + private String birthDate; + + @SerializedName("PatientID") + private String id; + + @SerializedName("PatientName") + private String name; + + @SerializedName("PatientSex") + private String sex; + + public String getBirthDate() { + return birthDate; + } + + public void setBirthDate(String birthDate) { + this.birthDate = birthDate; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + /** + * Parse birthdate into LocalDate + * @return {@link LocalDate} instance + */ + public LocalDate getBirthDateInstance() { + return LocalDate.parse(birthDate, DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Patient patient = (Patient) o; + return Objects.equals(id, patient.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} diff --git a/src/main/java/org/isf/orthanc/model/Query.java b/src/main/java/org/isf/orthanc/model/Query.java new file mode 100644 index 000000000..a1872d345 --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/Query.java @@ -0,0 +1,77 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class Query { + @SerializedName("Modality") + private String modality; + + @SerializedName("StudyDate") + private String studyDate; + + @SerializedName("PatientID") + private String patientID; + + public Query() { + + } + + public Query(String patientID) { + this.patientID = patientID; + } + + public Query(String modality, String studyDate, String patientID) { + this(patientID); + this.modality = modality; + this.studyDate = studyDate; + } + + public String getModality() { + return modality; + } + + public void setModality(String modality) { + this.modality = modality; + } + + public String getStudyDate() { + return studyDate; + } + + public void setStudyDate(String studyDate) { + this.studyDate = studyDate; + } + + public String getPatientID() { + return patientID; + } + + public void setPatientID(String patientID) { + this.patientID = patientID; + } +} diff --git a/src/main/java/org/isf/orthanc/model/Series.java b/src/main/java/org/isf/orthanc/model/Series.java new file mode 100644 index 000000000..2c7bd457c --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/Series.java @@ -0,0 +1,140 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class Series { + @SerializedName("BodyPartExamined") + private String bodyPartExamined; + + @SerializedName("ImageOrientationPatient") + private String imageOrientationPatient; + + @SerializedName("Manufacturer") + private String manufacturer; + + @SerializedName("Modality") + private String modality; + + @SerializedName("OperatorsName") + private String operatorsName; + + @SerializedName("ProtocolName") + private String protocolName; + + @SerializedName("SeriesDescription") + private String seriesDescription; + + @SerializedName("SeriesInstanceUID") + private String instanceUID; + + @SerializedName("SeriesNumber") + private String number; + + @SerializedName("StationName") + private String stationName; + + public String getBodyPartExamined() { + return bodyPartExamined; + } + + public void setBodyPartExamined(String bodyPartExamined) { + this.bodyPartExamined = bodyPartExamined; + } + + public String getImageOrientationPatient() { + return imageOrientationPatient; + } + + public void setImageOrientationPatient(String imageOrientationPatient) { + this.imageOrientationPatient = imageOrientationPatient; + } + + public String getManufacturer() { + return manufacturer; + } + + public void setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + } + + public String getModality() { + return modality; + } + + public void setModality(String modality) { + this.modality = modality; + } + + public String getOperatorsName() { + return operatorsName; + } + + public void setOperatorsName(String operatorsName) { + this.operatorsName = operatorsName; + } + + public String getProtocolName() { + return protocolName; + } + + public void setProtocolName(String protocolName) { + this.protocolName = protocolName; + } + + public String getSeriesDescription() { + return seriesDescription; + } + + public void setSeriesDescription(String seriesDescription) { + this.seriesDescription = seriesDescription; + } + + public String getInstanceUID() { + return instanceUID; + } + + public void setInstanceUID(String instanceUID) { + this.instanceUID = instanceUID; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getStationName() { + return stationName; + } + + public void setStationName(String stationName) { + this.stationName = stationName; + } +} diff --git a/src/main/java/org/isf/orthanc/model/SeriesResponse.java b/src/main/java/org/isf/orthanc/model/SeriesResponse.java new file mode 100644 index 000000000..ed06d5ad7 --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/SeriesResponse.java @@ -0,0 +1,87 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class SeriesResponse extends BaseResponse { + @SerializedName("MainDicomTags") + private Series series; + + @SerializedName("ParentStudy") + private String parentStudyId; + + @SerializedName("Instances") + private List instancesIds; + + @SerializedName("ExpectedNumberOfInstances") + private String expectedNumberOfInstances; + + @SerializedName("Status") + private String status; + + public Series getSeries() { + return series; + } + + public void setSeries(Series series) { + this.series = series; + } + + public String getParentStudyId() { + return parentStudyId; + } + + public void setParentStudyId(String parentStudyId) { + this.parentStudyId = parentStudyId; + } + + public List getInstancesIds() { + return instancesIds; + } + + public void setInstancesIds(List instancesIds) { + this.instancesIds = instancesIds; + } + + public String getExpectedNumberOfInstances() { + return expectedNumberOfInstances; + } + + public void setExpectedNumberOfInstances(String expectedNumberOfInstances) { + this.expectedNumberOfInstances = expectedNumberOfInstances; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/src/main/java/org/isf/orthanc/model/Study.java b/src/main/java/org/isf/orthanc/model/Study.java new file mode 100644 index 000000000..8e30c3a6c --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/Study.java @@ -0,0 +1,155 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class Study { + @SerializedName("AccessionNumber") + private String accessionNumber; + + @SerializedName("InstitutionName") + private String institutionName; + + @SerializedName("ReferringPhysicianName") + private String referringPhysicianName; + + @SerializedName("StudyDate") + private String date; + + @SerializedName("StudyTime") + private String time; + + @SerializedName("StudyDescription") + private String description; + + @SerializedName("StudyID") + private String id; + + @SerializedName("StudyInstanceUID") + private String instanceUID; + + public String getAccessionNumber() { + return accessionNumber; + } + + public void setAccessionNumber(String accessionNumber) { + this.accessionNumber = accessionNumber; + } + + public String getInstitutionName() { + return institutionName; + } + + public void setInstitutionName(String institutionName) { + this.institutionName = institutionName; + } + + public String getReferringPhysicianName() { + return referringPhysicianName; + } + + public void setReferringPhysicianName(String referringPhysicianName) { + this.referringPhysicianName = referringPhysicianName; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getInstanceUID() { + return instanceUID; + } + + public void setInstanceUID(String instanceUID) { + this.instanceUID = instanceUID; + } + + /** + * Parse study date into LocalDate + * @return {@link LocalDate} instance + */ + public LocalDate getDateInstance() { + return LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyyMMdd")); + } + + /** + * Parse study time into LocalTime + * @return {@link LocalTime} instance + */ + public LocalTime getTimeInstance() { + return LocalTime.parse(time, DateTimeFormatter.ofPattern("HHmmss")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Study study = (Study) o; + return Objects.equals(id, study.id) && Objects.equals(instanceUID, study.instanceUID); + } + + @Override + public int hashCode() { + return Objects.hash(id, instanceUID); + } +} diff --git a/src/main/java/org/isf/orthanc/model/StudyResponse.java b/src/main/java/org/isf/orthanc/model/StudyResponse.java new file mode 100644 index 000000000..db7cda609 --- /dev/null +++ b/src/main/java/org/isf/orthanc/model/StudyResponse.java @@ -0,0 +1,76 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.model; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Silevester D. + * @since 1.15 + */ +public class StudyResponse extends BaseResponse { + @SerializedName("MainDicomTags") + private Study study; + + @SerializedName("ParentPatient") + private String parentPatientId; + + @SerializedName("PatientMainDicomTags") + private Patient patient; + + @SerializedName("Series") + private List seriesIds; + + public Study getStudy() { + return study; + } + + public void setStudy(Study study) { + this.study = study; + } + + public String getParentPatientId() { + return parentPatientId; + } + + public void setParentPatientId(String parentPatientId) { + this.parentPatientId = parentPatientId; + } + + public Patient getPatient() { + return patient; + } + + public void setPatient(Patient patient) { + this.patient = patient; + } + + public List getSeriesIds() { + return seriesIds; + } + + public void setSeriesIds(List seriesIds) { + this.seriesIds = seriesIds; + } +} diff --git a/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java b/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java new file mode 100644 index 000000000..a0280c2dc --- /dev/null +++ b/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java @@ -0,0 +1,190 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.service; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import org.isf.orthanc.client.OrthancAPIClient; +import org.isf.orthanc.client.OrthancFeignClientFactory; +import org.isf.orthanc.model.FindRequest; +import org.isf.orthanc.model.FindRequestLevel; +import org.isf.orthanc.model.InstanceResponse; +import org.isf.orthanc.model.Query; +import org.isf.orthanc.model.SeriesResponse; +import org.isf.orthanc.model.StudyResponse; +import org.isf.utils.exception.OHServiceException; +import org.springframework.stereotype.Service; + +import feign.Response; + +/** + * @author Silevester D. + * @since 1.15 + */ +@Service +public class OrthancAPIClientService { + + private final OrthancFeignClientFactory feignClientFactory; + + public OrthancAPIClientService(OrthancFeignClientFactory feignClientFactory) { + this.feignClientFactory = feignClientFactory; + } + + /** + * Test if the connection with ORTHANC server can be established + * + * @return true if connection successfully established, false otherwise + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public boolean testConnection() throws OHServiceException { + String time = getClient().testConnection(); + + return time != null && !time.isEmpty() ; + } + + /** + * Get Feign instance + * + * @return instance of {@link OrthancAPIClient} + * @throws OHServiceException see when {@link OrthancFeignClientFactory#createClient()} throws this exception + */ + public OrthancAPIClient getClient() throws OHServiceException { + return feignClientFactory.createClient(); + } + + /** + * Get all studies + * + * @return The list of studies + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public List getAllStudies() throws OHServiceException { + return getClient().getAllStudies(); + } + + /** + * Get patient's studies + *

This method uses the find tool of ORTHANC to matches patient and retrieve his studies. + * We assume that the value of the tag PatientId in the study's prop refers + * OH patient's ID.

+ * + * @return The list of studies related to the given patient ID + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public List getPatientStudiesById(String patientId) throws OHServiceException { + FindRequest request = new FindRequest( + FindRequestLevel.Study.name(), + true, + new Query(patientId) + ); + + return getClient().getPatientStudiesById(request); + } + + /** + * Get patient's studies + *

This method retrieves studies related to a given patient using patient's UUID + * stored in ORTHANC server.

+ * + * @return The list of studies related to the given patient ID + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public List getPatientStudiesByUuid(String patientUUID) throws OHServiceException { + return getClient().getPatientStudiesByUuid(patientUUID); + } + + /** + * Get a study using study's UUID + * + * @param studyId Study UUID + * @return the study that matched the provided UUID + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public StudyResponse getStudyById(String studyId) throws OHServiceException { + return getClient().getStudyById(studyId); + } + + /** + * Get study's series using study's UUID + * + * @param studyId Study UUID + * @return the list of study's series + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public List getStudySeries(String studyId) throws OHServiceException { + return getClient().getStudySeries(studyId); + } + + /** + * Get a series using series' UUID + * + * @param seriesId Series UUID + * @return the series that matched the provided UUID + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public SeriesResponse getSeriesById(String seriesId) throws OHServiceException { + return getClient().getSeriesById(seriesId); + } + + /** + * Get series' instances using series' UUID + * + * @param seriesId Series UUID + * @return the list of series' instances + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public List getSeriesInstances(String seriesId) throws OHServiceException { + return getClient().getSeriesInstances(seriesId); + } + + /** + * Get an instance using instance's UUID + * + * @param instanceId Instance UUID + * @return the instance that matched the provided UUID + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public InstanceResponse getInstanceById(String instanceId) throws OHServiceException { + return getClient().getInstanceById(instanceId); + } + + /** + * Get the preview of an instance using instance's UUID + * + * @param instanceId Instance UUID + * @return the instance preview image in PNG format + * @throws OHServiceException see when {@link #getClient()} throws this exception + */ + public byte[] getInstancePreview(String instanceId) throws OHServiceException { + // TODO: Better handle the file download to avoid {@link OutOfMemoryError} error + + try (Response response = getClient().getInstancePreview(instanceId)) { + InputStream inputStream = response.body().asInputStream(); + + return inputStream.readAllBytes(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java b/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java new file mode 100644 index 000000000..9b9031a6b --- /dev/null +++ b/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java @@ -0,0 +1,146 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.List; + +import org.isf.OHCoreTestCase; +import org.isf.orthanc.model.InstanceResponse; +import org.isf.orthanc.model.SeriesResponse; +import org.isf.orthanc.model.StudyResponse; +import org.isf.utils.exception.OHServiceException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Orthanc APIs call testing + *

+ * These tests are disabled by default because a valid running instance of + * ORTHANC may be missing. So you need to configure one using the SQL script + * loaded by this test before tests can run smoothly. The script is located at + * src/test/resources/org/isf/orthanc/service/LoadORTHANCSettings.sql + *

+ * + * @author Silevester D. + * @since 1.15 + */ +//@Disabled("Disabled because ORTHANC may not be properly configured") +public class OrthancAPIClientServiceTest extends OHCoreTestCase { + + @Autowired + private OrthancAPIClientService service; + + @Test + @DisplayName("Should successfully establish connection to ORTHANC server") + void testTestConnection() throws OHServiceException { + assertThat(service.testConnection()).isTrue(); + } + + @Test + @DisplayName("Should successfully get all studies") + void testGetAllStudies() throws OHServiceException { + List studies = service.getAllStudies(); + + assertThat(studies).isNotNull(); + assertThat(studies).isNotEmpty(); + assertThat(studies.get(0).getStudy()).isNotNull(); + } + + @Test + @DisplayName("Should successfully get patient's studies using ID") + void testGetPatientStudiesById() throws OHServiceException { + List studies = service.getPatientStudiesById("TCGA-CS-5396"); + + assertThat(studies).isNotNull(); + assertThat(studies).isNotEmpty(); + assertThat(studies.get(0).getStudy()).isNotNull(); + } + + @Test + @DisplayName("Should successfully get patient's studies using UUID") + void testGetPatientStudiesByUuid() throws OHServiceException { + List studies = service.getPatientStudiesByUuid("2eb243f6-71d716cd-f195ef0b-09e7c680-9937a931"); + + assertThat(studies).isNotNull(); + assertThat(studies).isNotEmpty(); + assertThat(studies.get(0).getStudy()).isNotNull(); + } + + @Test + @DisplayName("Should successfully get study using study's ID") + void testGetStudyById() throws OHServiceException { + StudyResponse studies = service.getStudyById("8fb3d973-4449cad4-c21bb79d-81c41b56-b9412373"); + + assertThat(studies).isNotNull(); + assertThat(studies.getStudy()).isNotNull(); + assertThat(studies.getLastUpdateInstance()).isInstanceOf(LocalDateTime.class); + } + + @Test + @DisplayName("Should successfully get study's series using study's ID") + void testGetStudySeries() throws OHServiceException { + List series = service.getStudySeries("8fb3d973-4449cad4-c21bb79d-81c41b56-b9412373"); + + assertThat(series).isNotNull(); + assertThat(series).isNotEmpty(); + assertThat(series.get(0).getSeries()).isNotNull(); + } + + @Test + @DisplayName("Should successfully get series using series' ID") + void testGetSeriesById() throws OHServiceException { + SeriesResponse series = service.getSeriesById("61af62b9-54162615-e473cdc1-e3676d55-0b5d7a4b"); + + assertThat(series).isNotNull(); + assertThat(series.getInstancesIds()).isNotEmpty(); + } + + @Test + @DisplayName("Should successfully get series' instances using series' ID") + void testGetSeriesInstances() throws OHServiceException { + List instances = service.getSeriesInstances("61af62b9-54162615-e473cdc1-e3676d55-0b5d7a4b"); + + assertThat(instances).isNotNull(); + assertThat(instances.get(0).getInstance()).isNotNull(); + } + + @Test + @DisplayName("Should successfully get instance using instance's ID") + void testGetInstanceById() throws OHServiceException { + InstanceResponse instance = service.getInstanceById("d4ea97a3-dad85671-26c5ffaf-6b9ed334-6a2fad91"); + + assertThat(instance).isNotNull(); + assertThat(instance.getInstance()).isNotNull(); + } + + @Test + @DisplayName("Should successfully get instance preview") + void testGetInstancePreview() throws OHServiceException { + byte[] pngImage = service.getInstancePreview("d4ea97a3-dad85671-26c5ffaf-6b9ed334-6a2fad91"); + + assertThat(pngImage).isNotNull(); + } +} diff --git a/src/test/resources/orthanc.properties b/src/test/resources/orthanc.properties new file mode 100644 index 000000000..fa159526b --- /dev/null +++ b/src/test/resources/orthanc.properties @@ -0,0 +1,5 @@ +orthanc.base-url=https://orthanc.uni2growcameroun.com/ +orthanc.explorer-url=https://orthanc.uni2growcameroun.com/app/explorer.html +orthanc.username=admin +orthanc.password=adminADMIN!123# +orthanc.enabled=true \ No newline at end of file From 49f3ea23049bc4641f345ae2f9c50f171bd06841 Mon Sep 17 00:00:00 2001 From: SilverD3 Date: Mon, 2 Dec 2024 15:20:38 +0100 Subject: [PATCH 2/5] chore:migration scripts for radiology permissions --- sql/load_demo_data.sql | 4 ++++ sql/step_04_all_following_steps.sql | 1 + sql/step_a115_add_radiology_permissions.sql | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 sql/step_a115_add_radiology_permissions.sql diff --git a/sql/load_demo_data.sql b/sql/load_demo_data.sql index 4f931e48b..85ca5de93 100644 --- a/sql/load_demo_data.sql +++ b/sql/load_demo_data.sql @@ -1912,6 +1912,8 @@ INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (315,'admin',170,'1',NULL,NULL,NULL,NULL); INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (316,'admin',171,'1',NULL,NULL,NULL,NULL); INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (317,'laboratorist',96,'1',NULL,NULL,NULL,NULL); +INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (318,'admin',172,'1',NULL,NULL,NULL,NULL); +INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (319,'admin',173,'1',NULL,NULL,NULL,NULL); /*!40000 ALTER TABLE `oh_grouppermission` ENABLE KEYS */; UNLOCK TABLES; @@ -5040,6 +5042,8 @@ INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, ` INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (169,'usergroups.read','','1',NULL,NULL,NULL,NULL); INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (170,'usergroups.update','','1',NULL,NULL,NULL,NULL); INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (171,'usergroups.delete','','1',NULL,NULL,NULL,NULL); +INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (172,'radiology.access','','1',NULL,NULL,NULL,NULL); +INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (173,'radiology.read','','1',NULL,NULL,NULL,NULL); /*!40000 ALTER TABLE `oh_permissions` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/step_04_all_following_steps.sql b/sql/step_04_all_following_steps.sql index 14676f2cf..e49afb14d 100644 --- a/sql/step_04_all_following_steps.sql +++ b/sql/step_04_all_following_steps.sql @@ -108,3 +108,4 @@ source step_a111_add_missing_lock_columns.sql; source step_a112_users_and_groups_soft_deletion.sql; source step_a113_alter_table_medicalinventory.sql; source step_a114_medical_type_soft_deletion.sql; +source step_a115_add_radiology_permissions.sql; diff --git a/sql/step_a115_add_radiology_permissions.sql b/sql/step_a115_add_radiology_permissions.sql new file mode 100644 index 000000000..056c02d1d --- /dev/null +++ b/sql/step_a115_add_radiology_permissions.sql @@ -0,0 +1,5 @@ +INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (172,'radiology.access','','1',NULL,NULL,NULL,NULL); +INSERT INTO `oh_permissions` (`P_ID_A`, `P_NAME`, `P_DESCRIPTION`, `P_ACTIVE`, `P_CREATED_BY`, `P_CREATED_DATE`, `P_LAST_MODIFIED_BY`, `P_LAST_MODIFIED_DATE`) VALUES (173,'radiology.read','','1',NULL,NULL,NULL,NULL); + +INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (318,'admin',172,'1',NULL,NULL,NULL,NULL); +INSERT INTO `oh_grouppermission` (`GP_ID`, `GP_UG_ID_A`, `GP_P_ID_A`, `GP_ACTIVE`, `GP_CREATED_BY`, `GP_CREATED_DATE`, `GP_LAST_MODIFIED_BY`, `GP_LAST_MODIFIED_DATE`) VALUES (319,'admin',173,'1',NULL,NULL,NULL,NULL); \ No newline at end of file From c0c7bd37a4809359e3ae22c7c00871ad4f453101 Mon Sep 17 00:00:00 2001 From: SilverD3 Date: Wed, 4 Dec 2024 10:34:46 +0100 Subject: [PATCH 3/5] chore: add exceptions handling --- .../client/OrthancFeignClientFactory.java | 6 +- .../service/OrthancAPIClientService.java | 41 ++++++++----- .../isf/orthanc/utils/CustomErrorDecoder.java | 57 +++++++++++++++++++ .../exception/OHInternalServerException.java | 37 ++++++++++++ .../utils/exception/OHNotFoundException.java | 37 ++++++++++++ .../exception/OHRestClientException.java | 57 +++++++++++++++++++ .../service/OrthancAPIClientServiceTest.java | 12 +++- 7 files changed, 230 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/isf/orthanc/utils/CustomErrorDecoder.java create mode 100644 src/main/java/org/isf/utils/exception/OHInternalServerException.java create mode 100644 src/main/java/org/isf/utils/exception/OHNotFoundException.java create mode 100644 src/main/java/org/isf/utils/exception/OHRestClientException.java diff --git a/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java b/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java index 8e948c417..d1eb456d5 100644 --- a/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java +++ b/src/main/java/org/isf/orthanc/client/OrthancFeignClientFactory.java @@ -22,6 +22,7 @@ package org.isf.orthanc.client; import org.isf.generaldata.OrthancConfig; +import org.isf.orthanc.utils.CustomErrorDecoder; import org.isf.utils.exception.OHServiceException; import org.isf.utils.exception.model.OHExceptionMessage; import org.springframework.cloud.openfeign.support.SpringMvcContract; @@ -53,18 +54,19 @@ public OrthancFeignClientFactory() { * @return an instance of {@link OrthancAPIClient} * @throws OHServiceException When ORTHANC not properly configured */ - public OrthancAPIClient createClient() throws OHServiceException { + public OrthancAPIClient createClient(boolean reset) throws OHServiceException { if (!validateConfiguration()) { throw new OHServiceException(new OHExceptionMessage("ORTHANC server is not properly configured")); } - if (client != null) return client; + if (client != null && !reset) return client; client = Feign.builder() .encoder(new GsonEncoder()) .decoder(new GsonDecoder()) .requestInterceptor(new BasicAuthRequestInterceptor(OrthancConfig.ORTHANC_USERNAME, OrthancConfig.ORTHANC_PASSWORD)) .logger(new Slf4jLogger(OrthancAPIClient.class)) + .errorDecoder(new CustomErrorDecoder()) .logLevel(Logger.Level.FULL) .contract(new SpringMvcContract()) .target(OrthancAPIClient.class, OrthancConfig.ORTHANC_BASE_URL); diff --git a/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java b/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java index a0280c2dc..aabeac09f 100644 --- a/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java +++ b/src/main/java/org/isf/orthanc/service/OrthancAPIClientService.java @@ -33,6 +33,8 @@ import org.isf.orthanc.model.Query; import org.isf.orthanc.model.SeriesResponse; import org.isf.orthanc.model.StudyResponse; +import org.isf.utils.exception.OHInternalServerException; +import org.isf.utils.exception.OHRestClientException; import org.isf.utils.exception.OHServiceException; import org.springframework.stereotype.Service; @@ -56,9 +58,10 @@ public OrthancAPIClientService(OrthancFeignClientFactory feignClientFactory) { * * @return true if connection successfully established, false otherwise * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public boolean testConnection() throws OHServiceException { - String time = getClient().testConnection(); + public boolean testConnection() throws OHServiceException, OHRestClientException { + String time = feignClientFactory.createClient(true).testConnection(); return time != null && !time.isEmpty() ; } @@ -67,10 +70,11 @@ public boolean testConnection() throws OHServiceException { * Get Feign instance * * @return instance of {@link OrthancAPIClient} - * @throws OHServiceException see when {@link OrthancFeignClientFactory#createClient()} throws this exception + * @throws OHServiceException see when {@link OrthancFeignClientFactory#createClient(boolean)} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public OrthancAPIClient getClient() throws OHServiceException { - return feignClientFactory.createClient(); + public OrthancAPIClient getClient() throws OHServiceException, OHRestClientException { + return feignClientFactory.createClient(false); } /** @@ -78,8 +82,9 @@ public OrthancAPIClient getClient() throws OHServiceException { * * @return The list of studies * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public List getAllStudies() throws OHServiceException { + public List getAllStudies() throws OHServiceException, OHRestClientException { return getClient().getAllStudies(); } @@ -91,8 +96,9 @@ public List getAllStudies() throws OHServiceException { * * @return The list of studies related to the given patient ID * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public List getPatientStudiesById(String patientId) throws OHServiceException { + public List getPatientStudiesById(String patientId) throws OHServiceException, OHRestClientException { FindRequest request = new FindRequest( FindRequestLevel.Study.name(), true, @@ -109,8 +115,9 @@ public List getPatientStudiesById(String patientId) throws OHServ * * @return The list of studies related to the given patient ID * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public List getPatientStudiesByUuid(String patientUUID) throws OHServiceException { + public List getPatientStudiesByUuid(String patientUUID) throws OHServiceException, OHRestClientException { return getClient().getPatientStudiesByUuid(patientUUID); } @@ -120,8 +127,9 @@ public List getPatientStudiesByUuid(String patientUUID) throws OH * @param studyId Study UUID * @return the study that matched the provided UUID * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public StudyResponse getStudyById(String studyId) throws OHServiceException { + public StudyResponse getStudyById(String studyId) throws OHServiceException, OHRestClientException { return getClient().getStudyById(studyId); } @@ -131,8 +139,9 @@ public StudyResponse getStudyById(String studyId) throws OHServiceException { * @param studyId Study UUID * @return the list of study's series * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public List getStudySeries(String studyId) throws OHServiceException { + public List getStudySeries(String studyId) throws OHServiceException, OHRestClientException { return getClient().getStudySeries(studyId); } @@ -142,8 +151,9 @@ public List getStudySeries(String studyId) throws OHServiceExcep * @param seriesId Series UUID * @return the series that matched the provided UUID * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public SeriesResponse getSeriesById(String seriesId) throws OHServiceException { + public SeriesResponse getSeriesById(String seriesId) throws OHServiceException,OHRestClientException { return getClient().getSeriesById(seriesId); } @@ -153,8 +163,9 @@ public SeriesResponse getSeriesById(String seriesId) throws OHServiceException { * @param seriesId Series UUID * @return the list of series' instances * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public List getSeriesInstances(String seriesId) throws OHServiceException { + public List getSeriesInstances(String seriesId) throws OHServiceException,OHRestClientException { return getClient().getSeriesInstances(seriesId); } @@ -164,8 +175,9 @@ public List getSeriesInstances(String seriesId) throws OHServi * @param instanceId Instance UUID * @return the instance that matched the provided UUID * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public InstanceResponse getInstanceById(String instanceId) throws OHServiceException { + public InstanceResponse getInstanceById(String instanceId) throws OHServiceException,OHRestClientException { return getClient().getInstanceById(instanceId); } @@ -175,8 +187,9 @@ public InstanceResponse getInstanceById(String instanceId) throws OHServiceExcep * @param instanceId Instance UUID * @return the instance preview image in PNG format * @throws OHServiceException see when {@link #getClient()} throws this exception + * @throws OHRestClientException when 4xx or 5xx errors is thrown by Feign */ - public byte[] getInstancePreview(String instanceId) throws OHServiceException { + public byte[] getInstancePreview(String instanceId) throws OHServiceException, OHRestClientException { // TODO: Better handle the file download to avoid {@link OutOfMemoryError} error try (Response response = getClient().getInstancePreview(instanceId)) { diff --git a/src/main/java/org/isf/orthanc/utils/CustomErrorDecoder.java b/src/main/java/org/isf/orthanc/utils/CustomErrorDecoder.java new file mode 100644 index 000000000..0bedeb0f8 --- /dev/null +++ b/src/main/java/org/isf/orthanc/utils/CustomErrorDecoder.java @@ -0,0 +1,57 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.orthanc.utils; + +import org.isf.utils.exception.OHInternalServerException; +import org.isf.utils.exception.OHNotFoundException; +import org.isf.utils.exception.model.OHExceptionMessage; +import org.springframework.http.HttpStatus; + +import feign.Response; +import feign.codec.ErrorDecoder; + +/** + * Custom Error Decoder for Feign Exceptions + * + * @author Silevester D. + * @since v1.16 + */ +public class CustomErrorDecoder implements ErrorDecoder { + + private final ErrorDecoder defaultErrorDecoder = new Default(); + + @Override + public Exception decode(String methodKey, Response response) { + HttpStatus status = HttpStatus.valueOf(response.status()); + + if (status.is4xxClientError()) { + return new OHNotFoundException(new OHExceptionMessage(response.reason())); + } + + if (status.is5xxServerError()) { + return new OHInternalServerException(new OHExceptionMessage(response.reason())); + } + + return defaultErrorDecoder.decode(methodKey, response); + } +} + diff --git a/src/main/java/org/isf/utils/exception/OHInternalServerException.java b/src/main/java/org/isf/utils/exception/OHInternalServerException.java new file mode 100644 index 000000000..e868f493d --- /dev/null +++ b/src/main/java/org/isf/utils/exception/OHInternalServerException.java @@ -0,0 +1,37 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.utils.exception; + +import org.isf.utils.exception.model.OHExceptionMessage; + +/** + * Exception to throw when a REST client throws a 500 error + * + * @author Silevester D. + * @since v1.16 + */ +public class OHInternalServerException extends OHRestClientException { + + public OHInternalServerException(OHExceptionMessage message) { + super(message); + } +} diff --git a/src/main/java/org/isf/utils/exception/OHNotFoundException.java b/src/main/java/org/isf/utils/exception/OHNotFoundException.java new file mode 100644 index 000000000..239d51b54 --- /dev/null +++ b/src/main/java/org/isf/utils/exception/OHNotFoundException.java @@ -0,0 +1,37 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2023 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.utils.exception; + +import org.isf.utils.exception.model.OHExceptionMessage; + +/** + * Exception to throw when a REST client throws a 404 error + * + * @author Silevester D. + * @since v1.16 + */ +public class OHNotFoundException extends OHRestClientException { + + public OHNotFoundException(OHExceptionMessage message) { + super(message); + } +} diff --git a/src/main/java/org/isf/utils/exception/OHRestClientException.java b/src/main/java/org/isf/utils/exception/OHRestClientException.java new file mode 100644 index 000000000..58c17a9d2 --- /dev/null +++ b/src/main/java/org/isf/utils/exception/OHRestClientException.java @@ -0,0 +1,57 @@ +/* + * Open Hospital (www.open-hospital.org) + * Copyright © 2006-2024 Informatici Senza Frontiere (info@informaticisenzafrontiere.org) + * + * Open Hospital is a free and open source software for healthcare data management. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.isf.utils.exception; + +import org.isf.utils.exception.model.OHExceptionMessage; + +/** + * Base Exception for exceptions thrown by REST clients + *

+ * This exception class extends {@link RuntimeException} rather than {@link OHServiceException} + * because REST clients like Feign proxies their client interfaces. So if custom exceptions + * are not runtime exceptions, they cannot handle them correctly. + *

+ * + * @author Silevester D. + * @since v1.16 + */ +public class OHRestClientException extends RuntimeException { + private OHExceptionMessage message; + + public OHRestClientException(OHExceptionMessage message) { + super(message.getMessage()); + this.message = message; + } + + public OHRestClientException(Throwable cause, OHExceptionMessage message) { + super(message.getMessage(), cause); + this.message = message; + } + + public void setMessage(OHExceptionMessage message) { + this.message = message; + } + + public OHExceptionMessage getExceptionMessage() { + return message; + } +} diff --git a/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java b/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java index 9b9031a6b..74e11afed 100644 --- a/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java +++ b/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java @@ -22,6 +22,7 @@ package org.isf.orthanc.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.time.LocalDateTime; import java.util.List; @@ -30,7 +31,9 @@ import org.isf.orthanc.model.InstanceResponse; import org.isf.orthanc.model.SeriesResponse; import org.isf.orthanc.model.StudyResponse; +import org.isf.utils.exception.OHNotFoundException; import org.isf.utils.exception.OHServiceException; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -47,7 +50,7 @@ * @author Silevester D. * @since 1.15 */ -//@Disabled("Disabled because ORTHANC may not be properly configured") +@Disabled("Disabled because ORTHANC may not be properly configured") public class OrthancAPIClientServiceTest extends OHCoreTestCase { @Autowired @@ -99,6 +102,13 @@ void testGetStudyById() throws OHServiceException { assertThat(studies.getLastUpdateInstance()).isInstanceOf(LocalDateTime.class); } + @Test + @DisplayName("Should throw OHServiceException when trying to get study using a wrong ID") + void testGetStudyWithWrongIdThrowsException() { + assertThatThrownBy(() -> service.getStudyById("8fb3d973-4449ad4-c21bb79d-81c41b56-b9412373")) + .isInstanceOf(OHNotFoundException.class); + } + @Test @DisplayName("Should successfully get study's series using study's ID") void testGetStudySeries() throws OHServiceException { From 1b3c39dee616940ff3c9dd41583817fcc44ea36d Mon Sep 17 00:00:00 2001 From: SilverD3 Date: Wed, 4 Dec 2024 15:27:58 +0100 Subject: [PATCH 4/5] fix:fix issue with date parsing --- src/main/java/org/isf/orthanc/model/BaseResponse.java | 3 +++ src/main/java/org/isf/orthanc/model/Instance.java | 8 ++++++++ src/main/java/org/isf/orthanc/model/Patient.java | 4 ++++ src/main/java/org/isf/orthanc/model/Study.java | 8 ++++++++ 4 files changed, 23 insertions(+) diff --git a/src/main/java/org/isf/orthanc/model/BaseResponse.java b/src/main/java/org/isf/orthanc/model/BaseResponse.java index 1ba51fdf3..b098408fb 100644 --- a/src/main/java/org/isf/orthanc/model/BaseResponse.java +++ b/src/main/java/org/isf/orthanc/model/BaseResponse.java @@ -93,6 +93,9 @@ public void setLastUpdate(String lastUpdate) { * @return {@link LocalDateTime} instance */ public LocalDateTime getLastUpdateInstance() { + if (lastUpdate == null || lastUpdate.isEmpty()) { + return null; + } return LocalDateTime.parse(lastUpdate, DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss")); } } diff --git a/src/main/java/org/isf/orthanc/model/Instance.java b/src/main/java/org/isf/orthanc/model/Instance.java index ada9715ef..1aa7a4b22 100644 --- a/src/main/java/org/isf/orthanc/model/Instance.java +++ b/src/main/java/org/isf/orthanc/model/Instance.java @@ -114,6 +114,10 @@ public void setInstanceUID(String instanceUID) { * @return {@link LocalDate} instance */ public LocalDate getCreationDateInstance() { + if (creationDate == null || creationDate.isEmpty()) { + return null; + } + return LocalDate.parse(creationDate, DateTimeFormatter.ofPattern("yyyyMMdd")); } @@ -122,6 +126,10 @@ public LocalDate getCreationDateInstance() { * @return {@link LocalTime} instance */ public LocalTime getCreationTimeInstance() { + if (creationTime == null || creationTime.isEmpty()) { + return null; + } + return LocalTime.parse(creationTime, DateTimeFormatter.ofPattern("HHmmss")); } } diff --git a/src/main/java/org/isf/orthanc/model/Patient.java b/src/main/java/org/isf/orthanc/model/Patient.java index fc9e094bf..5565560eb 100644 --- a/src/main/java/org/isf/orthanc/model/Patient.java +++ b/src/main/java/org/isf/orthanc/model/Patient.java @@ -81,6 +81,10 @@ public void setSex(String sex) { * @return {@link LocalDate} instance */ public LocalDate getBirthDateInstance() { + if (birthDate == null || birthDate.isEmpty()) { + return null; + } + return LocalDate.parse(birthDate, DateTimeFormatter.ofPattern("yyyyMMdd")); } diff --git a/src/main/java/org/isf/orthanc/model/Study.java b/src/main/java/org/isf/orthanc/model/Study.java index 8e30c3a6c..378a8489f 100644 --- a/src/main/java/org/isf/orthanc/model/Study.java +++ b/src/main/java/org/isf/orthanc/model/Study.java @@ -127,6 +127,10 @@ public void setInstanceUID(String instanceUID) { * @return {@link LocalDate} instance */ public LocalDate getDateInstance() { + if (date == null || date.isEmpty()) { + return null; + } + return LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyyMMdd")); } @@ -135,6 +139,10 @@ public LocalDate getDateInstance() { * @return {@link LocalTime} instance */ public LocalTime getTimeInstance() { + if (time == null || time.isEmpty()) { + return null; + } + return LocalTime.parse(time, DateTimeFormatter.ofPattern("HHmmss")); } From 9b666f5026bb507f79f558b747d63c31c2d19100 Mon Sep 17 00:00:00 2001 From: SilverD3 Date: Mon, 9 Dec 2024 09:07:01 +0100 Subject: [PATCH 5/5] chore:refactor Date parser methods --- src/main/java/org/isf/orthanc/model/BaseResponse.java | 2 +- src/main/java/org/isf/orthanc/model/Instance.java | 4 ++-- src/main/java/org/isf/orthanc/model/Patient.java | 2 +- src/main/java/org/isf/orthanc/model/Study.java | 4 ++-- .../org/isf/orthanc/service/OrthancAPIClientServiceTest.java | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/isf/orthanc/model/BaseResponse.java b/src/main/java/org/isf/orthanc/model/BaseResponse.java index b098408fb..2eb03b6f5 100644 --- a/src/main/java/org/isf/orthanc/model/BaseResponse.java +++ b/src/main/java/org/isf/orthanc/model/BaseResponse.java @@ -92,7 +92,7 @@ public void setLastUpdate(String lastUpdate) { * * @return {@link LocalDateTime} instance */ - public LocalDateTime getLastUpdateInstance() { + public LocalDateTime lastUpdateToLocalDateTime() { if (lastUpdate == null || lastUpdate.isEmpty()) { return null; } diff --git a/src/main/java/org/isf/orthanc/model/Instance.java b/src/main/java/org/isf/orthanc/model/Instance.java index 1aa7a4b22..3dbc31469 100644 --- a/src/main/java/org/isf/orthanc/model/Instance.java +++ b/src/main/java/org/isf/orthanc/model/Instance.java @@ -113,7 +113,7 @@ public void setInstanceUID(String instanceUID) { * Parse instance creation date into LocalDate * @return {@link LocalDate} instance */ - public LocalDate getCreationDateInstance() { + public LocalDate creationDateToLocalDate() { if (creationDate == null || creationDate.isEmpty()) { return null; } @@ -125,7 +125,7 @@ public LocalDate getCreationDateInstance() { * Parse instance creation time into LocalTime * @return {@link LocalTime} instance */ - public LocalTime getCreationTimeInstance() { + public LocalTime creationTimeToLocalTime() { if (creationTime == null || creationTime.isEmpty()) { return null; } diff --git a/src/main/java/org/isf/orthanc/model/Patient.java b/src/main/java/org/isf/orthanc/model/Patient.java index 5565560eb..946a8153d 100644 --- a/src/main/java/org/isf/orthanc/model/Patient.java +++ b/src/main/java/org/isf/orthanc/model/Patient.java @@ -80,7 +80,7 @@ public void setSex(String sex) { * Parse birthdate into LocalDate * @return {@link LocalDate} instance */ - public LocalDate getBirthDateInstance() { + public LocalDate birthDateToLocalDate() { if (birthDate == null || birthDate.isEmpty()) { return null; } diff --git a/src/main/java/org/isf/orthanc/model/Study.java b/src/main/java/org/isf/orthanc/model/Study.java index 378a8489f..22a509223 100644 --- a/src/main/java/org/isf/orthanc/model/Study.java +++ b/src/main/java/org/isf/orthanc/model/Study.java @@ -126,7 +126,7 @@ public void setInstanceUID(String instanceUID) { * Parse study date into LocalDate * @return {@link LocalDate} instance */ - public LocalDate getDateInstance() { + public LocalDate dateToLocalDate() { if (date == null || date.isEmpty()) { return null; } @@ -138,7 +138,7 @@ public LocalDate getDateInstance() { * Parse study time into LocalTime * @return {@link LocalTime} instance */ - public LocalTime getTimeInstance() { + public LocalTime timeToLocalTime() { if (time == null || time.isEmpty()) { return null; } diff --git a/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java b/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java index 74e11afed..bb556c114 100644 --- a/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java +++ b/src/test/java/org/isf/orthanc/service/OrthancAPIClientServiceTest.java @@ -99,7 +99,7 @@ void testGetStudyById() throws OHServiceException { assertThat(studies).isNotNull(); assertThat(studies.getStudy()).isNotNull(); - assertThat(studies.getLastUpdateInstance()).isInstanceOf(LocalDateTime.class); + assertThat(studies.lastUpdateToLocalDateTime()).isInstanceOf(LocalDateTime.class); } @Test