Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.8.x] Backports + Upgrade FHIR core to 6.4.0 #6768

Merged
merged 4 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 79 additions & 2 deletions docs/modules/ROOT/pages/reference/extensions/fhir.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,84 @@ ifeval::[{doc-show-user-guide-link} == true]
Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
endif::[]

[id="extensions-fhir-usage"]
== Usage
[id="extensions-fhir-usage-configuring-the-fhircontext-in-native-mode"]
=== Configuring the `FhirContext` in native mode

To ensure `camel-quarkus-fhir` operates correctly in native mode, it is important that the FHIR component and data formats use a native mode optimized `FhirContext`.
Examples of how to achieve this follow below.

NOTE: To use a particular FHIR version in native mode, you must ensure that it is enabled via the configuration options mentioned below.

Endpoint configuration when using the default `R4` FHIR version.

[source,java]
----
public class FhirRoutes extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.to("fhir://create/resource?fhirContext=#R4&inBody=resourceAsString");
}
}
----

Endpoint configuration when using a custom FHIR version (e.g `R5`).

[source,java]
----
public class FhirRoutes extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.to("fhir://create/resource?fhirVersion=R5&fhirContext=#R5&inBody=resourceAsString");
}
}
----

Instead of setting the `fhirContext` option on every endpoint URI, you can instead configure it directly on the FHIR component.

[source,properties]
----
camel.component.fhir.fhir-context=#R4
----

FHIR data format configuration.

[source,java]
----
public class FhirRoutes extends RouteBuilder {
// Each FHIR version has a corresponding injectable named bean
@Inject
@Named("R4")
FhirContext r4FhirContext;

@Inject
@Named("R5")
FhirContext r5FhirContext;

@Override
public void configure() {
// Configure FhirJsonDataFormat with the default R4 FhirContext
FhirJsonDataFormat fhirJsonDataFormat = new FhirJsonDataFormat();
fhirJsonDataFormat.setFhirContext(r4FhirContext);

// Configure FhirXmlDataFormat with a custom version and the corresponding FhirContext
FhirXmlDataFormat fhirXmlDataFormat = new FhirXmlDataFormat();
fhirXmlDataFormat.setVersion("R5");
fhirXmlDataFormat.setFhirContext(r5FhirContext);

from("direct:marshalFhirJson")
.marshal(fhirJsonDataFormat);

from("direct:marshalFhirXml")
.marshal(fhirXmlDataFormat);
}
}
----


[id="extensions-fhir-ssl-in-native-mode"]
== SSL in native mode

Expand All @@ -57,8 +135,7 @@ https://quarkus.io/guides/native-and-ssl[Quarkus SSL guide].
[id="extensions-fhir-additional-camel-quarkus-configuration"]
== Additional Camel Quarkus configuration


By default, only FHIR versions `R4` & `DSTU3` are enabled in native mode, since they are the default values on the FHIR component and DataFormat.
By default, only FHIR version `R4` is enabled in native mode, since that is also the default version configured on the FHIR component and data formats.


[width="100%",cols="80,5,15",options="header"]
Expand Down
3 changes: 1 addition & 2 deletions extensions/fhir/runtime/src/main/doc/configuration.adoc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@

By default, only FHIR versions `R4` & `DSTU3` are enabled in native mode, since they are the default values on the FHIR component and DataFormat.
By default, only FHIR version `R4` is enabled in native mode, since that is also the default version configured on the FHIR component and data formats.
73 changes: 73 additions & 0 deletions extensions/fhir/runtime/src/main/doc/usage.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
=== Configuring the `FhirContext` in native mode

To ensure `camel-quarkus-fhir` operates correctly in native mode, it is important that the FHIR component and data formats use a native mode optimized `FhirContext`.
Examples of how to achieve this follow below.

NOTE: To use a particular FHIR version in native mode, you must ensure that it is enabled via the configuration options mentioned below.

Endpoint configuration when using the default `R4` FHIR version.

[source,java]
----
public class FhirRoutes extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.to("fhir://create/resource?fhirContext=#R4&inBody=resourceAsString");
}
}
----

