Skip to content
Open
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
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,75 @@ The `$install` operation is triggered with a POST to `[server]/ImplementationGui
}
```

## Server-side JavaScript execution ($execute-javascript)

The R4 system-level operation `$execute-javascript` runs a **server-side** JavaScript file (via the embedded GraalJS engine) to transform FHIR resources. Callers do **not** send code — they reference a script by name that an administrator has placed in a configured directory. The script receives the input resources as a JavaScript array called `input` and returns a single resource object or an array of resource objects, which are returned as `return` parameters.

Security model:

- Disabled by default; enable with `hapi.fhir.javascript_execution_enabled=true`.
- The `script` parameter is resolved to a file inside `hapi.fhir.javascript_execution_scripts_dir` only (bare file name, no path traversal).
- Each script runs in a GraalJS sandbox created with no host access — Java classes, the filesystem, the network and thread creation are all denied (no JVM/filesystem/network reach).
- Each invocation is bounded by `hapi.fhir.javascript_execution_timeout_seconds` (default `30`); a script that overruns is stopped and the call fails.

Configuration:

```yaml
hapi:
fhir:
javascript_execution_enabled: true
javascript_execution_scripts_dir: /scripts
javascript_execution_timeout_seconds: 30
```

Inputs may be supplied two ways (combined into `input` in order — inline resources first, then resolved references):

- `resource` (0..*) — an inline FHIR resource.
- `reference` (0..*) — a literal reference (e.g. `Patient/123`) that the server reads before the script runs.

Example request body to `POST [base]/$execute-javascript`:

```json
{
"resourceType": "Parameters",
"parameter": [
{ "name": "script", "valueString": "add-active" },
{ "name": "resource", "resource": { "resourceType": "Patient", "active": false, "name": [{ "family": "Doe" }] } },
{ "name": "reference", "valueReference": { "reference": "Patient/123" } }
]
}
```

### One-liner for quickly testing $execute-javascript with Docker

This feature lives in this source tree, so build the local image (it is not part of the published Docker Hub image). First create a sample script:

```bash
mkdir -p scripts && cat > scripts/add-active.js <<'EOF'
// Sets active=true on every input resource and returns them.
input.map(function (resource) {
resource.active = true;
return resource;
});
EOF
```

Then build and run, mounting the scripts directory and enabling the feature:

```bash
docker run -p 8080:8080 -v "$(pwd)/scripts:/scripts" -e "hapi.fhir.javascript_execution_enabled=true" -e "hapi.fhir.javascript_execution_scripts_dir=/scripts" hapiproject/hapi:latest
```

Once it is up, invoke the operation:

```bash
curl -s -X POST 'http://localhost:8080/fhir/$execute-javascript' \
-H 'Content-Type: application/fhir+json' \
-d '{"resourceType":"Parameters","parameter":[{"name":"script","valueString":"add-active"},{"name":"resource","resource":{"resourceType":"Patient","active":false,"name":[{"family":"Doe"}]}}]}'
```

The response is a `Parameters` resource whose `return` parameter holds the transformed `Patient` with `active` set to `true`.

## Enable OpenTelemetry auto-instrumentation

The container image includes the [OpenTelemetry Java auto-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation)
Expand Down
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,15 @@
<scope>test</scope>
</dependency>

<!-- GraalJS — embeddable JavaScript engine used by $execute-javascript. The 23.0.x line is
the last that supports JDK 17 (23.1+ requires JDK 21). Pulls in the polyglot Context API
via graal-sdk transitively; runs in interpreter mode on a stock (non-GraalVM) JDK. -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>23.0.8</version>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory;
import ca.uhn.fhir.jpa.starter.elastic.ElasticsearchBootSvcImpl;
import ca.uhn.fhir.jpa.starter.ig.IImplementationGuideOperationProvider;
import ca.uhn.fhir.jpa.starter.javascript.JavaScriptExecutionR4OperationProvider;
import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.mdm.provider.MdmProviderLoader;
Expand Down Expand Up @@ -358,6 +359,7 @@ public RestfulServer restfulServer(
ApplicationContext appContext,
Optional<IpsOperationProvider> theIpsOperationProvider,
Optional<IImplementationGuideOperationProvider> implementationGuideOperationProvider,
Optional<JavaScriptExecutionR4OperationProvider> javaScriptExecutionOperationProvider,
DiffProvider diffProvider) {
RestfulServer fhirServer = new RestfulServer(fhirSystemDao.getContext());

Expand Down Expand Up @@ -423,6 +425,8 @@ public RestfulServer restfulServer(

implementationGuideOperationProvider.ifPresent(fhirServer::registerProvider);

javaScriptExecutionOperationProvider.ifPresent(fhirServer::registerProvider);

/*
* If you are hosting this server at a specific DNS name, the server will try to
* figure out the FHIR base URL based on what the web container tells it, but
Expand Down
Loading
Loading