diff --git a/.gitignore b/.gitignore index 108173a..82bf165 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,72 @@ -target/ -.project -.classpath -.settings/ -.DS_Store -.idea/ +# 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 + +# 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 -logfile -*.log* \ No newline at end of file + +# 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 +target/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..c89a2f0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/../../../../../../../:\Users\khannan\source\repos\hfr-mediator\.idea/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..919ce1f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4361200 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml new file mode 100644 index 0000000..0f5b76a --- /dev/null +++ b/.idea/runConfigurations/All_Tests.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Build.xml b/.idea/runConfigurations/Build.xml new file mode 100644 index 0000000..5f179c6 --- /dev/null +++ b/.idea/runConfigurations/Build.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 67f9db7..9c2783c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,28 @@ file:///${project.basedir}/local-maven-repo + + + dnchembi_at_softmed_dot_co_dot_tz + Danford Nchembi + SoftMed Technologies + + + ilakozejumanne_at_softmed_dot_co_dot_tz + Ilakoze Jumanne + SoftMed Technologies + + + ibrahimmo_at_hhsc_dot_ca + Mohamed Ibrahim + Hamilton Health Sciences + + + khannani_at_hhsc_dot_ca + Nityan Khanna + Hamilton Health Sciences + + @@ -21,8 +43,8 @@ maven-compiler-plugin 3.2 - 1.7 - 1.7 + 1.8 + 1.8 @@ -41,7 +63,7 @@ - tz.go.moh.him.elmis.mediator.e9.MediatorMain + tz.go.moh.him.hfr.mediator.MediatorMain @@ -84,6 +106,32 @@ + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + attach-javadocs + + jar + + + + @@ -101,7 +149,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/src/main/java/tz/go/moh/him/hfr/mediator/DefaultOrchestrator.java b/src/main/java/tz/go/moh/him/hfr/mediator/DefaultOrchestrator.java deleted file mode 100644 index 1b45ca3..0000000 --- a/src/main/java/tz/go/moh/him/hfr/mediator/DefaultOrchestrator.java +++ /dev/null @@ -1,30 +0,0 @@ -package tz.go.moh.him.hfr.mediator; - -import akka.actor.UntypedActor; -import akka.event.Logging; -import akka.event.LoggingAdapter; -import org.apache.http.HttpStatus; -import org.openhim.mediator.engine.MediatorConfig; -import org.openhim.mediator.engine.messages.FinishRequest; -import org.openhim.mediator.engine.messages.MediatorHTTPRequest; - -public class DefaultOrchestrator extends UntypedActor { - LoggingAdapter log = Logging.getLogger(getContext().system(), this); - - private final MediatorConfig config; - - - public DefaultOrchestrator(MediatorConfig config) { - this.config = config; - } - - @Override - public void onReceive(Object msg) throws Exception { - if (msg instanceof MediatorHTTPRequest) { - FinishRequest finishRequest = new FinishRequest("A message from my new mediator!", "text/plain", HttpStatus.SC_OK); - ((MediatorHTTPRequest) msg).getRequestHandler().tell(finishRequest, getSelf()); - } else { - unhandled(msg); - } - } -} diff --git a/src/main/java/tz/go/moh/him/hfr/mediator/MediatorMain.java b/src/main/java/tz/go/moh/him/hfr/mediator/MediatorMain.java index d7cf1e2..a891678 100644 --- a/src/main/java/tz/go/moh/him/hfr/mediator/MediatorMain.java +++ b/src/main/java/tz/go/moh/him/hfr/mediator/MediatorMain.java @@ -6,36 +6,61 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.openhim.mediator.engine.*; +import tz.go.moh.him.hfr.mediator.orchestrator.AcknowledgementOrchestrator; +import tz.go.moh.him.hfr.mediator.orchestrator.FacilityOrchestrator; + import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Properties; +/** + * Represents the main application. + */ public class MediatorMain { + /** + * Represents the mediator registration info. + */ + private static final String MEDIATOR_REGISTRATION_INFO = "mediator-registration-info.json"; + + /** + * Builds the routing table. + * + * @return Returns the routing table. + * @throws RoutingTable.RouteAlreadyMappedException if the route is already mapped + */ private static RoutingTable buildRoutingTable() throws RoutingTable.RouteAlreadyMappedException { RoutingTable routingTable = new RoutingTable(); - //TODO Configure routes here - //... - routingTable.addRoute("/hfr", DefaultOrchestrator.class); + routingTable.addRoute("/hfr", FacilityOrchestrator.class); + routingTable.addRoute("/hfr-ack", AcknowledgementOrchestrator.class); return routingTable; } + /** + * Builds the startup actors configuration. + * + * @return Returns the startup actors configuration. + */ private static StartupActorsConfig buildStartupActorsConfig() { - StartupActorsConfig startupActors = new StartupActorsConfig(); - - //TODO Add own startup actors here - //... - - return startupActors; + return new StartupActorsConfig(); } + /** + * Loads the configuration. + * + * @param configPath The path of the configuration. + * @return Returns the configuration instance. + * @throws IOException if an IO exception occurs + * @throws RoutingTable.RouteAlreadyMappedException if the route is already mapped + */ private static MediatorConfig loadConfig(String configPath) throws IOException, RoutingTable.RouteAlreadyMappedException { MediatorConfig config = new MediatorConfig(); - if (configPath!=null) { + if (configPath != null) { Properties props = new Properties(); File conf = new File(configPath); InputStream in = FileUtils.openInputStream(conf); @@ -49,7 +74,7 @@ private static MediatorConfig loadConfig(String configPath) throws IOException, config.setName(config.getProperty("mediator.name")); config.setServerHost(config.getProperty("mediator.host")); - config.setServerPort( Integer.parseInt(config.getProperty("mediator.port")) ); + config.setServerPort(Integer.parseInt(config.getProperty("mediator.port"))); config.setRootTimeout(Integer.parseInt(config.getProperty("mediator.timeout"))); config.setCoreHost(config.getProperty("core.host")); @@ -62,18 +87,31 @@ private static MediatorConfig loadConfig(String configPath) throws IOException, config.setRoutingTable(buildRoutingTable()); config.setStartupActors(buildStartupActorsConfig()); - InputStream regInfo = MediatorMain.class.getClassLoader().getResourceAsStream("mediator-registration-info.json"); - RegistrationConfig regConfig = new RegistrationConfig(regInfo); + InputStream registrationInformation = MediatorMain.class.getClassLoader().getResourceAsStream(MEDIATOR_REGISTRATION_INFO); + + if (registrationInformation == null) { + throw new FileNotFoundException("Unable to locate " + MEDIATOR_REGISTRATION_INFO); + } + + RegistrationConfig regConfig = new RegistrationConfig(registrationInformation); + config.setRegistrationConfig(regConfig); - if (config.getProperty("mediator.heartbeats")!=null && "true".equalsIgnoreCase(config.getProperty("mediator.heartbeats"))) { + if (config.getProperty("mediator.heartbeats") != null && "true".equalsIgnoreCase(config.getProperty("mediator.heartbeats"))) { config.setHeartbeatsEnabled(true); } return config; } + /** + * The main entry point of the application. + * + * @param args The arguments. + * @throws Exception if an exception occurs + */ public static void main(String... args) throws Exception { + //setup actor system final ActorSystem system = ActorSystem.create("mediator"); //setup logger for main @@ -83,7 +121,7 @@ public static void main(String... args) throws Exception { log.info("Initializing mediator actors..."); String configPath = null; - if (args.length==2 && args[0].equals("--conf")) { + if (args != null && args.length == 2 && args[0].equals("--conf")) { configPath = args[1]; log.info("Loading mediator configuration from '" + configPath + "'..."); } else { @@ -91,17 +129,17 @@ public static void main(String... args) throws Exception { } MediatorConfig config = loadConfig(configPath); + + config.setSSLContext(new MediatorConfig.SSLContext(true)); + final MediatorServer server = new MediatorServer(system, config); //setup shutdown hook - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - log.info("Shutting down mediator"); - server.stop(); - system.shutdown(); - } - }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("Shutting down mediator"); + server.stop(); + system.shutdown(); + })); log.info("Starting mediator server..."); server.start(); diff --git a/src/main/java/tz/go/moh/him/hfr/mediator/domain/Ack.java b/src/main/java/tz/go/moh/him/hfr/mediator/domain/Ack.java new file mode 100644 index 0000000..ba02dff --- /dev/null +++ b/src/main/java/tz/go/moh/him/hfr/mediator/domain/Ack.java @@ -0,0 +1,77 @@ +package tz.go.moh.him.hfr.mediator.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +/** + * Represents an acknowledgement. + */ +public class Ack { + + /** + * The transaction id number. + */ + @JsonProperty("IL_IDNumber") + @SerializedName("IL_IDNumber") + private String transactionIdNumber; + + /** + * The status. + */ + @JsonProperty("status") + @SerializedName("status") + private String status; + + /** + * Initializes a new instance of the {@link Ack} class. + */ + public Ack() { + } + + /** + * Initializes a new instance of the {@link Ack} class. + * + * @param transactionIdNumber The transaction id number. + * @param status The status. + */ + public Ack(String transactionIdNumber, String status) { + this.setTransactionIdNumber(transactionIdNumber); + this.setStatus(status); + } + + /** + * Gets the transaction id number. + * + * @return Returns the transaction id number. + */ + public String getTransactionIdNumber() { + return transactionIdNumber; + } + + /** + * Sets the transaction id number. + * + * @param transactionIdNumber The transaction id number. + */ + public void setTransactionIdNumber(String transactionIdNumber) { + this.transactionIdNumber = transactionIdNumber; + } + + /** + * Gets the status. + * + * @return Returns the status. + */ + public String getStatus() { + return status; + } + + /** + * Sets the status. + * + * @param status The status. + */ + public void setStatus(String status) { + this.status = status; + } +} diff --git a/src/main/java/tz/go/moh/him/hfr/mediator/domain/HfrRequest.java b/src/main/java/tz/go/moh/him/hfr/mediator/domain/HfrRequest.java new file mode 100644 index 0000000..afdf9f8 --- /dev/null +++ b/src/main/java/tz/go/moh/him/hfr/mediator/domain/HfrRequest.java @@ -0,0 +1,447 @@ +package tz.go.moh.him.hfr.mediator.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +import java.util.HashMap; + +/** + * Represents a Health Facility Registry request message. + */ +public class HfrRequest { + + /** + * Represents the operation map for the post or update field. + */ + public static final HashMap OPERATION_MAP = new HashMap() {{ + put("P", "POST"); + put("U", "PUT"); + }}; + + /** + * The transaction id number from the IL. + */ + @JsonProperty("IL_IDNumber") + @SerializedName("IL_IDNumber") + private String transactionIdNumber; + /** + * The facility id number. + */ + @JsonProperty("Fac_IDNumber") + @SerializedName("Fac_IDNumber") + private String facilityIdNumber; + /** + * The facility name. + */ + @JsonProperty("Name") + @SerializedName("Name") + private String name; + /** + * The common facility name. + */ + @JsonProperty("Comm_FacName") + @SerializedName("Comm_FacName") + private String commonFacilityName; + /** + * The zone. + */ + @JsonProperty("Zone") + @SerializedName("Zone") + private String zone; + /** + * The region. + */ + @JsonProperty("Region") + @SerializedName("Region") + private String region; + /** + * The region code. + */ + @JsonProperty("RegionCode") + @SerializedName("RegionCode") + private String regionCode; + /** + * The district. + */ + @JsonProperty("District") + @SerializedName("District") + private String district; + /** + * The district code. + */ + @JsonProperty("DistrictCode") + @SerializedName("DistrictCode") + private String districtCode; + /** + * The council. + */ + @JsonProperty("Council") + @SerializedName("Council") + private String council; + /** + * The council code. + */ + @JsonProperty("CouncilCode") + @SerializedName("CouncilCode") + private String councilCode; + /** + * The ward. + */ + @JsonProperty("Ward") + @SerializedName("Ward") + private String ward; + /** + * The village street. + */ + @JsonProperty("VillageMtaa") + @SerializedName("VillageMtaa") + private String villageMtaa; + /** + * The facility type group. + */ + @JsonProperty("FacilityTypeGroup") + @SerializedName("FacilityTypeGroup") + private String facilityTypeGroup; + /** + * The facility type group code. + */ + @JsonProperty("FacilityTypeGroupCode") + @SerializedName("FacilityTypeGroupCode") + private String facilityTypeGroupCode; + /** + * The facility type. + */ + @JsonProperty("FacilityType") + @SerializedName("FacilityType") + private String facilityType; + /** + * The facility type code. + */ + @JsonProperty("FacilityTypeCode") + @SerializedName("FacilityTypeCode") + private String facilityTypeCode; + /** + * The ownership group. + */ + @JsonProperty("OwnershipGroup") + @SerializedName("OwnershipGroup") + private String ownershipGroup; + /** + * The ownership group code. + */ + @JsonProperty("OwnershipGroupCode") + @SerializedName("OwnershipGroupCode") + private String ownershipGroupCode; + /** + * The ownership. + */ + @JsonProperty("Ownership") + @SerializedName("Ownership") + private String ownership; + /** + * The ownership code. + */ + @JsonProperty("OwnershipCode") + @SerializedName("OwnershipCode") + private String ownershipCode; + /** + * The operating status. + */ + @JsonProperty("OperatingStatus") + @SerializedName("OperatingStatus") + private String operatingStatus; + /** + * The latitude. + */ + @JsonProperty("Latitude") + @SerializedName("Latitude") + private String latitude; + /** + * The longitude. + */ + @JsonProperty("Longitude") + @SerializedName("Longitude") + private String longitude; + /** + * The registration status. + */ + @JsonProperty("RegistrationStatus") + @SerializedName("RegistrationStatus") + private String registrationStatus; + /** + * The date time at which the record was created. + */ + @JsonProperty("CreatedAt") + @SerializedName("CreatedAt") + private String createdAt; + /** + * The date time at which the record was updated. + */ + @JsonProperty("UpdatedAt") + @SerializedName("UpdatedAt") + private String updatedAt; + /** + * The indicator as to whether the operating status changed from open to closed. + */ + @JsonProperty("OSchangeOpenedtoClose") + @SerializedName("OSchangeOpenedtoClose") + private String operatingStatusChangeOpenToClosed; + /** + * The indicator as to whether the operating status changed from closed to operational. + */ + @JsonProperty("OSchangeClosedtoOperational") + @SerializedName("OSchangeClosedtoOperational") + private String operatingStatusChangeClosedToOperational; + /** + * The indicator as to whether this message is a create or update. + */ + @JsonProperty("PostorUpdate") + @SerializedName("PostorUpdate") + private String postOrUpdate; + + /** + * Initializes a new instance of the {@link HfrRequest} class. + */ + public HfrRequest() { + } + + public String getFacilityIdNumber() { + return facilityIdNumber; + } + + public void setFacilityIdNumber(String facilityIdNumber) { + this.facilityIdNumber = facilityIdNumber; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCommonFacilityName() { + return commonFacilityName; + } + + public void setCommonFacilityName(String commonFacilityName) { + this.commonFacilityName = commonFacilityName; + } + + public String getZone() { + return zone; + } + + public void setZone(String zone) { + this.zone = zone; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getRegionCode() { + return regionCode; + } + + public void setRegionCode(String regionCode) { + this.regionCode = regionCode; + } + + public String getDistrict() { + return district; + } + + public void setDistrict(String district) { + this.district = district; + } + + public String getDistrictCode() { + return districtCode; + } + + public void setDistrictCode(String districtCode) { + this.districtCode = districtCode; + } + + public String getCouncil() { + return council; + } + + public void setCouncil(String council) { + this.council = council; + } + + public String getCouncilCode() { + return councilCode; + } + + public void setCouncilCode(String councilCode) { + this.councilCode = councilCode; + } + + public String getWard() { + return ward; + } + + public void setWard(String ward) { + this.ward = ward; + } + + public String getVillageMtaa() { + return villageMtaa; + } + + public void setVillageMtaa(String villageMtaa) { + this.villageMtaa = villageMtaa; + } + + public String getFacilityTypeGroup() { + return facilityTypeGroup; + } + + public void setFacilityTypeGroup(String facilityTypeGroup) { + this.facilityTypeGroup = facilityTypeGroup; + } + + public String getFacilityTypeGroupCode() { + return facilityTypeGroupCode; + } + + public void setFacilityTypeGroupCode(String facilityTypeGroupCode) { + this.facilityTypeGroupCode = facilityTypeGroupCode; + } + + public String getFacilityType() { + return facilityType; + } + + public void setFacilityType(String facilityType) { + this.facilityType = facilityType; + } + + public String getFacilityTypeCode() { + return facilityTypeCode; + } + + public void setFacilityTypeCode(String facilityTypeCode) { + this.facilityTypeCode = facilityTypeCode; + } + + public String getOwnershipGroup() { + return ownershipGroup; + } + + public void setOwnershipGroup(String ownershipGroup) { + this.ownershipGroup = ownershipGroup; + } + + public String getOwnershipGroupCode() { + return ownershipGroupCode; + } + + public void setOwnershipGroupCode(String ownershipGroupCode) { + this.ownershipGroupCode = ownershipGroupCode; + } + + public String getOwnership() { + return ownership; + } + + public void setOwnership(String ownership) { + this.ownership = ownership; + } + + public String getOwnershipCode() { + return ownershipCode; + } + + public void setOwnershipCode(String ownershipCode) { + this.ownershipCode = ownershipCode; + } + + public String getOperatingStatus() { + return operatingStatus; + } + + public void setOperatingStatus(String operatingStatus) { + this.operatingStatus = operatingStatus; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getRegistrationStatus() { + return registrationStatus; + } + + public void setRegistrationStatus(String registrationStatus) { + this.registrationStatus = registrationStatus; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } + + public String getOperatingStatusChangeOpenToClosed() { + return operatingStatusChangeOpenToClosed; + } + + public void setOperatingStatusChangeOpenToClosed(String operatingStatusChangeOpenToClosed) { + this.operatingStatusChangeOpenToClosed = operatingStatusChangeOpenToClosed; + } + + public String getOperatingStatusChangeClosedToOperational() { + return operatingStatusChangeClosedToOperational; + } + + public void setOperatingStatusChangeClosedToOperational(String operatingStatusChangeClosedToOperational) { + this.operatingStatusChangeClosedToOperational = operatingStatusChangeClosedToOperational; + } + + public String getPostOrUpdate() { + return postOrUpdate; + } + + public void setPostOrUpdate(String postOrUpdate) { + this.postOrUpdate = postOrUpdate; + } + + public String getTransactionIdNumber() { + return transactionIdNumber; + } + + public void setTransactionIdNumber(String transactionIdNumber) { + this.transactionIdNumber = transactionIdNumber; + } +} diff --git a/src/main/java/tz/go/moh/him/hfr/mediator/orchestrator/AcknowledgementOrchestrator.java b/src/main/java/tz/go/moh/him/hfr/mediator/orchestrator/AcknowledgementOrchestrator.java new file mode 100644 index 0000000..ff1bde8 --- /dev/null +++ b/src/main/java/tz/go/moh/him/hfr/mediator/orchestrator/AcknowledgementOrchestrator.java @@ -0,0 +1,138 @@ +package tz.go.moh.him.hfr.mediator.orchestrator; + +import akka.actor.ActorSelection; +import akka.actor.UntypedActor; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import com.google.gson.Gson; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.http.HttpStatus; +import org.json.JSONObject; +import org.openhim.mediator.engine.MediatorConfig; +import org.openhim.mediator.engine.messages.FinishRequest; +import org.openhim.mediator.engine.messages.MediatorHTTPRequest; +import org.openhim.mediator.engine.messages.MediatorHTTPResponse; +import tz.go.moh.him.hfr.mediator.domain.Ack; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents an acknowledgement orchestrator. + */ +public class AcknowledgementOrchestrator extends UntypedActor { + /** + * The logger instance. + */ + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + + /** + * The mediator configuration. + */ + private final MediatorConfig config; + + /** + * Represents an EPICOR ACK. + */ + private Ack ack; + + /** + * Represents a mediator request. + */ + private MediatorHTTPRequest workingRequest; + + /** + * Initializes a new instance of the {@link AcknowledgementOrchestrator} class. + * + * @param config The mediator configuration. + */ + public AcknowledgementOrchestrator(MediatorConfig config) { + this.config = config; + } + + /** + * Handles the received message. + * + * @param msg The received message. + */ + @Override + public void onReceive(Object msg) { + if (msg instanceof MediatorHTTPRequest) { + this.workingRequest = (MediatorHTTPRequest) msg; + this.ack = new Gson().fromJson(((MediatorHTTPRequest) msg).getBody(), Ack.class); + obtainOpenHIMTransactionByTransactionId(ack.getTransactionIdNumber()); + } else if (msg instanceof MediatorHTTPResponse) { + this.log.info("Received feedback from core"); + this.log.debug("Core Response code: " + ((MediatorHTTPResponse) msg).getStatusCode()); + this.log.debug("Core Response body: " + ((MediatorHTTPResponse) msg).getBody()); + updateOpenHIMTransactionByTransactionId(new JSONObject(((MediatorHTTPResponse) msg).getBody())); + + FinishRequest finishRequest = new FinishRequest("", "text/plain", HttpStatus.SC_OK); + + this.workingRequest.getRequestHandler().tell(finishRequest, getSelf()); + } + } + + /** + * Retrieves the OpenHIM transaction by id. + * + * @param transactionId The transaction id. + */ + private void obtainOpenHIMTransactionByTransactionId(String transactionId) { + + Map headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + + List> params = new ArrayList<>(); + + MediatorHTTPRequest obtainOpenHIMTransactionRequest = new MediatorHTTPRequest( + (this.workingRequest).getRequestHandler(), getSelf(), "Obtaining OpenHIM Transaction by transactionId", "GET", this.config.getProperty("core.scheme"), + this.config.getProperty("core.host"), Integer.parseInt(config.getProperty("core.api.port")), "/transactions/" + transactionId, + null, headers, params + ); + + ActorSelection coreApiConnector = getContext().actorSelection(config.userPathFor("core-api-connector")); + + coreApiConnector.tell(obtainOpenHIMTransactionRequest, getSelf()); + } + + /** + * Updates the OpenHIM transaction status. + * + * @param transaction The transaction. + */ + private void updateOpenHIMTransactionByTransactionId(JSONObject transaction) { + this.log.debug("Updating OpenHIM Transaction with ACK"); + + if (this.ack.getStatus().equals("Success")) { + transaction.getJSONObject("response").put("status", HttpStatus.SC_OK); + transaction.put("status", "Successful"); + } else { + transaction.getJSONObject("response").put("status", HttpStatus.SC_BAD_REQUEST); + transaction.put("status", "Failed"); + } + + transaction.getJSONObject("response").put("body", new Gson().toJson(this.ack)); + transaction.getJSONObject("response").put("timestamp", new Timestamp(System.currentTimeMillis())); + + Map headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + + List> params = new ArrayList<>(); + + MediatorHTTPRequest obtainOpenHIMTransactionRequest = new MediatorHTTPRequest( + (this.workingRequest).getRequestHandler(), getSelf(), "Updating OpenHIM Transaction by transactionId", "PUT", this.config.getProperty("epicor.scheme"), + this.config.getProperty("core.host"), Integer.parseInt(this.config.getProperty("core.api.port")), "/transactions/" + this.ack.getTransactionIdNumber(), + transaction.toString(), headers, params + ); + + ActorSelection coreApiConnector = getContext().actorSelection(config.userPathFor("core-api-connector")); + + coreApiConnector.tell(obtainOpenHIMTransactionRequest, getSelf()); + } +} \ No newline at end of file diff --git a/src/main/java/tz/go/moh/him/hfr/mediator/orchestrator/FacilityOrchestrator.java b/src/main/java/tz/go/moh/him/hfr/mediator/orchestrator/FacilityOrchestrator.java new file mode 100644 index 0000000..84e24b3 --- /dev/null +++ b/src/main/java/tz/go/moh/him/hfr/mediator/orchestrator/FacilityOrchestrator.java @@ -0,0 +1,92 @@ +package tz.go.moh.him.hfr.mediator.orchestrator; + +import akka.actor.ActorSelection; +import akka.actor.UntypedActor; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import com.google.gson.Gson; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.tuple.Pair; +import org.openhim.mediator.engine.MediatorConfig; +import org.openhim.mediator.engine.messages.MediatorHTTPRequest; +import org.openhim.mediator.engine.messages.MediatorHTTPResponse; +import tz.go.moh.him.hfr.mediator.domain.HfrRequest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a facility orchestrator. + */ +public class FacilityOrchestrator extends UntypedActor { + /** + * The logger instance. + */ + protected final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + + /** + * The mediator configuration. + */ + protected final MediatorConfig config; + + /** + * Represents a mediator request. + */ + protected MediatorHTTPRequest workingRequest; + + /** + * Initializes a new instance of the {@link FacilityOrchestrator} class. + * + * @param config The mediator configuration. + */ + public FacilityOrchestrator(MediatorConfig config) { + this.config = config; + } + + /** + * Handles the received message. + * + * @param msg The received message. + */ + @Override + public void onReceive(Object msg) { + if (msg instanceof MediatorHTTPRequest) { + + workingRequest = (MediatorHTTPRequest) msg; + + log.info("Received request: " + workingRequest.getHost() + " " + workingRequest.getMethod() + " " + workingRequest.getPath()); + + Map headers = new HashMap<>(); + + headers.put("Content-Type", "application/json"); + + if (Boolean.TRUE.toString().equals(config.getProperty("destination.auth.local.enabled"))) { + headers.put("Authorization", String.format("Basic: %s", Base64.encodeBase64String(String.format("%s:%s", config.getProperty("destination.auth.local.username"), config.getProperty("destination.auth.local.password")).getBytes()))); + } else if (Boolean.TRUE.toString().equals(config.getProperty("destination.auth.external.enabled"))) { + headers.put("Authorization", workingRequest.getHeaders().get("Authorization")); + } + + List> parameters = new ArrayList<>(); + + Gson gson = new Gson(); + + HfrRequest hfrRequest = gson.fromJson(workingRequest.getBody(), HfrRequest.class); + + hfrRequest.setTransactionIdNumber(workingRequest.getHeaders().get("x-openhim-transactionid")); + + MediatorHTTPRequest request = new MediatorHTTPRequest(workingRequest.getRequestHandler(), getSelf(), "Sending data", HfrRequest.OPERATION_MAP.get(hfrRequest.getPostOrUpdate()), + config.getProperty("destination.scheme"), config.getProperty("destination.host"), Integer.parseInt(config.getProperty("destination.api.port")), config.getProperty("destination.api.path"), + gson.toJson(hfrRequest), headers, parameters); + + ActorSelection httpConnector = getContext().actorSelection(config.userPathFor("http-connector")); + httpConnector.tell(request, getSelf()); + + } else if (msg instanceof MediatorHTTPResponse) { + workingRequest.getRequestHandler().tell(((MediatorHTTPResponse) msg).toFinishRequest(), getSelf()); + } else { + unhandled(msg); + } + } +} diff --git a/src/main/resources/error-messages.json b/src/main/resources/error-messages.json new file mode 100644 index 0000000..4bee033 --- /dev/null +++ b/src/main/resources/error-messages.json @@ -0,0 +1,3 @@ +{ + "ERR01": "" +} \ No newline at end of file diff --git a/src/main/resources/mediator-registration-info.json b/src/main/resources/mediator-registration-info.json index 655cc67..292c3ca 100644 --- a/src/main/resources/mediator-registration-info.json +++ b/src/main/resources/mediator-registration-info.json @@ -1,26 +1,35 @@ { - "urn": "urn:uuid:a02f38d0-4fe0-11eb-a36e-65769507fb41", + "urn": "urn:uuid:83c54769-5622-4cec-9f58-ccfc0ad24382", "version": "0.1.0", - "name": "HFR-Mediator", - "description": "An openHIM mediator for handling integration between Health Facility Registry and various health systems", + "name": "HFR Mediator", + "description": "A mediator for handling sharing of health facilities information from Health Facility Registry to other health information systems", "endpoints": [ { - "name": "HFR-Mediator Route", + "name": "HFR Mediator Route", "host": "localhost", "port": "3003", "path": "/hfr", "type": "http" + }, + { + "name": "HFR Mediator Route", + "host": "localhost", + "port": "3003", + "path": "/hfr-ack", + "type": "http" } ], "defaultChannelConfig": [ { - "name": "HFR-Mediator", + "name": "HFR Mediator", "urlPattern": "^/hfr$", "type": "http", - "allow": ["hfr-mediator"], + "allow": [ + "hfr-mediator" + ], "routes": [ { - "name": "HFR-Mediator Route", + "name": "HFR Mediator Route", "host": "localhost", "port": "3003", "path": "/hfr", @@ -28,6 +37,24 @@ "primary": "true" } ] + }, + { + "name": "HFR ACK Mediator", + "urlPattern": "^/hfr-ack$", + "description": "An OpenHIM mediator for handling acknowledgments", + "type": "http", + "allow": [ + "hfr-mediator" + ], + "routes": [ + { + "name": "ACK", + "host": "localhost", + "port": "3003", + "path": "/hfr-ack", + "type": "http" + } + ] } ] } diff --git a/src/main/resources/mediator.properties b/src/main/resources/mediator.properties index cef97f4..5bdcbf4 100644 --- a/src/main/resources/mediator.properties +++ b/src/main/resources/mediator.properties @@ -5,7 +5,19 @@ mediator.port=3003 mediator.timeout=60000 mediator.heartbeats=true +core.scheme=http core.host=localhost core.api.port=8080 core.api.user=root@openhim.org core.api.password=openhim-password + +destination.host=localhost +destination.api.port=9000 +destination.api.path=/epicor +destination.scheme=http + +destination.auth.local.enabled=true +destination.auth.local.username=username +destination.auth.local.password=password + +destination.auth.external.enabled=false \ No newline at end of file diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/DefaultOrchestratorTest.java b/src/test/java/tz/go/moh/him/hfr/mediator/DefaultOrchestratorTest.java deleted file mode 100644 index 9afeda0..0000000 --- a/src/test/java/tz/go/moh/him/hfr/mediator/DefaultOrchestratorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package tz.go.moh.him.hfr.mediator; - -import akka.actor.ActorRef; -import akka.actor.ActorSystem; -import akka.actor.Props; -import akka.testkit.JavaTestKit; -import java.util.Collections; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.*; -import org.openhim.mediator.engine.MediatorConfig; -import org.openhim.mediator.engine.messages.FinishRequest; -import org.openhim.mediator.engine.messages.MediatorHTTPRequest; - -import static org.junit.Assert.*; - -public class DefaultOrchestratorTest { - - static ActorSystem system; - - @BeforeClass - public static void setup() { - system = ActorSystem.create(); - } - - @AfterClass - public static void teardown() { - JavaTestKit.shutdownActorSystem(system); - system = null; - } - - @Before - public void setUp() throws Exception { - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void testMediatorHTTPRequest() throws Exception { - new JavaTestKit(system) {{ - final MediatorConfig testConfig = new MediatorConfig("hfr-mediator", "localhost", 3003); - final ActorRef defaultOrchestrator = system.actorOf(Props.create(DefaultOrchestrator.class, testConfig)); - - MediatorHTTPRequest POST_Request = new MediatorHTTPRequest( - getRef(), - getRef(), - "unit-test", - "POST", - "http", - null, - null, - "/hfr", - "test message", - Collections.singletonMap("Content-Type", "text/plain"), - Collections.>emptyList() - ); - - defaultOrchestrator.tell(POST_Request, getRef()); - - final Object[] out = - new ReceiveWhile(Object.class, duration("1 second")) { - @Override - protected Object match(Object msg) throws Exception { - if (msg instanceof FinishRequest) { - return msg; - } - throw noMatch(); - } - }.get(); - - boolean foundResponse = false; - - for (Object o : out) { - if (o instanceof FinishRequest) { - foundResponse = true; - } - } - - assertTrue("Must send FinishRequest", foundResponse); - }}; - } -} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/domain/AckTest.java b/src/test/java/tz/go/moh/him/hfr/mediator/domain/AckTest.java new file mode 100644 index 0000000..c944801 --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/domain/AckTest.java @@ -0,0 +1,58 @@ +package tz.go.moh.him.hfr.mediator.domain; + +import com.google.gson.Gson; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Contains tests for the {@link Ack} class. + */ +public class AckTest { + + /** + * Tests the deserialization of an Ack. + */ + @Test + public void testDeserializeAck() { + InputStream stream = AckTest.class.getClassLoader().getResourceAsStream("success_response.json"); + + Assert.assertNotNull(stream); + + String data; + + try { + data = IOUtils.toString(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Assert.assertNotNull(data); + + Gson gson = new Gson(); + + Ack ack = gson.fromJson(data, Ack.class); + + Assert.assertEquals("Success", ack.getStatus()); + Assert.assertEquals("123456789", ack.getTransactionIdNumber()); + } + + /** + * Tests the serialization of an Ack. + */ + @Test + public void testSerializeAck() { + + Ack ack = new Ack("123412341234", "Fail"); + + Gson gson = new Gson(); + + String actual = gson.toJson(ack); + + Assert.assertTrue(actual.contains("Fail")); + Assert.assertTrue(actual.contains("123412341234")); + } +} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/domain/HfrRequestTest.java b/src/test/java/tz/go/moh/him/hfr/mediator/domain/HfrRequestTest.java new file mode 100644 index 0000000..1d6cfad --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/domain/HfrRequestTest.java @@ -0,0 +1,125 @@ +package tz.go.moh.him.hfr.mediator.domain; + +import com.google.gson.Gson; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Contains tests for the {@link HfrRequest} class. + */ +public class HfrRequestTest { + + /** + * Tests the deserialization of an HFR request. + */ + @Test + public void testDeserializeHfrRequest() { + InputStream stream = HfrRequestTest.class.getClassLoader().getResourceAsStream("request.json"); + + Assert.assertNotNull(stream); + + String data; + + try { + data = IOUtils.toString(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Assert.assertNotNull(data); + + Gson gson = new Gson(); + + HfrRequest hfrRequest = gson.fromJson(data, HfrRequest.class); + + Assert.assertEquals("105651-4", hfrRequest.getFacilityIdNumber()); + Assert.assertEquals("Muhimbili", hfrRequest.getName()); + Assert.assertEquals("Muhimbili ", hfrRequest.getCommonFacilityName()); + Assert.assertEquals("Eastern Zone", hfrRequest.getZone()); + Assert.assertEquals("Mfaume", hfrRequest.getVillageMtaa()); + Assert.assertEquals("Upanga Magharibi", hfrRequest.getWard()); + Assert.assertEquals("Ilala MC", hfrRequest.getCouncil()); + Assert.assertEquals("-6.801501", hfrRequest.getLatitude()); + Assert.assertEquals("2017-10-27T05:54:48.000Z", hfrRequest.getUpdatedAt()); + } + + /** + * Tests the serialization of an HFR request. + */ + @Test + public void testSerializeHfrRequest() { + HfrRequest request = new HfrRequest(); + + request.setCommonFacilityName("Test Facility"); + request.setCouncil("Test Council"); + request.setCouncilCode("Test Council Code"); + request.setCreatedAt("2017-10-27T05:54:48.000Z"); + request.setDistrict("Test District"); + request.setDistrictCode("Test District Code"); + request.setFacilityIdNumber("12345"); + request.setFacilityType("Test Facility Type"); + request.setFacilityTypeCode("Test Facility Type Code"); + request.setFacilityTypeGroup("Test Facility Type Group"); + request.setFacilityTypeGroupCode("Test Facility Type Group Code"); + request.setLatitude("-6.801501"); + request.setLongitude("39.274157"); + request.setName("Test Facility Name"); + request.setOperatingStatus("Operating"); + request.setOperatingStatusChangeClosedToOperational("N"); + request.setOperatingStatusChangeOpenToClosed("N"); + request.setOwnership("Test Ownership"); + request.setOwnershipCode("Test Ownership Code"); + request.setOwnershipGroup("Test Ownership Group"); + request.setOwnershipGroupCode("Test Ownership Group Code"); + request.setPostOrUpdate("P"); + request.setRegion("Test Region"); + request.setRegionCode("Test Region Code"); + request.setRegistrationStatus("Registered"); + request.setTransactionIdNumber("A519A929-D02E-4731-8853-8F636CB63F76"); + request.setUpdatedAt("2017-10-28T05:54:48.000Z"); + request.setVillageMtaa("Test Village Name"); + request.setWard("Test Ward"); + request.setVillageMtaa("Test Village"); + request.setZone("Test Zone"); + + Gson gson = new Gson(); + + String actual = gson.toJson(request, HfrRequest.class); + + Assert.assertTrue(actual.contains(request.getCommonFacilityName())); + Assert.assertTrue(actual.contains(request.getCouncil())); + Assert.assertTrue(actual.contains(request.getCouncilCode())); + Assert.assertTrue(actual.contains(request.getCreatedAt())); + Assert.assertTrue(actual.contains(request.getDistrict())); + Assert.assertTrue(actual.contains(request.getDistrictCode())); + Assert.assertTrue(actual.contains(request.getFacilityIdNumber())); + Assert.assertTrue(actual.contains(request.getFacilityType())); + Assert.assertTrue(actual.contains(request.getFacilityTypeCode())); + Assert.assertTrue(actual.contains(request.getFacilityTypeGroup())); + Assert.assertTrue(actual.contains(request.getFacilityTypeGroupCode())); + Assert.assertTrue(actual.contains(request.getLatitude())); + Assert.assertTrue(actual.contains(request.getLongitude())); + Assert.assertTrue(actual.contains(request.getName())); + Assert.assertTrue(actual.contains(request.getOperatingStatus())); + Assert.assertTrue(actual.contains(request.getOperatingStatusChangeClosedToOperational())); + Assert.assertTrue(actual.contains(request.getOperatingStatusChangeOpenToClosed())); + Assert.assertTrue(actual.contains(request.getOperatingStatusChangeOpenToClosed())); + Assert.assertTrue(actual.contains(request.getOwnership())); + Assert.assertTrue(actual.contains(request.getOwnershipCode())); + Assert.assertTrue(actual.contains(request.getOwnershipGroup())); + Assert.assertTrue(actual.contains(request.getOwnershipGroupCode())); + Assert.assertTrue(actual.contains(request.getPostOrUpdate())); + Assert.assertTrue(actual.contains(request.getRegion())); + Assert.assertTrue(actual.contains(request.getRegionCode())); + Assert.assertTrue(actual.contains(request.getRegistrationStatus())); + Assert.assertTrue(actual.contains(request.getTransactionIdNumber())); + Assert.assertTrue(actual.contains(request.getUpdatedAt())); + Assert.assertTrue(actual.contains(request.getVillageMtaa())); + Assert.assertTrue(actual.contains(request.getWard())); + Assert.assertTrue(actual.contains(request.getZone())); + } +} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/mock/MockDestination.java b/src/test/java/tz/go/moh/him/hfr/mediator/mock/MockDestination.java new file mode 100644 index 0000000..5b20b43 --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/mock/MockDestination.java @@ -0,0 +1,92 @@ +package tz.go.moh.him.hfr.mediator.mock; + +import com.google.gson.Gson; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.openhim.mediator.engine.messages.MediatorHTTPRequest; +import org.openhim.mediator.engine.testing.MockHTTPConnector; +import tz.go.moh.him.hfr.mediator.domain.HfrRequest; +import tz.go.moh.him.hfr.mediator.orchestrator.FacilityOrchestratorOrchestratorTest; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Map; + +/** + * Represents a mock destination. + */ +public class MockDestination extends MockHTTPConnector { + + /** + * Initializes a new instance of the {@link MockDestination} class. + */ + public MockDestination() { + } + + /** + * Gets the response. + * + * @return Returns the response. + */ + @Override + public String getResponse() { + return null; + } + + /** + * Gets the status code. + * + * @return Returns the status code. + */ + @Override + public Integer getStatus() { + return 200; + } + + /** + * Gets the HTTP headers. + * + * @return Returns the HTTP headers. + */ + @Override + public Map getHeaders() { + return Collections.emptyMap(); + } + + /** + * Handles the message. + * + * @param msg The message. + */ + @Override + public void executeOnReceive(MediatorHTTPRequest msg) { + + InputStream stream = FacilityOrchestratorOrchestratorTest.class.getClassLoader().getResourceAsStream("request.json"); + + Assert.assertNotNull(stream); + + Gson gson = new Gson(); + + HfrRequest expected; + + try { + expected = gson.fromJson(IOUtils.toString(stream), HfrRequest.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + + HfrRequest actual = gson.fromJson(msg.getBody(), HfrRequest.class); + + Assert.assertNotNull(actual); + Assert.assertNotNull(expected); + + Assert.assertEquals(expected.getCommonFacilityName(), actual.getCommonFacilityName()); + Assert.assertEquals(expected.getCouncil(), actual.getCouncil()); + Assert.assertEquals(expected.getCouncilCode(), actual.getCouncilCode()); + Assert.assertEquals(expected.getCreatedAt(), actual.getCreatedAt()); + Assert.assertEquals(expected.getDistrict(), actual.getDistrict()); + Assert.assertEquals(expected.getFacilityIdNumber(), actual.getFacilityIdNumber()); + Assert.assertEquals(expected.getFacilityType(), actual.getFacilityType()); + } +} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/mock/MockOpenHIM.java b/src/test/java/tz/go/moh/him/hfr/mediator/mock/MockOpenHIM.java new file mode 100644 index 0000000..062f544 --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/mock/MockOpenHIM.java @@ -0,0 +1,83 @@ +package tz.go.moh.him.hfr.mediator.mock; + +import org.apache.commons.io.IOUtils; +import org.json.JSONObject; +import org.junit.Assert; +import org.openhim.mediator.engine.MediatorConfig; +import org.openhim.mediator.engine.connectors.CoreAPIConnector; +import org.openhim.mediator.engine.messages.MediatorHTTPRequest; +import org.openhim.mediator.engine.messages.MediatorHTTPResponse; +import tz.go.moh.him.hfr.mediator.orchestrator.BaseOrchestratorTest; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a mock OpenHIM. + */ +public class MockOpenHIM extends CoreAPIConnector { + + /** + * Represents a sample OpenHIM transaction. + */ + private static String openHIMSampleTransaction; + + /** + * Initializes a new instance of the {@link MockOpenHIM} class. + */ + public MockOpenHIM() { + this(BaseOrchestratorTest.loadConfig(null)); + } + + /** + * Initializes a new instance of the {@link MockOpenHIM} class. + * + * @param config The configuration. + */ + public MockOpenHIM(MediatorConfig config) { + super(config); + + try { + + InputStream stream = MockOpenHIM.class.getClassLoader().getResourceAsStream("openhim_transaction.json"); + + if (stream == null) { + throw new RuntimeException("File not found: openhim_transaction.json"); + } + + openHIMSampleTransaction = IOUtils.toString(stream); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Handles the message. + * + * @param msg The message. + */ + @Override + public void onReceive(Object msg) { + if (!(msg instanceof MediatorHTTPRequest)) { + return; + } + + MediatorHTTPRequest workingRequest = (MediatorHTTPRequest) msg; + + if (workingRequest.getMethod().equals("GET")) { + Map headers = new HashMap<>(); + headers.put("Content-Type", "text/application/json"); + + MediatorHTTPResponse mediatorHTTPResponse = new MediatorHTTPResponse((MediatorHTTPRequest) msg, openHIMSampleTransaction, 200, headers); + workingRequest.getRespondTo().tell(mediatorHTTPResponse, getSelf()); + } else if (workingRequest.getMethod().equals("PUT")) { + JSONObject receivedUpdatedTransaction = new JSONObject(((MediatorHTTPRequest) msg).getBody()); + + Assert.assertEquals(200, receivedUpdatedTransaction.getJSONObject("response").getInt("status")); + Assert.assertEquals("Successful", receivedUpdatedTransaction.getString("status")); + } + } +} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/AcknowledgementOrchestratorOrchestratorTest.java b/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/AcknowledgementOrchestratorOrchestratorTest.java new file mode 100644 index 0000000..b8c1200 --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/AcknowledgementOrchestratorOrchestratorTest.java @@ -0,0 +1,82 @@ +package tz.go.moh.him.hfr.mediator.orchestrator; + +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.testkit.JavaTestKit; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.openhim.mediator.engine.messages.FinishRequest; +import org.openhim.mediator.engine.messages.MediatorHTTPRequest; +import org.openhim.mediator.engine.testing.MockLauncher; +import org.openhim.mediator.engine.testing.TestingUtils; +import tz.go.moh.him.hfr.mediator.mock.MockOpenHIM; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Contains tests for the {@link tz.go.moh.him.hfr.mediator.orchestrator.AcknowledgementOrchestrator} class. + */ +public class AcknowledgementOrchestratorOrchestratorTest extends BaseOrchestratorTest { + + /** + * Run setup before each test execution. + */ + @Override + public void before() { + List actorsToLaunch = new LinkedList<>(); + + actorsToLaunch.add(new MockLauncher.ActorToLaunch("core-api-connector", MockOpenHIM.class)); + + TestingUtils.launchActors(system, configuration.getName(), actorsToLaunch); + } + + /** + * Tests the AcknowledgementOrchestrator class. + * + * @throws IOException if an IO exception occurs + */ + @Test + public void testAck() throws IOException { + new JavaTestKit(system) {{ + final ActorRef orchestrator = system.actorOf(Props.create(AcknowledgementOrchestrator.class, configuration)); + + InputStream stream = FacilityOrchestratorOrchestratorTest.class.getClassLoader().getResourceAsStream("success_response.json"); + + Assert.assertNotNull(stream); + + MediatorHTTPRequest request = new MediatorHTTPRequest( + getRef(), + getRef(), + "unit-test", + "POST", + "http", + null, + null, + "/hfr-ack", + IOUtils.toString(stream), + Collections.singletonMap("Content-Type", "application/json"), + Collections.emptyList() + ); + + orchestrator.tell(request, getRef()); + + final Object[] out = new ReceiveWhile(Object.class, duration("3 seconds")) { + @Override + protected Object match(Object msg) { + if (msg instanceof FinishRequest) { + return msg; + } + throw noMatch(); + } + }.get(); + + Assert.assertTrue(Arrays.stream(out).anyMatch(c -> c instanceof FinishRequest)); + }}; + } +} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/BaseOrchestratorTest.java b/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/BaseOrchestratorTest.java new file mode 100644 index 0000000..05dfabd --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/BaseOrchestratorTest.java @@ -0,0 +1,118 @@ +package tz.go.moh.him.hfr.mediator.orchestrator; + +import akka.actor.ActorSystem; +import akka.testkit.JavaTestKit; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.openhim.mediator.engine.MediatorConfig; +import org.openhim.mediator.engine.testing.MockLauncher; +import org.openhim.mediator.engine.testing.TestingUtils; +import tz.go.moh.him.hfr.mediator.mock.MockDestination; +import tz.go.moh.him.hfr.mediator.mock.MockOpenHIM; + +import java.io.File; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +public abstract class BaseOrchestratorTest { + + /** + * Represents the configuration. + */ + protected static MediatorConfig configuration; + + /** + * Represents the system actor. + */ + protected static ActorSystem system; + + /** + * Runs cleanup after class execution. + */ + @AfterClass + public static void afterClass() { + TestingUtils.clearRootContext(system, configuration.getName()); + JavaTestKit.shutdownActorSystem(system); + system = null; + } + + /** + * Runs initialization before each class execution. + */ + @BeforeClass + public static void beforeClass() { + try { + configuration = loadConfig(null); + system = ActorSystem.create(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Loads the mediator configuration. + * + * @param configPath The configuration path. + * @return Returns the mediator configuration. + */ + public static MediatorConfig loadConfig(String configPath) { + MediatorConfig config = new MediatorConfig(); + + + try { + if (configPath != null) { + Properties props = new Properties(); + File conf = new File(configPath); + InputStream in = FileUtils.openInputStream(conf); + props.load(in); + IOUtils.closeQuietly(in); + + config.setProperties(props); + } else { + config.setProperties("mediator.properties"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + config.setName(config.getProperty("mediator.name")); + config.setServerHost(config.getProperty("mediator.host")); + config.setServerPort(Integer.parseInt(config.getProperty("mediator.port"))); + config.setRootTimeout(Integer.parseInt(config.getProperty("mediator.timeout"))); + + config.setCoreHost(config.getProperty("core.host")); + config.setCoreAPIUsername(config.getProperty("core.api.user")); + config.setCoreAPIPassword(config.getProperty("core.api.password")); + + config.setCoreAPIPort(Integer.parseInt(config.getProperty("core.api.port"))); + config.setHeartbeatsEnabled(true); + + return config; + } + + /** + * Runs cleanup after each test execution. + */ + @After + public void after() { + } + + /** + * Runs initialization before each test execution. + */ + @Before + public void before() { + List actorsToLaunch = new LinkedList<>(); + + actorsToLaunch.add(new MockLauncher.ActorToLaunch("http-connector", MockDestination.class)); + actorsToLaunch.add(new MockLauncher.ActorToLaunch("core-api-connector", MockOpenHIM.class)); + + TestingUtils.launchActors(system, configuration.getName(), actorsToLaunch); + } +} diff --git a/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/FacilityOrchestratorOrchestratorTest.java b/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/FacilityOrchestratorOrchestratorTest.java new file mode 100644 index 0000000..a982f93 --- /dev/null +++ b/src/test/java/tz/go/moh/him/hfr/mediator/orchestrator/FacilityOrchestratorOrchestratorTest.java @@ -0,0 +1,64 @@ +package tz.go.moh.him.hfr.mediator.orchestrator; + +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.testkit.JavaTestKit; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.openhim.mediator.engine.messages.FinishRequest; +import org.openhim.mediator.engine.messages.MediatorHTTPRequest; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; + +/** + * Contains tests for the {@link FacilityOrchestrator} class. + */ +public class FacilityOrchestratorOrchestratorTest extends BaseOrchestratorTest { + + /** + * Tests the mediator. + * + * @throws Exception if an exception occurs + */ + @Test + public void testHfrRequest() throws Exception { + new JavaTestKit(system) {{ + final ActorRef orchestrator = system.actorOf(Props.create(FacilityOrchestrator.class, configuration)); + + InputStream stream = FacilityOrchestratorOrchestratorTest.class.getClassLoader().getResourceAsStream("request.json"); + + Assert.assertNotNull(stream); + + MediatorHTTPRequest request = new MediatorHTTPRequest( + getRef(), + getRef(), + "unit-test", + "POST", + "http", + null, + null, + "/hfr", + IOUtils.toString(stream), + Collections.singletonMap("Content-Type", "application/json"), + Collections.emptyList() + ); + + orchestrator.tell(request, getRef()); + + final Object[] out = new ReceiveWhile(Object.class, duration("3 seconds")) { + @Override + protected Object match(Object msg) { + if (msg instanceof FinishRequest) { + return msg; + } + throw noMatch(); + } + }.get(); + + Assert.assertTrue(Arrays.stream(out).anyMatch(c -> c instanceof FinishRequest)); + }}; + } +} \ No newline at end of file diff --git a/src/test/resources/mediator.properties b/src/test/resources/mediator.properties new file mode 100644 index 0000000..5bdcbf4 --- /dev/null +++ b/src/test/resources/mediator.properties @@ -0,0 +1,23 @@ +# Mediator Properties +mediator.name=HFR-Mediator +mediator.host=localhost +mediator.port=3003 +mediator.timeout=60000 +mediator.heartbeats=true + +core.scheme=http +core.host=localhost +core.api.port=8080 +core.api.user=root@openhim.org +core.api.password=openhim-password + +destination.host=localhost +destination.api.port=9000 +destination.api.path=/epicor +destination.scheme=http + +destination.auth.local.enabled=true +destination.auth.local.username=username +destination.auth.local.password=password + +destination.auth.external.enabled=false \ No newline at end of file diff --git a/src/test/resources/openhim_transaction.json b/src/test/resources/openhim_transaction.json new file mode 100644 index 0000000..aec97aa --- /dev/null +++ b/src/test/resources/openhim_transaction.json @@ -0,0 +1,35 @@ +{ + "request": { + "host": "127.0.0.1", + "port": "5001", + "path": "/services_received", + "headers": { + "content-type": "application/json" + }, + "querystring": "", + "body": "{\\r\\n \\\"Fac_IDNumber\\\":\\\"105651-4\\\",\\r\\n \\\"Name\\\":\\\"Muhimbili\\\",\\r\\n \\\"Comm_FacName\\\":\\\"Muhimbili \\\",\\r\\n \\\"Zone\\\":\\\"Eastern Zone\\\",\\r\\n \\\"RegionCode\\\":\\\"TZ.ET.DS\\\",\\r\\n \\\"Region\\\":\\\"Dar es Salaam Region\\\",\\r\\n \\\"DistrictCode\\\":\\\"TZ.ET.DS.IL\\\",\\r\\n \\\"District\\\":\\\"Ilala District\\\",\\r\\n \\\"CouncilCode\\\":\\\"TZ.ET.DS.IL.2\\\",\\r\\n \\\"Council\\\":\\\"Ilala MC\\\",\\r\\n \\\"Ward\\\":\\\"Upanga Magharibi\\\",\\r\\n \\\"VillageMtaa\\\":\\\"Mfaume\\\",\\r\\n \\\"FacilityTypeGroupCode\\\":\\\"HOSP\\\",\\r\\n \\\"FacilityTypeGroup\\\":\\\"Hospital\\\",\\r\\n \\\"FacilityTypeCode\\\":\\\"NAT\\\",\\r\\n \\\"FacilityType\\\":\\\"National Hospital\\\",\\r\\n \\\"OwnershipGroupCode\\\":\\\"Publ\\\",\\r\\n \\\"OwnershipGroup\\\":\\\"Public\\\",\\r\\n \\\"OwnershipCode\\\":\\\"MoHCDGEC\\\",\\r\\n \\\"Ownership\\\":\\\"MoHCDGEC\\\",\\r\\n \\\"OperatingStatus\\\":\\\"Operating\\\",\\r\\n \\\"Latitude\\\":\\\"-6.801501\\\",\\r\\n \\\"Longitude\\\":\\\"39.274157\\\",\\r\\n \\\"RegistrationStatus\\\":\\\"Registered\\\",\\r\\n \\\"CreatedAt\\\":\\\"2013-08-20T10:44:22.000Z\\\",\\r\\n \\\"UpdatedAt\\\":\\\"2017-10-27T05:54:48.000Z\\\",\\r\\n \\\"OSchangeOpenedtoClose\\\":\\\"Y\\\",\\r\\n \\\"OSchangeClosedtoOperational\\\":\\\"N\\\",\\r\\n \\\"PostorUpdate\\\":\\\"P\\\"\\r\\n}", + "method": "POST", + "timestamp": "2021-01-04T06:15:48.662Z" + }, + "response": { + "status": 200, + "headers": { + "x-openhim-transactionid": "5ff2b29416a3c934156395d3" + }, + "body": "", + "timestamp": "2021-01-04T06:15:48.746Z" + }, + "error": null, + "childIDs": [], + "canRerun": true, + "autoRetry": false, + "wasRerun": false, + "_id": "5ff2b29416a3c934156395d3", + "status": "Successful", + "clientID": "5fec702016a3c93415637002", + "channelID": "5fec6e2416a3c93415636f2d", + "clientIP": "197.250.198.31", + "routes": [], + "orchestrations": [], + "__v": 0 +} \ No newline at end of file diff --git a/src/test/resources/request.json b/src/test/resources/request.json new file mode 100644 index 0000000..92f2990 --- /dev/null +++ b/src/test/resources/request.json @@ -0,0 +1,31 @@ +{ + "Fac_IDNumber":"105651-4", + "Name":"Muhimbili", + "Comm_FacName":"Muhimbili ", + "Zone":"Eastern Zone", + "RegionCode":"TZ.ET.DS", + "Region":"Dar es Salaam Region", + "DistrictCode":"TZ.ET.DS.IL", + "District":"Ilala District", + "CouncilCode":"TZ.ET.DS.IL.2", + "Council":"Ilala MC", + "Ward":"Upanga Magharibi", + "VillageMtaa":"Mfaume", + "FacilityTypeGroupCode":"HOSP", + "FacilityTypeGroup":"Hospital", + "FacilityTypeCode":"NAT", + "FacilityType":"National Hospital", + "OwnershipGroupCode":"Publ", + "OwnershipGroup":"Public", + "OwnershipCode":"MoHCDGEC", + "Ownership":"MoHCDGEC", + "OperatingStatus":"Operating", + "Latitude":"-6.801501", + "Longitude":"39.274157", + "RegistrationStatus":"Registered", + "CreatedAt":"2013-08-20T10:44:22.000Z", + "UpdatedAt":"2017-10-27T05:54:48.000Z", + "OSchangeOpenedtoClose":"Y", + "OSchangeClosedtoOperational":"N", + "PostorUpdate":"P" +} \ No newline at end of file diff --git a/src/test/resources/success_response.json b/src/test/resources/success_response.json new file mode 100644 index 0000000..f256559 --- /dev/null +++ b/src/test/resources/success_response.json @@ -0,0 +1,4 @@ +{ + "IL_IDNumber": "123456789", + "status": "Success" +}