Endpoint configuration when using a custom FHIR version (e.g `R5`).

[source,java]
----
public class FhirRoutes extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.to("fhir://create/resource?fhirVersion=R5&fhirContext=#R5&inBody=resourceAsString");
}
}
----

Instead of setting the `fhirContext` option on every endpoint URI, you can instead configure it directly on the FHIR component.

[source,properties]
----
camel.component.fhir.fhir-context=#R4
----

FHIR data format configuration.

[source,java]
----
public class FhirRoutes extends RouteBuilder {
// Each FHIR version has a corresponding injectable named bean
@Inject
@Named("R4")
FhirContext r4FhirContext;

@Inject
@Named("R5")
FhirContext r5FhirContext;

@Override
public void configure() {
// Configure FhirJsonDataFormat with the default R4 FhirContext
FhirJsonDataFormat fhirJsonDataFormat = new FhirJsonDataFormat();
fhirJsonDataFormat.setFhirContext(r4FhirContext);

// Configure FhirXmlDataFormat with a custom version and the corresponding FhirContext
FhirXmlDataFormat fhirXmlDataFormat = new FhirXmlDataFormat();
fhirXmlDataFormat.setVersion("R5");
fhirXmlDataFormat.setFhirContext(r5FhirContext);

from("direct:marshalFhirJson")
.marshal(fhirJsonDataFormat);

from("direct:marshalFhirXml")
.marshal(fhirXmlDataFormat);
}
}
----
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package org.apache.camel.quarkus.test;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -51,22 +50,30 @@ private AvailablePortFinder() {
}

/**
* Gets the next available port.
* Gets the next available TCP port.
*
* @throws IllegalStateException if there are no ports available
* @return the available port
*/
public static int getNextAvailable() {
return getNextAvailable(Protocol.TCP);
}

