directoryDiagnoses = entity.getDiagnosisAvailable().stream()
.filter(diagnosis -> diagnosis != null && correctedDiagnoses.containsKey(diagnosis) && correctedDiagnoses.get(diagnosis) != null)
diff --git a/src/main/java/de/samply/directory_sync_service/fhir/FhirApi.java b/src/main/java/de/samply/directory_sync_service/fhir/FhirApi.java
index 8236518..a858e56 100644
--- a/src/main/java/de/samply/directory_sync_service/fhir/FhirApi.java
+++ b/src/main/java/de/samply/directory_sync_service/fhir/FhirApi.java
@@ -31,7 +31,6 @@
import java.util.function.Function;
import java.util.HashSet;
-import de.samply.directory_sync_service.model.StarModelData;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
diff --git a/src/main/java/de/samply/directory_sync_service/model/StarModelData.java b/src/main/java/de/samply/directory_sync_service/model/StarModelData.java
index 9df5c90..49f4cbc 100644
--- a/src/main/java/de/samply/directory_sync_service/model/StarModelData.java
+++ b/src/main/java/de/samply/directory_sync_service/model/StarModelData.java
@@ -272,10 +272,12 @@ public int getFactCount() {
*
* Note: This method directly modifies the factTables in-place.
*
- * @param diagnoses Maps FHIR diagnoses onto Directory diagnoses.
+ * @param diagnoses Maps FHIR diagnoses onto Directory diagnoses. If null, no corrections are applied.
* @throws NullPointerException if diagnoses or any fact in factTables is null.
*/
public void applyDiagnosisCorrections(Map diagnoses) {
+ if (diagnoses == null)
+ return;
for (Map fact: factTables) {
if (!fact.containsKey("disease"))
continue;
diff --git a/src/main/java/de/samply/directory_sync_service/sync/BiobanksUpdater.java b/src/main/java/de/samply/directory_sync_service/sync/BiobanksUpdater.java
new file mode 100644
index 0000000..53653a9
--- /dev/null
+++ b/src/main/java/de/samply/directory_sync_service/sync/BiobanksUpdater.java
@@ -0,0 +1,123 @@
+package de.samply.directory_sync_service.sync;
+
+import de.samply.directory_sync_service.Util;
+import de.samply.directory_sync_service.directory.DirectoryApi;
+import de.samply.directory_sync_service.directory.model.Biobank;
+import de.samply.directory_sync_service.fhir.FhirApi;
+import de.samply.directory_sync_service.model.BbmriEricId;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.hl7.fhir.r4.model.Organization;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Updates the biobanks in the local FHIR store with metadata from the Directory.
+ */
+public class BiobanksUpdater {
+ private static final Logger logger = LoggerFactory.getLogger(BiobanksUpdater.class);
+ public static final Function UPDATE_BIOBANK_NAME = t -> {
+ t.fhirBiobank.setName(t.dirBiobank.getName());
+ return t;
+ };
+
+ /**
+ * Updates all biobanks from the FHIR server with information from the Directory.
+ *
+ * @return the individual {@link OperationOutcome}s from each update
+ */
+ public static boolean updateBiobanksInFhirStore(FhirApi fhirApi, DirectoryApi directoryApi) {
+ // Retrieve the list of all biobanks
+ List organizations = fhirApi.listAllBiobanks();
+
+ // Check if the result is a failure or success
+ boolean succeeded = true;
+ if (organizations == null) {
+ logger.warn("error retrieving the biobanks");
+ succeeded = false;
+ } else {
+ // If successful, process each biobank and update it on the FHIR server if necessary
+ for (Organization organization : organizations) {
+ // Update each biobank and report any errors
+ if (!updateBiobankInFhirStore(fhirApi, directoryApi, organization)) {
+ logger.warn("updateBiobankOnFhirServerIfNecessary: problem updating: " + organization.getIdElement().getValue());
+ succeeded = false;
+ }
+ }
+ }
+
+ return succeeded;
+ }
+
+ /**
+ * Takes a biobank from FHIR and updates it with current information from the Directory.
+ *
+ * @param fhirBiobank the biobank to update.
+ * @return the {@link OperationOutcome} from the FHIR server update
+ */
+ private static boolean updateBiobankInFhirStore(FhirApi fhirApi, DirectoryApi directoryApi, Organization fhirBiobank) {
+ logger.info("updateBiobankOnFhirServerIfNecessary: entered");
+
+ // Retrieve the biobank's BBMRI-ERIC identifier from the FHIR organization
+ Optional bbmriEricIdOpt = FhirApi.bbmriEricId(fhirBiobank);
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: bbmriEricIdOpt: " + bbmriEricIdOpt);
+
+ // Check if the identifier is present, if not, return false
+ if (!bbmriEricIdOpt.isPresent()) {
+ logger.warn("updateBiobankOnFhirServerIfNecessary: Missing BBMRI-ERIC identifier");
+ return false;
+ }
+ BbmriEricId bbmriEricId = bbmriEricIdOpt.get();
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: bbmriEricId: " + bbmriEricId);
+
+ // Fetch the corresponding biobank from the Directory API
+ Biobank directoryBiobank = directoryApi.fetchBiobank(bbmriEricId);
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: directoryBiobank: " + directoryBiobank);
+
+ // Check if fetching the biobank was successful, if not, return false
+ if (directoryBiobank == null) {
+ logger.warn("updateBiobankOnFhirServerIfNecessary: Failed to fetch biobank from Directory API");
+ return false;
+ }
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: Create a BiobankTuple containing the FHIR biobank and the Directory biobank");
+
+ // Create a BiobankTuple containing the FHIR biobank and the Directory biobank
+ BiobankTuple biobankTuple = new BiobankTuple(fhirBiobank, directoryBiobank);
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: Update the biobank name if necessary");
+
+ // Update the biobank name if necessary
+ BiobankTuple updatedBiobankTuple = UPDATE_BIOBANK_NAME.apply(biobankTuple);
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: Check if any changes have been made; if not, return a no-update necessary outcome");
+
+ // Check if any changes have been made; if not, return true (because this outcome is OK)
+ if (!updatedBiobankTuple.hasChanged()) {
+ logger.info("updateBiobankOnFhirServerIfNecessary: No update necessary");
+ return true;
+ }
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: Update the biobank resource on the FHIR server if changes were made");
+
+ // Update the biobank resource on the FHIR server
+ OperationOutcome updateOutcome = fhirApi.updateResource(updatedBiobankTuple.fhirBiobank);
+
+ String errorMessage = Util.getErrorMessageFromOperationOutcome(updateOutcome);
+
+ if (!errorMessage.isEmpty()) {
+ logger.warn("updateBiobankOnFhirServerIfNecessary: Problem during FHIR store update");
+ return false;
+ }
+
+ logger.info("updateBiobankOnFhirServerIfNecessary: done!");
+
+ return true;
+ }
+}
diff --git a/src/main/java/de/samply/directory_sync_service/sync/CollectionUpdater.java b/src/main/java/de/samply/directory_sync_service/sync/CollectionUpdater.java
new file mode 100644
index 0000000..3764338
--- /dev/null
+++ b/src/main/java/de/samply/directory_sync_service/sync/CollectionUpdater.java
@@ -0,0 +1,99 @@
+package de.samply.directory_sync_service.sync;
+
+import de.samply.directory_sync_service.Util;
+import de.samply.directory_sync_service.converter.FhirCollectionToDirectoryCollectionPutConverter;
+import de.samply.directory_sync_service.directory.DirectoryApi;
+import de.samply.directory_sync_service.directory.MergeDirectoryCollectionGetToDirectoryCollectionPut;
+import de.samply.directory_sync_service.directory.model.DirectoryCollectionGet;
+import de.samply.directory_sync_service.directory.model.DirectoryCollectionPut;
+import de.samply.directory_sync_service.fhir.FhirApi;
+import de.samply.directory_sync_service.fhir.model.FhirCollection;
+import de.samply.directory_sync_service.model.BbmriEricId;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Update collections in the Directory with data from the local FHIR store.
+ */
+public class CollectionUpdater {
+ private static final Logger logger = LoggerFactory.getLogger(CollectionUpdater.class);
+
+ /**
+ * Take information from the FHIR store and send aggregated updates to the Directory.
+ *
+ * This is a multi step process:
+ * 1. Fetch a list of collections objects from the FHIR store. These contain aggregated
+ * information over all specimens in the collections.
+ * 2. Convert the FHIR collection objects into Directory collection PUT DTOs. Copy
+ * over avaialble information from FHIR, converting where necessary.
+ * 3. Using the collection IDs found in the FHIR store, send queries to the Directory
+ * and fetch back the relevant GET collections. If any of the collection IDs cannot be
+ * found, this ie a breaking error.
+ * 4. Transfer data from the Directory GET collections to the corresponding Directory PUT
+ * collections.
+ * 5. Push the new information back to the Directory.
+ *
+ * @param defaultCollectionId The default collection ID to use for fetching collections from the FHIR store.
+ * @return A list of OperationOutcome objects indicating the outcome of the update operation.
+ */
+ public static boolean sendUpdatesToDirectory(FhirApi fhirApi, DirectoryApi directoryApi, Map correctedDiagnoses, String defaultCollectionId) {
+ try {
+ BbmriEricId defaultBbmriEricCollectionId = BbmriEricId
+ .valueOf(defaultCollectionId)
+ .orElse(null);
+
+ List fhirCollection = fhirApi.fetchFhirCollections(defaultBbmriEricCollectionId);
+ if (fhirCollection == null) {
+ logger.warn("Problem getting collections from FHIR store");
+ return false;
+ }
+ logger.info("__________ sendUpdatesToDirectory: FHIR collection count): " + fhirCollection.size());
+
+ DirectoryCollectionPut directoryCollectionPut = FhirCollectionToDirectoryCollectionPutConverter.convert(fhirCollection);
+ if (directoryCollectionPut == null) {
+ logger.warn("Problem converting FHIR attributes to Directory attributes");
+ return false;
+ }
+ logger.info("__________ sendUpdatesToDirectory: 1 directoryCollectionPut.getCollectionIds().size()): " + directoryCollectionPut.getCollectionIds().size());
+
+ List collectionIds = directoryCollectionPut.getCollectionIds();
+ String countryCode = directoryCollectionPut.getCountryCode();
+ directoryApi.relogin();
+ DirectoryCollectionGet directoryCollectionGet = directoryApi.fetchCollectionGetOutcomes(countryCode, collectionIds);
+ if (directoryCollectionGet == null) {
+ logger.warn("Problem getting collections from Directory");
+ return false;
+ }
+ logger.info("__________ sendUpdatesToDirectory: 1 directoryCollectionGet.getItems().size()): " + directoryCollectionGet.getItems().size());
+
+ if (!MergeDirectoryCollectionGetToDirectoryCollectionPut.merge(directoryCollectionGet, directoryCollectionPut)) {
+ logger.warn("Problem merging Directory GET attributes to Directory PUT attributes");
+ return false;
+ }
+ logger.info("__________ sendUpdatesToDirectory: 2 directoryCollectionGet.getItems().size()): " + directoryCollectionGet.getItems().size());
+
+ // Apply corrections to ICD 10 diagnoses, to make them compatible with
+ // the Directory.
+ directoryCollectionPut.applyDiagnosisCorrections(correctedDiagnoses);
+ logger.info("__________ sendUpdatesToDirectory: 2 directoryCollectionPut.getCollectionIds().size()): " + directoryCollectionPut.getCollectionIds().size());
+
+ directoryApi.relogin();
+ OperationOutcome updateOutcome = directoryApi.updateEntities(directoryCollectionPut);
+ String errorMessage = Util.getErrorMessageFromOperationOutcome(updateOutcome);
+
+ if (!errorMessage.isEmpty()) {
+ logger.warn("sendUpdatesToDirectory: Problem during star model update");
+ return false;
+ }
+
+ return true;
+ } catch (Exception e) {
+ logger.warn("sendUpdatesToDirectory - unexpected error: " + Util.traceFromException(e));
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/de/samply/directory_sync_service/sync/DiagnosisCorrections.java b/src/main/java/de/samply/directory_sync_service/sync/DiagnosisCorrections.java
new file mode 100644
index 0000000..6b3add2
--- /dev/null
+++ b/src/main/java/de/samply/directory_sync_service/sync/DiagnosisCorrections.java
@@ -0,0 +1,72 @@
+package de.samply.directory_sync_service.sync;
+
+import de.samply.directory_sync_service.Util;
+import de.samply.directory_sync_service.converter.FhirToDirectoryAttributeConverter;
+import de.samply.directory_sync_service.directory.DirectoryApi;
+import de.samply.directory_sync_service.fhir.FhirApi;
+import de.samply.directory_sync_service.model.BbmriEricId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DiagnosisCorrections {
+ private static final Logger logger = LoggerFactory.getLogger(DiagnosisCorrections.class);
+
+ /**
+ * Generates corrections to the diagnoses obtained from the FHIR store, to make them
+ * compatible with the Directory. You should supply this method with an empty map
+ * via the correctedDiagnoses variable. This map will be filled by the method and
+ * you can subsequently use it elsewhere.
+ *
+ * This method performs the following steps:
+ *
+ * * Retrieves diagnoses from the FHIR store for specimens with identifiable collections and their associated patients.
+ * * Converts raw ICD-10 codes into MIRIAM-compatible codes.
+ * * Collects corrected diagnosis codes from the Directory API based on the MIRIAM-compatible codes.
+ *
+ * @param fhirApi
+ * @param directoryApi
+ * @param defaultCollectionId Default collection ID. May be null.
+ * @return A list containing diagnosis corrections.
+ * If any errors occur during the process, null is returned.
+ */
+ public static Map generateDiagnosisCorrections(FhirApi fhirApi, DirectoryApi directoryApi, String defaultCollectionId) {
+ try {
+ Map correctedDiagnoses = new HashMap();
+ // Convert string version of collection ID into a BBMRI ERIC ID.
+ BbmriEricId defaultBbmriEricCollectionId = BbmriEricId
+ .valueOf(defaultCollectionId)
+ .orElse(null);
+
+ // Get all diagnoses from the FHIR store for specemins with identifiable
+ // collections and their associated patients.
+ List fhirDiagnoses = fhirApi.fetchDiagnoses(defaultBbmriEricCollectionId);
+ if (fhirDiagnoses == null) {
+ logger.warn("Problem getting diagnosis information from FHIR store");
+ return null;
+ }
+ logger.info("__________ generateDiagnosisCorrections: fhirDiagnoses.size(): " + fhirDiagnoses.size());
+
+ // Convert the raw ICD 10 codes into MIRIAM-compatible codes and put the
+ // codes into a map with identical keys and values.
+ fhirDiagnoses.forEach(diagnosis -> {
+ String miriamDiagnosis = FhirToDirectoryAttributeConverter.convertDiagnosis(diagnosis);
+ correctedDiagnoses.put(miriamDiagnosis, miriamDiagnosis);
+ });
+ logger.info("__________ generateDiagnosisCorrections: 1 correctedDiagnoses.size(): " + correctedDiagnoses.size());
+
+ // Get corrected diagnosis codes from the Directory
+ directoryApi.collectDiagnosisCorrections(correctedDiagnoses);
+ logger.info("__________ generateDiagnosisCorrections: 2 correctedDiagnoses.size(): " + correctedDiagnoses.size());
+
+ return correctedDiagnoses;
+ } catch (Exception e) {
+ logger.error("generateDiagnosisCorrections - unexpected error: " + Util.traceFromException(e));
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/de/samply/directory_sync_service/sync/StarModelUpdater.java b/src/main/java/de/samply/directory_sync_service/sync/StarModelUpdater.java
new file mode 100644
index 0000000..5d6e329
--- /dev/null
+++ b/src/main/java/de/samply/directory_sync_service/sync/StarModelUpdater.java
@@ -0,0 +1,89 @@
+package de.samply.directory_sync_service.sync;
+
+import de.samply.directory_sync_service.Util;
+import de.samply.directory_sync_service.directory.CreateFactTablesFromStarModelInputData;
+import de.samply.directory_sync_service.directory.DirectoryApi;
+import de.samply.directory_sync_service.fhir.FhirApi;
+import de.samply.directory_sync_service.fhir.PopulateStarModelInputData;
+import de.samply.directory_sync_service.model.BbmriEricId;
+import de.samply.directory_sync_service.model.StarModelData;
+import org.hl7.fhir.r4.model.OperationOutcome;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Update the star model data in the Directory with data from the local FHIR store.
+ */
+public class StarModelUpdater {
+ private static final Logger logger = LoggerFactory.getLogger(StarModelUpdater.class);
+
+ /**
+ * Sends updates for Star Model data to the Directory service, based on FHIR store information.
+ * This method fetches Star Model input data from the FHIR store, generates star model fact tables,
+ * performs diagnosis corrections, and then updates the Directory service with the prepared data.
+ *
+ * The method handles errors by returning a list of OperationOutcome objects describing the issues.
+ *
+ * @param fhirApi
+ * @param directoryApi
+ * @param correctedDiagnoses
+ * @param defaultCollectionId The default BBMRI-ERIC collection ID for fetching data from the FHIR store.
+ * @param minDonors The minimum number of donors required for a fact to be included in the star model output.
+ * @param maxFacts The maximum number of facts to be included in the star model output. Negative number means no limit.
+ * @return A list of OperationOutcome objects indicating the outcome of the star model updates.
+ *
+ * @throws IllegalArgumentException if the defaultCollectionId is not a valid BbmriEricId.
+ */
+ public static boolean sendStarModelUpdatesToDirectory(FhirApi fhirApi, DirectoryApi directoryApi, Map correctedDiagnoses, String defaultCollectionId, int minDonors, int maxFacts) {
+ logger.info("__________ sendStarModelUpdatesToDirectory: minDonors: " + minDonors);
+ try {
+ BbmriEricId defaultBbmriEricCollectionId = BbmriEricId
+ .valueOf(defaultCollectionId)
+ .orElse(null);
+
+ // Pull data from the FHIR store and save it in a format suitable for generating
+ // star model hypercubes.
+ StarModelData starModelInputData = (new PopulateStarModelInputData(fhirApi)).populate(defaultBbmriEricCollectionId);
+ if (starModelInputData == null) {
+ logger.warn("Problem getting star model information from FHIR store");
+ return false;
+ }
+ logger.info("__________ sendStarModelUpdatesToDirectory: number of collection IDs: " + starModelInputData.getInputCollectionIds().size());
+
+ directoryApi.relogin();
+
+ // Hypercubes containing less than the minimum number of donors will not be
+ // included in the star model output.
+ starModelInputData.setMinDonors(minDonors);
+
+ // Take the patient list and the specimen list from starModelInputData and
+ // use them to generate the star model fact tables.
+ CreateFactTablesFromStarModelInputData.createFactTables(starModelInputData, maxFacts);
+ logger.info("__________ sendStarModelUpdatesToDirectory: 1 starModelInputData.getFactCount(): " + starModelInputData.getFactCount());
+
+ // Apply corrections to ICD 10 diagnoses, to make them compatible with
+ // the Directory.
+ starModelInputData.applyDiagnosisCorrections(correctedDiagnoses);
+ logger.info("__________ sendStarModelUpdatesToDirectory: 2 starModelInputData.getFactCount(): " + starModelInputData.getFactCount());
+
+ // Send fact tables to Direcory.
+ directoryApi.relogin();
+ OperationOutcome updateOutcome = directoryApi.updateStarModel(starModelInputData);
+ String errorMessage = Util.getErrorMessageFromOperationOutcome(updateOutcome);
+
+ if (!errorMessage.isEmpty()) {
+ logger.warn("sendStarModelUpdatesToDirectory: Problem during star model update");
+ return false;
+ }
+
+ return true;
+ } catch (Exception e) {
+ logger.warn("sendStarModelUpdatesToDirectory - unexpected error: " + Util.traceFromException(e));
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/de/samply/directory_sync_service/sync/Sync.java b/src/main/java/de/samply/directory_sync_service/sync/Sync.java
index de745a9..9911c49 100644
--- a/src/main/java/de/samply/directory_sync_service/sync/Sync.java
+++ b/src/main/java/de/samply/directory_sync_service/sync/Sync.java
@@ -1,36 +1,14 @@
package de.samply.directory_sync_service.sync;
-import de.samply.directory_sync_service.Util;
-import de.samply.directory_sync_service.converter.FhirCollectionToDirectoryCollectionPutConverter;
-import de.samply.directory_sync_service.directory.CreateFactTablesFromStarModelInputData;
import de.samply.directory_sync_service.directory.DirectoryApi;
-import de.samply.directory_sync_service.directory.MergeDirectoryCollectionGetToDirectoryCollectionPut;
-import de.samply.directory_sync_service.fhir.PopulateStarModelInputData;
-import de.samply.directory_sync_service.model.BbmriEricId;
-import de.samply.directory_sync_service.directory.model.Biobank;
-import de.samply.directory_sync_service.directory.model.DirectoryCollectionGet;
-import de.samply.directory_sync_service.directory.model.DirectoryCollectionPut;
import de.samply.directory_sync_service.fhir.FhirApi;
-import de.samply.directory_sync_service.fhir.model.FhirCollection;
-import de.samply.directory_sync_service.converter.FhirToDirectoryAttributeConverter;
-import de.samply.directory_sync_service.model.StarModelData;
import java.io.IOException;
import java.util.Map;
-import org.hl7.fhir.r4.model.OperationOutcome;
-import org.hl7.fhir.r4.model.Organization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Function;
-
-import static org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity.INFORMATION;
-
/**
* Provides functionality to synchronize between a BBMRI Directory instance and a FHIR store in both directions.
* This class provides methods to update biobanks, synchronize collection sizes, generate diagnosis corrections,
@@ -63,13 +41,6 @@ public class Sync {
private final int directoryMinDonors;
private final int directoryMaxFacts;
private final boolean directoryMock;
- private Map correctedDiagnoses = null;
- private FhirApi fhirApi;
- private DirectoryApi directoryApi;
- public static final Function UPDATE_BIOBANK_NAME = t -> {
- t.fhirBiobank.setName(t.dirBiobank.getName());
- return t;
- };
public Sync(String retryMax, String retryInterval, String fhirStoreUrl, String directoryUrl, String directoryUserName, String directoryUserPass, String directoryDefaultCollectionId, boolean directoryAllowStarModel, int directoryMinDonors, int directoryMaxFacts, boolean directoryMock) {
this.retryMax = retryMax;
@@ -106,26 +77,28 @@ public void syncWithDirectoryFailover() {
}
}
- public boolean syncWithDirectory() {
+ private boolean syncWithDirectory() {
+ Map correctedDiagnoses = null;
// Re-initialize helper classes every time this method gets called
- fhirApi = new FhirApi(fhirStoreUrl);
- directoryApi = new DirectoryApi(directoryUrl, directoryMock, directoryUserName, directoryUserPass);
+ FhirApi fhirApi = new FhirApi(fhirStoreUrl);
+ DirectoryApi directoryApi = new DirectoryApi(directoryUrl, directoryMock, directoryUserName, directoryUserPass);
- if (!Util.reportOperationOutcomes(generateDiagnosisCorrections(directoryDefaultCollectionId))) {
- logger.warn("syncWithDirectory: there was a problem during diagnosis corrections");
+ correctedDiagnoses = DiagnosisCorrections.generateDiagnosisCorrections(fhirApi, directoryApi, directoryDefaultCollectionId);
+ if (correctedDiagnoses == null) {
+ logger.warn("syncWithDirectory: there was a problem during diagnosis corrections");
return false;
}
if (directoryAllowStarModel)
- if (!Util.reportOperationOutcomes(sendStarModelUpdatesToDirectory(directoryDefaultCollectionId, directoryMinDonors, directoryMaxFacts))) {
+ if (!StarModelUpdater.sendStarModelUpdatesToDirectory(fhirApi, directoryApi, correctedDiagnoses, directoryDefaultCollectionId, directoryMinDonors, directoryMaxFacts)) {
logger.warn("syncWithDirectory: there was a problem during star model update to Directory");
return false;
}
- if (!Util.reportOperationOutcomes(sendUpdatesToDirectory(directoryDefaultCollectionId))) {
+ if (!CollectionUpdater.sendUpdatesToDirectory(fhirApi, directoryApi, correctedDiagnoses, directoryDefaultCollectionId)) {
logger.warn("syncWithDirectory: there was a problem during sync to Directory");
return false;
}
- if (!updateAllBiobanksOnFhirServerIfNecessary()) {
+ if (!BiobanksUpdater.updateBiobanksInFhirStore(fhirApi, directoryApi)) {
logger.warn("syncWithDirectory: there was a problem during sync from Directory");
return false;
}
@@ -133,272 +106,4 @@ public boolean syncWithDirectory() {
logger.info("__________ syncWithDirectory: all synchronization tasks finished");
return true;
}
-
- /**
- * Updates all biobanks from the FHIR server with information from the Directory.
- *
- * @return the individual {@link OperationOutcome}s from each update
- */
- private boolean updateAllBiobanksOnFhirServerIfNecessary() {
- // Retrieve the list of all biobanks
- List organizations = fhirApi.listAllBiobanks();
-
- // Check if the result is a failure or success
- boolean succeeded = true;
- if (organizations == null) {
- logger.warn("error retrieving the biobanks");
- succeeded = false;
- } else {
- // If successful, process each biobank and update it on the FHIR server if necessary
- for (Organization organization : organizations) {
- // Update each biobank and report any errors
- if (!updateBiobankOnFhirServerIfNecessary(organization)) {
- logger.warn("updateBiobankOnFhirServerIfNecessary: problem updating: " + organization.getIdElement().getValue());
- succeeded = false;
- }
- }
- }
-
- return succeeded;
- }
-
- /**
- * Takes a biobank from FHIR and updates it with current information from the Directory.
- *
- * @param fhirBiobank the biobank to update.
- * @return the {@link OperationOutcome} from the FHIR server update
- */
- private boolean updateBiobankOnFhirServerIfNecessary(Organization fhirBiobank) {
- logger.info("updateBiobankOnFhirServerIfNecessary: entered");
-
- // Retrieve the biobank's BBMRI-ERIC identifier from the FHIR organization
- Optional bbmriEricIdOpt = FhirApi.bbmriEricId(fhirBiobank);
-
- logger.info("updateBiobankOnFhirServerIfNecessary: bbmriEricIdOpt: " + bbmriEricIdOpt);
-
- // Check if the identifier is present, if not, return false
- if (!bbmriEricIdOpt.isPresent()) {
- logger.warn("updateBiobankOnFhirServerIfNecessary: Missing BBMRI-ERIC identifier");
- return false;
- }
- BbmriEricId bbmriEricId = bbmriEricIdOpt.get();
-
- logger.info("updateBiobankOnFhirServerIfNecessary: bbmriEricId: " + bbmriEricId);
-
- // Fetch the corresponding biobank from the Directory API
- Biobank directoryBiobank = directoryApi.fetchBiobank(bbmriEricId);
-
- logger.info("updateBiobankOnFhirServerIfNecessary: directoryBiobank: " + directoryBiobank);
-
- // Check if fetching the biobank was successful, if not, return false
- if (directoryBiobank == null) {
- logger.warn("updateBiobankOnFhirServerIfNecessary: Failed to fetch biobank from Directory API");
- return false;
- }
-
- logger.info("updateBiobankOnFhirServerIfNecessary: Create a BiobankTuple containing the FHIR biobank and the Directory biobank");
-
- // Create a BiobankTuple containing the FHIR biobank and the Directory biobank
- BiobankTuple biobankTuple = new BiobankTuple(fhirBiobank, directoryBiobank);
-
- logger.info("updateBiobankOnFhirServerIfNecessary: Update the biobank name if necessary");
-
- // Update the biobank name if necessary
- BiobankTuple updatedBiobankTuple = UPDATE_BIOBANK_NAME.apply(biobankTuple);
-
- logger.info("updateBiobankOnFhirServerIfNecessary: Check if any changes have been made; if not, return a no-update necessary outcome");
-
- // Check if any changes have been made; if not, return true (because this outcome is OK)
- if (!updatedBiobankTuple.hasChanged()) {
- logger.info("updateBiobankOnFhirServerIfNecessary: No update necessary");
- return true;
- }
-
- logger.info("updateBiobankOnFhirServerIfNecessary: Update the biobank resource on the FHIR server if changes were made");
-
- // Update the biobank resource on the FHIR server
- OperationOutcome updateOutcome = fhirApi.updateResource(updatedBiobankTuple.fhirBiobank);
-
- String errorMessage = Util.getErrorMessageFromOperationOutcome(updateOutcome);
-
- if (!errorMessage.isEmpty()) {
- logger.warn("updateBiobankOnFhirServerIfNecessary: Problem during FHIR store update");
- return false;
- }
-
- logger.info("updateBiobankOnFhirServerIfNecessary: done!");
-
- return true;
- }
-
- /**
- * Generates corrections to the diagnoses obtained from the FHIR store, to make them
- * compatible with the Directory. You should supply this method with an empty map
- * via the correctedDiagnoses variable. This map will be filled by the method and
- * you can subsequently use it elsewhere.
- *
- * This method performs the following steps:
- *
- * * Retrieves diagnoses from the FHIR store for specimens with identifiable collections and their associated patients.
- * * Converts raw ICD-10 codes into MIRIAM-compatible codes.
- * * Collects corrected diagnosis codes from the Directory API based on the MIRIAM-compatible codes.
- *
- * @param defaultCollectionId Default collection ID. May be null.
- * @return A list containing a single OperationOutcome indicating the success of the diagnosis corrections process.
- * If any errors occur during the process, an OperationOutcome with error details is returned.
- */
- private List generateDiagnosisCorrections(String defaultCollectionId) {
- correctedDiagnoses = new HashMap();
- try {
- // Convert string version of collection ID into a BBMRI ERIC ID.
- BbmriEricId defaultBbmriEricCollectionId = BbmriEricId
- .valueOf(defaultCollectionId)
- .orElse(null);
-
- // Get all diagnoses from the FHIR store for specemins with identifiable
- // collections and their associated patients.
- List fhirDiagnoses = fhirApi.fetchDiagnoses(defaultBbmriEricCollectionId);
- if (fhirDiagnoses == null) {
- logger.warn("Problem getting diagnosis information from FHIR store");
- }
- logger.info("__________ generateDiagnosisCorrections: fhirDiagnoses.size(): " + fhirDiagnoses.size());
-
- // Convert the raw ICD 10 codes into MIRIAM-compatible codes and put the
- // codes into a map with identical keys and values.
- fhirDiagnoses.forEach(diagnosis -> {
- String miriamDiagnosis = FhirToDirectoryAttributeConverter.convertDiagnosis(diagnosis);
- correctedDiagnoses.put(miriamDiagnosis, miriamDiagnosis);
- });
- logger.info("__________ generateDiagnosisCorrections: 1 correctedDiagnoses.size(): " + correctedDiagnoses.size());
-
- // Get corrected diagnosis codes from the Directory
- directoryApi.collectDiagnosisCorrections(correctedDiagnoses);
- logger.info("__________ generateDiagnosisCorrections: 2 correctedDiagnoses.size(): " + correctedDiagnoses.size());
-
- // Return a successful outcome.
- OperationOutcome outcome = new OperationOutcome();
- outcome.addIssue().setSeverity(INFORMATION).setDiagnostics("Diagnosis corrections generated successfully");
- return Collections.singletonList(outcome);
- } catch (Exception e) {
- return Util.createErrorOutcome("generateDiagnosisCorrections - unexpected error: " + Util.traceFromException(e));
- }
- }
-
- /**
- * Sends updates for Star Model data to the Directory service, based on FHIR store information.
- * This method fetches Star Model input data from the FHIR store, generates star model fact tables,
- * performs diagnosis corrections, and then updates the Directory service with the prepared data.
- *
- * The method handles errors by returning a list of OperationOutcome objects describing the issues.
- *
- *
- * @param defaultCollectionId The default BBMRI-ERIC collection ID for fetching data from the FHIR store.
- * @param minDonors The minimum number of donors required for a fact to be included in the star model output.
- * @param maxFacts The maximum number of facts to be included in the star model output. Negative number means no limit.
- * @return A list of OperationOutcome objects indicating the outcome of the star model updates.
- *
- * @throws IllegalArgumentException if the defaultCollectionId is not a valid BbmriEricId.
- */
- private List sendStarModelUpdatesToDirectory(String defaultCollectionId, int minDonors, int maxFacts) {
- logger.info("__________ sendStarModelUpdatesToDirectory: minDonors: " + minDonors);
- try {
- BbmriEricId defaultBbmriEricCollectionId = BbmriEricId
- .valueOf(defaultCollectionId)
- .orElse(null);
-
- // Pull data from the FHIR store and save it in a format suitable for generating
- // star model hypercubes.
- StarModelData starModelInputData = (new PopulateStarModelInputData(fhirApi)).populate(defaultBbmriEricCollectionId);
- if (starModelInputData == null)
- return Util.createErrorOutcome("Problem getting star model information from FHIR store");
- logger.info("__________ sendStarModelUpdatesToDirectory: number of collection IDs: " + starModelInputData.getInputCollectionIds().size());
-
- directoryApi.relogin();
-
- // Hypercubes containing less than the minimum number of donors will not be
- // included in the star model output.
- starModelInputData.setMinDonors(minDonors);
-
- // Take the patient list and the specimen list from starModelInputData and
- // use them to generate the star model fact tables.
- CreateFactTablesFromStarModelInputData.createFactTables(starModelInputData, maxFacts);
- logger.info("__________ sendStarModelUpdatesToDirectory: 1 starModelInputData.getFactCount(): " + starModelInputData.getFactCount());
-
- // Apply corrections to ICD 10 diagnoses, to make them compatible with
- // the Directory.
- if (correctedDiagnoses != null)
- starModelInputData.applyDiagnosisCorrections(correctedDiagnoses);
- logger.info("__________ sendStarModelUpdatesToDirectory: 2 starModelInputData.getFactCount(): " + starModelInputData.getFactCount());
-
- // Send fact tables to Direcory.
- directoryApi.relogin();
- List starModelUpdateOutcome = Collections.singletonList(directoryApi.updateStarModel(starModelInputData));
- logger.info("__________ sendStarModelUpdatesToDirectory: star model has been updated");
- // Return some kind of results count or whatever
- return starModelUpdateOutcome;
- } catch (Exception e) {
- return Util.createErrorOutcome("sendStarModelUpdatesToDirectory - unexpected error: " + Util.traceFromException(e));
- }
- }
-
- /**
- * Take information from the FHIR store and send aggregated updates to the Directory.
- *
- * This is a multi step process:
- * 1. Fetch a list of collections objects from the FHIR store. These contain aggregated
- * information over all specimens in the collections.
- * 2. Convert the FHIR collection objects into Directory collection PUT DTOs. Copy
- * over avaialble information from FHIR, converting where necessary.
- * 3. Using the collection IDs found in the FHIR store, send queries to the Directory
- * and fetch back the relevant GET collections. If any of the collection IDs cannot be
- * found, this ie a breaking error.
- * 4. Transfer data from the Directory GET collections to the corresponding Directory PUT
- * collections.
- * 5. Push the new information back to the Directory.
- *
- * @param defaultCollectionId The default collection ID to use for fetching collections from the FHIR store.
- * @return A list of OperationOutcome objects indicating the outcome of the update operation.
- */
- private List sendUpdatesToDirectory(String defaultCollectionId) {
- try {
- BbmriEricId defaultBbmriEricCollectionId = BbmriEricId
- .valueOf(defaultCollectionId)
- .orElse(null);
-
- List fhirCollection = fhirApi.fetchFhirCollections(defaultBbmriEricCollectionId);
- if (fhirCollection == null)
- return Util.createErrorOutcome("Problem getting collections from FHIR store");
- logger.info("__________ sendUpdatesToDirectory: FHIR collection count): " + fhirCollection.size());
-
- DirectoryCollectionPut directoryCollectionPut = FhirCollectionToDirectoryCollectionPutConverter.convert(fhirCollection);
- if (directoryCollectionPut == null)
- return Util.createErrorOutcome("Problem converting FHIR attributes to Directory attributes");
- logger.info("__________ sendUpdatesToDirectory: 1 directoryCollectionPut.getCollectionIds().size()): " + directoryCollectionPut.getCollectionIds().size());
-
- List collectionIds = directoryCollectionPut.getCollectionIds();
- String countryCode = directoryCollectionPut.getCountryCode();
- directoryApi.relogin();
- DirectoryCollectionGet directoryCollectionGet = directoryApi.fetchCollectionGetOutcomes(countryCode, collectionIds);
- if (directoryCollectionGet == null)
- return Util.createErrorOutcome("Problem getting collections from Directory");
- logger.info("__________ sendUpdatesToDirectory: 1 directoryCollectionGet.getItems().size()): " + directoryCollectionGet.getItems().size());
-
- if (!MergeDirectoryCollectionGetToDirectoryCollectionPut.merge(directoryCollectionGet, directoryCollectionPut))
- return Util.createErrorOutcome("Problem merging Directory GET attributes to Directory PUT attributes");
- logger.info("__________ sendUpdatesToDirectory: 2 directoryCollectionGet.getItems().size()): " + directoryCollectionGet.getItems().size());
-
- // Apply corrections to ICD 10 diagnoses, to make them compatible with
- // the Directory.
- if (correctedDiagnoses != null)
- directoryCollectionPut.applyDiagnosisCorrections(correctedDiagnoses);
- logger.info("__________ sendUpdatesToDirectory: 2 directoryCollectionPut.getCollectionIds().size()): " + directoryCollectionPut.getCollectionIds().size());
-
- directoryApi.relogin();
- List outcomes = Collections.singletonList(directoryApi.updateEntities(directoryCollectionPut));
- logger.info("__________ sendUpdatesToDirectory: 2 outcomes: " + outcomes);
- return outcomes;
- } catch (Exception e) {
- return Util.createErrorOutcome("sendUpdatesToDirectory - unexpected error: " + Util.traceFromException(e));
- }
- }
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 715f3a0..b35d2e1 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -8,7 +8,7 @@ ds:
store:
url: "http://bridgehead-bbmri-blaze:8080/fhir"
directory:
- url: "https://directory.bbmri-eric.eu"
+ url: "https://directory-backend.molgenis.net"
user:
name: ""
pass: ""