From 86cc2c46d54a81c29b5a1959e9948500a745c98f Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Mon, 28 Oct 2019 03:13:04 -0400 Subject: [PATCH] issue #2 - condense projects and update FHIRServerUsersGuide.md 1. moved java classes from `fhir-client-sample` and `fhir-connectathon-client` into `fhir-client/src/test/java` and deleted these projects 2. updated FHIRServerUsersGuide 3. organized FHIRConfiguration properties and removed stale properties: * jsonParserLenient * jsonParserValidating * encryption 4. removed stale properties (whclsfRouter and the above) from all fhir-server-config.json examples Signed-off-by: Lee Surprenant --- docs/Conformance.md | 13 +- docs/FHIRServerUsersGuide.md | 415 ++++++------------ fhir-client-sample/.gitignore | 0 fhir-client-sample/pom.xml | 41 -- .../src/main/resources/fhir-client.properties | 21 - .../src/main/resources/fhirClientKeystore.jks | Bin 2233 -> 0 bytes .../main/resources/fhirClientTruststore.jks | Bin 939 -> 0 bytes fhir-client-sample/src/test/java/.empty | 0 fhir-client-sample/src/test/resources/.empty | 0 .../client/test/mains}/FHIRClientSample.java | 12 +- .../com/ibm/fhir/client/test/mains/Main.java | 33 -- .../client/test/mains}/PatientTrackTest.java | 24 +- .../src/test/resources/testdata}/patient.json | 0 .../resources/testdata}/patientNoDiv.json | 0 .../ibm/fhir/config/FHIRConfiguration.java | 51 ++- .../config/test/FHIRConfigHelperTest.java | 13 - .../config/default/fhir-server-config.json | 30 +- .../config/tenant5/fhir-server-config.json | 24 +- fhir-connectathon-clients/.gitignore | 1 - fhir-connectathon-clients/pom.xml | 52 --- .../src/main/java/.empty | 0 .../src/main/resources/.empty | 0 .../src/test/resources/fhir-client.properties | 22 - .../src/test/resources/fhirClientKeystore.jks | Bin 2233 -> 0 bytes .../test/resources/fhirClientTruststore.jks | Bin 2698 -> 0 bytes .../config/default/fhir-server-config.json | 6 +- .../fhir/server/resources/FHIRResource.java | 23 +- 27 files changed, 224 insertions(+), 557 deletions(-) delete mode 100644 fhir-client-sample/.gitignore delete mode 100644 fhir-client-sample/pom.xml delete mode 100644 fhir-client-sample/src/main/resources/fhir-client.properties delete mode 100644 fhir-client-sample/src/main/resources/fhirClientKeystore.jks delete mode 100644 fhir-client-sample/src/main/resources/fhirClientTruststore.jks delete mode 100644 fhir-client-sample/src/test/java/.empty delete mode 100644 fhir-client-sample/src/test/resources/.empty rename {fhir-client-sample/src/main/java/com/ibm/fhir/client/sample => fhir-client/src/test/java/com/ibm/fhir/client/test/mains}/FHIRClientSample.java (95%) delete mode 100644 fhir-client/src/test/java/com/ibm/fhir/client/test/mains/Main.java rename {fhir-connectathon-clients/src/test/java/com/ibm/fhir/connectathon/patient => fhir-client/src/test/java/com/ibm/fhir/client/test/mains}/PatientTrackTest.java (96%) rename {fhir-connectathon-clients/src/test/resources => fhir-client/src/test/resources/testdata}/patient.json (100%) rename {fhir-connectathon-clients/src/test/resources => fhir-client/src/test/resources/testdata}/patientNoDiv.json (100%) delete mode 100644 fhir-connectathon-clients/.gitignore delete mode 100644 fhir-connectathon-clients/pom.xml delete mode 100644 fhir-connectathon-clients/src/main/java/.empty delete mode 100644 fhir-connectathon-clients/src/main/resources/.empty delete mode 100644 fhir-connectathon-clients/src/test/resources/fhir-client.properties delete mode 100644 fhir-connectathon-clients/src/test/resources/fhirClientKeystore.jks delete mode 100644 fhir-connectathon-clients/src/test/resources/fhirClientTruststore.jks diff --git a/docs/Conformance.md b/docs/Conformance.md index 010b8ab6796..9be90739cdf 100644 --- a/docs/Conformance.md +++ b/docs/Conformance.md @@ -4,11 +4,22 @@ The IBM FHIR Server aims to be a conformant implementation of the HL7 FHIR speci ## Capability statement The HL7 FHIR specification defines [an interaction](https://www.hl7.org/fhir/R4/http.html#capabilities) for retrieving a machine-readable description of the server's capabilities via the `[base]/metadata` endpoint. The IBM FHIR Server implements this interaction and generates a `CapabilityStatement` resource based on the current server configuration. While the `CapabilityStatement` resource is ideal for certain uses, this markdown document provides a human-readable summary of important details, with a special focus on limitations of the current implementation and deviations from the specification. +The IBM FHIR Server supports only version 4.0.0 of the specification and presently has no support for the MIME-type parameter `fhirVersion`. + ## FHIR HTTP API -The HL7 FHIR specification is more than just a data format. It defines an [HTTP API](https://www.hl7.org/fhir/R4/http.html) for creating, reading, updating, deleting, and searching over FHIR resources. The IBM FHIR Server implements the full API for every resource defined in the specification, with the following exceptions: +The HL7 FHIR specification is more than just a data format. It defines an [HTTP API](https://www.hl7.org/fhir/R4/http.html) for creating, reading, updating, deleting, and searching over FHIR resources. The IBM FHIR Server implements almost the full API for every resource defined in the specification, with the following exceptions: * history is only supported at the resource instance level (no resource type history and no whole-system history) * there are parts of the FHIR search specification which are not fully implemented as documented in the following section +The IBM FHIR Server implements a linear versioning scheme for resources and fully implements the `vread` and `history` interactions, as well as version-aware updates. + +### General parameters +The `_format` parameter is supported and provides a useful mechanism for requesting a specific format (`XML` or `JSON`) in requests made from a browser. In the absence of either an `Accept` header or a `_format` query parameter, the server defaults to `application/fhir+json`. + +The `_pretty` parameter is not currently supported, but should be added as part of https://github.com/IBM/FHIR/issues/269. + +The `_summary` and `_elements` parameters are supported on the search interaction as documented. + ## Search The IBM FHIR Server supports search parameters of type `Number`, `Date/DateTime`, `String`, `Token`, `Reference`, `Quantity`, and `URI`. diff --git a/docs/FHIRServerUsersGuide.md b/docs/FHIRServerUsersGuide.md index 205a4d3786e..a547670dd23 100644 --- a/docs/FHIRServerUsersGuide.md +++ b/docs/FHIRServerUsersGuide.md @@ -20,7 +20,7 @@ lastupdated: "2019-09-04" * [3.2 Property names](#32-property-names) * [3.3 Tenant-specific configuration properties](#33-tenant-specific-configuration-properties) * [3.4 Persistence layer configuration](#34-persistence-layer-configuration) -- [4 Customization options](#4-customization-options) +- [4 Customization](#4-customization) * [4.1 Extended operations](#41-extended-operations) * [4.2 Notification Service](#42-notification-service) * [4.3 Persistence interceptors](#43-persistence-interceptors) @@ -35,162 +35,115 @@ lastupdated: "2019-09-04" * [5.1 Configuration properties reference](#51-configuration-properties-reference) * [5.2 Keystores, truststores, and the FHIR server](#52-keystores-truststores-and-the-fhir-server) * [5.3 Custom HTTP Headers](#53-custom-http-headers) - * [5.4 Notes about the FHIRJsonParser](#54-notes-about-the-fhirjsonparser) - [6 Related topics](#6-related-topics) # 1 Overview - -The IBM FHIR Server provides a REST API that is patterned after the HL7 FHIR specification and supports the full set of FHIR-defined resource types. -The FHIR server is intended to be a common component for providing FHIR capabilities within health services and solutions. +The IBM FHIR Server implements the HL7 FHIR HTTP API and supports the full set of FHIR-defined resource types. +This FHIR server is intended to be a common component for providing FHIR capabilities within health services and solutions. ## 1.1 Recent updates -View information about recent changes that were made to this document. For more information about changes that were made to the FHIR server codebase, see the [CHANGELOG](CHANGELOG.md). - -### Release 2.2 -* Moved User Guide to this repo converted to Markdown -* Split large table in [Section 5.1](#51-configuration-properties-reference) into three separate tables to improve display on GitHub Enterprise -* Replaced references to Jenkins with links to Artifactory -* Deleted the Historical Information section -* Introduced [CHANGELOG.md](CHANGELOG.md) for tracking changes to the FHIR server from one version to the next -* Introduced [Conformance.md](Conformance.md) for documenting spec coverage and list any deviations -* Added section on upgrading from one version to the next - -### Release 2.1 -IBM FHIR Server version 2.1 was developed under the Watson Health development organization. - -### Release 1.2 -* Added information about the update/create feature. -* Added information about the new JSON-based configuration model, and updated configuration examples throughout the document. -* Updated the information about configuring the FHIR server for IBM Db2®. -* Added instructions for creating the database and the schema that is required for OAuth 2.0 support. -* Corrected links to Liberty OAuth documentation. -* Added the section on Local References within request bundles. -* Added information about configuring the JDBC persistence layer. -* Added information about tenant-specific configuration parameters. -* Added reference table of supported configuration properties ([Section 5.1](#51-configuration-properties-reference)). -* Added information about tenant-specific data store configuration. -* Added the table of contents. -* Corrected link to installer. +View information about recent changes that were made to this document. For more information about changes that were made to the FHIR server codebase, see the corresponding release notes from the GitHub Releases tab. + +### Release 4.0 +* Initial release of the IBM FHIR Server for HL7 FHIR R4 # 2 Installation ## 2.1 Installing a new server -1. To install the FHIR server, first obtain the installation package `fhir-server-distribution.zip`. +1. To install the FHIR server, build or download the `fhir-install` zip installed (e.g. `fhir-server-distribution.zip` or `fhir-install-4.0.0-rc1-20191014-1610`). The Maven build creates the zip package under `fhir-install/target`. Alternatively, releases will be made available from the [Releases tab](https://github.com/ibm/fhir/releases). -2. Decompress the `.zip` file into a clean directory (referred to as `/fhir-installer` here): +2. Unzip the `.zip` package into a clean directory (referred to as `fhir-installer` here): ``` - mkdir /fhir-installer - cd /fhir-installer - unzip fhir-install-2.2.0-5-201810161109.zip + mkdir fhir-installer + cd fhir-installer + unzip fhir-server-distribution.zip ``` -3. Determine an installation location for the WebSphere® Liberty server and FHIR server web app. Example: `/opt/ibm/fhir-server` +3. Determine an install location for the OpenLiberty server and the FHIR server webapp. Example: `/opt/ibm/fhir-server` 4. Run the `install.sh/.bat` script to install the server: ``` - cd /fhir-installer ./fhir-server-dist/install.sh /opt/ibm/fhir-server ``` -This step installs the WebSphere Liberty runtime and the FHIR server web application. The Liberty runtime is installed in a directory that is called `wlp` within the installation directory that you specify. For example, in the preceding command, the root directory of the Liberty server runtime is `/opt/ibm/fhir-server/wlp`. +This step installs the OpenLiberty runtime and the FHIR server web application. The Liberty runtime is installed in a directory called `wlp` within the installation directory that you specify. For example, in the preceding command, the root directory of the Liberty server runtime would be `/opt/ibm/fhir-server/wlp`. 5. Configure the fhir-server's `server.xml` file as needed by completing the following steps: * Configure the ports that the server listen on. The server is installed with only port 9443 (HTTPS) enabled by default. To change the port numbers, modify the values in the `httpEndpoint` element. * Configure a server keystore and truststore. The FHIR server is installed with a default keystore file that contains a single self-signed certificate for localhost. For production use, you must create and configure your own keystore and truststore files for the FHIR server deployment (that is, generate your own server certificate or obtain a trusted certificate, and then share the public key certificate with API consumers so that they can insert it into their client-side truststore). The keystore and truststore files are used along with the server's HTTPS endpoint and the FHIR server's client-certificate-based authentication protocol to secure the FHIR server's endpoint. For more information, see [Section 5.2 Keystores, truststores, and the FHIR server](#52-keystores-truststores-and-the-fhir-server). -* Configure an appropriate user registry. The FHIR server is installed with a basic user registry that contains a single user named `fhiruser`. For production use, it's best to configure your own user registry. More information about configuring user registries, see the WebSphere Liberty documentation. +* Configure an appropriate user registry. The FHIR server is installed with a basic user registry that contains a single user named `fhiruser`. For production use, it's best to configure your own user registry. For more information about configuring user registries, see the [OpenLiberty documentation](https://openliberty.io/guides/security-intro.html#configuring-the-user-registry). 6. Configure the `fhir-server-config.json`[1](#f1) configuration file as needed: -* By default, the FHIR server is installed with the JDBC persistence layer configured to use an Embedded Derby database. This configuration provides a convenient default, but for production usage, it's best to configure the persistence layer to use Db2. For more information, see [Section 3.4 Persistence layer configuration](#34-persistence-layer-configuration). -* Configure an encryption key to support the encryption of REST API payloads. +* By default, the FHIR server is installed with the JDBC persistence layer configured to use an Embedded Derby database. This configuration provides a convenient default, but for production usage it's best to configure the persistence layer to use IBM Db2. For more information, see [Section 3.4 Persistence layer configuration](#34-persistence-layer-configuration). +* See [Section 3 Configuration](#3-configuration) for more configuration options. 7. Make sure that your selected database product is running and ready to accept requests. -* If you're using Db2, make sure that it's started and that the Db2 server is listening on the port that is configured in your `fhir-server-config.json`. Also, make sure that you've created or updated the schema to be used, and that you've configured the schema name in the datasource entries of the `fhir-server-config.json` file. As described in [Section 3.4.1.1.2 Db2](#34112-db2), the fhir-install package includes scripts that invoke liquibase to create the database and database schema. - -8. Before you start the server, make sure that you are using a FIPS-configured Java™ 8, which is required by the FHIR server. Java 8 is installed as part of the installation package, which is in the following directory: `${WLP_HOME}/ibm-java-x86_64-80`. Be sure to set the `JAVA_HOME` environment variable to this location. The FHIR server does not initialize properly if you are not using Java 8, and the IBMJCEFIPS configuration is required due to the encryption requirements of the audit logging component. +* If you're using Db2, make sure that it's listening on the port that is configured in your `fhir-server-config.json`. Also, make sure that you've created or updated the schema to be used, and that you've configured the schema name in the datasource entries of the `fhir-server-config.json` file. As described in [Section 3.4.1.1.2 Db2](#34112-db2), the `fhir-persistence-schema` module uses `fhir-database-utils` to create the database and database schema. -9. To start and stop the server, use the WebSphere Liberty server command: +8. To start and stop the server, use the Liberty server command: ``` /bin/server start fhir-server /bin/server stop fhir-server ``` -10. After you start the server, you can verify that it's running properly by invoking the `metadata` REST API, like this: +9. After you start the server, you can verify that it's running properly by invoking the `$healthcheck` endpoint like this: ``` -curl -k -u https://:/fhir-server/api/v1/metadata +curl -k -u https://:/fhir-server/api/v4/$endpoint ``` where `` is one of the users configured in `server.xml` (default is `fhiruser`). The preceding command should produce output similar to the following: ``` { - "resourceType" : "Conformance", - "version" : "1.0.0", - "name" : "IBM FHIR Server server", + "resourceType" : "CapabilityStatement", + "version" : "4.0.0", + "name" : "IBM FHIR Server", "publisher" : "IBM Corporation", - "date" : "Mon Jun 27 16:06:45 CDT 2016", - "description" : "IBM FHIR Server version 1.0.0 build id development", - "copyright" : "(c) Copyright IBM Corporation 2016", + "description" : "IBM FHIR Server version 4.0.0 build id development", + "copyright" : "(C) Copyright IBM Corporation 2016, 2019", "kind" : "instance", "software" : { "id" : "development", "name" : "IBM FHIR Server", - "version" : "1.0.0" + "version" : "4.0.0" }, - "fhirVersion" : "1.0.2 - r4", - "format" : [ "application/json", "application/json+fhir", "application/xml", "application/xml+fhir" ] - ... + "fhirVersion" : "4.0.0", + "format" : [ "json","xml","application/json","application/fhir+json","application/xml","application/fhir+xml" ] + … } ``` -For more information about the conformance of the implementation, see [Conformance.md](Conformance.md). +For more information about the capabilities of the implementation, see [Conformance.md](Conformance.md). ## 2.2 Upgrading an existing server The FHIR server does not include an upgrade installer. To upgrade a server to the next version, you can run the installer on a separate server, and then copy the resulting configuration files over to the existing server. -To manage database updates over time, the FHIR server uses [liquibase](https://www.liquibase.org/). By defining all schema updates via changeset, liquibase can detect the currently installed version of the database and apply any new changes that are needed to bring the database to the current level. +To manage database updates over time, the FHIR server uses custom tools from the `fhir-database-utils` project. Through the use of a metadata table, the database utilities can detect the currently installed version of the database and apply any new changes that are needed to bring the database to the current level. Complete the following steps to upgrade the server: 1. Run the fhir-installer on a separate server. 2. Configure the new server as appropriate (`fhir-server.xml` and anything under the `fhir-server/config` and `fhir-server/userlib` directories). 3. Back up your database. -4. Run the liquibase migration script (see example at [Section 3.4.1.1.2 Db2](#34112-db2)). +4. Run the migration program (see [Section 3.4.1.1.2 Db2](#34112-db2)). 5. Disable traffic to the old server and enable traffic to the new server -### 2.2.1 Upgrading from 2.1.x to 2.2.0 -The FHIR 2.2.0 release contains a few database updates, which makes it difficult to avoid downtime during an upgrade. Be particularly aware of the following changes during the upgrade: -1. Issue #53 - expanded stored procedure parametertype arrays; and -2. Issue #13 - fixed race condition in the `add_resource` stored procedures. - -FHIR 2.2.0 upgrades WebSphere Liberty from version 17.0.0.1 to version 18.0.0.2. There are also updates to several Liberty features and a few of the FHIR server dependencies. If you modified your `server.xml`, you might want to merge your changes with the new `server.xml` that was created by the installer. - -Finally, for users who extended the FHIR server (via custom operations or custom persistence options), Release 2.2.0 contains a few breaking changes to the Java libraries. The keys changes to be aware of are: -1. Issue #63 - refactored FHIRException class hierarchy and improved error handling; and -2. Issue #40 - introduced required getHealth() method on the FHIRPersistence interface (for use with the new $healthcheck operation). - -A complete list of changes is available in the [CHANGELOG](CHANGELOG.md). - # 3 Configuration This chapter contains information about the various ways in which the FHIR server can be configured by users. ## 3.1 Encoded passwords -In the examples contained within the following sections, you'll see encoded passwords and other values which appear as `“{xor}...”`. These values have been encoded by the `securityUtility` command provided by the WebSphere Liberty server. -To encode a string value, run the following command: +In the examples within the following sections, you'll see the default password `change-password`. In order to secure your server, these values should be changed. + +Optionally, the values can be encoded via the Liberty `securityUtility` command. For example, to encode a string value with the default `{xor}` encoding, run the following command: ``` /bin/securityUtility encode stringToEncode ``` -and the following output is generated: - -`{xor}abc-change-me=` - -This output can then be copied and pasted into your server.xml or `fhir-server-config.json` file as needed. - -The `fhir-server-config.json` does not support the securityUtility's {aes} encoding at this time, but per the limits to protection through password encryption[a], this encoding does not provide significant additional security beyond `exclusive or` (XOR) encoding. +The output of this command can then be copied and pasted into your `server.xml` or `fhir-server-config.json` file as needed. The `fhir-server-config.json` does not support the securityUtility's `{aes}` encoding at this time, but per the limits to protection through password encryption[a], this encoding does not provide significant additional security beyond `exclusive or` (XOR) encoding. ## 3.2 Property names Configuration properties stored within a `fhir-server-config.json` file are structured in a hierarchical manner. Here is an example: ``` { - "fhirServer":{ + "fhirServer":{ "core":{ "truststoreLocation":"resources/security/fhirTruststore.jks", "truststorePassword":"change-password", @@ -200,61 +153,54 @@ Configuration properties stored within a `fhir-server-config.json` file are stru } ``` -To enable the encryption feature, you would set the “enabled” field within the “encryption” sub-structure to true. - -Throughout this document, we use a path notation to refer to property names. For example, the name of the `enabled` property in the preceding example would be `fhirServer/test/enabled`. This refers to the fact that the `enabled` field exists within the `test` field within the `fhirServer` field. +Throughout this document, we use a path notation to refer to property names. For example, the name of the `truststorePassword` property in the preceding example would be `fhirServer/test/enabled`. ## 3.3 Tenant-specific configuration properties The FHIR server supports certain multi-tenant features. One such feature is the ability to set certain configuration properties on a per-tenant basis. -In general, the configuration properties for a particular tenant are stored in the `/wlp/usr/servers/fhir-server/config//fhir-server-config.json` file, where `` refers to the tenant's “short name” or tenant id. The global configuration is considered to be associated with a tenant named `default`, so those properties are stored in the `/wlp/usr/servers/fhir-server/config/default/fhir-server-config.json` file. Similarly, tenant-specific search parameters are found at `/wlp/usr/servers/fhir-server/config//extension-search-parameters.json` whereas the global/default extension search parameters are at `/wlp/usr/servers/fhir-server/config/default/extension-search-parameters.json`. Search parameters are handled like a single configuration properly; providing a tenant-specific file will override the global/default extension search parameters. -[FHIRSearchConfiguration.md](FHIRSearchConfiguration.md) +In general, the configuration properties for a particular tenant are stored in the `/wlp/usr/servers/fhir-server/config//fhir-server-config.json` file, where `` refers to the tenant's “short name” or tenant id.The global configuration is considered to be associated with a tenant named `default`, so those properties are stored in the `/wlp/usr/servers/fhir-server/config/default/fhir-server-config.json` file. Similarly, tenant-specific search parameters are found at `/wlp/usr/servers/fhir-server/config//extension-search-parameters.json` whereas the global/default extension search parameters are at `/wlp/usr/servers/fhir-server/config/default/extension-search-parameters.json`. + +Search parameters are handled like a single configuration properly; providing a tenant-specific file will override the global/default extension search parameters as defined at [FHIRSearchConfiguration.md](FHIRSearchConfiguration.md). More information about multi-tenant support can be found in [Section 4.9 Multi-tenancy](#49-multi-tenancy). ## 3.4 Persistence layer configuration -The FHIR server is architected in a way that allows deployers to select the persistence layer implementation that fits their needs. Currently, the FHIR server includes a JDBC persistence layer which supports both Derby and Db2. +The FHIR server is architected in a way that allows deployers to select the persistence layer implementation that fits their needs. Currently, the FHIR server includes a JDBC persistence layer which supports both Apache Derby and IBM Db2. + The FHIR server is delivered with a default configuration that is already configured to use the JDBC persistence layer implementation with an Embedded Derby database. This provides the easiest out-of-the-box experience since it requires very little setup. The sections that follow in this chapter will focus on how to configure the JDBC persistence layer implementation with either Embedded Derby or Db2. ### 3.4.1 Configuring the JDBC persistence layer #### 3.4.1.1 Database preparation Before you can configure the FHIR server to use the JDBC persistence layer implementation, you first need to prepare the database. This step depends on the database product in use. + ##### 3.4.1.1.1 Embedded Derby (default) -If you are configuring the FHIR server to use a single embedded Derby database, then you can configure the FHIR server to create the database and the required FHIR server schema DDL (tables, and so on) automatically during server startup. To configure the FHIR server to “bootstrap” the database during server startup, modify the `fhirServer/persistence/jdbc/bootstrapDb` property in `fhir-server-config.json`, as in the following example: +If you are configuring the FHIR server to use a single embedded Derby database, then you can configure the FHIR server to create the database and the schema and tables during startup. To configure the FHIR server to “bootstrap” the database in this way, modify the `fhirServer/persistence/jdbc/bootstrapDb` property in `fhir-server-config.json` as in the following example: ``` { "fhirServer":{ - ... + … "persistence":{ - ... + … "jdbc":{ "bootstrapDb":true, - ... + … }, - ... + … } } } ``` -This configuration causes the server to run the liquibase tool during server startup to bring the schema DDL in sync with the currently installed FHIR server code. This database bootstrap step is only performed for a Derby database. +This database bootstrap step is only performed for a Derby database. ##### 3.4.1.1.2 Db2 -If you configure the FHIR server to use a Db2 database, you must create the database if it doesn't already exist, and then run the liquibase tool to create the necessary schema (tables, indexes, and other elements). The FHIR server distribution includes some database-related tools, including the `createDB.sh` script. You can use this script to create your database, or you can create it on your own. To run the `createDB.sh` script, run the following command[2](#f2): -``` -/fhir/bin/createDB.sh -``` -where `` represents the user name of the administrative user for the database. This user name is typically the same as the administrative user associated with the Db2 instance in which the database is being created (typically `db2inst1`). Specify a user name that is appropriate for your situation. If you don't specify a user name, the currently logged in user is used. - -Note: The `createDB.sh` script is provided for your convenience, but you can choose the method that you use to create the database. The default name of the database is `FHIRDB`, but you can choose another name. Make sure that you configure the datastore-related properties to reflect your chosen database name. +If you configure the FHIR server to use an IBM Db2 database, you must +1. create the database if it doesn't already exist; and +2. execute `com.ibm.fhir.schema.app.Main` from the `fhir-persistence-schema` jar file to create the necessary schema (tables, indexes, and other elements). -After the database is created, run the liquibase command to create the schema (tables, indexes, and other elements) as in the following example: -``` -cd /fhir/bin/schemaddl - -./liquibase --defaultsFile=./fhir.properties --changeLogFile=ddl/db2/normalized-schema/fhirserver.db2.normalized.xml update -``` +For a detailed guide on configuring IBM Db2 on Cloud for the IBM FHIR Server, see [DB2OnCloudSetup.md](DB2OnCloudSetup.md). +TODO: improve documentation on installing the database schema. #### 3.4.1.2 FHIR server configuration To configure the FHIR server to use the JDBC persistence layer, complete the following steps: @@ -262,20 +208,21 @@ To configure the FHIR server to use the JDBC persistence layer, complete the fol ``` { “fhirServer”: { - ... + … “persistence”: { "factoryClassname": "com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory", … } } ``` + 2. Next, modify the `fhirServer/persistence/jdbc/dataSourceJndiName` property in `fhir-server-config.json` to specify the proxy datasource's JNDI name, like this: ``` { “fhirServer”: { - ... + … “persistence”: { - ... + … "jdbc": { … “dataSourceJndiName”: “jdbc/fhirProxyDataSource” @@ -284,13 +231,14 @@ To configure the FHIR server to use the JDBC persistence layer, complete the fol } } ``` + 3. Next, modify the `fhirServer/persistence/datasources` property group to reflect the datastore(s) that you want to use. The following example defines the `default` datastore as an embedded derby database located in `wlp/usr/servers/fhir-server/derby/fhirDB`: ``` { "fhirServer":{ - ... + … "persistence":{ - ... + … "datasources": { "default": { "type": "derby", @@ -303,11 +251,11 @@ To configure the FHIR server to use the JDBC persistence layer, complete the fol } ``` -The next example defines the `default` datastore as a DB2 database accessible on the `db2server1` host: +The next example defines the `default` datastore as a Db2 database accessible on the `db2server1` host: ``` { "fhirServer":{ - ... + … "persistence":{ "datasources": { "default": { @@ -323,7 +271,7 @@ The next example defines the `default` datastore as a DB2 database accessible on } } } - ... + … } } } @@ -370,11 +318,9 @@ Here is a simple example of a single (default) datastore: }, "jdbc":{ “bootstrapDb":true, - "schemaType":"normalized", "dataSourceJndiName": "jdbc/fhirDB" }, - - } + } } } ``` @@ -388,10 +334,10 @@ Furthermore, the REST API consumers associated with Acme applications will be co { "__comment":"Acme's FHIR server configuration", "fhirServer":{ - ... + … "persistence":{ "factoryClassname":"com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory", - ... + … "datasources": { "study1": { "type": "db2", @@ -416,7 +362,7 @@ Furthermore, the REST API consumers associated with Acme applications will be co } } } - ... + … } } } @@ -448,19 +394,19 @@ Within each tenant's `fhir-server-config.json` file, the `fhirServer/persistence } ``` -The `type` property indicates the database type (currently the supported types are `db2` and `derby`). +The `type` property indicates the database type (currently only `db2` or `derby`). The `connectionProperties` property is a set of driver-specific properties needed to connect to an instance of that database type. For a Db2-related datasource definition, any bean property supported by the `DB2XADataSource` class can be specified within the `connectionProperties` property group. For a discussion of the specific properties that can be used to configure a `DB2XADataSource` instance, see the [Db2 Knowledge Center](https://www.ibm.com/support/knowledgecenter/SSEPGG_11.1.0/com.ibm.db2.luw.apdv.java.doc/src/tpc/imjcc_rjvdsprp.html). For a Derby-related datasource definition, any bean property supported by the `EmbeddedXADataSource` class can be specified within the `connectionProperties` property group. For more information about the properties supported by the `EmbeddedXADataSource` class, and its super classes, see the [Apache Derby documentation](https://db.apache.org/derby/docs/10.13/publishedapi/org/apache/derby/jdbc/EmbeddedXADataSource.html). -# 4 Customization options +# 4 Customization You can modify the default server implementation by taking advantage of the IBM FHIR server's extensibility. The following extension points are available: - * Custom operations framework: The IBM FHIR Server defines an operations framework that builds on the FHIR OperationDefinition resource in order to extend the FHIR REST API with custom endpoints. - * Pluggable audit service: Logging and auditing options with Cloud Auditing Data Federation (CADF) over Apache Kafka. - * Persistence interceptors: Intercept requests before and/or after each persistence operation. - * Resource validation: Supports FHIRPath-based validation of FHIR resources on create or update and allows for the addition of custom constraints based on FHIR StructureDefinition profiles. + * Custom operations framework: The IBM FHIR Server defines an operations framework that builds on the FHIR OperationDefinition resource in order to extend the FHIR REST API with custom endpoints. + * Pluggable audit service: Logging and auditing options including Cloud Auditing Data Federation (CADF) over Apache Kafka. + * Persistence interceptors: Intercept requests before and/or after each persistence action. + * Resource validation: FHIRPath-based validation of FHIR resources on create or update with the ability to extend the system with custom constraints. ## 4.1 Extended operations In addition to the standard REST API (create, update, search, and so forth), the IBM FHIR Server supports the FHIR operations framework as described in the [FHIR specification]( https://www.hl7.org/fhir/r4/operations.html). @@ -487,7 +433,6 @@ The `$healthcheck` operation returns the health of the FHIR server and its datas In addition to the provided operations, the FHIR server supports user-provided custom operations through a Java Service Provider Interface (SPI). To contribute an operation: - 1. Implement each operation as a Java class that extends `com.ibm.fhir.operation.AbstractOperation` from `fhir-operation.jar`. Ensure that your implementation returns an appropriate `OperationDefinition` in its `getDefinition()` method, because the framework validates both the request and response payloads to ensure that they conform to the definition. 2. Create a file named `com.ibm.fhir.operation.FHIROperation` with one or more fully qualified `FHIROperation` classnames and package it in your jar under `META-INF/services/`. 3. Include your jar file under the `/wlp/usr/servers/fhir-server/userlib/` directory of your installation. @@ -506,10 +451,10 @@ The `FHIRNotificationEvent` class defines the information that is published by t Field name | Type | Description |--------------| ----- | ------------| `operationType`| String | The operation associated with the notification event. Valid values are _create_ and _update_. -`location` | String | The location URI of the resource associated with the notification event. To retrieve this resource, invoke a GET request using the location URI value as the URL string. -`lastUpdated` | String | The date and time of the last update made to the resource associated with the notification event. -`resourceId` | String | The logical id of the resource associated with the notification event. -`resource` | String | A stringified JSON object which is the resource associated with the notification event. +`location` | String | The location URI of the resource associated with the notification event. To retrieve this resource, invoke a GET request using the location URI value as the URL string. +`lastUpdated` | String | The date and time of the last update made to the resource associated with the notification event. +`resourceId` | String | The logical id of the resource associated with the notification event. +`resource` | String | A stringified JSON object which is the resource associated with the notification event. The following JSON is an example of a serialized notification event: ``` @@ -518,7 +463,7 @@ The following JSON is an example of a serialized notification event: "location":"Observation/3859/_history/1", "operationType":"create", "resourceId":"3859", - "resource":{ ...... } + "resource":{ …… } } ``` @@ -539,10 +484,10 @@ The WebSocket implementation of the notification service will publish notificati } ``` -The WebSocket location URI is `ws://:/fhir-server/api/v1/notification`, where `` and `` represent the host and port of the FHIR server's REST API endpoint. So for example, if the FHIR server endpoint's base URL is `https://localhost:9080/fhir-server/api/v1` then the corresponding location of the WebSocket would be `ws://localhost:9080/fhir-server/api/v1/notification`. +The WebSocket location URI is `ws://:/fhir-server/api/v4/notification`, where `` and `` represent the host and port of the FHIR server's REST API endpoint. So for example, if the FHIR server endpoint's base URL is `https://localhost:9443/fhir-server/api/v4` then the corresponding location of the WebSocket would be `ws://localhost:9443/fhir-server/api/v4/notification`. ### 4.2.3 Kafka -The Kafka implementation of the notification service will publish notification event messages to a Kafka topic. To configure the Kafka notification publisher, configure properties in the `fhir-server-config.json` file as indicated in the following example: +The Kafka implementation of the notification service will publish notification event messages to a Kafka topic. To configure the Kafka notification publisher, configure properties in the `fhir-server-config.json` file as indicatesd in the following example: ``` { @@ -695,31 +640,23 @@ The following example shows the JSON for enabling _update_ operations to create ``` ## 4.6 FHIR client API + ### 4.6.1 Overview -Along with the FHIR server, we also offer a high-level Java API that can be used to invoke the FHIR REST APIs. The FHIR Client API is based on the JAX-RS 2.0 standard and provides several capabilities that make it an attractive client API for use with the FHIR server: -* Easily configured via properties. -* Supports the use of an SSL transport (HTTPS:) along with the ability to configure a client truststore file. -* Can be configured to perform client certificate-based authentication as an alternative to basic authentication. -* Supports encryption of REST API payloads, working in concert with the FHIR server. +In addition to the server, we also offer a Java API for invoking the FHIR REST APIs. The IBM FHIR Client API is based on the JAX-RS 2.0 standard and provides a simple properties-driven client that can be easily configured for a given endpoint, mutual authentication, request/response logging, and more. ### 4.6.2 Maven coordinates -The FHIR Client API can be found on the WHC Nexus artifact server. In order to use the FHIR Client API within your own application, you'll need to specify the `fhir-client` artifact as a dependency within your `pom.xml` file, as in the following example: +To use the FHIR Client from your application, specify the `fhir-client` artifact as a dependency within your `pom.xml` file, as in the following example: ``` com.ibm.fhir fhir-client - ${fhir.server.version} + ${fhir.client.version} ``` -where `${fhir.server.version}` is one of: -* `99-SNAPSHOT` (associated with the 'master' branch) -* `1.1.0-SNAPSHOT` (associated with the 'release-1.1.x' branch) -* `1.0.0-SNAPSHOT` (associated with the 'release-1.0.x' branch) - -### 4.6.3 Sample -Within the master branch of the FHIR Git repository, you can find the “fhir-client-sample” project which provides a stand-alone sample application that uses the FHIR client API. +### 4.6.3 Sample usage +For examples on how to use the IBM FHIR Client, look for tests like `com.ibm.fhir.client.test.mains.FHIRClientSample` from the `fhir-client` project in git. Additionally, the FHIR Client is heavilly used from our integration tests in `fhir-server-test`. ## 4.7 FHIR command-line interface (fhir-cli) The FHIR command-line interface (fhir-cli for short) is a command that can be used to invoke FHIR REST API operations from the command line. The compressed file for installing the fhir-cli tool zip is part of the FHIR server installation in `${WLP_HOME}/fhir/client/fhir-cli.zip`, and the `fhir-cli.zip` file is also available from [our Artifactory server]( @@ -798,7 +735,7 @@ FHIR Client Command Line Interface (fhir-cli) (c) Copyright IBM Corporation, Invoking operation _create_... done! (2719ms) Status code: 201 -Location URI: http://localhost:9080/fhir-server/api/v1/Patient/26b694ef-cea7-4485-a896-5ac2a1da9f64/_history/1 +Location URI: http://localhost:9443/fhir-server/api/v4/Patient/26b694ef-cea7-4485-a896-5ac2a1da9f64/_history/1 ETag: W/"1" Last modified: 2016-09-13T20:51:21.048Z ``` @@ -823,7 +760,7 @@ FHIR Client Command Line Interface (fhir-cli) (c) Copyright IBM Corporation, Invoking operation _update_... done! (2707ms) Status code: 200 -Location URI: http://localhost:9080/fhir-server/api/v1/Patient/26b694ef-cea7-4485-a896-5ac2a1da9f64/_history/2 +Location URI: http://localhost:9443/fhir-server/api/v4/Patient/26b694ef-cea7-4485-a896-5ac2a1da9f64/_history/2 ETag: W/"2" Last modified: 2016-09-13T21:11:48.988Z ``` @@ -895,7 +832,7 @@ Response resource: ``` ## 4.8 Using local references within request bundles -Inter-dependencies between resources are typically defined by one resource containing a field of type `Reference` which contains an _external reference_[5](#f5) to another resource. For example, an `Observation` resource could reference a `Patient` resource via the Observation's `subject` field. The value that is stored in the `Reference-type` field (for example, `subject` in the case of the `Observation` resource) could be an absolute URL, such as `https://fhirserver1:9443/fhir-server/api/v1/Patient/12345`, or a relative URL (for example, `“Patient/12345”`). +Inter-dependencies between resources are typically defined by one resource containing a field of type `Reference` which contains an _external reference_[5](#f5) to another resource. For example, an `Observation` resource could reference a `Patient` resource via the Observation's `subject` field. The value that is stored in the `Reference-type` field (for example, `subject` in the case of the `Observation` resource) could be an absolute URL, such as `https://fhirserver1:9443/fhir-server/api/v4/Patient/12345`, or a relative URL (for example, `“Patient/12345”`). In order to establish a reference to a resource, you must first know its resource identifier. However, if you are using a request bundle to create both the referenced resource (`Patient` in this example) and the resource which references it (`Observation`), then it is impossible to know the `Patient`resource identifier before the request bundle has been process (that is, before the new `Patient` resource is created). @@ -910,7 +847,7 @@ Thankfully, the HL7 FHIR specification defines a way to express a dependency bet "fullUrl" : "urn:uuid:7113a0bb-d9e0-49df-9855-887409388c69", "resource" : { "resourceType" : "Patient", - ... + … }, "request" : { "method" : "POST", @@ -919,11 +856,11 @@ Thankfully, the HL7 FHIR specification defines a way to express a dependency bet }, { "resource" : { "resourceType" : "Observation", - ... + … "subject" : { "reference" : "urn:uuid:7113a0bb-d9e0-49df-9855-887409388c69" }, - ... + … }, "request" : { "method" : "POST", @@ -938,13 +875,11 @@ In order to reference a resource via a local reference, you must first define a After you define a local identifier for the referenced resource, you can then define one or more references to that resource by using the local identifier instead of an external identifier. In the preceding example, you can see that the Observation's `subject.reference` field specifies the Patient's local identifier as specified in the `fullUrl` field of the Patient's request entry. ### 4.8.1 Processing rules -There is really only one main rule related to the use of local references within a request bundle: - -A local identifier must be defined via a request entry's `fullUrl` field before that local identifier can be used in a local reference. +There is one rule for the use of local references within a request bundle: A local identifier must be defined via a request entry's `fullUrl` field before that local identifier can be used in a local reference. -In the example in [Section 4.9.0.1](#4901-example-1-observation-references-patient-via-local-reference), you can see that there are two POST requests and the `Patient` request entry appears in the bundle before the `Observation` request entry, so that example satisfies the rule because the FHIR server will process the POST request entries in the order in which they appear within the request bundle. +In the example in [Section 4.8.0.1](#4801-example-1-observation-references-patient-via-local-reference), you can see that there are two POST requests and the `Patient` request entry appears in the bundle before the `Observation` request entry. This example satisfies the rule because the FHIR server will process the POST request entries in the order in which they appear within the request bundle. -If, however, those entries were reversed, the FHIR server returns an error when processing the `Observation` request entry, because the `Patient` local identifier is not defined yet. +If, however, those entries were reversed, the FHIR server would return an error when processing the `Observation` request entry, because the `Patient` local identifier is not defined yet. The following example also satisfies the rule: @@ -957,11 +892,11 @@ The following example also satisfies the rule: "resource" : { "resourceType" : "Observation", "id" : "25b1fe08-7612-45eb-af80-7e15d9806b2b", - ... - "subject" : { + … + "subject" : { "reference" : "urn:Patient_1" - }, - ... + }, + … }, "request" : { "method" : "PUT", @@ -971,7 +906,7 @@ The following example also satisfies the rule: "fullUrl" : "urn:Patient_1", "resource" : { "resourceType" : "Patient", - ... + … }, "request" : { "method" : "POST", @@ -985,7 +920,7 @@ The FHIR server first processes all POST requests found within a request bundle While processing a POST or PUT request entry within a request bundle, the FHIR server will detect the use of a local identifier within the entry's `fullUrl` field, and will establish a mapping between that local identifier and the corresponding external identifier that results from performing the POST or PUT operation. -For example, in Example 1 from [Section 4.9.0.1](#4901-example-1-observation-references-patient-via-local-reference), the FHIR server detects the use of the local identifier in the `Patient` request entry (`urn:uuid:7113a0bb-d9e0-49df-9855-887409388c69`) and -- after creating the new `Patient` resource -- establishes a mapping between the local identifier and the resulting external reference associated with the new `Patient` (for example, `Patient/1cc5d299-d2be-4f93-8745-a121232ffe5b`). +For example, in Example 1 from [Section 4.8.0.1](#4801-example-1-observation-references-patient-via-local-reference), the FHIR server detects the use of the local identifier in the `Patient` request entry (`urn:uuid:7113a0bb-d9e0-49df-9855-887409388c69`) and -- after creating the new `Patient` resource -- establishes a mapping between the local identifier and the resulting external reference associated with the new `Patient` (for example, `Patient/1cc5d299-d2be-4f93-8745-a121232ffe5b`). Then when the FHIR server processes the POST request for the `Observation`, it detects the use of the local reference and substitutes the corresponding external reference for it before creating the new `Observation` resource. Here is an example of a response bundle for the request bundle depicted in Example 1 in which we can see that the Observation's `subject.reference` field now contains a proper external reference to the newly-created `Patient` resource: @@ -998,7 +933,7 @@ Then when the FHIR server processes the POST request for the `Observation`, it d "resource" : { "resourceType" : "Patient", "id" : "1cc5d299-d2be-4f93-8745-a121232ffe5b", - ... + … }, "response" : { "id" : "1cc5d299-d2be-4f93-8745-a121232ffe5b", @@ -1011,11 +946,11 @@ Then when the FHIR server processes the POST request for the `Observation`, it d "resource" : { "resourceType" : "Observation", "id" : "22b21fcf-8d00-492d-9de0-e25ddd409eaf", - ... + … "subject" : { "reference" : "Patient/1cc5d299-d2be-4f93-8745-a121232ffe5b" }, - ... + … }, "response" : { "id" : "22b21fcf-8d00-492d-9de0-e25ddd409eaf", @@ -1174,7 +1109,7 @@ The CADF audit logging service gets event streams service credential from env va { "api_key": "xxxxxxxxxxxxxxxx_xxxxx_xxxxxxxxxxxxxxxxxxx", "apikey": "xxxxxxxxxxxxxxxx_xxxxx_xxxxxxxxxxxxxxxxxxx", - ... + … "kafka_brokers_sasl": [ "broker-1-0server:9093", "broker-2-0server:9093", @@ -1183,23 +1118,23 @@ The CADF audit logging service gets event streams service credential from env va "broker-3-0server:9093", "broker-0-0server:9093" ], - ... + … } ``` The service credential is generated automatically when you run ``` - ibmcloud ks cluster-service-bind --cluster --namespace --service ... + ibmcloud ks cluster-service-bind --cluster --namespace --service … ``` to bind your event streams service instance to your Kubernetes cluster. And then in the YAML file for your Kubernetes deployment, specify the environment variable EVENT_STREAMS_AUDIT_BINDING that references the binding key of the generated secret(binding-) as following: ``` - name: EVENT_STREAMS_AUDIT_BINDING - valueFrom: - secretKeyRef: - key: binding - name: binding- -``` + valueFrom: + secretKeyRef: + key: binding + name: binding- +``` please refer to https://cloud.ibm.com/docs/containers?topic=containers-service-binding for detailed instruction if need. ### 4.11.3 Query CADF events in COS @@ -1237,11 +1172,9 @@ This section contains reference information about each of the configuration prop ### 5.1.1 Property descriptions | Property Name | Type | Description | |-------------------------------|------|-----------------| -|`fhirServer/core/defaultPrettyPrint`|boolean|A boolean flag which indicates whether JSON "Pretty Printing" or XML Formatted output should be the container default for responses.| +|`fhirServer/core/defaultPrettyPrint`|boolean|A boolean flag which indicates whether "Pretty Printing" should be used by default. Applies to both XML and JSON.| |`fhirServer/core/tenantIdHeaderName`|string|The name of the request header that will be used to specify the tenant-id for each incoming FHIR REST API request. For headers with semicolon-delimited parts, setting a header name like `:` will select the value from the part of header ``'s value with a name of `` (e.g. setting `X-Test:part1` would select `someValue` from the header `X-Test: part1=someValue;part2=someOtherValue`).| |`fhirServer/core/dataSourceIdHeaderName`|string|The name of the request header that will be used to specify the datastore-id for each incoming FHIR REST API request. For headers with semicolon-delimited parts, setting a header name like `:` will select the value from the part of header ``'s value with a name of `` (e.g. setting `X-Test:part1` would select `someValue` from the header `X-Test: part1=someValue;part2=someOtherValue`).| -|`fhirServer/core/jsonParserLenient`|boolean|A boolean flag which indicates whether the FHIRJsonParser will be lenient with respect to element cardinality (singleton vs array) and string values for numbers/booleans.| -|`fhirServer/core/jsonParserValidating`|boolean|A boolean flag which indicates whether the FHIRJsonParser will do limited validation during the parse including checking for missing required fields and unrecognized fields.| |`fhirServer/searchParameterFilter`|property list|A set of inclusion rules for search parameters. See [FHIR Search Configuration](FHIRSearchConfiguration.md#12-Configuration--Filtering-of-search-parameters) for more information.| |`fhirServer/notifications/common/includeResourceTypes`|string list|A comma-separated list of resource types for which notification event messages should be published.| |`fhirServer/notifications/websocket/enabled`|boolean|A boolean flag which indicates whether or not websocket notifications are enabled.| @@ -1252,8 +1185,7 @@ This section contains reference information about each of the configuration prop |`fhirServer/persistence/common/updateCreateEnabled`|boolean|A boolean flag which indicates whether or not the 'update/create' feature should be enabled in the selected persistence layer.| |`fhirServer/persistence/datasources`|map|A map containing datasource definitions. See [Section 3.4.2.3 Datastore configuration reference](#3423-datastore-configuration-reference) for more information.| |`fhirServer/persistence/jdbc/dataSourceJndiName`|string|The JNDI name of the DataSource to be used by the JDBC persistence layer.| -|`fhirServer/persistence/jdbc/bootstrapDb`|boolean|A boolean flag which indicates whether the JDBC persistence layer should attempt to run the liquibase-based schema creation at server startup time.| -|`fhirServer/persistence/jdbc/schemaType`|string|Indicates the type of schema to be used by the JDBC persistence layer. Valid values are “basic” and “normalized”.| +|`fhirServer/persistence/jdbc/bootstrapDb`|boolean|A boolean flag which indicates whether the JDBC persistence layer should attempt to create or update the database and schema at server startup time.| |`fhirServer/oauth/regUrl`|string|The registration URL associated with the OAuth 2.0 authentication/authorization support.| |`fhirServer/oauth/authUrl`|string|The authorization URL associated with the OAuth 2.0 authentication/authorization support.| |`fhirServer/oauth/tokenUrl`|string|The token URL associated with the OAuth 2.0 authentication/authorization support.| @@ -1270,8 +1202,6 @@ This section contains reference information about each of the configuration prop |`fhirServer/core/defaultPrettyPrint`|false| |`fhirServer/core/tenantIdHeaderName`|`X-FHIR-TENANT-ID`| |`fhirServer/core/dataSourceIdHeaderName`|`X-FHIR-DSID`| -|`fhirServer/core/jsonParserLenient`|false| -|`fhirServer/core/jsonParserValidating`|true| |`fhirServer/searchParameterFilter`|`"*": [*]`| |`fhirServer/notifications/common/includeResourceTypes`|["*"]| |`fhirServer/notifications/websocket/enabled`|false| @@ -1283,10 +1213,10 @@ This section contains reference information about each of the configuration prop |`fhirServer/persistence/datasources`|embedded Derby database: derby/fhirDB| |`fhirServer/persistence/jdbc/dataSourceJndiName`|jdbc/fhirProxyDataSource| |`fhirServer/persistence/jdbc/bootstrapDb`|false| -|`fhirServer/persistence/jdbc/schemaType`|“basic”| |`fhirServer/oauth/regUrl`|""| |`fhirServer/oauth/authUrl`|""| |`fhirServer/oauth/tokenUrl`|""| +|`fhirServer/audit/serviceClassName`|""| |`fhirServer/audit/serviceProperties/auditTopic`|FHIR_AUDIT| |`fhirServer/audit/serviceProperties/geoCity`|Dallas| |`fhirServer/audit/serviceProperties/geoState`|TX| @@ -1299,8 +1229,6 @@ This section contains reference information about each of the configuration prop |`fhirServer/core/defaultPrettyPrint`|Y|Y| |`fhirServer/core/tenantIdHeaderName`|N|Y| |`fhirServer/core/dataSourceIdHeaderName`|N|N| -|`fhirServer/core/jsonParserLenient`|Y|Y| -|`fhirServer/core/jsonParserValidating`|Y|Y| |`fhirServer/searchParameterFilter`|Y|Y| |`fhirServer/notifications/common/includeResourceTypes`|N|N| |`fhirServer/notifications/websocket/enabled`|Y|Y| @@ -1312,7 +1240,6 @@ This section contains reference information about each of the configuration prop |`fhirServer/persistence/datasources`|Y|N| |`fhirServer/persistence/jdbc/dataSourceJndiName`|N|N| |`fhirServer/persistence/jdbc/bootstrapDb`|N|N| -|`fhirServer/persistence/jdbc/schemaType`|N|N| |`fhirServer/oauth/regUrl`|N|N| |`fhirServer/oauth/authUrl`|N|N| |`fhirServer/oauth/tokenUrl`|N|N| @@ -1398,7 +1325,7 @@ Although the steps required to configure certificate-based authentication for a In the default configuration, the FHIR server acts as an authorization server as well as a resource server. The FHIR server's conformance statement includes the OAuth-related URLs you will need to get started. The following excerpt from a conformance statement shows sample OAuth-related URLs (token, authorize, and register) as values of the `valueUri` elements. ``` -... +… "rest": [ { "mode": "server", @@ -1422,7 +1349,7 @@ In the default configuration, the FHIR server acts as an authorization server as ] } ], -... +… ``` First, a client application (web or mobile) must register itself with the Open ID Connect Provider using the following URL: @@ -1430,99 +1357,11 @@ First, a client application (web or mobile) must register itself with the Open I For more information about client registration, see the [WebSphere Application Server Liberty base documentation](https://www.ibm.com/support/knowledgecenter/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_client_registration.html). -By default, a Derby database is used to persist the OAuth-related configuration. If you would like to use Db2 instead of Derby, complete the following steps before using the OAuth URLs for client registration or authorization: - -1. (If you intend to use Db2 for both the persistence layer for FHIR server and for OAuth configuration, skip to Step 2.) Modify `WLP_HOME>/fhir/bin/createDB.sql` to use `OAUTH2B` as the database name instead of `FHIRDB`, and the run the following command to create the `OAUTH2DB` database: - ``` - /fhir/bin/createDB.sh - ``` - where `` represents the username that will be the administrative user for the database. This is typically the same as the administrative user associated with the Db2 instance in which the database is being created. - Complete this step one time only . - - Use a username which is appropriate for your situation. If omitted, the current logged in user will be used. +By default, a Derby database is used to persist the OAuth-related configuration. - Note: You can create the database using any method you choose. The “createDB.sh” script is simply provided for your convenience. Also, you can choose an alternate name for the database, but make sure you configure the datasource entries in 'server.xml' accordingly. However, we recommend that you do not reuse “FHIRDB” for OAuth configuration. +TODO: document how to configure Db2 for use with the Liberty OAuth 2.0 feature. -2. [once after each install] Create or update the schema DDL by completing the following steps: - - a. Modify /fhir/bin/schemaddl/oauth.properties to reflect your database parameters. In particular, make sure that the url, username, and password properties are set correctly. Note that you can modify the schema name that is configured as part of the url property to suit your requirements (but make sure your datasource entries in 'server.xml' are configured accordingly). - - b. Run the following commands: - - cd /fhir/bin/schemaddl - - ./liquibase --defaultsFile=./oauth.properties –changeLogFile=ddl/db2/oauth.xml update - - - This will create or update your schema to ensure that it is in sync with the freshly-installed FHIR server code. - - Note: The preceding instructions assume that you are running commands on the FHIR server host and reference the `` directory (that is, the root installation directory where you installed the FHIR server). To run these commands directly from the Db2 server host, download the `fhir-schemaddl-distribution.zip` file to your Db2 server host from the same location as the `fhir-server-distribution.zip` file, and decompress it into a clean directory. You can then follow the preceding instructions,substituting the directory where you expanded the ZIP file for the `` variable. - -3. Modify the `server.xml` file to uncomment the Db2-related configuration, and also make sure that the `OAuthDataSource` configuration is commented out under the `Derby/Embedded-related configuration` section. The Db2-related configuration consists of a `library` element, and a `dataSource` element. - - The following example presents a typical Db2-related configuration: - - ``` - - - - - - - - - - ``` - -4. Make sure that you configure the correct `user`, `password`, `serverName` and `portNumber` properties for your Db2 server. For OAuth configuration, do not edit the `currentSchema` property. - -5. Restart the FHIR server. - - After you register a client, it is assigned a `client_id` and `client_secret`. The `client_id`, `client_secret`, `token` URL (`https://:/oauth2/endpoint/oauth2-provider/token`) and `authorize` URL (`https://:/oauth2/endpoint/oauth2-provider/authorize`) can be used to obtain an access token. For more information about obtaining an access token using the various authorization grant types, see [OAuth 2.0 service invocation](https://www.ibm.com/support/knowledgecenter/SS7K4U_liberty/com.ibm.websphere.wlp.zseries.doc/ae/cwlp_oauth_invoking.html) in the WebSphere Liberty documentation. - - NOTE: By default, `client_credentials` is configured as the grant type in `server.xml`. To change this, modify the `oauthProvider` configuration as follows for SMART on FHIR applications: - - ``` - - authorization_code - - - ``` - - - `authorization_code` is the recommended grant type as mentioned by the SMART on FHIR project (http://docs.smarthealthit.org/authorization/best-practices). - -You can use the access token that you obtain in the Authorization Header as a Bearer token to access protected resources on fhir-server. For example: -``` -GET /Patient/ HTTP/1.1 - Host: :/fhir-server/api/v1 - Authorization: Bearer -``` - -If you would like to configure a different authorization server, uncomment the following lines in `server.xml`: - -``` - - -``` - -After you register your application, you will be assigned an id and secret. Update `clientId` and `clientSecret` with the assigned values. Update the `authorizationEndpointUrl` and `tokenEndpointUrl` to the correct URLs for your authorization server. The application should be registered to use `https://:/oidcclient/redirect/client01` as a redirect URL. Also, update the published OAuth URLs in the conformance statement by modifying the following values in `fhir-server-config.json`: - -``` -"fhirServer":{ - …... - "oauth":{ - "regUrl": "https://:9443/oidc/endpoint/oidc-provider/registration", - "authUrl": "https://:9443/oauth2/endpoint/oauth2-provider/authorize", - "tokenUrl": "https://:9443/oauth2/endpoint/oauth2-provider/token" - }, -``` - -SMART on FHIR applications use the conformance statement to determine the OAuth URLs to use for authorization. +SMART on FHIR applications use the capability statement to determine the OAuth URLs to use for authorization. ## 5.3 Custom HTTP Headers IBM FHIR Server Supports the following custom HTTP Headers: @@ -1531,10 +1370,6 @@ IBM FHIR Server Supports the following custom HTTP Headers: |------------------|----------------------------| |`X-FHIR-TENANT-ID`|Specifies which tenant config should be used for the request. Default is `default`. Header name can be overridden via config property `fhirServer/core/tenantIdHeaderName`.| |`X-FHIR-DSID`|Specifies which datastore config should be used for the request. Default is `default`. Header name can be overridden via config property `fhirServer/core/dataSourceIdHeaderName`.| -|`X-FHIR-FORMATTED`|Specifies whether the response from FHIR Server should be formatted (pretty-printed). Note that this can inflate the size of responses and affect performance. Possible values "true" or "false" override the default, handled as specified in [Section 5.1 Configuration properties reference](#51-configuration-properties-reference).| - -## 5.4 Notes about the FHIRJsonParser -The FHIRJsonParser can be configured through the `jsonParserLenient` and `jsonParserValidating` properties. Please see [Section 5.1 Configuration properties reference](#51-configuration-properties-reference) for more information. All `fhir_comments` properties that appear in the JSON input structure are dropped during deserialization. Therefore, they will not appear in the output after re-serialization. # 6 Related topics For more information about topics related to configuring a FHIR server, see the following documentation: @@ -1548,13 +1383,13 @@ For more information about topics related to configuring a FHIR server, see the 1 The fhir-server-config.json file contains configuration information associated with the FHIR server. The global configuration is located in `/wlp/usr/servers/fhir-server/config/default/fhir-server-config.json`, with tenant-specific configuration contained in `config//fhir-server-config.json`. [↩](#a1) -2 When running database-related commands (e.g. createDB.sh, liquibase, etc.) you'll need to make sure that the DB2-related executables are in your PATH, and also that you are logged in as a user that has the necessary authority to create the database and/or create the schema. Normally, if you log in as the DB2 administrative user (typically “db2inst1”) then you should be fine. [↩](#a2) +2 When running database-related commands (e.g. createDB.sh, liquibase, etc.) you'll need to make sure that the Db2-related executables are in your PATH, and also that you are logged in as a user that has the necessary authority to create the database and/or create the schema. Normally, if you log in as the Db2 administrative user (typically “db2inst1”) then you should be fine. [↩](#a2) 3 The names of these request headers are configurable within the FHIR server's fhir-server-config.json file. For more information, see [Section 5.1 Configuration properties reference](#51-configuration-properties-reference). [↩](#a3) 4 For more information on multi-tenant support, including multi-tenant configuration properties, jump to [Section 4.9 Multi-Tenancy](#49-multi-tenancy). [↩](#a4) -5 An external reference is a reference to a resource which is meaningful outside a particular request bundle. The value typically includes the resource type and the resource identifier, and could be an absolute or relative URL. Examples: `https://fhirserver1:9443/fhir-server/api/v1/Patient/12345`, `Patient/12345`, etc. [↩](#a5) +5 An external reference is a reference to a resource which is meaningful outside a particular request bundle. The value typically includes the resource type and the resource identifier, and could be an absolute or relative URL. Examples: `https://fhirserver1:9443/fhir-server/api/v4/Patient/12345`, `Patient/12345`, etc. [↩](#a5) 6 A local reference is a reference used within a request bundle that refers to another resource within the same request bundle and is meaningful only within that request bundle. A local reference starts with `urn:`. [↩](#a6) diff --git a/fhir-client-sample/.gitignore b/fhir-client-sample/.gitignore deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/fhir-client-sample/pom.xml b/fhir-client-sample/pom.xml deleted file mode 100644 index 3d835de2095..00000000000 --- a/fhir-client-sample/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - 4.0.0 - com.ibm.fhir - 4.0.0-SNAPSHOT - fhir-client-sample - Sample application that uses the FHIR Client API - - - com.ibm.fhir - 4.0.0-SNAPSHOT - - - - - ${fhir.server.groupId} - fhir-client - ${fhir.server.version} - - - - - - - - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - 1.8 - 1.8 - true - source,lines,vars - - - - - - diff --git a/fhir-client-sample/src/main/resources/fhir-client.properties b/fhir-client-sample/src/main/resources/fhir-client.properties deleted file mode 100644 index 3a0bc69e7ad..00000000000 --- a/fhir-client-sample/src/main/resources/fhir-client.properties +++ /dev/null @@ -1,21 +0,0 @@ -# FHIR Client Properties - -fhirclient.rest.base.url = http://localhost:9080/fhir-server/api/v4 - -fhirclient.default.mimetype = application/fhir+json - -fhirclient.basicauth.enabled = true -fhirclient.basicauth.username = fhiruser -fhirclient.basicauth.password = change-password - -fhirclient.clientauth.enabled = false -fhirclient.keystore.location = fhirClientKeystore.jks -fhirclient.keystore.password = change-password -fhirclient.keystore.key.password = change-password -fhirclient.truststore.location = fhirClientTruststore.jks -fhirclient.truststore.password = change-password - -fhirclient.encryption.enabled = false -fhirclient.encryption.keystore.location = fhirkeys.jceks -fhirclient.encryption.keystore.password = change-password -fhirclient.encryption.key.password = change-password diff --git a/fhir-client-sample/src/main/resources/fhirClientKeystore.jks b/fhir-client-sample/src/main/resources/fhirClientKeystore.jks deleted file mode 100644 index 9ad2ffc403ce866b45bde8cb4df5fe7da1330daa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmcgt`8yN}7oOcVhB1~b7h}yn(->nZM3&s_Tb3|}n6BNRP_99et`5e)P$Rr5^PcmZ!{u-}0001V0N_6mAcc?v!zfzB z7)tPg7V@#YBoqLEf-qDF7lVNDYrudIpgIT%1VRB2DrALeJDEMxI2F_aPnMQC**cb9EmjR(wE4zMbkR!nDW)P9 ztY5dklbNZJDS8-9tQ#Mr}L0|BY^sLa6G)r4KcU%a8}G0CsbL)zCnLX z_P-DJJtDZMI>s1;2i(b2+8fthE&aCYyo2q#TWnYo4docH)eeqU zR>g&{!^VbZ%V^B+e-2J;XGA8*(n4jRf4OV-jyBuhx_jj6%V70Czp~lK^s4yq>Pgp! zotZ`>ZHu&NvKS!gl3Cp{F=F#Y5oc`cIKk9B$U2UK!1>yZ^6k7~r!w8ena{0FPBt<# zSu{i=&Q2BiwwZqFF&^6dOp$B#`D4G#(XQ|(4eeegMMz8MK-6pEXJrJ)Jlcn7qtVf| zTRb#yA!LO9Syfgv*=Zr1!vX~Gf~Ngqa6PyCQ^(BN`~R3O){nAZ+# z+?=5;?Ktz#iBRj^)q<*WAjI)I-4x%BM7LAVKGGV{7~h z$*;JqBKe!<+m8e)D zn2X~(|D!|L+m%qf(U&QC;wy*@?R)&pyjw{@kwPEZ#(ho7^U`#M=~AU_f;@V_StE19 z$-o~9NirU&f534zb8kmZSeMB;-tpAx&^uL5NC6$cTCt{41UJiV1^OC{^p>ARP>W*U z8AzyaX(qS#KfF8iJyEZK@s8@@bnco!Cbq{L5r$my>Kr?6{-jbaCxg+J8$c~JToxANZ;s{M5U5!f6gQHv^E}N004mh zz)-0(>W5|Ju#ILZ5d#jxkq;lKLgG%-xwih1(Y4@~;9pJ9ty{m&iT)QSiy!o^&gL0FXl z6Kb5!wdw$8ntM~740!>?KYySm=*1Me^;VK`6H=^|^Pv39O*J&nms5OIiTRw!sk#1n?5Mu0X;6zUOL}v9^aaS10awYEA0aF)n+7_k(hgz9Q@ zB9RY-Vz7>ZAh*w~lyt4wvC0V#i>Y{9 z{G_z38~D*DiwLWeBI=5313H`R?cDQgMQP669sE-T2|V8ICsaHn5+&cX5Js5#&)y9) z`LL$UIbp-L*jje1n)MT}0mU>1X={A%#(w%xSnISL8t6Hd3#8RGNg}xXwdMlT)h-nq Pj@q)1E@gw!^S}QMp8v%O diff --git a/fhir-client-sample/src/main/resources/fhirClientTruststore.jks b/fhir-client-sample/src/main/resources/fhirClientTruststore.jks deleted file mode 100644 index 578a9f14500956a50efa4baa50622e738c16305f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 939 zcmezO_TO6u1_mY|W(3o$xs}UnwSC?Ff%bS zF|lZPyxVQS%f_kI=F#?@mywa1mBAp@klTQhjX9KsO_(V(7{=icW(tWgkJkT|n2XHI@{ zVopYWafyMPIIp3Zfw`fnp{22jiCGkgYiM9-4CT_x8BL5z$N|8}%D~*j$j@NV#K^_e z#K_2Sb8E?d#kbmPTfA?wJDV!HUDc|0&awEk=as#ef=_i`=#}#Mx2mcPzt6g|YPVgB z;?cEyuAkP;@4Fzk#(9f{^X0y-CtudYn)+?A<1A%vo@9Dp+l2-`rcO4V<+IpA%nn+v z+;ni0<;PT}N3Ol^Wc!PLc}_{&@^-(=MJDf6I&x3ARs@;rHU5zPbHP3Hl zQh*$Yz*GbbL`DYrx{ZEas#S)I4-qY1~Wxs#0cAKt?Ir^90F-Z^HgdwXSj z{?BZ8c){#?gJV3+w~7sa|@OaIDc v(g{{ diff --git a/fhir-client-sample/src/test/java/.empty b/fhir-client-sample/src/test/java/.empty deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/fhir-client-sample/src/test/resources/.empty b/fhir-client-sample/src/test/resources/.empty deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/fhir-client-sample/src/main/java/com/ibm/fhir/client/sample/FHIRClientSample.java b/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/FHIRClientSample.java similarity index 95% rename from fhir-client-sample/src/main/java/com/ibm/fhir/client/sample/FHIRClientSample.java rename to fhir-client/src/test/java/com/ibm/fhir/client/test/mains/FHIRClientSample.java index a7030b8e48f..8c10d9432d8 100644 --- a/fhir-client-sample/src/main/java/com/ibm/fhir/client/sample/FHIRClientSample.java +++ b/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/FHIRClientSample.java @@ -4,11 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ibm.fhir.client.sample; +package com.ibm.fhir.client.test.mains; import static com.ibm.fhir.model.type.String.string; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -21,6 +20,7 @@ import com.ibm.fhir.client.FHIRClientFactory; import com.ibm.fhir.client.FHIRResponse; import com.ibm.fhir.model.format.Format; +import com.ibm.fhir.model.generator.FHIRGenerator; import com.ibm.fhir.model.resource.Observation; import com.ibm.fhir.model.resource.OperationOutcome; import com.ibm.fhir.model.resource.Patient; @@ -36,7 +36,6 @@ import com.ibm.fhir.model.type.code.ContactPointSystem; import com.ibm.fhir.model.type.code.ContactPointUse; import com.ibm.fhir.model.type.code.ObservationStatus; -import com.ibm.fhir.model.util.FHIRUtil; /** * This class is sample code that demonstrates the use of the FHIR Server's Client API. This sample is not necessarily @@ -51,9 +50,6 @@ public static void main(String[] args) throws Exception { sample.run(); } - public FHIRClientSample() { - } - public void run() throws Exception { String location; @@ -169,9 +165,7 @@ protected String getLocationLogicalId(String location) { private void displayOperationOutcome(FHIRResponse response) { try { OperationOutcome oo = response.getResource(OperationOutcome.class); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - FHIRUtil.write(oo, Format.JSON, baos); - System.out.println("OperationOutcome: \n" + baos.toString()); + FHIRGenerator.generator(Format.JSON).generate(oo, System.out); } catch (Throwable t) { System.out.println("Could not display OperationOutcome from REST API response."); } diff --git a/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/Main.java b/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/Main.java deleted file mode 100644 index 72edd96914e..00000000000 --- a/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/Main.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * (C) Copyright IBM Corp. 2016,2019 - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.ibm.fhir.client.test.mains; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; - -import com.ibm.fhir.core.FHIRMediaType; -import com.ibm.fhir.model.format.Format; -import com.ibm.fhir.model.generator.FHIRGenerator; -import com.ibm.fhir.model.resource.Patient; -import com.ibm.fhir.provider.FHIRProvider; - -public class Main { - public static void main(String[] args) throws Exception { - Client client = ClientBuilder.newBuilder() - .register(new FHIRProvider()) - .build(); - WebTarget target = client.target("http://fhirtest.uhn.ca/baseDstu2"); - Response response = target.path("Patient/5149").request(FHIRMediaType.APPLICATION_FHIR_JSON).get(); - Patient patient = response.readEntity(Patient.class); - - FHIRGenerator.generator( Format.JSON, false).generate(patient, System.out); - System.out.println(""); - FHIRGenerator.generator( Format.XML, false).generate(patient, System.out); - } -} diff --git a/fhir-connectathon-clients/src/test/java/com/ibm/fhir/connectathon/patient/PatientTrackTest.java b/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/PatientTrackTest.java similarity index 96% rename from fhir-connectathon-clients/src/test/java/com/ibm/fhir/connectathon/patient/PatientTrackTest.java rename to fhir-client/src/test/java/com/ibm/fhir/client/test/mains/PatientTrackTest.java index 3829cfbbbc3..7ef55dbf14e 100644 --- a/fhir-connectathon-clients/src/test/java/com/ibm/fhir/connectathon/patient/PatientTrackTest.java +++ b/fhir-client/src/test/java/com/ibm/fhir/client/test/mains/PatientTrackTest.java @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.ibm.fhir.connectathon.patient; +package com.ibm.fhir.client.test.mains; import static com.ibm.fhir.client.FHIRRequestHeader.header; import static com.ibm.fhir.model.type.String.string; @@ -17,8 +17,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import org.testng.annotations.Test; - import com.ibm.fhir.client.FHIRClient; import com.ibm.fhir.client.FHIRClientFactory; import com.ibm.fhir.client.FHIRParameters; @@ -62,12 +60,10 @@ private static void log(String msg) { System.out.println(msg); } - @Test public void printHeader() { - log("Starting execution of PatientTrackTest (c) IBM Corporation, 2016."); + log("Starting execution of PatientTrackTest (C) IBM Corporation 2016,2019."); } - @Test(dependsOnMethods = { "printHeader" }) public void connectToServer() throws Exception { // Load up our properties file. Properties clientProperties = readProperties(CLIENT_PROPERTIES_FILENAME); @@ -79,7 +75,6 @@ public void connectToServer() throws Exception { log("Connecting to FHIR endpoint: " + baseUrl); } - @Test(dependsOnMethods = { "connectToServer" }) public void inspectConformanceStatement() throws Exception { FHIRResponse response = client.metadata(); if (response.getStatus() != Status.OK.getStatusCode()) { @@ -118,7 +113,6 @@ public void inspectConformanceStatement() throws Exception { log("Server supports search? " + (supportsSearch ? "yes" : "no")); } - @Test(dependsOnMethods = { "inspectConformanceStatement" }) public void registerPatient() throws Exception { String location; @@ -154,7 +148,6 @@ public void registerPatient() throws Exception { patient = displayResponsePatient("Retrieved newly-registered patient:", client, response); } - @Test(dependsOnMethods = { "registerPatient" }) public void updatePatient() throws Exception { log("\n2) Update a patient"); if (!supportsUpdate) { @@ -195,7 +188,6 @@ public void updatePatient() throws Exception { } } - @Test(dependsOnMethods = { "updatePatient" }) public void retrieveHistory() throws Exception { log("\n3) Retrieve patient history"); if (!supportsHistory) { @@ -217,7 +209,6 @@ public void retrieveHistory() throws Exception { } - @Test(dependsOnMethods = { "retrieveHistory" }) public void searchByName() throws Exception { // 4) Search for a patient by name. log("\n4) Search for a patient by name"); @@ -333,4 +324,15 @@ private Patient modifyPatientContents(Patient patient) { return patient; } + + public static void main(String[] args) throws Exception { + PatientTrackTest test = new PatientTrackTest(); + test.printHeader(); + test.connectToServer(); + test.inspectConformanceStatement(); + test.registerPatient(); + test.updatePatient(); + test.retrieveHistory(); + test.searchByName(); + } } diff --git a/fhir-connectathon-clients/src/test/resources/patient.json b/fhir-client/src/test/resources/testdata/patient.json similarity index 100% rename from fhir-connectathon-clients/src/test/resources/patient.json rename to fhir-client/src/test/resources/testdata/patient.json diff --git a/fhir-connectathon-clients/src/test/resources/patientNoDiv.json b/fhir-client/src/test/resources/testdata/patientNoDiv.json similarity index 100% rename from fhir-connectathon-clients/src/test/resources/patientNoDiv.json rename to fhir-client/src/test/resources/testdata/patientNoDiv.json diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java index 66e70d7a0d0..3f3fca94fc9 100644 --- a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java +++ b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java @@ -12,63 +12,67 @@ import java.util.List; import java.util.logging.Logger; +/** + * This class serves up a singleton instance of ConfigurationService containing the FHIR Server's configuration. + */ public class FHIRConfiguration { private static final Logger log = Logger.getLogger(FHIRConfiguration.class.getName()); - /** - * This class serves up a singleton instance of ConfigurationService containing the FHIR Server's configuration. - */ public static final String CONFIG_LOCATION = "config"; public static final String CONFIG_FILE_BASENAME = "fhir-server-config.json"; public static final String DEFAULT_TENANT_ID = "default"; public static final String DEFAULT_DATASTORE_ID = "default"; - // Configuration properties used by various FHIR Server components. + // Core server properties + public static final String PROPERTY_TENANT_ID_HEADER_NAME = "fhirServer/core/tenantIdHeaderName"; + public static final String PROPERTY_DATASTORE_ID_HEADER_NAME = "fhirServer/core/datastoreIdHeaderName"; + public static final String PROPERTY_DEFAULT_TENANT_ID = "fhirServer/core/defaultTenantId"; + public static final String PROPERTY_DEFAULT_PRETTY_PRINT = "fhirServer/core/defaultPrettyPrint"; + + public static final String PROPERTY_SEARCH_PARAMETER_FILTER = "fhirServer/searchParameterFilter"; + + // Auth and security properties public static final String PROPERTY_TRUSTSTORE_LOCATION = "fhirServer/core/truststoreLocation"; public static final String PROPERTY_TRUSTSTORE_PASSWORD = "fhirServer/core/truststorePassword"; public static final String PROPERTY_OAUTH_REGURL = "fhirServer/oauth/regUrl"; public static final String PROPERTY_OAUTH_AUTHURL = "fhirServer/oauth/authUrl"; public static final String PROPERTY_OAUTH_TOKENURL = "fhirServer/oauth/tokenUrl"; + + public static final String PROPERTY_AUTHFILTER_ENABLED = "fhirServer/authFilter/enabled"; + public static final String PROPERTY_AUTHORIZED_CLIENT_CERT_CLIENT_CN = "fhirServer/authFilter/authorizedClientCertClientCN"; + public static final String PROPERTY_AUTHORIZED_CLIENT_CERT_ISSUER_OU = "fhirServer/authFilter/authorizedClientCertIssuerOU"; + + // Audit config properties public static final String PROPERTY_AUDIT_SERVICE_CLASS_NAME = "fhirServer/audit/serviceClassName"; public static final String PROPERTY_AUDIT_SERVICE_PROPERTIES = "fhirServer/audit/serviceProperties"; public static final String PROPERTY_AUDIT_PATIENT_ID_EXTURL = "fhirServer/audit/patientIdExtensionUrl"; public static final String PROPERTY_AUDIT_RESOURCE_NAME_EXTURL = "fhirServer/audit/resourceNameExtensionUrl"; - public static final String PROPERTY_ENCRYPTION = "fhirServer/encryption"; public static final String PROPERTY_UPDATE_CREATE_ENABLED = "fhirServer/persistence/common/updateCreateEnabled"; + + // Notification config properties public static final String PROPERTY_NOTIFICATION_RESOURCE_TYPES = "fhirServer/notifications/common/includeResourceTypes"; public static final String PROPERTY_WEBSOCKET_ENABLED = "fhirServer/notifications/websocket/enabled"; public static final String PROPERTY_KAFKA_ENABLED = "fhirServer/notifications/kafka/enabled"; public static final String PROPERTY_KAFKA_TOPICNAME = "fhirServer/notifications/kafka/topicName"; public static final String PROPERTY_KAFKA_CONNECTIONPROPS = "fhirServer/notifications/kafka/connectionProperties"; + + // Persistence layer properties public static final String PROPERTY_PERSISTENCE_FACTORY = "fhirServer/persistence/factoryClassname"; + public static final String PROPERTY_DATASOURCES = "fhirServer/persistence/datasources"; public static final String PROPERTY_JDBC_BOOTSTRAP_DB = "fhirServer/persistence/jdbc/bootstrapDb"; public static final String PROPERTY_JDBC_DATASOURCE_JNDINAME = "fhirServer/persistence/jdbc/dataSourceJndiName"; public static final String PROPERTY_JDBC_ENABLE_CODE_SYSTEMS_CACHE = "fhirServer/persistence/jdbc/enableCodeSystemsCache"; public static final String PROPERTY_JDBC_ENABLE_PARAMETER_NAMES_CACHE = "fhirServer/persistence/jdbc/enableParameterNamesCache"; public static final String PROPERTY_JDBC_ENABLE_RESOURCE_TYPES_CACHE = "fhirServer/persistence/jdbc/enableResourceTypesCache"; - public static final String PROPERTY_TENANT_ID_HEADER_NAME = "fhirServer/core/tenantIdHeaderName"; - public static final String PROPERTY_DATASTORE_ID_HEADER_NAME = "fhirServer/core/datastoreIdHeaderName"; - public static final String PROPERTY_DEFAULT_TENANT_ID = "fhirServer/core/defaultTenantId"; - public static final String PROPERTY_DEFAULT_PRETTY_PRINT = "fhirServer/core/defaultPrettyPrint"; - public static final String PROPERTY_JSON_PARSER_LENIENT = "fhirServer/core/jsonParserLenient"; - public static final String PROPERTY_JSON_PARSER_VALIDATING = "fhirServer/core/jsonParserValidating"; - public static final String PROPERTY_DATASOURCES = "fhirServer/persistence/datasources"; - public static final String PROPERTY_SEARCH_PARAMETER_FILTER = "fhirServer/searchParameterFilter"; - public static final String PROPERTY_AUTHFILTER_ENABLED = "fhirServer/authFilter/enabled"; - public static final String PROPERTY_AUTHORIZED_CLIENT_CERT_CLIENT_CN = "fhirServer/authFilter/authorizedClientCertClientCN"; - public static final String PROPERTY_AUTHORIZED_CLIENT_CERT_ISSUER_OU = "fhirServer/authFilter/authorizedClientCertIssuerOU"; - public static final String PROPERTY_RESOURCE_TYPES_REQUIRING_SUBJECT_ID = "fhirServer/whclsfRouter/resourceNamesRequiringPatientId"; - public static final String PROPERTY_STUDY_SCOPED_RESOURCES = "fhirServer/whclsfRouter/resourceNamesRequiringConsentEnforcement"; - + // Custom header names public static final String DEFAULT_TENANT_ID_HEADER_NAME = "X-FHIR-TENANT-ID"; public static final String DEFAULT_DATASTORE_ID_HEADER_NAME = "X-FHIR-DSID"; public static final String DEFAULT_PRETTY_RESPONSE_HEADER_NAME = "X-FHIR-FORMATTED"; public static final String FHIR_SERVER_DEFAULT_CONFIG = "config/default/fhir-server-config.json"; - // Optional "home directory" for config files. Defaults to current directory. private static String configHome = ""; @@ -86,8 +90,10 @@ public static FHIRConfiguration getInstance() { /** * This method is used to configure an explicit top-level directory where FHIR Server configuration * information is expected to reside. + *

