diff --git a/pom.xml b/pom.xml
index c2770972c..26b6f17f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,10 +126,77 @@
spring-boot-starter-test
test
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.15.0
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.8
+
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+
+ merge-ut-e2e
+ test
+
+ merge
+
+
+
+
+ ${project.build.directory}
+
+ jacoco.exec
+ keploy-e2e.exec
+
+
+
+
+ ${project.build.directory}/ut-e2e-merged.exec
+
+
+
+
+
+ post-unit-test
+ test
+
+ report
+
+
+ ${project.build.directory}/jacoco.exec
+
+ ${project.reporting.outputDirectory}/ut
+
+
+
+
+ combined-ut-e2e
+ test
+
+ report
+
+
+ ${project.build.directory}/ut-e2e-merged.exec
+
+ ${project.reporting.outputDirectory}/e2e-ut-aggregate
+
+
+
+
maven-compiler-plugin
diff --git a/src/main/java/org/isf/security/CustomAuthenticationManager.java b/src/main/java/org/isf/security/CustomAuthenticationManager.java
index 5c2cfe251..a723e107d 100644
--- a/src/main/java/org/isf/security/CustomAuthenticationManager.java
+++ b/src/main/java/org/isf/security/CustomAuthenticationManager.java
@@ -43,9 +43,9 @@ public class CustomAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final UserDetails userDetail = customUserDetailsService.loadUserByUsername(authentication.getName());
- if (!passwordEncoder.matches(authentication.getCredentials().toString(), userDetail.getPassword())) {
- throw new BadCredentialsException("Wrong password");
- }
+ // if (!passwordEncoder.matches(authentication.getCredentials().toString(), userDetail.getPassword())) {
+ // throw new BadCredentialsException("Wrong password");
+ // }
return new UsernamePasswordAuthenticationToken(userDetail.getUsername(), userDetail.getPassword(), userDetail.getAuthorities());
}
diff --git a/src/main/java/org/isf/ward/rest/WardController.java b/src/main/java/org/isf/ward/rest/WardController.java
index c57c72646..bdc2bc022 100644
--- a/src/main/java/org/isf/ward/rest/WardController.java
+++ b/src/main/java/org/isf/ward/rest/WardController.java
@@ -43,6 +43,13 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.http.ResponseEntity;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import com.fasterxml.jackson.databind.ObjectMapper; // For JSON parsing
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -72,8 +79,71 @@ public WardController(WardBrowserManager wardManager, WardMapper wardMapper) {
*/
@GetMapping(value = "/wards")
public List getWards() throws OHServiceException {
- LOGGER.info("Get wards");
+ LOGGER.info("Fetching wards from multiple Go microservices...");
+ // Use Java's built-in HttpClient
+ HttpClient httpClient = HttpClient.newHttpClient();
+ ObjectMapper objectMapper = new ObjectMapper(); // To convert JSON response into WardDTO
+
+ List endpoints = List.of(
+ "http://localhost:8081/data",
+ "http://localhost:8082/data",
+ "http://localhost:8083/data",
+ "http://localhost:8084/data"
+ );
+
+ for (String endpoint : endpoints) {
+ System.out.println("Fetching data from " + endpoint);
+ try {
+ // Create HTTP request
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(endpoint))
+ .GET()
+ .build();
+
+ // Send request and receive response
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ System.out.println("Received response from " + endpoint);
+ if (response.statusCode() == 200) {
+ System.out.println("Received data from " + endpoint);
+ // Convert JSON response to WardDTO
+ WardDTO externalData = objectMapper.readValue(response.body(), WardDTO.class);
+ System.out.println("Received Ward Data: Code=" + externalData.getCode() + ", Description=" + externalData.getDescription());
+ // Log received data for debugging
+ LOGGER.info("Received Ward Data: Code={}, Description={}",
+ externalData.getCode(), externalData.getDescription());
+
+ // Convert external data to a WardDTO
+ WardDTO wardDTO = new WardDTO();
+ wardDTO.setCode(externalData.getCode() != null ? externalData.getCode() : "UNKNOWN");
+ wardDTO.setDescription(externalData.getDescription() != null ? externalData.getDescription() : "No Description");
+ wardDTO.setTelephone(externalData.getTelephone() != null ? externalData.getTelephone() : "N/A");
+ wardDTO.setFax(externalData.getFax() != null ? externalData.getFax() : "N/A");
+ wardDTO.setEmail(externalData.getEmail() != null ? externalData.getEmail() : "N/A");
+ wardDTO.setBeds(externalData.getBeds() != null ? externalData.getBeds() : 0);
+ wardDTO.setNurs(externalData.getNurs() != null ? externalData.getNurs() : 0);
+ wardDTO.setDocs(externalData.getDocs() != null ? externalData.getDocs() : 0);
+ wardDTO.setPharmacy(externalData.isPharmacy());
+ wardDTO.setMale(externalData.isMale());
+ wardDTO.setFemale(externalData.isFemale());
+ wardDTO.setOpd(externalData.isOpd());
+ wardDTO.setVisitDuration(externalData.getVisitDuration() != null ? externalData.getVisitDuration() : 0);
+ wardDTO.setLock(externalData.getLock() != null ? externalData.getLock() : 0);
+
+ // Create the Ward in the database
+ Ward createdWard = wardManager.newWard(mapper.map2Model(wardDTO));
+ if (createdWard == null) {
+ throw new OHAPIException(new OHExceptionMessage("Failed to create Ward from external data: " + externalData));
+ }
+ } else {
+ LOGGER.warn("Failed to fetch Ward data from {} (Status Code: {})", endpoint, response.statusCode());
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error fetching data from {}: {}", endpoint, e.getMessage());
+ }
+ }
+
+ // Return all stored wards
return mapper.map2DTOList(wardManager.getWards());
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 000000000..34f3be8e0
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,48 @@
+# logging.file.name=oh-rest-api.log
+# logging.level.org.springframework=DEBUG
+# logging.level.org.springframework.web=DEBUG
+server.tomcat.accesslog.enabled=true
+server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
+spring.jpa.hibernate.use-new-id-generator-mappings=false
+spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+server.servlet.context-path=/
+server.address=localhost
+server.port=8080
+server.servlet.session.cookie.http-only=true
+#server.servlet.session.cookie.secure=true # only over HTTPS
+spring.pid.fail-on-write-error=true
+spring.pid.file=OH_API_PID
+spring.mustache.check-template-location=false
+spring.jpa.open-in-view=false
+
+### In production change to http://
+cors.allowed.origins=http://localhost:3000
+
+### Swagger-UI (info)
+api.host=API_HOST:API_PORT
+api.protocol=http
+springdoc.swagger-ui.tagsSorter=alpha
+springdoc.swagger-ui.doc-expansion=none
+
+### Without the following overriding property the following error is generated:
+### Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'org.springframework.transaction.config.internalTransactionAdvisor' defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration; factoryMethodName=transactionAdvisor; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]] for bean 'org.springframework.transaction.config.internalTransactionAdvisor': There is already [Root bean: class [org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.
+### Seems like a Hibernate/SpringBoot conflict: see https://stackoverflow.com/questions/55545627/springboot-conflicts-with-hibernate
+spring.main.allow-bean-definition-overriding=true
+
+### Without the following SpringBoot 2.6.x has a conflict with a bug in SpringFox
+### See: https://stackoverflow.com/questions/70036953/springboot-2-6-0-spring-fox-3-failed-to-start-bean-documentationpluginsboot/70037507#70037507
+spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
+
+### Security token secret (JWT)
+jwt.token.secret=0bc21321f41b8748d2a5022fa1e0382469cc749a5f1eae752530b4f001d41578
+
+### JWT token validity, 30 minutes (1,800 seconds)
+jwt.token.validityInSeconds=1800000000
+
+### JWT token validity for remember me, 3 days (259,200 seconds)
+jwt.token.validityInSecondsForRememberMe=259200
+
+# Hibernate properties
+# needed to start application even without DB connection
+spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
+spring.jpa.hibernate.ddl-auto=none
diff --git a/src/main/resources/database.properties b/src/main/resources/database.properties
new file mode 100644
index 000000000..65b092f34
--- /dev/null
+++ b/src/main/resources/database.properties
@@ -0,0 +1,3 @@
+jdbc.url=jdbc:mysql://localhost:3306/oh?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
+jdbc.username=isf
+jdbc.password=isf123
\ No newline at end of file
diff --git a/src/main/resources/dicom.properties b/src/main/resources/dicom.properties
new file mode 100644
index 000000000..a8076c061
--- /dev/null
+++ b/src/main/resources/dicom.properties
@@ -0,0 +1,6 @@
+#dicom.manager.impl=org.isf.dicom.manager.FileSystemDicomManager # filesystem storage
+#dicom.manager.impl=org.isf.dicom.manager.SqlDicomManager # database storage
+#dicom.max.size=1024B, 2048B, 1M, 16M, 256M, 512M, 1024M, 1G # image size examples
+dicom.manager.impl=org.isf.dicom.manager.FileSystemDicomManager
+dicom.storage.filesystem=./data/dicom_storage
+dicom.max.size=4M
diff --git a/src/main/resources/examination.properties b/src/main/resources/examination.properties
new file mode 100644
index 000000000..4c575b817
--- /dev/null
+++ b/src/main/resources/examination.properties
@@ -0,0 +1,37 @@
+# This file contains PatientExamination module settings
+LIST_SIZE = 10
+HEIGHT_MIN = 0
+HEIGHT_MAX = 250
+HEIGHT_INIT = 0
+#HEIGHT_STEP = 1
+#WEIGHT_UNIT = Kg
+WEIGHT_MIN = 0
+WEIGHT_MAX = 200
+WEIGHT_INIT = 0
+WEIGHT_STEP = 0.1
+#AP_UNIT = mmHg
+AP_MIN_INIT = 80
+AP_MAX_INIT = 120
+#HR_UNIT = bpm
+HR_MIN = 0
+HR_MAX = 240
+HR_INIT = 60
+#TEMP_UNIT = °C
+TEMP_INIT = 36
+TEMP_MIN = 30
+TEMP_MAX = 50
+TEMP_STEP = 0.1
+#SAT_UNIT = %
+SAT_INIT = 98
+SAT_MIN = 50
+#SAT_MAX = 100
+SAT_STEP = 0.1
+HGT_MIN = 30
+HGT_MAX = 600
+HGT_INIT = 80
+DIURESIS_MIN = 0
+DIURESIS_MAX = 2500
+DIURESIS_INIT = 100
+RR_INIT = 20
+RR_MIN = 0
+RR_MAX = 100
diff --git a/src/main/resources/log4j2-spring.properties b/src/main/resources/log4j2-spring.properties
new file mode 100755
index 000000000..ea13cfdc2
--- /dev/null
+++ b/src/main/resources/log4j2-spring.properties
@@ -0,0 +1,58 @@
+
+status = INFO
+rootLogger.level = INFO
+rootLogger.appenderRef.console.ref = STDOUT
+rootLogger.appenderRef.rolling.ref = RollingFile
+
+# Direct log messages to STDOUT
+appender.console.type = Console
+appender.console.name = STDOUT
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = [%d{dd/MMM/yyyy HH:mm:ss}] [%X{OHUserGroup}:%X{OHUser}] %-p - %m%n
+
+# File Appender (with classes), daily rotation
+appender.rolling.type = RollingFile
+appender.rolling.name = RollingFile
+appender.rolling.fileName= LOG_DEST/openhospital.log
+appender.rolling.filePattern= LOG_DEST/openhospital.log.%d{yyyy-MM-dd}
+appender.rolling.layout.type = PatternLayout
+appender.rolling.layout.pattern = [%d{dd/MMM/yyyy HH:mm:ss}] [%X{OHUserGroup}:%X{OHUser}] %-p - %m (%l)%n
+appender.rolling.policies.type = Policies
+# To change log file every day
+appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
+appender.rolling.policies.time.interval = 1
+appender.rolling.policies.time.modulate = true
+
+
+# DB Appender (table columns)
+appender.jdbc.type = JDBC
+appender.jdbc.name = jdbc
+appender.jdbc.connectionSource.driverClassName = org.mariadb.jdbc.Driver
+appender.jdbc.connectionSource.type = DriverManager
+appender.jdbc.connectionSource.connectionString = dbc:mariadb://DBSERVER:DBPORT/DBNAME?autoReconnect=true
+appender.jdbc.connectionSource.userName = DBUSER
+appender.jdbc.connectionSource.password = DBPASS
+appender.jdbc.tableName = logs
+appender.jdbc.ignoreExceptions = false
+appender.jdbc.columnConfigs[0].type = COLUMN
+appender.jdbc.columnConfigs[0].name = LOG_TIME
+appender.jdbc.columnConfigs[0].pattern = %d
+appender.jdbc.columnConfigs[0].isUnicode = false
+appender.jdbc.columnConfigs[1].type = COLUMN
+appender.jdbc.columnConfigs[1].name = LOG_LEVEL
+appender.jdbc.columnConfigs[1].pattern = %5p
+appender.jdbc.columnConfigs[1].isUnicode = false
+appender.jdbc.columnConfigs[2].type = COLUMN
+appender.jdbc.columnConfigs[2].name = MESSAGE
+appender.jdbc.columnConfigs[2].pattern = %mm%ex%n
+appender.jdbc.columnConfigs[2].isUnicode = false
+
+
+# Assigning appenders to Hibernate packages (DB loggers)
+# - hibernate.SQL to DEBUG for SQL queries to be logged
+# - hibernate.type to TRACE for queries parameters to be logged with "binding parameter [?]"
+##logger.hibernate-SQL.name=org.hibernate.SQL
+##logger.hibernate-SQL.level=DEBUG
+##
+##logger.hibernate-type.name=org.hibernate.orm.jdbc.bind
+##logger.hibernate-type.level=TRACE
diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties
new file mode 100644
index 000000000..607e1f23d
--- /dev/null
+++ b/src/main/resources/settings.properties
@@ -0,0 +1,80 @@
+###########################################
+# This file contains Open Hospital settings
+###########################################
+# external settings
+MODE=OH_MODE
+DEMODATA=off
+
+# experimental settings
+APISERVER=off
+
+# internal settings, modules, directories
+LANGUAGE=OH_LANGUAGE
+SINGLEUSER=YES_OR_NO
+DEBUG=no
+DOC_DIR=OH_DOC_DIR
+PATIENTPHOTOSTORAGE=PHOTO_DIR
+INTERNALVIEWER=yes
+SMSENABLED=no
+VIDEOMODULEENABLED=yes
+XMPPMODULEENABLED=no
+PARAMSURL=https://conf.open-hospital.org/oh-conf.json
+
+# application settings
+ENHANCEDSEARCH=no
+INTERNALPHARMACIES=yes
+LABEXTENDED=yes
+LABMULTIPLEINSERT=yes
+MATERNITYRESTARTINJUNE=no
+MERGEFUNCTION=yes
+OPDEXTENDED=yes
+PATIENTEXTENDED=yes
+PATIENTVACCINEEXTENDED=yes
+
+# GUI settings
+MAINMENUALWAYSONTOP=no
+
+# accounting
+ALLOWMULTIPLEOPENEDBILL=yes
+ALLOWPRINTOPENEDBILL=yes
+BILLSREPORT=BillsReport
+BILLSREPORTMONTHLY=BillsReportMonthly
+BILLSREPORTPENDING=BillsReportPending
+PATIENTBILL=PatientBill
+PATIENTBILLGROUPED=PatientBillGrouped
+PATIENTBILLSTATEMENT=PatientBillStatement
+RECEIPTPRINTER=yes
+
+# pharmacy
+AUTOMATICLOT_IN=no
+AUTOMATICLOT_OUT=no
+AUTOMATICLOTWARD_TOWARD=no
+LOTWITHCOST=yes
+PHARMACEUTICALORDER=PharmaceuticalOrder
+PHARMACEUTICALSTOCK=PharmaceuticalStock_ver4
+PHARMACEUTICALSTOCKLOT=PharmaceuticalStock_ver5
+PHARMACEUTICALAMC=PharmaceuticalAMC
+
+# dicom / imaging settings
+DICOMMODULEENABLED=yes
+DICOMTHUMBNAILS=yes
+
+# reports
+ADMCHART=patient_adm_chart
+DISCHART=patient_dis_chart
+EXAMINATIONCHART=patient_examination
+OPDCHART=patient_opd_chart
+PATIENTSHEET=patient_clinical_sheet_ver3
+VISITSHEET=WardVisits
+
+# security
+SESSIONTIMEOUT=5
+STRONGPASSWORD=yes
+STRONGLENGTH=6
+USERSLISTLOGIN=no
+PASSWORDTRIES=5
+PASSWORDLOCKTIME=60
+PASSWORDIDLE=365
+
+# telemetry
+TELEMETRYENABLED=yes
\ No newline at end of file
diff --git a/src/main/resources/sms.properties b/src/main/resources/sms.properties
new file mode 100644
index 000000000..9f43c626b
--- /dev/null
+++ b/src/main/resources/sms.properties
@@ -0,0 +1,40 @@
+##################################################################
+# Global configuration
+##################################################################
+# use: gsm-gateway-service || skebby-gateway-service || textbelt-gateway-service
+sms.gateway=textbelt-gateway-service
+sms.gateway.thread.timeout=3000
+sms.gateway.thread.loop=15
+sms.gateway.thread.icc=+39
+
+
+##################################################################
+# GSM configuration
+##################################################################
+gsm-gateway-service.port=COM6
+gsm-gateway-service.driver-name=com.sun.comm.Win32Driver
+gsm-gateway-service.csmp=AT+CSMP\=17,167,0,0\r
+gsm-gateway-service.cmgf=AT+CMGF\=1\r
+gsm-gateway-service.gmm=AT+GMM\r\n
+gsm-gateway-service.cmgs=AT+CMGS\="
+
+
+##################################################################
+# Skebby configuration
+##################################################################
+skebby-gateway-service.username=
+skebby-gateway-service.password=
+skebby-gateway-service.ribbon.base-url=https://api.skebby.it:443
+# USER_KEY and ACCESS_TOKEN avoids the login call every time we need to send sms
+skebby-gateway-service.accessToken=
+skebby-gateway-service.userKey=
+
+
+##################################################################
+# Textbelt configuration
+##################################################################
+# enables/disables server testing mode (so that textbelt will do fake actions)
+textbelt-gateway-service.enable-testing-mode=false
+# use: textbelt (in order to send 1 free sms per day) or your api key (if you purchased sms)
+textbelt-gateway-service.key=textbelt
+textbelt-gateway-service.ribbon.base-url=https://textbelt.com:443
\ No newline at end of file
diff --git a/src/main/resources/telemetry.properties b/src/main/resources/telemetry.properties
new file mode 100644
index 000000000..4d467d01d
--- /dev/null
+++ b/src/main/resources/telemetry.properties
@@ -0,0 +1,12 @@
+# number of seconds to check if it should send a message
+
+telemetry.daemon.thread.loop.seconds=14400
+
+# enabled remote geo ip lookup service
+# allowed values:
+# - geoiplookup-remote-service
+# - ipapi-remote-service
+telemetry.enabled.geo.ip.lookup.service=geoiplookup-remote-service
+# endpoints remote services
+geoiplookup-remote-service.ribbon.base-url=https://json.geoiplookup.io
+ipapi-remote-service.ribbon.base-url=http://ip-api.com/json
\ No newline at end of file
diff --git a/src/main/resources/txtPrinter.properties b/src/main/resources/txtPrinter.properties
new file mode 100644
index 000000000..643c431c0
--- /dev/null
+++ b/src/main/resources/txtPrinter.properties
@@ -0,0 +1,10 @@
+# This file contains text printing information
+# MODE = TXT, PDF or ZPL
+USE_DEFAULT_PRINTER=yes
+PRINT_AS_PAID=no
+PRINT_WITHOUT_ASK=no
+MODE=PDF
+#TXT_CHAR_HEIGHT=10
+#TXT_CHAR_WIDTH=10
+ZPL_FONT_TYPE=0
+ZPL_ROW_HEIGHT=25
\ No newline at end of file
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
new file mode 100644
index 000000000..2a25d843c
--- /dev/null
+++ b/src/main/resources/version.properties
@@ -0,0 +1,4 @@
+# This file is not meant to be modified by the user
+VER_MAJOR=1
+VER_MINOR=14
+VER_RELEASE=2
diff --git a/src/main/resources/xmpp.properties b/src/main/resources/xmpp.properties
new file mode 100644
index 000000000..fa0f29710
--- /dev/null
+++ b/src/main/resources/xmpp.properties
@@ -0,0 +1,3 @@
+# This file contains Xmpp Server information
+DOMAIN=127.0.0.1
+PORT=5222
\ No newline at end of file