/**
* Gets the next available port for the given protocol.
*
* @param protocol the network protocol to reserve the port for. Either TCP or UDP
* @throws IllegalStateException if there are no ports available
* @return the available port
*/
public static int getNextAvailable(Protocol protocol) {
// Using AvailablePortFinder in native applications can be problematic
// E.g The reserved port may be allocated at build time and preserved indefinitely at runtime. I.e it never changes on each execution of the native application
logWarningIfNativeApplication();

while (true) {
try (ServerSocket ss = new ServerSocket()) {
ss.setReuseAddress(true);
ss.bind(new InetSocketAddress((InetAddress) null, 0), 1);

int port = ss.getLocalPort();
try {
int port = protocol.getPort();
if (!isQuarkusReservedPort(port)) {
String callerClassName = getCallerClassName();
String value = RESERVED_PORTS.putIfAbsent(port, callerClassName);
Expand All @@ -82,20 +89,34 @@ public static int getNextAvailable() {
}

/**
* Reserve a list of random and not in use network ports and place them in Map.
* Reserve a list of random and not in use TCP network ports and places them in Map.
*/
public static Map<String, Integer> reserveNetworkPorts(String... names) {
return reserveNetworkPorts(Function.identity(), names);
}

/**
* Reserve a list of random and not in use network ports and place them in Map.
* Reserve a list of random and not in use network ports for the given protocol and places them in Map.
*/
public static Map<String, Integer> reserveNetworkPorts(Protocol protocol, String... names) {
return reserveNetworkPorts(protocol, Function.identity(), names);
}

/**
* Reserve a list of random and not in use TCP network ports and places them in Map.
*/
public static <T> Map<String, T> reserveNetworkPorts(Function<Integer, T> converter, String... names) {
return reserveNetworkPorts(Protocol.TCP, converter, names);
}

/**
* Reserve a list of random and not in use network ports for the given protocol and places them in Map.
*/
public static <T> Map<String, T> reserveNetworkPorts(Protocol protocol, Function<Integer, T> converter, String... names) {
Map<String, T> reservedPorts = new HashMap<>();

for (String name : names) {
reservedPorts.put(name, converter.apply(getNextAvailable()));
reservedPorts.put(name, converter.apply(getNextAvailable(protocol)));
}

return reservedPorts;
Expand Down Expand Up @@ -139,4 +160,25 @@ private static void logWarningIfNativeApplication() {
+ "Pass the reserved port to the native application under test with QuarkusTestResource or via an HTTP request");
}
}

public enum Protocol {
TCP,
UDP;

int getPort() throws IOException {
if (this.equals(TCP)) {
try (ServerSocket socket = new ServerSocket()) {
socket.setReuseAddress(true);
socket.bind(null);
return socket.getLocalPort();
}
}

try (DatagramSocket socket = new DatagramSocket()) {
// NOTE: There's no need for socket.bind as it happens during DatagramSocket instantiation
socket.setReuseAddress(true);
return socket.getLocalPort();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ public void configure() throws Exception {
.toF("fhir-%s://create/resource?inBody=resource", sanitizedFhirVersion);

fromF("direct:createResourceAsString-%s", sanitizedFhirVersion)
.choice()
.when(simple("${header.encodeAs} == 'encodeJson'"))
.marshal(fhirJsonDataFormat)
.otherwise()
.marshal(fhirXmlDataFormat)
.end()
.convertBodyTo(String.class)
.toF("fhir-%s://create/resource?inBody=resourceAsString", sanitizedFhirVersion);

// Dataformats
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.camel.quarkus.component.fhir.it;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
Expand Down Expand Up @@ -125,18 +124,10 @@ public JsonObject createPatientAsStringResource(
patient.addAddress().addLine(address);
patient.addName().addGiven(firstName).addFamily(lastName);

String patientString = null;
Map<String, Object> headers = new HashMap<>();
headers.put(encodeAs, Boolean.TRUE);
headers.put("encodeAs", encodeAs);

if (encodeAs.equals("encodeJson")) {
patientString = fhirContextInstance.get().newJsonParser().encodeResourceToString(patient);
} else {
patientString = fhirContextInstance.get().newXmlParser().encodeResourceToString(patient);
}

MethodOutcome result = producerTemplate.requestBodyAndHeaders("direct:createResourceAsString-dstu2-hl7org",
patientString,
MethodOutcome result = producerTemplate.requestBodyAndHeaders("direct:createResourceAsString-dstu2-hl7org", patient,
headers,
MethodOutcome.class);

Expand Down Expand Up @@ -177,7 +168,7 @@ public JsonObject createPatient(

@Path("/fhir2json")
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
public Response fhir2json(
@QueryParam("firstName") String firstName,
@QueryParam("lastName") String lastName,
Expand All @@ -188,19 +179,16 @@ public Response fhir2json(
patient.addName().addGiven(firstName).addFamily(lastName);

String patientString = fhirContextInstance.get().newJsonParser().encodeResourceToString(patient);

try (InputStream response = producerTemplate.requestBody("direct:json-to-dstu2-hl7org", patientString,
InputStream.class)) {
return Response
.created(new URI("https:camel.apache.org/"))
.entity(response)
.build();
}
String response = producerTemplate.requestBody("direct:json-to-dstu2-hl7org", patientString, String.class);
return Response
.created(new URI("https:camel.apache.org/"))
.entity(response)
.build();
}

@Path("/fhir2xml")
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_XML)
public Response fhir2xml(
@QueryParam("firstName") String firstName,
@QueryParam("lastName") String lastName,
Expand All @@ -211,14 +199,11 @@ public Response fhir2xml(
patient.addName().addGiven(firstName).addFamily(lastName);

String patientString = fhirContextInstance.get().newXmlParser().encodeResourceToString(patient);

try (InputStream response = producerTemplate.requestBody("direct:xml-to-dstu2-hl7org", patientString,
InputStream.class)) {
return Response
.created(new URI("https:camel.apache.org/"))
.entity(response)
.build();
}
String response = producerTemplate.requestBody("direct:xml-to-dstu2-hl7org", patientString, String.class);
return Response
.created(new URI("https:camel.apache.org/"))
.entity(response)
.build();
}

/////////////////////
Expand Down
Loading
Loading