diff --git a/module-core/pom.xml b/module-core/pom.xml
index 324dd5b..cb4ebee 100644
--- a/module-core/pom.xml
+++ b/module-core/pom.xml
@@ -20,8 +20,17 @@
UTF-8
+
+ yyyy-MM-dd HH:mm:ss
+ ${maven.build.timestamp}
+
+
5.2.5
+
+ scm:git:https://github.com/intranda/goobi-vocabulary-server
+ scm:git:https://github.com/intranda/goobi-vocabulary-server
+
io.goobi.vocabulary
@@ -162,6 +171,41 @@
+
+
+ org.codehaus.mojo
+ buildnumber-maven-plugin
+ 1.4
+
+
+ validate
+
+ create
+
+
+
+
+ 7
+ false
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+
+ ${project.name}
+ ${buildDate}
+ ${project.version}
+ ${buildNumber}
+
+
+
+
+
+
maven-compiler-plugin
diff --git a/module-core/src/main/java/io/goobi/vocabulary/api/MaintenanceController.java b/module-core/src/main/java/io/goobi/vocabulary/api/MaintenanceController.java
index f1303a6..0cc0249 100644
--- a/module-core/src/main/java/io/goobi/vocabulary/api/MaintenanceController.java
+++ b/module-core/src/main/java/io/goobi/vocabulary/api/MaintenanceController.java
@@ -1,7 +1,7 @@
package io.goobi.vocabulary.api;
+import io.goobi.vocabulary.maintenance.selfcheck.SelfCheckResult;
import io.goobi.vocabulary.service.manager.MaintenanceManager;
-import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -16,14 +16,7 @@ public MaintenanceController(MaintenanceManager maintenanceManager) {
}
@GetMapping("/selfcheck")
- public ResponseEntity selfCheck() {
- String status = maintenanceManager.testAllData();
- if (status.contains("FAIL")) {
- return ResponseEntity.internalServerError()
- .body(status);
- } else {
- return ResponseEntity.ok()
- .body(status);
- }
+ public SelfCheckResult selfCheck() {
+ return maintenanceManager.performFullSelfCheck();
}
}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/api/MonitoringController.java b/module-core/src/main/java/io/goobi/vocabulary/api/MonitoringController.java
new file mode 100644
index 0000000..9c498ea
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/api/MonitoringController.java
@@ -0,0 +1,25 @@
+package io.goobi.vocabulary.api;
+
+import io.goobi.vocabulary.maintenance.MonitoringResult;
+import io.goobi.vocabulary.service.manager.MaintenanceManager;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/v1")
+public class MonitoringController {
+ private final MaintenanceManager maintenanceManager;
+
+ public MonitoringController(MaintenanceManager maintenanceManager) {
+ this.maintenanceManager = maintenanceManager;
+ }
+
+ @GetMapping("/monitoring")
+ public MonitoringResult monitoring() {
+ return maintenanceManager.getMonitoringResult();
+ }
+
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/FlywayInformation.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/FlywayInformation.java
new file mode 100644
index 0000000..d7c8b9f
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/FlywayInformation.java
@@ -0,0 +1,6 @@
+package io.goobi.vocabulary.maintenance;
+
+import java.util.Date;
+
+public record FlywayInformation(String schemaVersion, String description, Date date, int executionTime, boolean success) {
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/ManifestReader.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/ManifestReader.java
new file mode 100644
index 0000000..a0d860b
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/ManifestReader.java
@@ -0,0 +1,36 @@
+package io.goobi.vocabulary.maintenance;
+
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+@Getter
+@Service
+public class ManifestReader {
+ private final Manifest manifest;
+
+ private final String revision;
+ private final String version;
+ private final String buildDate;
+
+ public ManifestReader() throws IllegalArgumentException, IOException {
+ manifest = new Manifest(getClass().getResourceAsStream("/META-INF/MANIFEST.MF"));
+ Attributes mainAttributes = manifest.getMainAttributes();
+ revision = getOptionalValue(mainAttributes, "Revision").orElse("unknown");
+ version = getOptionalValue(mainAttributes, "Version").orElse("unknown");
+ buildDate = getOptionalValue(mainAttributes, "Build-Date").orElse("unknown");
+ }
+
+ private Optional getOptionalValue(Attributes attributes, String attributeName) throws IllegalArgumentException {
+ String result = attributes.getValue(attributeName);
+ if (StringUtils.isBlank(result)) {
+ result = null;
+ }
+ return Optional.ofNullable(result);
+ }
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/MonitoringResult.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/MonitoringResult.java
new file mode 100644
index 0000000..5da4e48
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/MonitoringResult.java
@@ -0,0 +1,6 @@
+package io.goobi.vocabulary.maintenance;
+
+import io.goobi.vocabulary.maintenance.selfcheck.SelfCheckResult;
+
+public record MonitoringResult(MonitoringState monitoring, VersionsCollection versions, FlywayInformation flyway, SelfCheckResult selfCheck) {
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/MonitoringState.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/MonitoringState.java
new file mode 100644
index 0000000..a42d3de
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/MonitoringState.java
@@ -0,0 +1,4 @@
+package io.goobi.vocabulary.maintenance;
+
+public record MonitoringState(String database, String selfCheck) {
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/VersionInformation.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/VersionInformation.java
new file mode 100644
index 0000000..cd9aafd
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/VersionInformation.java
@@ -0,0 +1,4 @@
+package io.goobi.vocabulary.maintenance;
+
+public record VersionInformation(String version, String hash) {
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/VersionsCollection.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/VersionsCollection.java
new file mode 100644
index 0000000..c8b537d
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/VersionsCollection.java
@@ -0,0 +1,4 @@
+package io.goobi.vocabulary.maintenance;
+
+public record VersionsCollection(VersionInformation core) {
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/selfcheck/SelfCheckResult.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/selfcheck/SelfCheckResult.java
new file mode 100644
index 0000000..8d7f2a9
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/selfcheck/SelfCheckResult.java
@@ -0,0 +1,9 @@
+package io.goobi.vocabulary.maintenance.selfcheck;
+
+import java.util.Date;
+
+public record SelfCheckResult(Date date, ValidationResult types, ValidationResult vocabularies, ValidationResult schemas, ValidationResult records) {
+ public boolean success() {
+ return types.errors().isEmpty() && vocabularies.errors().isEmpty() && schemas.errors().isEmpty() && records.errors().isEmpty();
+ }
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/maintenance/selfcheck/ValidationResult.java b/module-core/src/main/java/io/goobi/vocabulary/maintenance/selfcheck/ValidationResult.java
new file mode 100644
index 0000000..1c87522
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/maintenance/selfcheck/ValidationResult.java
@@ -0,0 +1,6 @@
+package io.goobi.vocabulary.maintenance.selfcheck;
+
+import java.util.List;
+
+public record ValidationResult(List errors) {
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/service/manager/MaintenanceManager.java b/module-core/src/main/java/io/goobi/vocabulary/service/manager/MaintenanceManager.java
index abfc4e5..fde262b 100644
--- a/module-core/src/main/java/io/goobi/vocabulary/service/manager/MaintenanceManager.java
+++ b/module-core/src/main/java/io/goobi/vocabulary/service/manager/MaintenanceManager.java
@@ -1,6 +1,15 @@
package io.goobi.vocabulary.service.manager;
+import com.fasterxml.jackson.databind.ObjectMapper;
import io.goobi.vocabulary.exception.VocabularyException;
+import io.goobi.vocabulary.maintenance.FlywayInformation;
+import io.goobi.vocabulary.maintenance.ManifestReader;
+import io.goobi.vocabulary.maintenance.MonitoringResult;
+import io.goobi.vocabulary.maintenance.MonitoringState;
+import io.goobi.vocabulary.maintenance.VersionInformation;
+import io.goobi.vocabulary.maintenance.VersionsCollection;
+import io.goobi.vocabulary.maintenance.selfcheck.SelfCheckResult;
+import io.goobi.vocabulary.maintenance.selfcheck.ValidationResult;
import io.goobi.vocabulary.model.jpa.FieldTypeEntity;
import io.goobi.vocabulary.model.jpa.VocabularyEntity;
import io.goobi.vocabulary.model.jpa.VocabularyRecordEntity;
@@ -10,14 +19,23 @@
import io.goobi.vocabulary.repositories.VocabularyRepository;
import io.goobi.vocabulary.repositories.VocabularySchemaRepository;
import io.goobi.vocabulary.validation.Validator;
+import lombok.extern.slf4j.Slf4j;
+import org.flywaydb.core.Flyway;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.stereotype.Service;
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Optional;
+@Slf4j
@Service
public class MaintenanceManager {
+ public static final File SELF_CHECK_CACHE_FILE = new File("/tmp/vocabulary-server-self-check.json");
+
private final FieldTypeRepository fieldTypeRepository;
private final VocabularyRepository vocabularyRepository;
private final VocabularySchemaRepository vocabularySchemaRepository;
@@ -28,7 +46,12 @@ public class MaintenanceManager {
private final Validator vocabularySchemaValidator;
private final Validator vocabularyRecordValidator;
- public MaintenanceManager(FieldTypeRepository fieldTypeRepository, VocabularyRepository vocabularyRepository, VocabularySchemaRepository vocabularySchemaRepository, VocabularyRecordRepository vocabularyRecordRepository, Validator fieldTypeValidator, Validator vocabularyValidator, Validator vocabularySchemaValidator, Validator vocabularyRecordValidator) {
+ private final ManifestReader manifestReader;
+ private final Flyway flyway;
+
+ private final ObjectMapper objectMapper;
+
+ public MaintenanceManager(FieldTypeRepository fieldTypeRepository, VocabularyRepository vocabularyRepository, VocabularySchemaRepository vocabularySchemaRepository, VocabularyRecordRepository vocabularyRecordRepository, Validator fieldTypeValidator, Validator vocabularyValidator, Validator vocabularySchemaValidator, Validator vocabularyRecordValidator, ManifestReader manifestReader, Flyway flyway, ObjectMapper objectMapper) {
this.fieldTypeRepository = fieldTypeRepository;
this.vocabularyRepository = vocabularyRepository;
this.vocabularyValidator = vocabularyValidator;
@@ -37,47 +60,90 @@ public MaintenanceManager(FieldTypeRepository fieldTypeRepository, VocabularyRep
this.fieldTypeValidator = fieldTypeValidator;
this.vocabularySchemaValidator = vocabularySchemaValidator;
this.vocabularyRecordValidator = vocabularyRecordValidator;
+ this.manifestReader = manifestReader;
+ this.flyway = flyway;
+ this.objectMapper = objectMapper;
}
- public String testAllData() {
- List selfCheckResults = new LinkedList<>();
+ public SelfCheckResult performFullSelfCheck() {
+ ValidationResult types = new ValidationResult(selfTest(fieldTypeRepository, fieldTypeValidator));
+ ValidationResult vocabularies = new ValidationResult(selfTest(vocabularyRepository, vocabularyValidator));
+ ValidationResult vocabularySchemas = new ValidationResult(selfTest(vocabularySchemaRepository, vocabularySchemaValidator));
+ ValidationResult vocabularyRecords = new ValidationResult(selfTest(vocabularyRecordRepository, vocabularyRecordValidator));
- selfCheckResults.add(selfTest("Types", fieldTypeRepository, fieldTypeValidator));
- selfCheckResults.add(selfTest("Vocabularies", vocabularyRepository, vocabularyValidator));
- selfCheckResults.add(selfTest("Vocabulary Schemas", vocabularySchemaRepository, vocabularySchemaValidator));
- selfCheckResults.add(selfTest("Vocabulary Records", vocabularyRecordRepository, vocabularyRecordValidator));
-
- return String.join("\n", selfCheckResults);
+ SelfCheckResult result = new SelfCheckResult(new Date(), types, vocabularies, vocabularySchemas, vocabularyRecords);
+ saveSelfCheckResult(result);
+ return result;
}
-
- private String selfTest(String entityName, ListCrudRepository repo, Validator validator) {
+ private List selfTest(ListCrudRepository repo, Validator validator) {
List errors = new LinkedList<>();
for (Entity entity : repo.findAll()) {
try {
validator.validate(entity);
} catch (VocabularyException e) {
- errors.add(dumpException(e, 1));
+ StringBuilder s = new StringBuilder();
+ dumpException(s, e);
+ errors.add(s.toString());
}
}
- if (errors.isEmpty()) {
- return entityName + ": OK";
- } else {
- return entityName + ": FAIL"
- + String.join("", errors);
- }
+ return errors;
}
- private String dumpException(VocabularyException ex, int level) {
- StringBuilder s = new StringBuilder();
- s.append('\n')
- .append("\t".repeat(level))
- .append(ex.getMessage());
+ private void dumpException(StringBuilder s, VocabularyException ex) {
+ s.append(ex.getMessage());
if (ex.getCauses() != null) {
for (VocabularyException c : ex.getCauses()) {
- s.append(dumpException(c, level + 1));
+ s.append("; ");
+ dumpException(s, c);
}
}
- return s.toString();
+ }
+
+ public MonitoringResult getMonitoringResult() {
+ VersionInformation coreVersion = new VersionInformation(manifestReader.getVersion(), manifestReader.getRevision());
+ VersionsCollection versions = new VersionsCollection(coreVersion);
+ FlywayInformation flywayInformation = getFlywayInformation();
+ Optional selfCheckResult = loadSelfCheckResult();
+ MonitoringState monitoringState = getMonitoringState(flywayInformation, selfCheckResult);
+ return new MonitoringResult(monitoringState, versions, flywayInformation, selfCheckResult.orElse(null));
+ }
+
+ private MonitoringState getMonitoringState(FlywayInformation flywayInformation, Optional selfCheckStatus) {
+ String database = flywayInformation.success() ? "ok" : "error";
+ String selfCheck = "unknown";
+ if (selfCheckStatus.isPresent()) {
+ selfCheck = selfCheckStatus.get().success() ? "ok" : "error";
+ }
+ return new MonitoringState(database, selfCheck);
+ }
+
+ private FlywayInformation getFlywayInformation() {
+ String version = flyway.info().current().getVersion().getVersion();
+ String description = flyway.info().current().getDescription();
+ Date date = flyway.info().current().getInstalledOn();
+ int executionTime = flyway.info().current().getExecutionTime();
+ boolean success = flyway.info().current().getState().isApplied();
+ return new FlywayInformation(version, description, date, executionTime, success);
+ }
+
+ private void saveSelfCheckResult(SelfCheckResult result) {
+ try {
+ objectMapper.writeValue(SELF_CHECK_CACHE_FILE, result);
+ } catch (IOException e) {
+ log.error("Error saving self-check result", e);
+ }
+ }
+
+ private Optional loadSelfCheckResult() {
+ if (!MaintenanceManager.SELF_CHECK_CACHE_FILE.exists()) {
+ return Optional.empty();
+ }
+ try {
+ return Optional.of(objectMapper.readValue(MaintenanceManager.SELF_CHECK_CACHE_FILE, SelfCheckResult.class));
+ } catch (IOException e) {
+ log.error("Error reading cached self-check results", e);
+ return Optional.empty();
+ }
}
}