This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch worktree-fhir-auth-tx-fix
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot-examples.git

commit 081150e48753a96f8e053a617b1dc63bbfd37eb3
Author: croway <[email protected]>
AuthorDate: Thu Sep 3 19:24:52 2026 +0200

    Fix fhir-auth-tx: catch FHIR client connection failures, refresh docs
    
    The route only caught ProtocolException, but connection failures to the
    FHIR server actually surface as FhirClientConnectionException or
    RuntimeCamelException. Since those went uncaught, the file consumer never
    marked the input file as handled, causing it to be reprocessed on every
    poll forever. Broaden the onException clause to cover both, add a bounded
    redelivery policy with backoff, add a catch-all handler as a backstop, and
    set moveFailed on the file consumer as defense in depth.
    
    Also update the stale README, which still described the old
    hl7v2.patient/HL7v2 flow instead of the actual patients.csv + transaction
    Bundle flow, and pointed at a personal GitHub fork for the auth-enabled
    HAPI server. Replace it with a Docker Compose setup (HAPI FHIR server
    behind an nginx reverse proxy enforcing HTTP Basic auth) under
    fhir-auth-tx/containers, and point serverUrl at it.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_016WKaCqZQd9uUiQR6QMZJkJ
---
 fhir-auth-tx/containers/docker-compose.yml         | 20 +++++++++
 fhir-auth-tx/containers/nginx/htpasswd             |  1 +
 fhir-auth-tx/containers/nginx/nginx.conf           | 20 +++++++++
 fhir-auth-tx/readme.adoc                           | 49 ++++++++++++++++------
 .../src/main/java/sample/camel/MyCamelRouter.java  | 26 ++++++++----
 .../src/main/resources/application.properties      |  2 +-
 6 files changed, 98 insertions(+), 20 deletions(-)

diff --git a/fhir-auth-tx/containers/docker-compose.yml 
b/fhir-auth-tx/containers/docker-compose.yml
new file mode 100644
index 00000000..a444efbe
--- /dev/null
+++ b/fhir-auth-tx/containers/docker-compose.yml
@@ -0,0 +1,20 @@
+services:
+  fhir:
+    image: hapiproject/hapi:v7.2.0
+    container_name: fhir-auth-tx-server
+    environment:
+      - hapi.fhir.fhir_version=DSTU3
+      - hapi.fhir.reuse_cached_search_results_millis=-1
+    expose:
+      - "8080"
+
+  fhir-auth-proxy:
+    image: nginx:1.27-alpine
+    container_name: fhir-auth-tx-proxy
+    depends_on:
+      - fhir
+    volumes:
+      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
+      - ./nginx/htpasswd:/etc/nginx/htpasswd:ro
+    ports:
+      - "8080:8080"
diff --git a/fhir-auth-tx/containers/nginx/htpasswd 
b/fhir-auth-tx/containers/nginx/htpasswd
new file mode 100644
index 00000000..200248e6
--- /dev/null
+++ b/fhir-auth-tx/containers/nginx/htpasswd
@@ -0,0 +1 @@
+admin:$apr1$fhirauth$ejUiLM/yipVqUKQ8hQ4i7/
diff --git a/fhir-auth-tx/containers/nginx/nginx.conf 
b/fhir-auth-tx/containers/nginx/nginx.conf
new file mode 100644
index 00000000..c47c4e1a
--- /dev/null
+++ b/fhir-auth-tx/containers/nginx/nginx.conf
@@ -0,0 +1,20 @@
+events {
+    worker_connections 1024;
+}
+
+http {
+    server {
+        listen 8080;
+
+        location / {
+            auth_basic "FHIR Server";
+            auth_basic_user_file /etc/nginx/htpasswd;
+
+            proxy_pass http://fhir:8080;
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+        }
+    }
+}
diff --git a/fhir-auth-tx/readme.adoc b/fhir-auth-tx/readme.adoc
index 32abd064..33ac12f0 100644
--- a/fhir-auth-tx/readme.adoc
+++ b/fhir-auth-tx/readme.adoc
@@ -7,13 +7,21 @@ This is an example application of the `camel-fhir` component. 
We'll be using `ca
 
 The Camel route is located in the `MyCamelRouter` class.
 