* For example, by calling this method with value "/mydir", then we'd expect - * to find config files whose names are of the form: "/mydir/config/<tenant-id&rt;/fhir-server-config.json". + * to find config files whose names are of the form: {@code "/mydir/config//fhir-server-config.json"} + *

* The default location for config files is the current working directory (i.e. "" - the empty string). * @param s the new config home directory name */ @@ -104,7 +110,8 @@ public static void setConfigHome(String s) { /** * Returns the "home" directory for FHIR Server configuration information (this directory will contain - * the "config" directory, etc.). + * the "config" directory, etc.). + *

* The default value of this property is "" which is interpretted to mean the current working directory * (which for a running FHIR Server will be $WLP_HOME/wlp/usr/servers/fhir-server). */ diff --git a/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java b/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java index d0f5a1c5b69..bd66a48cb05 100644 --- a/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java +++ b/fhir-config/src/test/java/com/ibm/fhir/config/test/FHIRConfigHelperTest.java @@ -274,19 +274,6 @@ public void testTenant5() throws Exception { l = FHIRConfigHelper.getStringListProperty("collection/groupB/stringList1"); assertNotNull(l); assertEquals(expectedList1, l); - - PropertyGroup pg = FHIRConfigHelper.getPropertyGroup("whclsfRouter/config"); - assertNotNull(pg); - assertNotNull(pg.getBooleanProperty("supportsTransaction")); - assertNotNull(pg.getArrayProperty("routingRules")); - - b = FHIRConfigHelper.getBooleanProperty("whclsfRouter/consentEnforcementEnabled", null); - assertNotNull(b); - assertEquals(Boolean.TRUE, b); - - b = FHIRConfigHelper.getBooleanProperty("whclsfRouter/consentManagementEnabled", null); - assertNotNull(b); - assertEquals(Boolean.FALSE, b); } @Test diff --git a/fhir-config/src/test/resources/config/default/fhir-server-config.json b/fhir-config/src/test/resources/config/default/fhir-server-config.json index 527453420f1..ed5b5161c1a 100644 --- a/fhir-config/src/test/resources/config/default/fhir-server-config.json +++ b/fhir-config/src/test/resources/config/default/fhir-server-config.json @@ -1,25 +1,21 @@ { - "collection": { - "tenant": "default", - "groupA": { - "stringProp1": "defaultValue1", - "stringProp2": "defaultValue2" - }, - "groupB": { - "boolProp1": false, - "boolProp2": "false", - "stringList1": ["token1", - "token2"] - }, + "collection": { + "tenant": "default", + "groupA": { + "stringProp1": "defaultValue1", + "stringProp2": "defaultValue2" + }, + "groupB": { + "boolProp1": false, + "boolProp2": "false", + "stringList1": ["token1", + "token2"] + }, "groupC": { "intProp1": 12345, "intProp2": "12345", "doubleProp1": 12345.001, "doubleProp2": "12345.001" } - }, - "whclsfRouter": { - "consentEnforcementEnabled": "true", - "consentManagementEnabled": "false" - } + } } diff --git a/fhir-config/src/test/resources/config/tenant5/fhir-server-config.json b/fhir-config/src/test/resources/config/tenant5/fhir-server-config.json index 459c66f7023..a405b782faa 100644 --- a/fhir-config/src/test/resources/config/tenant5/fhir-server-config.json +++ b/fhir-config/src/test/resources/config/tenant5/fhir-server-config.json @@ -1,17 +1,11 @@ { - "collection": { - "tenant": "tenant5", - "groupA": { - "newProp1": "newValue1" - }, - "groupB": { - "newBoolProp1": "true" - } - }, - "whclsfRouter": { - "config": { - "supportsTransaction": false, - "routingRules": [] - } - } + "collection": { + "tenant": "tenant5", + "groupA": { + "newProp1": "newValue1" + }, + "groupB": { + "newBoolProp1": "true" + } + } } diff --git a/fhir-connectathon-clients/.gitignore b/fhir-connectathon-clients/.gitignore deleted file mode 100644 index 397b4a7624e..00000000000 --- a/fhir-connectathon-clients/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log diff --git a/fhir-connectathon-clients/pom.xml b/fhir-connectathon-clients/pom.xml deleted file mode 100644 index 01ad86c1b55..00000000000 --- a/fhir-connectathon-clients/pom.xml +++ /dev/null @@ -1,52 +0,0 @@ - - 4.0.0 - - com.ibm.fhir - fhir-parent - 4.0.0-SNAPSHOT - ../fhir-parent - - - fhir-connectathon-clients - - - true - ${project.groupId} - ${project.version} - - - - - ${fhir.server.groupId} - fhir-client - ${fhir.server.version} - test - - - ${project.groupId} - fhir-model - ${project.version} - test-jar - test - - - org.json - json - test - - - org.testng - testng - test - - - com.ibm.fhir - fhir-persistence - ${project.version} - test-jar - test - - - - diff --git a/fhir-connectathon-clients/src/main/java/.empty b/fhir-connectathon-clients/src/main/java/.empty deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/fhir-connectathon-clients/src/main/resources/.empty b/fhir-connectathon-clients/src/main/resources/.empty deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/fhir-connectathon-clients/src/test/resources/fhir-client.properties b/fhir-connectathon-clients/src/test/resources/fhir-client.properties deleted file mode 100644 index 3d9c7243bfa..00000000000 --- a/fhir-connectathon-clients/src/test/resources/fhir-client.properties +++ /dev/null @@ -1,22 +0,0 @@ -# FHIR Client Properties - -fhirclient.rest.base.url = http://localhost:9080/fhir-server/api/v4 - -fhirclient.default.mimetype = application/fhir+json -#fhirclient.default.mimetype = application/fhir+json - -fhirclient.basicauth.enabled = true -fhirclient.basicauth.username = fhiruser -fhirclient.basicauth.password = change-password - -fhirclient.clientauth.enabled = false -fhirclient.keystore.location = fhirClientKeystore.jks -fhirclient.keystore.password = change-password -fhirclient.keystore.key.password = change-password -fhirclient.truststore.location = fhirClientTruststore.jks -fhirclient.truststore.password = change-password - -fhirclient.encryption.enabled = false -fhirclient.encryption.keystore.location = fhirkeys.jceks -fhirclient.encryption.keystore.password = change-password -fhirclient.encryption.key.password = change-password diff --git a/fhir-connectathon-clients/src/test/resources/fhirClientKeystore.jks b/fhir-connectathon-clients/src/test/resources/fhirClientKeystore.jks deleted file mode 100644 index f61b1afe9e68a6f25eb90fdc0348bc1dc08e6a24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmcgt=U3B-63s6OU4amKREUU@(2}KuqQHuw`H&V=Ix$pD07DWGa9Kl9QA81VfCxws zq>1z*h9+GEq!?6Cqx2@=(n5K-=e=|GFL)pBhkIu}%-oqXv%9dn00M!)`vCri2#M_P z9Y9kj(rJGC8u{DPy|*9`9~i@g?qWpwg;n_hD1ZjT0Dun!WkP3%m0ImM<6aZyNfIl# zM5zkij!_oI*R)umn^CpurZf5Abx+3t<7~gP`?U7nUsS-#A)1O$YM{eQ=QB-#@pBe3 zX_Yn|DK3?Ae@>n>`cz8v>Ah5MY#cp!dnaC5Wr*AWzSi58Qo!qQ@@pKX&PtO6@G zdX;{fpw_B;Y}h-dvr2Q%_m;78x>?s4S~pU6Fo}8%A!UcyTf#TKi=&q3R)kI{7--zf zM`af^D7|h>Q2h$hrE4^s5mU{)(-C$C`DHzi7U@^5J=%ts@aa_U(4Ec)C&i%24sc!7 z#`}$&Em=EAt|q5XDmiLWer=CQXylk>XdKp1kQXgZaBEYo6_m@YkmkyQHH{1cMh@t~ zNgBn7B74rgwvCaWGzxZWhEv*wEx$EWlB!=r5Y3Y}TDEzkAj=Sy>h7Q8?)e?wUP+#k zx$`^=>H6YZ`TO&@Q%a7njn7`r&twG8q|AH>acFO%A7xNejKtDy?b>&*OWLgG821IX zjz*!azbALIUIyclj}+24fr*rz_tyP>{lYD|GEK8T%m#tkwU20YvTE!ux4~mK|BMyt zNh?WZHOW`8)Cry2aj+K3}{5VbkOrpNgVnYUWME6GHs(srP&9FkXpp{F7{V_&x4J}|@D*TJzoV=6+ck6 zqQj{ao=ajMTTYnFMIIBGNB_a5xP(bJ-o3FJ9PMn_C7{h{02Wm&K+2^99ylM#W*Iw9 z+z-Lm+%uzp2*H97O87tP>ynpzdRut~7ecyg+K^vbBTXws7hW!4wA&|mO!`+(a)KPX z$z4W~+<&BGzIRt*u-DtBnotwV&Z_=cBFLw#Ih>8lP9Soh7@{aCf|YJa*fui~s_JS| z<^0hzO%Jy!(=*L`58-gP9sREOS6uRX+WaV3%cW2xd-GuaSL5*MnOT>CSf>kb{dvM| zaq?*sYg#vS@a{m0XUCnrj08{ls7fA9bzVklTNREd9l?9aGP) zw<<}}=h~5fxFI?`(-_GPd{uD_hjfdJ5v)nQ?SLf#_371k8;ZYhaDg`9C;1zlEdi75GCOsKZK!=A3opKGxBW>TGcfkdYZScb6 zt>aHtEXJ%I?^M*-J*FrFdtU6RvGuT$R^!#Bsk1JQpEro&ZW`H_z8gBe`S*F`yQraq z(FcJbS1?S7FNO)W$%cRdFc=C`mlwwz zSQtj^mkxmo`uO=%>A~Jq%n_M`SX~Sbi^FPZ>-?tY{EK2SSe^gU|5FuA;OIZw-~SE} zCLjjFFacpO697Qt-1wV%tX!6f^m3`M)vgVlFn_@(sdD+*qaOn$jLS|a$YmQup80N$ z3eS26MzAhwk&w@^wGf41XDx} zZcYY*E3v;^uL+FRc0}772!b+q*5D(oOd88#J<_NeCRM-iB;PFsg%sdSHC;tFq6<6k z;*?59?yK4v=bct@$rd8<`Qe)H1ioTQkfb%kDvTW~BkG;sMKKo!G|CIhmhV~p`(IhU zsfqgVI&Q;5IjoAKINXaRKVF3Y7(Y#TPlHP=dQCNJXSC${zVgvJ(TRkb9SHXl(3wjV zHzcVQfFS?~U?DLIn8W)6JS+;8hDzS0fVU)^`H{>Z@UqF{g|c*mM#F!axW7aDCITQy zu-LZwwKl&S#IDa~>RDritlvOF?WkC+wXE9QNOqGtN6%yP;^!#GT0!W-r2jdLR1{I> z#9VCzQ3wpLX*mUkrn=4MG%qFI)e2jZR+YKKnK@)f_Y8kYGKU}Q<;@jXh4$Dp@MXfp zl}<#`QYAPaB}&ov{}viR@wa={aIcWvCHFn-wz&J#EY{pLmnXv=TBww_W2B|Qf)OYD zXqdGQ2XW*)#6=%hNp8F4JlGlL% diff --git a/fhir-connectathon-clients/src/test/resources/fhirClientTruststore.jks b/fhir-connectathon-clients/src/test/resources/fhirClientTruststore.jks deleted file mode 100644 index 4de2c5bc636a68588e162ed11d81b790438aa68c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2698 zcmcgtdpJ~U7oWZ7f^j#Jgl0HOE@RIym^n?t5b;qi5uY;6Om1U_nL!g>jJwJ~M^Z>h zosvr;l5{8GL+L{4;!>SNLJ`$`Goq)?IsbhBeEWI!^So=l>;3Ju-o4iE_jTm!2n+@T zzrQFtJA}?Bz+iw6S$MY$hV;dgATkVwx(bO<4Uh@m#z5`=34rA zbK*Kk5hZ7o?36sE2mta(7_@^R56Pk6-Ut*1^!8jZWHI1ITY?ItB;Aq0$T+ch9441T zz(LET9yCVNjU|ZT^1`s5Ty~Togv;S$UGc7Xdpuz!v_cvJ$6)^+;=U2<$>K$`cvyQ| zED_ROtVpng$dDB@_xMRnvc$9`nv=*RvL6)RxRB4c zC`&4vOBO|=v)Pin(S$Ul4mpgn1w@3f|6(}AusB!`CZ3=QDNA?I7+Eh+s43qfLpa=wD7gy3xQ0otBq`mcylTRta4aXjkZOUtXm80;r zCdTFyHD;i=#jYe4KM5>q>+-X0mMuPqiJo5f^sSM1K>zV>o9%Vy3kA;p4kY-aX2;ui zp5Tf)#GgJV>^%D*Gq~+^&+)W7OQv`53|UnrF}YQ>{$0L`%=e_)F_o(RT^~2o2DaFyhdYi0jZX#?^p!*} z*Vdni?+>4)_KDRpZC*baYf+f!9?V&$MQopSydIQt#5~LJD)FP5PVx2wuawgd1}eOc zyAxk0wtHrb?h_$jrIrLc_Xe*Te(@#u^P1A`e(MC8?m$|B>IWG)s#e~L8!HAfXKw2F z1?@(+i)UF0k-gb!BL#cEI8Wl%Xw_VkDLMX|m|>N8zj*p!E5e^X)_gtajZsitN2Wms z#o2h5MuV?;tB+H|OW7RH+b@=A=f2wTB|; zMWu5fxy7t#Gj;Na7%~;TOg)R_E2Sj|T->}~^O;?A*A12YMeiG3n(l79(WbqLO!pt? z@6eQ~RzjQ19IYnKV$L-&k{Xj&JXZwa01PM=0aK6&n3x+a9esqPZ}+HU&D(3~A4`(m zpR_#Whc=)$unxV2bR^BDqlC~vsOu&%59QzK@s#WPpw~=kJbu?*u5oTyb(8@>j6lFa zWYOHP=wPHa2*JJ`Hb7`V>L^)b6o5p^z=7{85Q>swV-XyP4F#eUBmoE@SqT6EH~_7O z+!g{sK*KIrAc&xtnZ?A!NPApL0{jIIz%#hvW^7gvooyzW6VthsDUTJw<%bBkJfSHg zf{zd7M?kVDR8YKvgq|R0>1UTA?4UK!>Y`Oelw|99cm|Ixg_l6{`RqB|c{=${IPM$2 zaf!Vx5if~`Vi%GG00b8bhiD5n1pa_3_@7V(kimBpAbH7rZd?XG;%69$B&|agfrm^) z|I7s<%s&yt&twc20RYT-GRw!?>k?5M_DjL<(Cq4K%!#EWE!Ux~Rl>~i;jMXv`bYO1 zlMz%@*zd;-u|`{djn;z0wQ|Jwp5{GmwA^GymJb`Uy|J}0J};yCo`c~Q$4B6?q^G66GaJWWnC$AhWM1%kZ;aE-(q{FFi7X7! znjCe8+IDhg>8a$=mrjdt6!t;ZYQ*sR04#&W#7V~+Oj*g=>&+O|bFG9`!c@}n7@ zx@YaHt?ln+qz_J>4IsHyua=8~CuEbZUg+GT1g4=BP8~*hSzaTbuDVu5e$N8?9rlmv z{KlJc{(W2Z$Q4=_=yJwHcM$!DhZR14dgRquqif|k4!_@xb| zG?kjymvXv^Z&Shcee2&jC1uCi>39w+b`L#|6Q}Oe!(^X2S@G`&ZoRG@fuWUa%gZ&C zXEx~`#p&&I>ILvyVOTUFoS@sxTPv$&$s&+}QhlezLYij`>j z5A5l|_dB-X#n;hk{pT8Y+G_CrQ-cwI>7uz&T(;|+Te^W!S z%aiObJh?P5XDHj}bLP#8Rh8a(ePh(Rf}|??^JkR;-y9?IXn