diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/.gitignore b/JeMPI_Apps/JeMPI_BackupRestoreAPI/.gitignore
new file mode 100644
index 000000000..344687dd2
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/.gitignore
@@ -0,0 +1,116 @@
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Maven template
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# SonarLint
+.idea/sonarlint
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# Ignore config-api.json, should be copied from JeMPI_Apps/JeMPI_Configuration on build
+config-api.json
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/build.sh b/JeMPI_Apps/JeMPI_BackupRestoreAPI/build.sh
new file mode 100755
index 000000000..77b8e8d5b
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+set -u
+
+source $PROJECT_DEVOPS_DIR/conf/images/conf-app-images.sh
+source ../build-check-jdk.sh
+
+JAR_FILE=${BACKUP_RESTORE_API_JAR}
+APP_IMAGE=${BACKUP_RESTORE_API_IMAGE}
+APP=backup-restore-api
+source ../build-app-image.sh
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/checkstyle/suppression.xml b/JeMPI_Apps/JeMPI_BackupRestoreAPI/checkstyle/suppression.xml
new file mode 100644
index 000000000..ae92a113b
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/checkstyle/suppression.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/docker/.gitignore b/JeMPI_Apps/JeMPI_BackupRestoreAPI/docker/.gitignore
new file mode 100644
index 000000000..c9d5c946a
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/docker/.gitignore
@@ -0,0 +1,3 @@
+*
+!.gitignore
+!Dockerfile
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/docker/Dockerfile b/JeMPI_Apps/JeMPI_BackupRestoreAPI/docker/Dockerfile
new file mode 100644
index 000000000..800a1da52
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/docker/Dockerfile
@@ -0,0 +1,13 @@
+ARG JAVA_VERSION
+
+FROM eclipse-temurin:${JAVA_VERSION}-jre
+
+ADD JeMPI_BackupRestoreAPI-1.0-SNAPSHOT-spring-boot.jar /app/app.jar
+
+RUN printf "#!/bin/bash\n\
+cd /app\n\
+java -server -XX:MaxRAMPercentage=80 -jar /app/app.jar\n" > /entrypoint.sh
+
+RUN chmod +x /entrypoint.sh
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/pom.xml b/JeMPI_Apps/JeMPI_BackupRestoreAPI/pom.xml
new file mode 100644
index 000000000..8fff416ae
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/pom.xml
@@ -0,0 +1,244 @@
+
+
+ 4.0.0
+
+ org.jembi.jempi
+ JeMPI
+ 1.0-SNAPSHOT
+
+
+ JeMPI_BackupRestoreAPI
+ jar
+
+
+ ${project.groupId}.backuprestoreapi.api.BackupRestoreAPI
+
+
+
+
+
+ org.jembi.jempi
+ JeMPI_LibShared
+ 1.0-SNAPSHOT
+
+
+
+ org.jembi.jempi
+ JeMPI_LibMPI
+ 1.0-SNAPSHOT
+
+
+
+ org.jembi.jempi
+ JeMPI_LibAPI
+ 1.0-SNAPSHOT
+
+
+
+ org.postgresql
+ postgresql
+
+
+
+ com.typesafe
+ config
+
+
+
+ io.vavr
+ vavr
+
+
+
+ org.apache.kafka
+ kafka-clients
+
+
+
+ org.apache.kafka
+ kafka-streams
+
+
+
+ com.typesafe.akka
+ akka-actor-typed_${scala.tools.version}
+
+
+
+ com.typesafe.akka
+ akka-stream_${scala.tools.version}
+
+
+
+ com.typesafe.akka
+ akka-http_${scala.tools.version}
+
+
+
+ com.typesafe.akka
+ akka-http-jackson_${scala.tools.version}
+
+
+
+ ch.megard
+ akka-http-cors_${scala.tools.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+ commons-codec
+ commons-codec
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+ org.apache.commons
+ commons-text
+
+
+
+ io.dgraph
+ dgraph4j
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ test
+
+
+
+ org.apache.logging.log4j
+ log4j-jcl
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+
+ com.typesafe.akka
+ akka-actor-testkit-typed_${scala.tools.version}
+ test
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+
+ validate
+ compile
+
+ checkstyle/suppression.xml
+
+
+ check
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${version.org.springframework.boot.spring-boot-maven-plugin}
+
+
+
+ repackage
+
+
+ spring-boot
+ ${start-class}
+
+
+
+
+
+
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ [1.0.0,)
+
+ enforce
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/push.sh b/JeMPI_Apps/JeMPI_BackupRestoreAPI/push.sh
new file mode 100755
index 000000000..cde15df24
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/push.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+set -u
+
+source $PROJECT_DEVOPS_DIR/conf.env
+source $PROJECT_DEVOPS_DIR/conf/images/conf-app-images.sh
+
+docker tag ${BACKUP_RESTORE_API_IMAGE} ${REGISTRY_NODE_IP}/${BACKUP_RESTORE_API_IMAGE}
+docker push ${REGISTRY_NODE_IP}/${BACKUP_RESTORE_API_IMAGE}
+docker rmi ${REGISTRY_NODE_IP}/${BACKUP_RESTORE_API_IMAGE}
+
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/AppConfig.java b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/AppConfig.java
new file mode 100644
index 000000000..96e79d99d
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/AppConfig.java
@@ -0,0 +1,124 @@
+package org.jembi.jempi.backuprestoreapi;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.util.Arrays;
+
+public final class AppConfig {
+
+ private static final Logger LOGGER = LogManager.getLogger(AppConfig.class);
+ private static final Config SYSTEM_PROPERTIES = ConfigFactory.systemProperties();
+ private static final Config SYSTEM_ENVIRONMENT = ConfigFactory.systemEnvironment();
+ public static final Config CONFIG = new Builder().withSystemEnvironment()
+ .withSystemProperties()
+ .withOptionalRelativeFile("/conf/server.production.conf")
+ .withOptionalRelativeFile("/conf/server.staging.conf")
+ .withOptionalRelativeFile("/conf/server.test.conf")
+ .withResource("application.local.conf")
+ .withResource("application.conf")
+ .build();
+ public static final String POSTGRESQL_IP = CONFIG.getString("POSTGRESQL_IP");
+ public static final Integer POSTGRESQL_PORT = CONFIG.getInt("POSTGRESQL_PORT");
+
+ public static final String POSTGRESQL_USER = CONFIG.getString("POSTGRESQL_USER");
+ public static final String POSTGRESQL_PASSWORD = CONFIG.getString("POSTGRESQL_PASSWORD");
+ public static final String POSTGRESQL_NOTIFICATIONS_DB = CONFIG.getString("POSTGRESQL_NOTIFICATIONS_DB");
+ public static final String POSTGRESQL_AUDIT_DB = CONFIG.getString("POSTGRESQL_AUDIT_DB");
+ public static final String KAFKA_BOOTSTRAP_SERVERS = CONFIG.getString("KAFKA_BOOTSTRAP_SERVERS");
+ public static final String KAFKA_APPLICATION_ID = CONFIG.getString("KAFKA_APPLICATION_ID");
+ private static final String[] DGRAPH_ALPHA_HOSTS = CONFIG.getString("DGRAPH_HOSTS").split(",");
+ private static final int[] DGRAPH_ALPHA_PORTS = Arrays.stream(CONFIG.getString("DGRAPH_PORTS").split(",")).mapToInt(s -> {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException ex) {
+ return Integer.MIN_VALUE;
+ }
+ }).toArray();
+
+ public static final String LINKER_IP = CONFIG.getString("LINKER_IP");
+ public static final Integer LINKER_HTTP_PORT = CONFIG.getInt("LINKER_HTTP_PORT");
+
+ public static final String CONTROLLER_IP = CONFIG.getString("CONTROLLER_IP");
+ public static final Integer CONTROLLER_HTTP_PORT = CONFIG.getInt("CONTROLLER_HTTP_PORT");
+ public static final Integer API_HTTP_PORT = CONFIG.getInt("API_HTTP_PORT");
+ public static final Integer BACKUP_RESTORE_API_HTTP_PORT = CONFIG.getInt("BACKUP_RESTORE_API_HTTP_PORT");
+ public static final Level GET_LOG_LEVEL = Level.toLevel(CONFIG.getString("LOG4J2_LEVEL"));
+
+ private AppConfig() {
+ }
+
+ public static String[] getDGraphHosts() {
+ return DGRAPH_ALPHA_HOSTS;
+ }
+
+ public static int[] getDGraphPorts() {
+ return DGRAPH_ALPHA_PORTS;
+ }
+
+ private static class Builder {
+ private Config conf = ConfigFactory.empty();
+
+ Builder() {
+ LOGGER.info("Loading configs first row is highest priority, second row is fallback and so on");
+ }
+
+ // This should return the current executing user path
+ private static String getExecutionDirectory() {
+ return SYSTEM_PROPERTIES.getString("user.dir");
+ }
+
+ Builder withSystemProperties() {
+ conf = conf.withFallback(SYSTEM_PROPERTIES);
+ LOGGER.info("Loaded system properties into config");
+ return this;
+ }
+
+ Builder withSystemEnvironment() {
+ conf = conf.withFallback(SYSTEM_ENVIRONMENT);
+ LOGGER.info("Loaded system environment into config");
+ return this;
+ }
+
+ Builder withResource(final String resource) {
+ Config resourceConfig = ConfigFactory.parseResources(resource);
+ String empty = resourceConfig.entrySet().isEmpty()
+ ? " contains no values"
+ : "";
+ conf = conf.withFallback(resourceConfig);
+ LOGGER.info("Loaded config file from resource ({}){}", resource, empty);
+ return this;
+ }
+
+ Builder withOptionalFile(final String path) {
+ File secureConfFile = new File(path);
+ if (secureConfFile.exists()) {
+ LOGGER.info("Loaded config file from path ({})", path);
+ conf = conf.withFallback(ConfigFactory.parseFile(secureConfFile));
+ } else {
+ LOGGER.info("Attempted to load file from path ({}) but it was not found", path);
+ }
+ return this;
+ }
+
+ Builder withOptionalRelativeFile(final String path) {
+ return withOptionalFile(getExecutionDirectory() + path);
+ }
+
+ Config build() {
+ // Resolve substitutions.
+ conf = conf.resolve();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Logging properties. Make sure sensitive data such as passwords or secrets are not logged!");
+ LOGGER.debug(conf.root().render());
+ }
+ return conf;
+ }
+
+ }
+
+}
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/Ask.java b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/Ask.java
new file mode 100644
index 000000000..2dc4a099d
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/Ask.java
@@ -0,0 +1,56 @@
+package org.jembi.jempi.backuprestoreapi;
+
+import akka.actor.typed.ActorRef;
+import akka.actor.typed.ActorSystem;
+import akka.actor.typed.javadsl.AskPattern;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jembi.jempi.shared.models.GlobalConstants;
+import org.jembi.jempi.shared.models.ApiModels;
+import org.jembi.jempi.shared.models.RestoreGoldenRecords;
+
+import java.util.concurrent.CompletionStage;
+
+public final class Ask {
+
+ private static final Logger LOGGER = LogManager.getLogger(Ask.class);
+
+ private Ask() {
+ }
+
+ static CompletionStage getGidsAll(
+ final ActorSystem actorSystem,
+ final ActorRef backEnd) {
+ CompletionStage stage = AskPattern
+ .ask(backEnd,
+ BackEnd.GetGidsAllRequest::new,
+ java.time.Duration.ofSeconds(GlobalConstants.TIMEOUT_DGRAPH_QUERY_SECS),
+ actorSystem.scheduler());
+ return stage.thenApply(response -> response);
+
+ }
+
+ static CompletionStage getExpandedGoldenRecord(
+ final ActorSystem actorSystem,
+ final ActorRef backEnd,
+ final ApiModels.ApiGoldenRecords payload) {
+ final CompletionStage stage = AskPattern
+ .ask(backEnd,
+ replyTo -> new BackEnd.GetExpandedGoldenRecordRequest(replyTo, payload.gid()),
+ java.time.Duration.ofSeconds(GlobalConstants.TIMEOUT_DGRAPH_QUERY_SECS),
+ actorSystem.scheduler());
+ return stage.thenApply(response -> response);
+ }
+
+ static CompletionStage postGoldenRecord(
+ final ActorSystem actorSystem,
+ final ActorRef backEnd,
+ final RestoreGoldenRecords payload) {
+ CompletionStage stage = AskPattern
+ .ask(backEnd,
+ replyTo -> new BackEnd.PostGoldenRecordRequest(replyTo, payload),
+ java.time.Duration.ofSeconds(GlobalConstants.TIMEOUT_DGRAPH_QUERY_SECS),
+ actorSystem.scheduler());
+ return stage.thenApply(response -> response);
+ }
+}
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/BackEnd.java b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/BackEnd.java
new file mode 100644
index 000000000..ad3f7ca7a
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/BackEnd.java
@@ -0,0 +1,362 @@
+package org.jembi.jempi.backuprestoreapi;
+
+import akka.actor.typed.ActorRef;
+import akka.actor.typed.Behavior;
+import akka.actor.typed.javadsl.*;
+import io.vavr.control.Either;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jembi.jempi.libmpi.LibMPI;
+import org.jembi.jempi.libmpi.MpiGeneralError;
+import org.jembi.jempi.libmpi.MpiServiceError;
+import org.jembi.jempi.shared.models.*;
+
+import java.util.Collections;
+import java.util.List;
+
+public final class BackEnd extends AbstractBehavior {
+
+ private static final Logger LOGGER = LogManager.getLogger(BackEnd.class);
+ private final String pgIP;
+ private final Integer pgPort;
+ private final String pgUser;
+ private final String pgPassword;
+ private final String pgNotificationsDb;
+ private final String pgAuditDb;
+ private final PsqlNotifications psqlNotifications;
+ private LibMPI libMPI = null;
+ private String[] dgraphHosts = null;
+ private int[] dgraphPorts = null;
+
+ private BackEnd(
+ final Level debugLevel,
+ final ActorContext context,
+ final String[] dgraphHosts,
+ final int[] dgraphPorts,
+ final String sqlIP,
+ final int sqlPort,
+ final String sqlUser,
+ final String sqlPassword,
+ final String sqlNotificationsDb,
+ final String sqlAuditDb,
+ final String kafkaBootstrapServers,
+ final String kafkaClientId) {
+ super(context);
+ try {
+ this.libMPI = null;
+ this.dgraphHosts = dgraphHosts;
+ this.dgraphPorts = dgraphPorts;
+ this.pgIP = sqlIP;
+ this.pgPort = sqlPort;
+ this.pgUser = sqlUser;
+ this.pgPassword = sqlPassword;
+ this.pgNotificationsDb = sqlNotificationsDb;
+ this.pgAuditDb = sqlAuditDb;
+ psqlNotifications = new PsqlNotifications(sqlIP, sqlPort, sqlNotificationsDb, sqlUser, sqlPassword);
+ openMPI(kafkaBootstrapServers, kafkaClientId, debugLevel);
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ throw e;
+ }
+
+ }
+
+ public static Behavior create(
+ final Level level,
+ final String[] dgraphHosts,
+ final int[] dgraphPorts,
+ final String sqlIP,
+ final int sqlPort,
+ final String sqlUser,
+ final String sqlPassword,
+ final String sqlNotificationsDb,
+ final String sqlAuditDb,
+ final String kafkaBootstrapServers,
+ final String kafkaClientId) {
+ return Behaviors.setup(context -> new BackEnd(level,
+ context,
+ dgraphHosts,
+ dgraphPorts,
+ sqlIP,
+ sqlPort,
+ sqlUser,
+ sqlPassword,
+ sqlNotificationsDb,
+ sqlAuditDb,
+ kafkaBootstrapServers,
+ kafkaClientId));
+ }
+
+ private void openMPI(
+ final String kafkaBootstrapServers,
+ final String kafkaClientId,
+ final Level debugLevel) {
+ libMPI = new LibMPI(debugLevel, dgraphHosts, dgraphPorts, kafkaBootstrapServers, kafkaClientId);
+ }
+
+ @Override
+ public Receive createReceive() {
+ return actor();
+ }
+
+ public Receive actor() {
+ ReceiveBuilder builder = newReceiveBuilder();
+ return builder.onMessage(CountGoldenRecordsRequest.class, this::countGoldenRecordsHandler)
+ .onMessage(CountInteractionsRequest.class, this::countInteractionsHandler)
+ .onMessage(CountRecordsRequest.class, this::countRecordsHandler)
+ .onMessage(FindExpandedSourceIdRequest.class, this::findExpandedSourceIdHandler)
+ .onMessage(GetGidsAllRequest.class, this::getGidsAllHandler)
+ .onMessage(GetInteractionRequest.class, this::getInteractionHandler)
+ .onMessage(GetExpandedInteractionsRequest.class, this::getExpandedInteractionsHandler)
+ .onMessage(GetExpandedGoldenRecordRequest.class, this::getExpandedGoldenRecordHandler)
+ .onMessage(PostGoldenRecordRequest.class, this::postGoldenRecordRequestHandler)
+ .onMessage(GetExpandedGoldenRecordsRequest.class, this::getExpandedGoldenRecordsHandler)
+ .build();
+ }
+ private Behavior countGoldenRecordsHandler(final CountGoldenRecordsRequest request) {
+ try {
+ final long count = libMPI.countGoldenRecords();
+ request.replyTo.tell(new CountGoldenRecordsResponse(Either.right(count)));
+ } catch (Exception exception) {
+ LOGGER.error("libMPI.countGoldenRecords failed with error message: {}", exception.getMessage());
+ request.replyTo.tell(
+ new CountGoldenRecordsResponse(Either.left(new MpiServiceError.GeneralError(exception.getMessage()))));
+ }
+ return Behaviors.same();
+ }
+
+ private Behavior countInteractionsHandler(final CountInteractionsRequest request) {
+ try {
+ final long count = libMPI.countInteractions();
+ request.replyTo.tell(new CountInteractionsResponse(Either.right(count)));
+ } catch (Exception exception) {
+ LOGGER.error("libMPI.countPatientRecords failed with error message: {}", exception.getMessage());
+ request.replyTo.tell(
+ new CountInteractionsResponse(Either.left(new MpiServiceError.GeneralError(exception.getMessage()))));
+ }
+ return Behaviors.same();
+ }
+
+ private Behavior countRecordsHandler(final CountRecordsRequest request) {
+ final var recs = libMPI.countGoldenRecords();
+ final var docs = libMPI.countInteractions();
+ request.replyTo.tell(new CountRecordsResponse(recs, docs));
+ return Behaviors.same();
+ }
+
+ private Behavior findExpandedSourceIdHandler(final FindExpandedSourceIdRequest request) {
+ final var sourceIdList = libMPI.findExpandedSourceIdList(request.facility, request.client);
+ request.replyTo.tell(new FindExpandedSourceIdResponse(sourceIdList));
+
+ return Behaviors.same();
+ }
+
+ private Behavior getGidsAllHandler(final GetGidsAllRequest request) {
+ var recs = libMPI.findGoldenIds();
+ request.replyTo.tell(new GetGidsAllResponse(recs));
+ return Behaviors.same();
+ }
+
+ private Behavior getExpandedGoldenRecordHandler(final GetExpandedGoldenRecordRequest request) {
+ ExpandedGoldenRecord expandedGoldenRecord = null;
+ try {
+ expandedGoldenRecord = libMPI.findExpandedGoldenRecord(request.goldenId);
+ } catch (Exception e) {
+ LOGGER.error(e.getLocalizedMessage(), e);
+ LOGGER.error("libMPI.findExpandedGoldenRecord failed for goldenId: {} with error: {}",
+ request.goldenId,
+ e.getMessage());
+ }
+
+ if (expandedGoldenRecord == null) {
+ request.replyTo
+ .tell(new GetExpandedGoldenRecordResponse(Either.left(new MpiServiceError.GoldenIdDoesNotExistError(
+ "Golden Record does not exist",
+ request.goldenId))));
+ } else {
+ request.replyTo.tell(new GetExpandedGoldenRecordResponse(Either.right(expandedGoldenRecord)));
+ }
+ return Behaviors.same();
+ }
+
+ private Behavior getExpandedGoldenRecordsHandler(final GetExpandedGoldenRecordsRequest request) {
+ List goldenRecords = null;
+ try {
+ goldenRecords = libMPI.findExpandedGoldenRecords(request.goldenIds);
+ } catch (Exception exception) {
+ LOGGER.error("libMPI.findExpandedGoldenRecords failed for goldenIds: {} with error: {}",
+ request.goldenIds,
+ exception.getMessage());
+ }
+
+ if (goldenRecords == null) {
+ request.replyTo
+ .tell(new GetExpandedGoldenRecordsResponse(Either.left(new MpiServiceError.GoldenIdDoesNotExistError(
+ "Golden Records do not exist",
+ Collections.singletonList(request.goldenIds).toString()))));
+ } else {
+ request.replyTo.tell(new GetExpandedGoldenRecordsResponse(Either.right(goldenRecords)));
+ }
+ return Behaviors.same();
+ }
+
+ private Behavior postGoldenRecordRequestHandler(final PostGoldenRecordRequest request) {
+ String goldenRecords = null;
+ try {
+ goldenRecords = libMPI.postGoldenRecord(request.goldenRecord);
+ } catch (Exception exception) {
+ LOGGER.error("libMPI.postGoldenRecord failed for goldenIds: {} with error: {}",
+ request.goldenRecord,
+ exception.getMessage());
+ }
+ request.replyTo.tell(new PostGoldenRecordResponse(goldenRecords));
+ return Behaviors.same();
+ }
+
+ private Behavior getExpandedInteractionsHandler(final GetExpandedInteractionsRequest request) {
+ List expandedInteractions = null;
+ try {
+ expandedInteractions = libMPI.findExpandedInteractions(request.patientIds);
+ } catch (Exception exception) {
+ LOGGER.error("libMPI.findExpandedPatientRecords failed for patientIds: {} with error: {}",
+ request.patientIds,
+ exception.getMessage());
+ }
+
+ if (expandedInteractions == null) {
+ request.replyTo
+ .tell(new GetExpandedInteractionsResponse(Either.left(new MpiServiceError.InteractionIdDoesNotExistError(
+ "Patient Records do not exist",
+ Collections.singletonList(request.patientIds).toString()))));
+ } else {
+ request.replyTo.tell(new GetExpandedInteractionsResponse(Either.right(expandedInteractions)));
+ }
+ return Behaviors.same();
+ }
+
+ private Behavior getInteractionHandler(final GetInteractionRequest request) {
+ Interaction interaction = null;
+ try {
+ interaction = libMPI.findInteraction(request.iid);
+ } catch (Exception exception) {
+ LOGGER.error("libMPI.findPatientRecord failed for patientId: {} with error: {}", request.iid,
+ exception.getMessage());
+ }
+
+ if (interaction == null) {
+ request.replyTo.tell(new GetInteractionResponse(Either.left(new MpiServiceError.InteractionIdDoesNotExistError(
+ "Interaction not found",
+ request.iid))));
+ } else {
+ request.replyTo.tell(new GetInteractionResponse(Either.right(interaction)));
+ }
+ return Behaviors.same();
+ }
+
+
+
+ public interface Event {
+ }
+
+ public interface EventResponse {
+ }
+
+ public record CountGoldenRecordsRequest(ActorRef replyTo) implements Event {
+ }
+
+ public record CountGoldenRecordsResponse(Either count) implements EventResponse {
+ }
+
+ public record CountInteractionsRequest(ActorRef replyTo) implements Event {
+ }
+
+ public record CountInteractionsResponse(Either count) implements EventResponse {
+ }
+
+ public record CountRecordsRequest(ActorRef replyTo) implements Event {
+ }
+
+ public record CountRecordsResponse(
+ long goldenRecords,
+ long patientRecords) implements EventResponse {
+ }
+
+ public record GetGidsPagedRequest(
+ ActorRef replyTo,
+ long offset,
+ long length) implements Event {
+ }
+
+ public record GetGidsPagedResponse(List goldenIds) implements EventResponse {
+ }
+
+ public record GetGoldenRecordAuditTrailRequest(
+ ActorRef replyTo,
+ String uid) implements Event {
+ }
+
+ public record GetGoldenRecordAuditTrailResponse(List auditTrail) {
+ }
+
+ public record GetGidsAllRequest(ActorRef replyTo) implements Event {
+ }
+
+ public record GetGidsAllResponse(List records) implements EventResponse {
+ }
+
+ public record FindExpandedSourceIdRequest(
+ ActorRef replyTo,
+ String facility,
+ String client) implements Event {
+ }
+
+ public record FindExpandedSourceIdResponse(List records) implements EventResponse {
+ }
+
+ public record GetExpandedGoldenRecordRequest(
+ ActorRef replyTo,
+ String goldenId) implements Event {
+ }
+
+ public record GetExpandedGoldenRecordResponse(Either goldenRecord)
+ implements EventResponse {
+ }
+
+ public record GetExpandedGoldenRecordsRequest(
+ ActorRef replyTo,
+ List goldenIds) implements Event {
+ }
+
+ public record GetExpandedGoldenRecordsResponse(
+ Either> expandedGoldenRecords) implements EventResponse {
+ }
+
+ public record GetExpandedInteractionsRequest(
+ ActorRef replyTo,
+ List patientIds) implements Event {
+ }
+
+ public record GetExpandedInteractionsResponse(
+ Either> expandedPatientRecords) implements EventResponse {
+ }
+
+ public record GetInteractionRequest(
+ ActorRef replyTo,
+ String iid) implements Event {
+ }
+
+ public record GetInteractionResponse(Either patient) implements EventResponse {
+ }
+
+ public record PostGoldenRecordRequest(
+ ActorRef replyTo,
+ RestoreGoldenRecords goldenRecord) implements Event {
+ }
+
+ public record PostGoldenRecordResponse(String goldenRecord)
+ implements EventResponse {
+ }
+
+
+}
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/JsonFieldsConfig.java b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/JsonFieldsConfig.java
new file mode 100644
index 000000000..93f8b141a
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/JsonFieldsConfig.java
@@ -0,0 +1,99 @@
+package org.jembi.jempi.backuprestoreapi;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jembi.jempi.shared.utils.AppUtils;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import java.io.*;
+import java.nio.file.FileSystems;
+
+public final class JsonFieldsConfig {
+
+ private static final Logger LOGGER = LogManager.getLogger(JsonFieldsConfig.class);
+ public String jsonFields;
+ private JSONArray fields;
+
+ public JsonFieldsConfig(final String resourceFilename) {
+ try {
+ load(resourceFilename);
+ } catch (Exception e) {
+ LOGGER.debug(e);
+ }
+ }
+
+
+ private JSONArray buildFieldsResponsePayload(
+ final JSONArray systemFields,
+ final JSONArray customFields) {
+ JSONArray result = new JSONArray();
+ // Process system fields
+ for (Object systemField : systemFields) {
+ JSONObject field = (JSONObject) systemField;
+ // Mark field as readonly
+ field.put("readOnly", true);
+ // Merge array values
+ result.add(field);
+ }
+ // Process custom fields
+ for (Object customField : customFields) {
+ // Convert field names from snake case to camel case
+ JSONObject field = (JSONObject) customField;
+ String fieldName = (String) field.get("fieldName");
+ field.put("fieldName", AppUtils.snakeToCamelCase(fieldName));
+ // Remove extra attributes
+ field.remove("indexGoldenRecord");
+ field.remove("indexPatient");
+ field.remove("m");
+ field.remove("u");
+ // Mark field as editable
+ field.put("readOnly", false);
+ // Merge array values
+ result.add(field);
+ }
+ return result;
+ }
+
+ private InputStream getFileStreamFromResource(final String resourceFilename) {
+ ClassLoader classLoader = getClass().getClassLoader();
+ return classLoader.getResourceAsStream(resourceFilename);
+ }
+
+ public void load(final String filename) {
+ final var separator = FileSystems.getDefault().getSeparator();
+ final var filePath = "%sapp%sconf_system%s%s".formatted(separator, separator, separator, filename);
+ final var file = new File(filePath);
+ try (Reader reader = new InputStreamReader(new FileInputStream(file))) {
+ JSONParser jsonParser = new JSONParser();
+ Object obj = jsonParser.parse(reader);
+ JSONObject config = (JSONObject) obj;
+ // System fields are fields that exists regardless of the implementation
+ JSONArray systemFields = (JSONArray) config.get("systemFields");
+ // Custom fields depend on the needs of the implementation
+ JSONArray customFields = (JSONArray) config.get("fields");
+ jsonFields = buildFieldsResponsePayload(systemFields, customFields).toJSONString();
+ } catch (IOException | ParseException e) {
+ throw new RuntimeException(e);
+ }
+
+//
+// JSONParser jsonParser = new JSONParser();
+// try (Reader reader = new InputStreamReader(getFileStreamFromResource(resourceFilename))) {
+// // Read JSON file
+// Object obj = jsonParser.parse(reader);
+// JSONObject config = (JSONObject) obj;
+// // System fields are fields that exists regardless of the implementation
+// JSONArray systemFields = (JSONArray) config.get("systemFields");
+// // Custom fields depend on the needs of the implementation
+// JSONArray customFields = (JSONArray) config.get("fields");
+// jsonFields = buildFieldsResponsePayload(systemFields, customFields).toJSONString();
+// } catch (ParseException | IOException e) {
+// LOGGER.error(e.getLocalizedMessage(), e);
+// fields = new JSONArray();
+// }
+ }
+}
+
diff --git a/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/MapError.java b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/MapError.java
new file mode 100644
index 000000000..80d6af6c0
--- /dev/null
+++ b/JeMPI_Apps/JeMPI_BackupRestoreAPI/src/main/java/org/jembi/jempi/backuprestoreapi/MapError.java
@@ -0,0 +1,42 @@
+package org.jembi.jempi.backuprestoreapi;
+
+import akka.http.javadsl.marshallers.jackson.Jackson;
+import akka.http.javadsl.marshalling.Marshaller;
+import akka.http.javadsl.model.RequestEntity;
+import akka.http.javadsl.model.StatusCodes;
+import akka.http.javadsl.server.Route;
+import org.jembi.jempi.libmpi.MpiGeneralError;
+import org.jembi.jempi.libmpi.MpiServiceError;
+
+import static akka.http.javadsl.server.Directives.complete;
+import static org.jembi.jempi.shared.utils.AppUtils.OBJECT_MAPPER;
+
+public final class MapError {
+
+ private static final Marshaller