Skip to content

Commit aed26ec

Browse files
committed
feat(agent): add support for SQL database settings
1 parent c154242 commit aed26ec

10 files changed

Lines changed: 73 additions & 45 deletions

File tree

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/DataStoreFactory.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ public interface DataStoreFactory {
99
*/
1010
DataStore resolveDataStore();
1111
}
12-

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/config/DataStoreConfiguration.java

Lines changed: 0 additions & 18 deletions
This file was deleted.

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/controller/EntityController.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package eu.bbmri_eric.quality.agent.dataquality.controller;
22

33
import eu.bbmri_eric.quality.agent.dataquality.DataStore;
4+
import eu.bbmri_eric.quality.agent.dataquality.DataStoreFactory;
45
import java.util.NoSuchElementException;
56
import org.json.JSONObject;
67
import org.springframework.http.MediaType;
@@ -13,15 +14,16 @@
1314
@RestController
1415
@RequestMapping("/api/entities")
1516
class EntityController {
16-
private final DataStore dataStore;
17+
private final DataStoreFactory dataStoreFactory;
1718

18-
EntityController(DataStore dataStore) {
19-
this.dataStore = dataStore;
19+
EntityController(DataStoreFactory dataStoreFactory) {
20+
this.dataStoreFactory = dataStoreFactory;
2021
}
2122

2223
@GetMapping("{entityType}/{id}")
2324
public ResponseEntity<String> getEntity(
2425
@PathVariable String entityType, @PathVariable String id) {
26+
DataStore dataStore = dataStoreFactory.resolveDataStore();
2527
try {
2628
JSONObject response = dataStore.getEntity(entityType, id);
2729
if (response == null) {
@@ -39,6 +41,7 @@ public ResponseEntity<String> getEntity(
3941

4042
@GetMapping("health")
4143
public ResponseEntity<String> checkHealth() {
44+
DataStore dataStore = dataStoreFactory.resolveDataStore();
4245
try {
4346
JSONObject healthResult = dataStore.checkHealth();
4447

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/impl/BlazeFHIRStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
import org.springframework.http.HttpMethod;
3939
import org.springframework.http.ResponseEntity;
4040
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
41-
import org.springframework.stereotype.Component;
4241
import org.springframework.web.client.HttpClientErrorException;
4342
import org.springframework.web.client.RestTemplate;
43+
4444
class BlazeFHIRStore implements FHIRServer {
4545
private static final Logger log = LoggerFactory.getLogger(BlazeFHIRStore.class);
4646
private final RestTemplateBuilder restTemplateBuilder;

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/impl/DataStoreFactoryImpl.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import eu.bbmri_eric.quality.agent.settings.DatabaseType;
66
import eu.bbmri_eric.quality.agent.settings.SettingsService;
77
import eu.bbmri_eric.quality.agent.settings.dto.SettingsDTO;
8+
import eu.bbmri_eric.quality.agent.settings.event.SettingsUpdatedEvent;
89
import lombok.extern.slf4j.Slf4j;
910
import org.springframework.boot.web.client.RestTemplateBuilder;
11+
import org.springframework.context.event.EventListener;
1012
import org.springframework.stereotype.Component;
1113

1214
@Slf4j
@@ -15,6 +17,7 @@ class DataStoreFactoryImpl implements DataStoreFactory {
1517
private final SettingsService settingsService;
1618
private final BlazeFHIRStore fhirDataStore;
1719
private final SqlDataStore sqlDataStore;
20+
private volatile DataStore currentDataStore;
1821

1922
DataStoreFactoryImpl(SettingsService settingsService, RestTemplateBuilder restTemplateBuilder) {
2023
this.settingsService = settingsService;
@@ -24,7 +27,23 @@ class DataStoreFactoryImpl implements DataStoreFactory {
2427

2528
@Override
2629
public DataStore resolveDataStore() {
27-
SettingsDTO settings = settingsService.getSettings();
30+
DataStore cached = currentDataStore;
31+
if (cached != null) {
32+
return cached;
33+
}
34+
return resolveFromSettings(settingsService.getSettings());
35+
}
36+
37+
@EventListener
38+
public void onSettingsUpdated(SettingsUpdatedEvent event) {
39+
log.info("Settings updated, refreshing active data store");
40+
this.currentDataStore = resolveFromSettings(event.getSettings());
41+
if (event.getSettings().getDatabaseType() == DatabaseType.FHIR) {
42+
fhirDataStore.onSettingsUpdated(event);
43+
}
44+
}
45+
46+
private DataStore resolveFromSettings(SettingsDTO settings) {
2847
DatabaseType databaseType = settings != null ? settings.getDatabaseType() : null;
2948
if (databaseType == null) {
3049
log.warn("Database type not configured, defaulting to FHIR data store.");

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/impl/EntityCountStep.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package eu.bbmri_eric.quality.agent.dataquality.impl;
22

3+
import eu.bbmri_eric.quality.agent.dataquality.DataStore;
4+
import eu.bbmri_eric.quality.agent.dataquality.DataStoreFactory;
35
import eu.bbmri_eric.quality.agent.dataquality.FHIRServer;
46
import eu.bbmri_eric.quality.agent.dataquality.ReportPipelineStep;
57
import eu.bbmri_eric.quality.agent.dataquality.domain.Report;
@@ -10,16 +12,22 @@
1012
@Component
1113
class EntityCountStep implements ReportPipelineStep {
1214

13-
private final FHIRServer fhirStore;
15+
private final DataStoreFactory dataStoreFactory;
1416

15-
EntityCountStep(FHIRServer fhirStore) {
16-
this.fhirStore = fhirStore;
17+
EntityCountStep(DataStoreFactory dataStoreFactory) {
18+
this.dataStoreFactory = dataStoreFactory;
1719
}
1820

1921
@Override
2022
public Report execute(Report report) {
2123
log.info("Counting entities for report id: {}", report.getId());
2224

25+
DataStore dataStore = dataStoreFactory.resolveDataStore();
26+
if (!(dataStore instanceof FHIRServer fhirStore)) {
27+
log.info("Skipping entity count: not a FHIR data store");
28+
return report;
29+
}
30+
2331
Integer patientCount = fhirStore.countResources("Patient");
2432
Integer sampleCount = fhirStore.countResources("Specimen");
2533

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/impl/QualityChecksStep.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package eu.bbmri_eric.quality.agent.dataquality.impl;
22

33
import eu.bbmri_eric.quality.agent.dataquality.DataStore;
4+
import eu.bbmri_eric.quality.agent.dataquality.DataStoreFactory;
45
import eu.bbmri_eric.quality.agent.dataquality.FHIRServer;
56
import eu.bbmri_eric.quality.agent.dataquality.ReportPipelineStep;
67
import eu.bbmri_eric.quality.agent.dataquality.domain.DataQualityCheck;
@@ -23,31 +24,35 @@
2324
class QualityChecksStep implements ReportPipelineStep {
2425

2526
private final QualityCheckRepository repository;
26-
private final DataStore dataStore;
27+
private final DataStoreFactory dataStoreFactory;
2728
private final ModelMapper modelMapper;
2829

2930
QualityChecksStep(
30-
QualityCheckRepository repository, DataStore dataStore, ModelMapper modelMapper) {
31+
QualityCheckRepository repository,
32+
DataStoreFactory dataStoreFactory,
33+
ModelMapper modelMapper) {
3134
this.repository = repository;
32-
this.dataStore = dataStore;
35+
this.dataStoreFactory = dataStoreFactory;
3336
this.modelMapper = modelMapper;
3437
}
3538

3639
@Override
3740
public Report execute(Report report) {
3841
log.info("Running quality checks for report id: {}", report.getId());
42+
DataStore dataStore = dataStoreFactory.resolveDataStore();
3943
List<DataQualityCheck> dataQualityChecks = compileChecksToRun(dataStore);
40-
runRelevantChecks(report, dataQualityChecks);
44+
runRelevantChecks(report, dataQualityChecks, dataStore);
4145
log.info("Completed quality checks for report id: {}", report.getId());
4246
return report;
4347
}
4448

45-
private void runRelevantChecks(Report report, List<DataQualityCheck> dataQualityChecks) {
49+
private void runRelevantChecks(
50+
Report report, List<DataQualityCheck> dataQualityChecks, DataStore dataStore) {
4651
for (DataQualityCheck dataQualityCheck : dataQualityChecks) {
4752
if (dataQualityCheck instanceof StratifiedDataQualityCheck stratifiedCheck) {
48-
executeStratifiedCheck(stratifiedCheck, report);
53+
executeStratifiedCheck(stratifiedCheck, report, dataStore);
4954
} else {
50-
executeCheck(dataQualityCheck, report);
55+
executeCheck(dataQualityCheck, report, dataStore);
5156
}
5257
}
5358
}
@@ -83,7 +88,8 @@ private Optional<DataQualityCheck> createBuiltInCheck(QualityCheck config) {
8388
return Optional.empty();
8489
}
8590

86-
private void executeStratifiedCheck(StratifiedDataQualityCheck check, Report report) {
91+
private void executeStratifiedCheck(
92+
StratifiedDataQualityCheck check, Report report, DataStore dataStore) {
8793
if (!(dataStore instanceof FHIRServer fhirStore)) {
8894
ResultDTO resultDTO = new ResultDTO("FHIR data store required for " + check.getName());
8995
Result result = modelMapper.map(resultDTO, Result.class);
@@ -105,7 +111,7 @@ private void executeStratifiedCheck(StratifiedDataQualityCheck check, Report rep
105111
}
106112
}
107113

108-
private void executeCheck(DataQualityCheck check, Report report) {
114+
private void executeCheck(DataQualityCheck check, Report report, DataStore dataStore) {
109115
ResultDTO resultDTO = check.execute(dataStore);
110116
Result result = modelMapper.map(resultDTO, Result.class);
111117
modelMapper.map(check, result);

agent/backend/src/main/java/eu/bbmri_eric/quality/agent/dataquality/impl/SqlDataStore.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package eu.bbmri_eric.quality.agent.dataquality.impl;
22

33
import eu.bbmri_eric.quality.agent.dataquality.DataStore;
4-
import eu.bbmri_eric.quality.agent.dataquality.dto.DatabaseHealthDTO;
54
import eu.bbmri_eric.quality.agent.dataquality.dto.DBStatus;
5+
import eu.bbmri_eric.quality.agent.dataquality.dto.DatabaseHealthDTO;
66
import eu.bbmri_eric.quality.agent.dataquality.dto.ResultDTO;
77
import org.json.JSONObject;
8-
import org.springframework.stereotype.Component;
98

109
class SqlDataStore implements DataStore {
1110
private static final String NOT_IMPLEMENTED_MESSAGE = "SQL data store not implemented yet.";
1211

13-
public SqlDataStore() {
14-
}
12+
public SqlDataStore() {}
1513

1614
@Override
1715
public JSONObject getEntity(String entityType, String id) {
@@ -36,4 +34,3 @@ public DatabaseHealthDTO checkHealthV2() {
3634
return new DatabaseHealthDTO(DBStatus.DOWN, NOT_IMPLEMENTED_MESSAGE, null);
3735
}
3836
}
39-

agent/backend/src/test/java/eu/bbmri_eric/quality/agent/dataquality/controller/DataStoreTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.jupiter.api.Assertions.assertTrue;
55

66
import eu.bbmri_eric.quality.agent.dataquality.DataStore;
7+
import eu.bbmri_eric.quality.agent.dataquality.DataStoreFactory;
78
import org.json.JSONObject;
89
import org.junit.jupiter.api.Test;
910
import org.mockito.Mockito;
@@ -14,10 +15,13 @@ class DataStoreTest {
1415
@Test
1516
void getEntity_success() throws Exception {
1617
DataStore mockDataStore = Mockito.mock(DataStore.class);
18+
DataStoreFactory mockFactory = Mockito.mock(DataStoreFactory.class);
19+
Mockito.when(mockFactory.resolveDataStore()).thenReturn(mockDataStore);
20+
1721
JSONObject entity = new JSONObject().put("id", "123").put("type", "Patient");
1822
Mockito.when(mockDataStore.getEntity("Patient", "123")).thenReturn(entity);
1923

20-
EntityController controller = new EntityController(mockDataStore);
24+
EntityController controller = new EntityController(mockFactory);
2125
ResponseEntity<String> response = controller.getEntity("Patient", "123");
2226

2327
assertEquals(200, response.getStatusCodeValue());
@@ -28,10 +32,13 @@ void getEntity_success() throws Exception {
2832
@Test
2933
void getEntity_error() throws Exception {
3034
DataStore mockDataStore = Mockito.mock(DataStore.class);
35+
DataStoreFactory mockFactory = Mockito.mock(DataStoreFactory.class);
36+
Mockito.when(mockFactory.resolveDataStore()).thenReturn(mockDataStore);
37+
3138
Mockito.when(mockDataStore.getEntity("Patient", "fail"))
3239
.thenThrow(new RuntimeException("Not found"));
3340

34-
EntityController controller = new EntityController(mockDataStore);
41+
EntityController controller = new EntityController(mockFactory);
3542
ResponseEntity<String> response = controller.getEntity("Patient", "fail");
3643

3744
assertEquals(500, response.getStatusCodeValue());

agent/frontend/src/components/HealthStatusBanner.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
: 'bi-exclamation-triangle-fill',
2222
]"
2323
></i>
24-
<strong>FHIR® Server:</strong> {{ healthStore.healthStatus.status }}
24+
<strong>{{ dataSourceLabel }}:</strong> {{ healthStore.healthStatus.status }}
2525
<span v-if="healthStore.healthStatus.details?.error" class="ms-2">
2626
- {{ healthStore.healthStatus.details.error }}</span
2727
>
2828
</span>
2929
<small v-if="healthStore.healthStatus.status !== 'UP'" class="mt-2 tip-text">
3030
<i class="bi bi-info-circle me-1"></i>
31-
Tip: Check your FHIR® server connection details in
31+
Tip: Check your {{ dataSourceLabel }} connection details in
3232
<router-link to="/settings" class="settings-link">Settings</router-link>
3333
</small>
3434
</div>
@@ -49,9 +49,16 @@
4949
</template>
5050

5151
<script setup>
52+
import { computed } from 'vue';
5253
import { useHealthStore } from '@/stores/healthStore.js';
54+
import { useSettingsStore } from '@/stores/settingsStore.js';
5355
5456
const healthStore = useHealthStore();
57+
const settingsStore = useSettingsStore();
58+
59+
const isSql = computed(() => settingsStore.settings?.databaseType === 'SQL');
60+
61+
const dataSourceLabel = computed(() => (isSql.value ? 'SQL Database' : 'FHIR® Server'));
5562
</script>
5663
5764
<style scoped>

0 commit comments

Comments
 (0)