diff --git a/build.gradle b/build.gradle index 70a5f1e..febc2e7 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ bootRun { } } +ext['tomcat.version'] = '9.0.78' dependencies { implementation "io.github.http-builder-ng:http-builder-ng-core:1.0.3" @@ -56,7 +57,10 @@ dependencies { implementation 'ca.uhn.hapi.fhir:hapi-fhir-jpaserver-mdm:5.4.0' implementation 'ca.uhn.hapi.fhir:hapi-fhir-testpage-overlay:5.4.0' implementation('org.springframework.boot:spring-boot-starter-data-jpa') - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation ('org.springframework.boot:spring-boot-starter-web'){ + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' + } + implementation 'org.springframework.boot:spring-boot-starter-jetty' implementation 'ch.qos.logback:logback-classic:1.2.3' implementation 'org.thymeleaf:thymeleaf:3.0.11.RELEASE' implementation 'org.springframework:spring-web' @@ -93,7 +97,7 @@ dependencies { //implementation 'ca.uhn.hapi.fhir:hapi-fhir-testpage-overlay:4.2.0:war' implementation 'ca.uhn.hapi.fhir:hapi-fhir-testpage-overlay:4.2.0:classes' // providedImplementation 'javax.servlet:javax.servlet-api:3.1.0' - + // implementation group:'org.apache.tomcat', name:'tomcat-juli', version:property('tomcat.version') implementation 'javax.interceptor:javax.interceptor-api:1.2.2' } diff --git a/dockerRunnerDev.sh b/dockerRunnerDev.sh index 9eb21be..3919aea 100755 --- a/dockerRunnerDev.sh +++ b/dockerRunnerDev.sh @@ -14,7 +14,7 @@ echo "Starting application in watch mode..." # Start load data process once server is running echo "Starting continuous data loader..." -( while ! grep -m1 "Tomcat started on port" < ./logs/runner.log; do +( while ! grep -m1 "Started Application in " < ./logs/runner.log; do sleep 1 done echo "loading data into test-ehr..." diff --git a/dockerRunnerProd.sh b/dockerRunnerProd.sh index e6df1fd..61cc14f 100755 --- a/dockerRunnerProd.sh +++ b/dockerRunnerProd.sh @@ -12,7 +12,7 @@ echo "*** Logs for 'gradle bootRun' ***" > ./logs/runner.log echo "Starting application in production mode..." # Start load data process once server is running -( while ! grep -m1 "Tomcat started on port" < ./logs/runner.log; do +( while ! grep -m1 "Started Application in " < ./logs/runner.log; do sleep 1 done echo "loading data into test-ehr..." diff --git a/fhirResourcesToLoad/rems_snow_medication_ursodiol.json b/fhirResourcesToLoad/rems_snow_medication_ursodiol.json new file mode 100644 index 0000000..e5f8389 --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_medication_ursodiol.json @@ -0,0 +1,50 @@ +{ + "resourceType": "Medication", + "id": "med-pat017-ursodiol", + "status": "active", + "code": { + "coding": [ + { + "system": "http://www.nlm.nih.gov/research/umls/rxnorm", + "code": "1303740", + "display": "ursodiol 250 MG Oral Capsule" + } + ] + }, + "form": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "858744", + "display": "Prolonged-release oral capsule" + } + ] + }, + "ingredient": [ + { + "itemCodeableConcept": { + "coding": [ + { + "system": "http://www.nlm.nih.gov/research/umls/rxnorm", + "code": "11065", + "display": "Ursodiol" + } + ] + }, + "strength": { + "numerator": { + "value": 70, + "system": "http://unitsofmeasure.org", + "code": "mg", + "unit": "mg" + }, + "denominator": { + "value": 1, + "system": "http://terminology.hl7.org/CodeSystem/v3-orderableDrugForm", + "code": "ERTAB", + "unit": "Capsule" + } + } + } + ] +} diff --git a/fhirResourcesToLoad/rems_snow_medicationstatement_ursodiol.json b/fhirResourcesToLoad/rems_snow_medicationstatement_ursodiol.json new file mode 100644 index 0000000..4ecc118 --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_medicationstatement_ursodiol.json @@ -0,0 +1,93 @@ +{ + "resourceType": "MedicationStatement", + "id": "medstate-pat017-ursodiol", + "status": "active", + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/medication-statement-category", + "code": "outpatient", + "display": "Outpatient" + } + ] + }, + "medicationReference": { + "reference": "Medication/med-pat017-ursodiol" + }, + "subject": { + "reference": "Patient/pat017", + "display": "Jon Snow" + }, + "effectiveDateTime": "2023-03-20", + "dateAsserted": "2021-03-22", + "informationSource": { + "reference": "Patient/pat017", + "display": "Jon Snow" + }, + "reasonCode": [ + { + "coding": [ + { + "code": "39400004", + "system": "http://snomed.info/sct", + "display": "Injury of Liver" + } + ] + } + ], + "dosage": [ + { + "sequence": 1, + "text": "70mg daily", + "timing": { + "repeat": { + "frequency": 1, + "period": 1, + "periodUnit": "d" + } + }, + "route": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "26643006", + "display": "Oral route" + } + ] + }, + "doseAndRate": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/dose-rate-type", + "code": "ordered", + "display": "Ordered" + } + ] + }, + "doseQuantity": { + "value": 1, + "unit": "Capsule", + "system": "http://terminology.hl7.org/CodeSystem/v3-orderableDrugForm", + "code": "ERTAB" + }, + "rateRatio": { + "numerator": { + "value": 1, + "unit": "pills" + }, + "denominator": { + "value": 1, + "unit": "day", + "system": "http://unitsofmeasure.org", + "code": "d" + + } + + } + } + ] + } + ] +} diff --git a/fhirResourcesToLoad/rems_snow_procedure_ercp.json b/fhirResourcesToLoad/rems_snow_procedure_ercp.json new file mode 100644 index 0000000..6074a28 --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_procedure_ercp.json @@ -0,0 +1,35 @@ +{ + "resourceType": "Procedure", + "id": "pr-4", + "status": "completed", + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "386718000", + "display": "Endoscopic retrograde cholangiopancreatography (Procedure)" + } + ], + "text": "Endoscopic retrograde cholangiopancreatography" + }, + "subject": { + "reference": "Patient/pat017" + }, + "performedDateTime": "2023-04-05", + "recorder": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "asserter": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "performer": [ + { + "actor": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + } + } + ] +} \ No newline at end of file diff --git a/fhirResourcesToLoad/rems_snow_procedure_hida.json b/fhirResourcesToLoad/rems_snow_procedure_hida.json new file mode 100644 index 0000000..a09157d --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_procedure_hida.json @@ -0,0 +1,35 @@ +{ + "resourceType": "Procedure", + "id": "pr-5", + "status": "completed", + "code": { + "coding": [ + { + "system": "http://www.ama-assn.org/go/cpt", + "code": "78226", + "display": "Hepatobiliary iminodiacetic acid scan" + } + ], + "text": "HIDA Scan" + }, + "subject": { + "reference": "Patient/pat017" + }, + "performedDateTime": "2023-04-05", + "recorder": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "asserter": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "performer": [ + { + "actor": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + } + } + ] +} \ No newline at end of file diff --git a/fhirResourcesToLoad/rems_snow_procedure_liver_biospy.json b/fhirResourcesToLoad/rems_snow_procedure_liver_biospy.json new file mode 100644 index 0000000..86586a6 --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_procedure_liver_biospy.json @@ -0,0 +1,35 @@ +{ + "resourceType": "Procedure", + "id": "pr-3", + "status": "completed", + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "86259008", + "display": "Liver Biopsy (Procedure)" + } + ], + "text": "Liver Biopsy" + }, + "subject": { + "reference": "Patient/pat017" + }, + "performedDateTime": "2023-04-05", + "recorder": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "asserter": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "performer": [ + { + "actor": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + } + } + ] +} \ No newline at end of file diff --git a/fhirResourcesToLoad/rems_snow_procedure_liver_imaging.json b/fhirResourcesToLoad/rems_snow_procedure_liver_imaging.json new file mode 100644 index 0000000..4dc6507 --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_procedure_liver_imaging.json @@ -0,0 +1,35 @@ +{ + "resourceType": "Procedure", + "id": "pr-2", + "status": "completed", + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "441802002", + "display": "Imaging of liver (Procedure)" + } + ], + "text": "Imaging of liver" + }, + "subject": { + "reference": "Patient/pat017" + }, + "performedDateTime": "2023-04-05", + "recorder": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "asserter": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "performer": [ + { + "actor": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + } + } + ] +} \ No newline at end of file diff --git a/fhirResourcesToLoad/rems_snow_procedure_liver_ultrasound.json b/fhirResourcesToLoad/rems_snow_procedure_liver_ultrasound.json new file mode 100644 index 0000000..aa4b85d --- /dev/null +++ b/fhirResourcesToLoad/rems_snow_procedure_liver_ultrasound.json @@ -0,0 +1,50 @@ +{ + "resourceType": "Procedure", + "id": "pr-1", + "status": "completed", + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "105377009", + "display": "Ultrasonography of liver (Procedure)" + } + ], + "text": "Ultrasonography of liver" + }, + "subject": { + "reference": "Patient/pat017" + }, + "performedDateTime": "2023-04-05", + "recorder": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "asserter": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + }, + "performer": [ + { + "actor": { + "reference": "Practitioner/example", + "display": "Dr Cecil Surgeon" + } + } + ], + "reasonCode": [ + { + "text": "Generalized abdominal pain 24 hours. Localized in RIF with rebound and guarding" + } + ], + "followUp": [ + { + "text": "ROS 5 days - 2023-04-10" + } + ], + "note": [ + { + "text": "Routine liver ultrasound - everything normal" + } + ] +} \ No newline at end of file diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/Application.java b/src/main/java/ca/uhn/fhir/jpa/starter/Application.java index 3f2916e..df387e4 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/Application.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/Application.java @@ -79,10 +79,23 @@ public ServletRegistrationBean overlayRegistrationBean() { registrationBean.setServlet(dispatcherServlet); registrationBean.addUrlMappings("/test-ehr/*"); registrationBean.setLoadOnStartup(1); + return registrationBean; } + @Bean + public ServletRegistrationBean traceServletRegistration() { + ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); + TraceServlet trace = new TraceServlet(); + beanFactory.autowireBean(trace); + servletRegistrationBean.setServlet(trace); + servletRegistrationBean.addUrlMappings("/"); + servletRegistrationBean.setLoadOnStartup(1); + + return servletRegistrationBean; + } + @Bean @PostConstruct public WebMvcConfigurer corsConfigurer() { diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/JettyConfiguration.java b/src/main/java/ca/uhn/fhir/jpa/starter/JettyConfiguration.java new file mode 100644 index 0000000..9a10b95 --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JettyConfiguration.java @@ -0,0 +1,88 @@ +package ca.uhn.fhir.jpa.starter; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.BufferOverflowException; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.ByteBufferOutputStream; +import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.util.StringUtil; + + +import org.eclipse.jetty.server.Server; +import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; +import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JettyConfiguration + implements WebServerFactoryCustomizer { + + @Override + public void customize(JettyServletWebServerFactory factory) { + JettyServerCustomizer customizer = new JettyServerCustomizer() { + @Override + public void customize(Server server) { + server.setErrorHandler(new CustomJettyErrorHandler()); + } + }; + factory.addServerCustomizers(customizer); + } + + + public class CustomJettyErrorHandler extends ErrorHandler { + + @Override + public void handle(String target, Request baseRequest, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, ServletException { + + + try { + // Get error message, sanitize it, just in case. + String message = StringUtil.sanitizeXmlString( + (String) request.getAttribute(Dispatcher.ERROR_MESSAGE) + ); + + // Get error code that will returned + int code = response.getStatus(); + + var charset = StandardCharsets.UTF_8; + + // Get writer used + var buffer = baseRequest.getResponse().getHttpOutput().getBuffer(); + var out = new ByteBufferOutputStream(buffer); + var writer = new PrintWriter(new OutputStreamWriter(out, charset)); + + // Set content type, encoding and write response + response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString()); + response.setCharacterEncoding(charset.name()); + writer.print("HTTP ERROR "); + writer.print(code); + writer.print("\nMessage: "); + writer.print(message); + writer.print("\nURI: "); + writer.print(request.getRequestURI()); + + + writer.flush(); + } catch (BufferOverflowException e) { + baseRequest.getResponse().resetContent(); + } + + baseRequest.getHttpChannel().sendResponseAndComplete(); + } + } +} \ No newline at end of file diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java index 5148685..b8ba8e9 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -21,7 +21,10 @@ public class JpaRestfulServer extends BaseJpaRestfulServer { Environment env; private static final long serialVersionUID = 1L; static final Logger logger = LoggerFactory.getLogger(JpaRestfulServer.class); - + @Override + protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setStatus(405); + } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/TraceServlet.java b/src/main/java/ca/uhn/fhir/jpa/starter/TraceServlet.java new file mode 100644 index 0000000..31b6faf --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/TraceServlet.java @@ -0,0 +1,15 @@ +package ca.uhn.fhir.jpa.starter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class TraceServlet extends HttpServlet { + @Override + protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setStatus(405); + } + +} diff --git a/src/main/resources/ClientFhirServerRealm.json b/src/main/resources/ClientFhirServerRealm.json index 808b97d..224a4af 100644 --- a/src/main/resources/ClientFhirServerRealm.json +++ b/src/main/resources/ClientFhirServerRealm.json @@ -5,7 +5,7 @@ "defaultSignatureAlgorithm" : "RS256", "revokeRefreshToken" : false, "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, + "accessTokenLifespan" : 1800, "accessTokenLifespanForImplicitFlow" : 900, "ssoSessionIdleTimeout" : 1800, "ssoSessionMaxLifespan" : 36000, @@ -2120,4 +2120,4 @@ "clientPolicies" : { "policies" : [ ] } -} \ No newline at end of file +}