-This example will read patients stored in csv files from a directory and 
convert them to FHIR dtsu3 patients and upload them to a configured FHIR 
server. Each file is uploaded in a new transaction.
+This example will read patients stored in csv files from a directory, convert 
each row to a FHIR dstu3 Patient and
+upload all the patients found in one file as a single FHIR transaction Bundle 
to a configured FHIR server that
+requires basic authentication. Each file is uploaded in a new transaction.
+
+If the FHIR server cannot be reached, or rejects the request, the route 
retries a few times with an increasing
+delay before giving up and logging the error, so a single bad or unreachable 
server does not cause the same file
+to be reprocessed forever.
 
 The example assumes you have a running FHIR server at your disposal, which is 
configured for basic authentication.
-You may use 
https://github.com/rkorytkowski/hapi-fhir/tree/basic-auth/hapi-fhir-jpaserver-example[hapi-fhir-jpa-server-example].
 You can start it up by running `mvn jetty:run`.
+The `containers` folder in this example contains a `docker-compose.yml` that 
starts a plain
+https://hub.docker.com/r/hapiproject/hapi[hapiproject/hapi] FHIR server behind 
an `nginx` reverse proxy configured
+with HTTP Basic authentication, so you don't need a custom FHIR server build 
to try this example.
 
-By default, the example uses 
`\http://localhost:8080/hapi-fhir-jpaserver-example/baseDstu3` as the FHIR 
server URL, DSTU3 as the FHIR version, BASIC authentication (`admin` as 
username and `Admin123` as password) and `target/work/fhir/input`
-as the directory to look for csv patients. 
+By default, the example uses `\http://localhost:8080/fhir` as the FHIR server 
URL, DSTU3 as the FHIR version, BASIC authentication (`admin` as username and 
`Admin123` as password) and `target/work/fhir/input`
+as the directory to look for csv patients.
 
 However, you can edit the `application.properties` file to change the defaults 
and provide your own configuration.
 
@@ -29,36 +37,53 @@ $ mvn package
 
 === Run
 
+Before running the application, start the auth-enabled FHIR server:
+
+```sh
+$ docker compose -f containers/docker-compose.yml up -d
+```
+
+This starts two containers: the HAPI FHIR JPA server itself, and an `nginx` 
proxy in front of it that enforces
+HTTP Basic authentication (`admin` / `Admin123`) and is published on 
`\http://localhost:8080`. The FHIR server
+container itself is not published to the host, so every request has to go 
through the authenticated proxy.
+
 You can run this example using:
 
 ```sh
 $ mvn spring-boot:run
 ```
 
-When the Camel application runs, you should see a folder created under 
`target/work/fhir/input`. Copy the file `hl7v2.patient`
+When the Camel application runs, you should see a folder created under 
`target/work/fhir/input`. Copy the file `patients.csv`
 located in the `src/main/data` folder into it. You should see the following 
output:
 ```
-2018-07-24 11:52:51.615  INFO 30666 --- [work/fhir/input] fhir-example: 
Converting hl7v2.patient
-2018-07-24 11:52:52.700  INFO 30666 --- [work/fhir/input] fhir-example: 
Inserting Patient: 
{"resourceType":"Patient","id":"100005056","name":[{"family":"Freeman","given":["Vincent"]}]}
-2018-07-24 11:52:56.995  INFO 30666 --- [ #2 - CamelFhir] fhir-example: 
Patient created successfully: ca.uhn.fhir.rest.api.MethodOutcome@270f03f1
+2026-09-03 11:52:51.615  INFO 30666 --- [work/fhir/input] fhir-example: 
Converting patients.csv
+2026-09-03 11:52:52.700  INFO 30666 --- [ #2 - CamelFhir] fhir-example: 
Patients created successfully: org.hl7.fhir.dstu3.model.Bundle@13651cde
 ```
 
 The Camel application can be stopped pressing kbd:[Ctrl+c] in the shell.
 
+Once you are done, stop the FHIR server containers:
+
+```sh
+$ docker compose -f containers/docker-compose.yml down
+```
+
 === To get health check
 
-To show a summary of spring boot health check
+The application binds to a random HTTP port (`server.port=0` in 
`application.properties`) so that it never
+collides with the FHIR server on port 8080. Look for a line such as `Tomcat 
started on port(s): 54321 (http)`
+in the console output to find the actual port, then show a summary of the 
spring boot health check with:
 
 ----
-curl -XGET -s http://localhost:8080/actuator/health
+curl -XGET -s http://localhost:<port>/actuator/health
 ----
 
 === Help and contributions
 
-If you hit any problem using Camel or have some feedback, 
+If you hit any problem using Camel or have some feedback,
 then please https://camel.apache.org/support.html[let us know].
 
-We also love contributors, 
+We also love contributors,
 so https://camel.apache.org/contributing.html[get involved] :-)
 
 The Camel riders!
diff --git a/fhir-auth-tx/src/main/java/sample/camel/MyCamelRouter.java 
b/fhir-auth-tx/src/main/java/sample/camel/MyCamelRouter.java
index c3965bbe..e9bdfe0e 100644
--- a/fhir-auth-tx/src/main/java/sample/camel/MyCamelRouter.java
+++ b/fhir-auth-tx/src/main/java/sample/camel/MyCamelRouter.java
@@ -16,18 +16,17 @@
  */
 package sample.camel;
 
-import java.util.ArrayList;
 import java.util.List;
 
+import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
 import org.apache.camel.LoggingLevel;
+import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.hc.core5.http.ProtocolException;
 
 import org.hl7.fhir.dstu3.model.Bundle;
 import org.hl7.fhir.dstu3.model.Identifier;
 import org.hl7.fhir.dstu3.model.Patient;
-import org.hl7.fhir.dstu3.model.Resource;
-import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.springframework.stereotype.Component;
 
 /**
@@ -40,11 +39,24 @@ public class MyCamelRouter extends RouteBuilder {
 
     @Override
     public void configure() throws Exception {
-        from("file:{{input}}").routeId("fhir-example")
-                .onException(ProtocolException.class)
+        // connection/protocol failures talking to the FHIR server: retry a 
few times with
+        // backoff, then give up on this file so it isn't reprocessed forever 
on every poll
+        onException(ProtocolException.class, 
FhirClientConnectionException.class, RuntimeCamelException.class)
+                .maximumRedeliveries(3)
+                .redeliveryDelay(2000)
+                .backOffMultiplier(2)
+                .useExponentialBackOff()
                 .handled(true)
-                .log(LoggingLevel.ERROR, "Error connecting to FHIR server with 
URL:{{serverUrl}}, please check the application.properties file 
${exception.message}")
-                .end()
+                .log(LoggingLevel.ERROR, "Error connecting to FHIR server with 
URL:{{serverUrl}}, please check the application.properties file and that the 
server is reachable ${exception.message}")
+                .end();
+
+        // anything else unexpected: log and move on instead of looping on the 
same file
+        onException(Exception.class)
+                .handled(true)
+                .log(LoggingLevel.ERROR, "Error processing ${file:name}: 
${exception.message}")
+                .end();
+
+        from("file:{{input}}?moveFailed=.error").routeId("fhir-example")
                 .log("Converting ${file:name}")
                 .unmarshal().csv()
                 .process(exchange -> {
diff --git a/fhir-auth-tx/src/main/resources/application.properties 
b/fhir-auth-tx/src/main/resources/application.properties
index dcb3aaf5..f34522b5 100644
--- a/fhir-auth-tx/src/main/resources/application.properties
+++ b/fhir-auth-tx/src/main/resources/application.properties
@@ -15,7 +15,7 @@
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
 
-serverUrl=http://localhost:8080/hapi-fhir-jpaserver-example/baseDstu3
+serverUrl=http://localhost:8080/fhir
 serverUser=admin
 serverPassword=Admin123
 

Reply via email to